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(Integer.toString(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 default: 155 return "Unknown"; 156 } 157 } 158 159 public void dumpJFIF(final ByteSource byteSource) throws ImageReadException, 160 IOException { 161 final Visitor visitor = new Visitor() { 162 // return false to exit before reading image data. 163 @Override 164 public boolean beginSOS() { 165 return true; 166 } 167 168 @Override 169 public void visitSOS(final int marker, final byte[] markerBytes, final byte[] imageData) { 170 Debug.debug("SOS marker. " + imageData.length + " bytes of image data."); 171 Debug.debug(""); 172 } 173 174 // return false to exit traversal. 175 @Override 176 public boolean visitSegment(final int marker, final byte[] markerBytes, 177 final int segmentLength, final byte[] segmentLengthBytes, 178 final byte[] segmentData) { 179 Debug.debug("Segment marker: " + Integer.toHexString(marker) 180 + " (" + getMarkerName(marker) + "), " 181 + segmentData.length + " bytes of segment data."); 182 return true; 183 } 184 }; 185 186 traverseJFIF(byteSource, visitor); 187 } 188}