View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.formats.jpeg;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  
22  import org.apache.commons.imaging.ImagingException;
23  import org.apache.commons.imaging.bytesource.ByteSource;
24  import org.apache.commons.imaging.common.BinaryFileParser;
25  import org.apache.commons.imaging.common.BinaryFunctions;
26  import org.apache.commons.imaging.common.ByteConversions;
27  import org.apache.commons.imaging.internal.Debug;
28  import org.apache.commons.io.IOUtils;
29  
30  public class JpegUtils extends BinaryFileParser {
31      public interface Visitor {
32          // return false to exit before reading image data.
33          boolean beginSos();
34  
35          // return false to exit traversal.
36          boolean visitSegment(int marker, byte[] markerBytes, int segmentLength, byte[] segmentLengthBytes, byte[] segmentData)
37                  throws ImagingException, IOException;
38  
39          void visitSos(int marker, byte[] markerBytes, byte[] imageData);
40      }
41  
42      public static String getMarkerName(final int marker) {
43          switch (marker) {
44          case JpegConstants.SOS_MARKER:
45              return "SOS_MARKER";
46          // case JPEG_APP0 :
47          // return "JPEG_APP0";
48          // case JPEG_APP0_MARKER :
49          // return "JPEG_APP0_MARKER";
50          case JpegConstants.JPEG_APP1_MARKER:
51              return "JPEG_APP1_MARKER";
52          case JpegConstants.JPEG_APP2_MARKER:
53              return "JPEG_APP2_MARKER";
54          case JpegConstants.JPEG_APP13_MARKER:
55              return "JPEG_APP13_MARKER";
56          case JpegConstants.JPEG_APP14_MARKER:
57              return "JPEG_APP14_MARKER";
58          case JpegConstants.JPEG_APP15_MARKER:
59              return "JPEG_APP15_MARKER";
60          case JpegConstants.JFIF_MARKER:
61              return "JFIF_MARKER";
62          case JpegConstants.SOF0_MARKER:
63              return "SOF0_MARKER";
64          case JpegConstants.SOF1_MARKER:
65              return "SOF1_MARKER";
66          case JpegConstants.SOF2_MARKER:
67              return "SOF2_MARKER";
68          case JpegConstants.SOF3_MARKER:
69              return "SOF3_MARKER";
70          case JpegConstants.DHT_MARKER:
71              return "SOF4_MARKER";
72          case JpegConstants.SOF5_MARKER:
73              return "SOF5_MARKER";
74          case JpegConstants.SOF6_MARKER:
75              return "SOF6_MARKER";
76          case JpegConstants.SOF7_MARKER:
77              return "SOF7_MARKER";
78          case JpegConstants.SOF8_MARKER:
79              return "SOF8_MARKER";
80          case JpegConstants.SOF9_MARKER:
81              return "SOF9_MARKER";
82          case JpegConstants.SOF10_MARKER:
83              return "SOF10_MARKER";
84          case JpegConstants.SOF11_MARKER:
85              return "SOF11_MARKER";
86          case JpegConstants.DAC_MARKER:
87              return "DAC_MARKER";
88          case JpegConstants.SOF13_MARKER:
89              return "SOF13_MARKER";
90          case JpegConstants.SOF14_MARKER:
91              return "SOF14_MARKER";
92          case JpegConstants.SOF15_MARKER:
93              return "SOF15_MARKER";
94          case JpegConstants.DQT_MARKER:
95              return "DQT_MARKER";
96          case JpegConstants.DRI_MARKER:
97              return "DRI_MARKER";
98          case JpegConstants.RST0_MARKER:
99              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 }