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