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}