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;
021
022import org.apache.commons.imaging.ImagingException;
023import org.apache.commons.imaging.bytesource.ByteSource;
024import org.apache.commons.imaging.common.BinaryFileParser;
025import org.apache.commons.imaging.common.BinaryFunctions;
026import org.apache.commons.imaging.common.ByteConversions;
027import org.apache.commons.imaging.internal.Debug;
028import org.apache.commons.io.IOUtils;
029
030public class JpegUtils extends BinaryFileParser {
031    public interface Visitor {
032        // return false to exit before reading image data.
033        boolean beginSos();
034
035        // return false to exit traversal.
036        boolean visitSegment(int marker, byte[] markerBytes, int segmentLength, byte[] segmentLengthBytes, byte[] segmentData)
037                throws ImagingException, IOException;
038
039        void visitSos(int marker, byte[] markerBytes, byte[] imageData);
040    }
041
042    public static String getMarkerName(final int marker) {
043        switch (marker) {
044        case JpegConstants.SOS_MARKER:
045            return "SOS_MARKER";
046        // case JPEG_APP0 :
047        // return "JPEG_APP0";
048        // case JPEG_APP0_MARKER :
049        // return "JPEG_APP0_MARKER";
050        case JpegConstants.JPEG_APP1_MARKER:
051            return "JPEG_APP1_MARKER";
052        case JpegConstants.JPEG_APP2_MARKER:
053            return "JPEG_APP2_MARKER";
054        case JpegConstants.JPEG_APP13_MARKER:
055            return "JPEG_APP13_MARKER";
056        case JpegConstants.JPEG_APP14_MARKER:
057            return "JPEG_APP14_MARKER";
058        case JpegConstants.JPEG_APP15_MARKER:
059            return "JPEG_APP15_MARKER";
060        case JpegConstants.JFIF_MARKER:
061            return "JFIF_MARKER";
062        case JpegConstants.SOF0_MARKER:
063            return "SOF0_MARKER";
064        case JpegConstants.SOF1_MARKER:
065            return "SOF1_MARKER";
066        case JpegConstants.SOF2_MARKER:
067            return "SOF2_MARKER";
068        case JpegConstants.SOF3_MARKER:
069            return "SOF3_MARKER";
070        case JpegConstants.DHT_MARKER:
071            return "SOF4_MARKER";
072        case JpegConstants.SOF5_MARKER:
073            return "SOF5_MARKER";
074        case JpegConstants.SOF6_MARKER:
075            return "SOF6_MARKER";
076        case JpegConstants.SOF7_MARKER:
077            return "SOF7_MARKER";
078        case JpegConstants.SOF8_MARKER:
079            return "SOF8_MARKER";
080        case JpegConstants.SOF9_MARKER:
081            return "SOF9_MARKER";
082        case JpegConstants.SOF10_MARKER:
083            return "SOF10_MARKER";
084        case JpegConstants.SOF11_MARKER:
085            return "SOF11_MARKER";
086        case JpegConstants.DAC_MARKER:
087            return "DAC_MARKER";
088        case JpegConstants.SOF13_MARKER:
089            return "SOF13_MARKER";
090        case JpegConstants.SOF14_MARKER:
091            return "SOF14_MARKER";
092        case JpegConstants.SOF15_MARKER:
093            return "SOF15_MARKER";
094        case JpegConstants.DQT_MARKER:
095            return "DQT_MARKER";
096        case JpegConstants.DRI_MARKER:
097            return "DRI_MARKER";
098        case JpegConstants.RST0_MARKER:
099            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}