    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}