    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}