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 static org.apache.commons.imaging.common.BinaryFunctions.getStreamBytes; 020import static org.apache.commons.imaging.common.BinaryFunctions.readAndVerifyBytes; 021import static org.apache.commons.imaging.common.BinaryFunctions.readByte; 022import static org.apache.commons.imaging.common.BinaryFunctions.readBytes; 023 024import java.io.IOException; 025import java.io.InputStream; 026import java.nio.ByteOrder; 027 028import org.apache.commons.imaging.ImageReadException; 029import org.apache.commons.imaging.common.BinaryFileParser; 030import org.apache.commons.imaging.common.ByteConversions; 031import org.apache.commons.imaging.common.bytesource.ByteSource; 032import org.apache.commons.imaging.internal.Debug; 033 034public class JpegUtils extends BinaryFileParser { 035 public JpegUtils() { 036 setByteOrder(ByteOrder.BIG_ENDIAN); 037 } 038 039 public interface Visitor { 040 // return false to exit before reading image data. 041 boolean beginSOS(); 042 043 void visitSOS(int marker, byte[] markerBytes, byte[] imageData); 044 045 // return false to exit traversal. 046 boolean visitSegment(int marker, byte[] markerBytes, 047 int segmentLength, byte[] segmentLengthBytes, 048 byte[] segmentData) throws ImageReadException, 049 IOException; 050 } 051 052 public void traverseJFIF(final ByteSource byteSource, final Visitor visitor) 053 throws ImageReadException, 054 IOException { 055 try (InputStream is = byteSource.getInputStream()) { 056 readAndVerifyBytes(is, JpegConstants.SOI, 057 "Not a Valid JPEG File: doesn't begin with 0xffd8"); 058 059 int markerCount; 060 for (markerCount = 0; true; markerCount++) { 061 final byte[] markerBytes = new byte[2]; 062 do { 063 markerBytes[0] = markerBytes[1]; 064 markerBytes[1] = readByte("marker", is, 065 "Could not read marker"); 066 } while ((0xff & markerBytes[0]) != 0xff 067 || (0xff & markerBytes[1]) == 0xff); 068 final int marker = ((0xff & markerBytes[0]) << 8) 069 | (0xff & markerBytes[1]); 070 071 if (marker == JpegConstants.EOI_MARKER || marker == JpegConstants.SOS_MARKER) { 072 if (!visitor.beginSOS()) { 073 return; 074 } 075 076 final byte[] imageData = getStreamBytes(is); 077 visitor.visitSOS(marker, markerBytes, imageData); 078 break; 079 } 080 081 final byte[] segmentLengthBytes = readBytes("segmentLengthBytes", is, 2, "segmentLengthBytes"); 082 final int segmentLength = ByteConversions.toUInt16(segmentLengthBytes, getByteOrder()); 083 if (segmentLength < 2) { 084 throw new ImageReadException("Invalid segment size"); 085 } 086 087 final byte[] segmentData = readBytes("Segment Data", 088 is, segmentLength - 2, 089 "Invalid Segment: insufficient data"); 090 091 if (!visitor.visitSegment(marker, markerBytes, segmentLength, segmentLengthBytes, segmentData)) { 092 return; 093 } 094 } 095 096 Debug.debug(markerCount + " markers"); 097 } 098 } 099 100 public static String getMarkerName(final int marker) { 101 switch (marker) { 102 case JpegConstants.SOS_MARKER: 103 return "SOS_MARKER"; 104 // case JPEG_APP0 : 105 // return "JPEG_APP0"; 106 // case JPEG_APP0_MARKER : 107 // return "JPEG_APP0_MARKER"; 108 case JpegConstants.JPEG_APP1_MARKER: 109 return "JPEG_APP1_MARKER"; 110 case JpegConstants.JPEG_APP2_MARKER: 111 return "JPEG_APP2_MARKER"; 112 case JpegConstants.JPEG_APP13_MARKER: 113 return "JPEG_APP13_MARKER"; 114 case JpegConstants.JPEG_APP14_MARKER: 115 return "JPEG_APP14_MARKER"; 116 case JpegConstants.JPEG_APP15_MARKER: 117 return "JPEG_APP15_MARKER"; 118 case JpegConstants.JFIF_MARKER: 119 return "JFIF_MARKER"; 120 case JpegConstants.SOF0_MARKER: 121 return "SOF0_MARKER"; 122 case JpegConstants.SOF1_MARKER: 123 return "SOF1_MARKER"; 124 case JpegConstants.SOF2_MARKER: 125 return "SOF2_MARKER"; 126 case JpegConstants.SOF3_MARKER: 127 return "SOF3_MARKER"; 128 case JpegConstants.DHT_MARKER: 129 return "SOF4_MARKER"; 130 case JpegConstants.SOF5_MARKER: 131 return "SOF5_MARKER"; 132 case JpegConstants.SOF6_MARKER: 133 return "SOF6_MARKER"; 134 case JpegConstants.SOF7_MARKER: 135 return "SOF7_MARKER"; 136 case JpegConstants.SOF8_MARKER: 137 return "SOF8_MARKER"; 138 case JpegConstants.SOF9_MARKER: 139 return "SOF9_MARKER"; 140 case JpegConstants.SOF10_MARKER: 141 return "SOF10_MARKER"; 142 case JpegConstants.SOF11_MARKER: 143 return "SOF11_MARKER"; 144 case JpegConstants.DAC_MARKER: 145 return "DAC_MARKER"; 146 case JpegConstants.SOF13_MARKER: 147 return "SOF13_MARKER"; 148 case JpegConstants.SOF14_MARKER: 149 return "SOF14_MARKER"; 150 case JpegConstants.SOF15_MARKER: 151 return "SOF15_MARKER"; 152 case JpegConstants.DQT_MARKER: 153 return "DQT_MARKER"; 154 case JpegConstants.DRI_MARKER: 155 return "DRI_MARKER"; 156 case JpegConstants.RST0_MARKER: 157 return "RST0_MARKER"; 158 case JpegConstants.RST1_MARKER: 159 return "RST1_MARKER"; 160 case JpegConstants.RST2_MARKER: 161 return "RST2_MARKER"; 162 case JpegConstants.RST3_MARKER: 163 return "RST3_MARKER"; 164 case JpegConstants.RST4_MARKER: 165 return "RST4_MARKER"; 166 case JpegConstants.RST5_MARKER: 167 return "RST5_MARKER"; 168 case JpegConstants.RST6_MARKER: 169 return "RST6_MARKER"; 170 case JpegConstants.RST7_MARKER: 171 return "RST7_MARKER"; 172 default: 173 return "Unknown"; 174 } 175 } 176 177 public void dumpJFIF(final ByteSource byteSource) throws ImageReadException, 178 IOException { 179 final Visitor visitor = new Visitor() { 180 // return false to exit before reading image data. 181 @Override 182 public boolean beginSOS() { 183 return true; 184 } 185 186 @Override 187 public void visitSOS(final int marker, final byte[] markerBytes, final byte[] imageData) { 188 Debug.debug("SOS marker. " + imageData.length + " bytes of image data."); 189 Debug.debug(""); 190 } 191 192 // return false to exit traversal. 193 @Override 194 public boolean visitSegment(final int marker, final byte[] markerBytes, 195 final int segmentLength, final byte[] segmentLengthBytes, 196 final byte[] segmentData) { 197 Debug.debug("Segment marker: " + Integer.toHexString(marker) 198 + " (" + getMarkerName(marker) + "), " 199 + segmentData.length + " bytes of segment data."); 200 return true; 201 } 202 }; 203 204 traverseJFIF(byteSource, visitor); 205 } 206}