001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.imaging.formats.jpeg; 018 019import java.io.IOException; 020import java.io.InputStream; 021 022import org.apache.commons.imaging.ImagingException; 023import org.apache.commons.imaging.bytesource.ByteSource; 024import org.apache.commons.imaging.common.BinaryFileParser; 025import org.apache.commons.imaging.common.BinaryFunctions; 026import org.apache.commons.imaging.common.ByteConversions; 027import org.apache.commons.imaging.internal.Debug; 028import org.apache.commons.io.IOUtils; 029 030public class JpegUtils extends BinaryFileParser { 031 public interface Visitor { 032 // return false to exit before reading image data. 033 boolean beginSos(); 034 035 // return false to exit traversal. 036 boolean visitSegment(int marker, byte[] markerBytes, int segmentLength, byte[] segmentLengthBytes, byte[] segmentData) 037 throws ImagingException, IOException; 038 039 void visitSos(int marker, byte[] markerBytes, byte[] imageData); 040 } 041 042 public static String getMarkerName(final int marker) { 043 switch (marker) { 044 case JpegConstants.SOS_MARKER: 045 return "SOS_MARKER"; 046 // case JPEG_APP0 : 047 // return "JPEG_APP0"; 048 // case JPEG_APP0_MARKER : 049 // return "JPEG_APP0_MARKER"; 050 case JpegConstants.JPEG_APP1_MARKER: 051 return "JPEG_APP1_MARKER"; 052 case JpegConstants.JPEG_APP2_MARKER: 053 return "JPEG_APP2_MARKER"; 054 case JpegConstants.JPEG_APP13_MARKER: 055 return "JPEG_APP13_MARKER"; 056 case JpegConstants.JPEG_APP14_MARKER: 057 return "JPEG_APP14_MARKER"; 058 case JpegConstants.JPEG_APP15_MARKER: 059 return "JPEG_APP15_MARKER"; 060 case JpegConstants.JFIF_MARKER: 061 return "JFIF_MARKER"; 062 case JpegConstants.SOF0_MARKER: 063 return "SOF0_MARKER"; 064 case JpegConstants.SOF1_MARKER: 065 return "SOF1_MARKER"; 066 case JpegConstants.SOF2_MARKER: 067 return "SOF2_MARKER"; 068 case JpegConstants.SOF3_MARKER: 069 return "SOF3_MARKER"; 070 case JpegConstants.DHT_MARKER: 071 return "SOF4_MARKER"; 072 case JpegConstants.SOF5_MARKER: 073 return "SOF5_MARKER"; 074 case JpegConstants.SOF6_MARKER: 075 return "SOF6_MARKER"; 076 case JpegConstants.SOF7_MARKER: 077 return "SOF7_MARKER"; 078 case JpegConstants.SOF8_MARKER: 079 return "SOF8_MARKER"; 080 case JpegConstants.SOF9_MARKER: 081 return "SOF9_MARKER"; 082 case JpegConstants.SOF10_MARKER: 083 return "SOF10_MARKER"; 084 case JpegConstants.SOF11_MARKER: 085 return "SOF11_MARKER"; 086 case JpegConstants.DAC_MARKER: 087 return "DAC_MARKER"; 088 case JpegConstants.SOF13_MARKER: 089 return "SOF13_MARKER"; 090 case JpegConstants.SOF14_MARKER: 091 return "SOF14_MARKER"; 092 case JpegConstants.SOF15_MARKER: 093 return "SOF15_MARKER"; 094 case JpegConstants.DQT_MARKER: 095 return "DQT_MARKER"; 096 case JpegConstants.DRI_MARKER: 097 return "DRI_MARKER"; 098 case JpegConstants.RST0_MARKER: 099 return "RST0_MARKER"; 100 case JpegConstants.RST1_MARKER: 101 return "RST1_MARKER"; 102 case JpegConstants.RST2_MARKER: 103 return "RST2_MARKER"; 104 case JpegConstants.RST3_MARKER: 105 return "RST3_MARKER"; 106 case JpegConstants.RST4_MARKER: 107 return "RST4_MARKER"; 108 case JpegConstants.RST5_MARKER: 109 return "RST5_MARKER"; 110 case JpegConstants.RST6_MARKER: 111 return "RST6_MARKER"; 112 case JpegConstants.RST7_MARKER: 113 return "RST7_MARKER"; 114 default: 115 return "Unknown"; 116 } 117 } 118 119 /** 120 * Constructs a new instance with the default, big-endian, byte order. 121 */ 122 public JpegUtils() { 123 // empty 124 } 125 126 public void dumpJfif(final ByteSource byteSource) throws ImagingException, IOException { 127 final Visitor visitor = new Visitor() { 128 // return false to exit before reading image data. 129 @Override 130 public boolean beginSos() { 131 return true; 132 } 133 134 // return false to exit traversal. 135 @Override 136 public boolean visitSegment(final int marker, final byte[] markerBytes, final int segmentLength, final byte[] segmentLengthBytes, 137 final byte[] segmentData) { 138 Debug.debug("Segment marker: " + Integer.toHexString(marker) + " (" + getMarkerName(marker) + "), " + segmentData.length 139 + " bytes of segment data."); 140 return true; 141 } 142 143 @Override 144 public void visitSos(final int marker, final byte[] markerBytes, final byte[] imageData) { 145 Debug.debug("SOS marker. " + imageData.length + " bytes of image data."); 146 Debug.debug(""); 147 } 148 }; 149 150 traverseJfif(byteSource, visitor); 151 } 152 153 public void traverseJfif(final ByteSource byteSource, final Visitor visitor) throws ImagingException, IOException { 154 try (InputStream is = byteSource.getInputStream()) { 155 BinaryFunctions.readAndVerifyBytes(is, JpegConstants.SOI, "Not a Valid JPEG File: doesn't begin with 0xffd8"); 156 157 int markerCount; 158 for (markerCount = 0; true; markerCount++) { 159 final byte[] markerBytes = new byte[2]; 160 do { 161 markerBytes[0] = markerBytes[1]; 162 markerBytes[1] = BinaryFunctions.readByte("marker", is, "Could not read marker"); 163 } while ((0xff & markerBytes[0]) != 0xff || (0xff & markerBytes[1]) == 0xff); 164 final int marker = (0xff & markerBytes[0]) << 8 | 0xff & markerBytes[1]; 165 166 if (marker == JpegConstants.EOI_MARKER || marker == JpegConstants.SOS_MARKER) { 167 if (!visitor.beginSos()) { 168 return; 169 } 170 171 final byte[] imageData = IOUtils.toByteArray(is); 172 visitor.visitSos(marker, markerBytes, imageData); 173 break; 174 } 175 176 final byte[] segmentLengthBytes = BinaryFunctions.readBytes("segmentLengthBytes", is, 2, "segmentLengthBytes"); 177 final int segmentLength = ByteConversions.toUInt16(segmentLengthBytes, getByteOrder()); 178 if (segmentLength < 2) { 179 throw new ImagingException("Invalid segment size"); 180 } 181 182 final byte[] segmentData = BinaryFunctions.readBytes("Segment Data", is, segmentLength - 2, "Invalid Segment: insufficient data"); 183 184 if (!visitor.visitSegment(marker, markerBytes, segmentLength, segmentLengthBytes, segmentData)) { 185 return; 186 } 187 } 188 189 Debug.debug(markerCount + " markers"); 190 } 191 } 192}