org.apache.commons.imaging.formats.jpeg.JpegImageParser.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.commons.imaging.formats.jpeg.JpegImageParser.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.imaging.formats.jpeg;

import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.imaging.ImageFormat;
import org.apache.commons.imaging.ImageInfo;
import org.apache.commons.imaging.ImageParser;
import org.apache.commons.imaging.ImageReadException;
import org.apache.commons.imaging.common.ByteOrder;
import org.apache.commons.imaging.common.IImageMetadata;
import org.apache.commons.imaging.common.bytesource.ByteSource;
import org.apache.commons.imaging.formats.jpeg.decoder.JpegDecoder;
import org.apache.commons.imaging.formats.jpeg.iptc.IptcParser;
import org.apache.commons.imaging.formats.jpeg.iptc.PhotoshopApp13Data;
import org.apache.commons.imaging.formats.jpeg.segments.App13Segment;
import org.apache.commons.imaging.formats.jpeg.segments.App14Segment;
import org.apache.commons.imaging.formats.jpeg.segments.App2Segment;
import org.apache.commons.imaging.formats.jpeg.segments.ComSegment;
import org.apache.commons.imaging.formats.jpeg.segments.DqtSegment;
import org.apache.commons.imaging.formats.jpeg.segments.GenericSegment;
import org.apache.commons.imaging.formats.jpeg.segments.JfifSegment;
import org.apache.commons.imaging.formats.jpeg.segments.Segment;
import org.apache.commons.imaging.formats.jpeg.segments.SofnSegment;
import org.apache.commons.imaging.formats.jpeg.segments.UnknownSegment;
import org.apache.commons.imaging.formats.jpeg.xmp.JpegXmpParser;
import org.apache.commons.imaging.formats.tiff.TiffField;
import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
import org.apache.commons.imaging.formats.tiff.TiffImageParser;
import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
import org.apache.commons.imaging.util.Debug;

public class JpegImageParser extends ImageParser implements JpegConstants {
    public JpegImageParser() {
        setByteOrder(ByteOrder.BIG_ENDIAN);
        // setDebug(true);
    }

    @Override
    protected ImageFormat[] getAcceptedTypes() {
        return new ImageFormat[] { ImageFormat.IMAGE_FORMAT_JPEG, //
        };
    }

    @Override
    public String getName() {
        return "Jpeg-Custom";
    }

    @Override
    public String getDefaultExtension() {
        return DEFAULT_EXTENSION;
    }

    private static final String DEFAULT_EXTENSION = ".jpg";

    private static final String ACCEPTED_EXTENSIONS[] = { ".jpg", ".jpeg", };

    @Override
    protected String[] getAcceptedExtensions() {
        return ACCEPTED_EXTENSIONS;
    }

    @Override
    public final BufferedImage getBufferedImage(final ByteSource byteSource, final Map<String, Object> params)
            throws ImageReadException, IOException {
        final JpegDecoder jpegDecoder = new JpegDecoder();
        return jpegDecoder.decode(byteSource);
    }

    private boolean keepMarker(final int marker, final int markers[]) {
        if (markers == null) {
            return true;
        }

        for (final int marker2 : markers) {
            if (marker2 == marker) {
                return true;
            }
        }

        return false;
    }

    public List<Segment> readSegments(final ByteSource byteSource, final int markers[],
            final boolean returnAfterFirst, final boolean readEverything) throws ImageReadException, IOException {
        final List<Segment> result = new ArrayList<Segment>();
        final JpegImageParser parser = this;
        final int[] sofnSegments = {
                // kJFIFMarker,
                SOF0Marker,

                SOF1Marker, SOF2Marker, SOF3Marker, SOF5Marker, SOF6Marker, SOF7Marker, SOF9Marker, SOF10Marker,
                SOF11Marker, SOF13Marker, SOF14Marker, SOF15Marker, };

        final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
            // return false to exit before reading image data.
            public boolean beginSOS() {
                return false;
            }

            public void visitSOS(final int marker, final byte markerBytes[], final byte imageData[]) {
            }

            // return false to exit traversal.
            public boolean visitSegment(final int marker, final byte markerBytes[], final int markerLength,
                    final byte markerLengthBytes[], final byte segmentData[])
                    throws ImageReadException, IOException {
                if (marker == EOIMarker) {
                    return false;
                }

                // Debug.debug("visitSegment marker", marker);
                // // Debug.debug("visitSegment keepMarker(marker, markers)",
                // keepMarker(marker, markers));
                // Debug.debug("visitSegment keepMarker(marker, markers)",
                // keepMarker(marker, markers));

                if (!keepMarker(marker, markers)) {
                    return true;
                }

                if (marker == JPEG_APP13_Marker) {
                    // Debug.debug("app 13 segment data", segmentData.length);
                    result.add(new App13Segment(parser, marker, segmentData));
                } else if (marker == JPEG_APP14_Marker) {
                    result.add(new App14Segment(marker, segmentData));
                } else if (marker == JPEG_APP2_Marker) {
                    result.add(new App2Segment(marker, segmentData));
                } else if (marker == JFIFMarker) {
                    result.add(new JfifSegment(marker, segmentData));
                } else if (Arrays.binarySearch(sofnSegments, marker) >= 0) {
                    result.add(new SofnSegment(marker, segmentData));
                } else if (marker == DQTMarker) {
                    result.add(new DqtSegment(marker, segmentData));
                } else if ((marker >= JPEG_APP1_Marker) && (marker <= JPEG_APP15_Marker)) {
                    result.add(new UnknownSegment(marker, segmentData));
                } else if (marker == COMMarker) {
                    result.add(new ComSegment(marker, segmentData));
                }

                if (returnAfterFirst) {
                    return false;
                }

                return true;
            }
        };

        new JpegUtils().traverseJFIF(byteSource, visitor);

        return result;
    }

    public static final boolean permissive = true;

    private byte[] assembleSegments(final List<App2Segment> v) throws ImageReadException {
        try {
            return assembleSegments(v, false);
        } catch (final ImageReadException e) {
            return assembleSegments(v, true);
        }
    }

    private byte[] assembleSegments(final List<App2Segment> v, final boolean start_with_zero)
            throws ImageReadException {
        if (v.size() < 1) {
            throw new ImageReadException("No App2 Segments Found.");
        }

        final int markerCount = v.get(0).num_markers;

        // if (permissive && (markerCount == 0))
        // markerCount = v.size();

        if (v.size() != markerCount) {
            throw new ImageReadException(
                    "App2 Segments Missing.  Found: " + v.size() + ", Expected: " + markerCount + ".");
        }

        Collections.sort(v);

        final int offset = start_with_zero ? 0 : 1;

        int total = 0;
        for (int i = 0; i < v.size(); i++) {
            final App2Segment segment = v.get(i);

            if ((i + offset) != segment.cur_marker) {
                dumpSegments(v);
                throw new ImageReadException("Incoherent App2 Segment Ordering.  i: " + i + ", segment[" + i
                        + "].cur_marker: " + segment.cur_marker + ".");
            }

            if (markerCount != segment.num_markers) {
                dumpSegments(v);
                throw new ImageReadException("Inconsistent App2 Segment Count info.  markerCount: " + markerCount
                        + ", segment[" + i + "].num_markers: " + segment.num_markers + ".");
            }

            total += segment.icc_bytes.length;
        }

        final byte result[] = new byte[total];
        int progress = 0;

        for (int i = 0; i < v.size(); i++) {
            final App2Segment segment = v.get(i);

            System.arraycopy(segment.icc_bytes, 0, result, progress, segment.icc_bytes.length);
            progress += segment.icc_bytes.length;
        }

        return result;
    }

    private void dumpSegments(final List<? extends Segment> v) {
        Debug.debug();
        Debug.debug("dumpSegments", v.size());

        for (int i = 0; i < v.size(); i++) {
            final App2Segment segment = (App2Segment) v.get(i);

            Debug.debug((i) + ": " + segment.cur_marker + " / " + segment.num_markers);
        }
        Debug.debug();
    }

    public List<Segment> readSegments(final ByteSource byteSource, final int markers[],
            final boolean returnAfterFirst) throws ImageReadException, IOException {
        return readSegments(byteSource, markers, returnAfterFirst, false);
    }

    @Override
    public byte[] getICCProfileBytes(final ByteSource byteSource, final Map<String, Object> params)
            throws ImageReadException, IOException {
        final List<Segment> segments = readSegments(byteSource, new int[] { JPEG_APP2_Marker, }, false);

        final List<App2Segment> filtered = new ArrayList<App2Segment>();
        if (segments != null) {
            // throw away non-icc profile app2 segments.
            for (int i = 0; i < segments.size(); i++) {
                final App2Segment segment = (App2Segment) segments.get(i);
                if (segment.icc_bytes != null) {
                    filtered.add(segment);
                }
            }
        }

        if (filtered.size() < 1) {
            return null;
        }

        final byte bytes[] = assembleSegments(filtered);

        if (debug) {
            System.out.println("bytes" + ": " + bytes.length);
        }

        if (debug) {
            System.out.println("");
        }

        return (bytes);
    }

    @Override
    public IImageMetadata getMetadata(final ByteSource byteSource, final Map<String, Object> params)
            throws ImageReadException, IOException {
        final TiffImageMetadata exif = getExifMetadata(byteSource, params);

        final JpegPhotoshopMetadata photoshop = getPhotoshopMetadata(byteSource, params);

        if (null == exif && null == photoshop) {
            return null;
        }

        final JpegImageMetadata result = new JpegImageMetadata(photoshop, exif);

        return result;
    }

    public static boolean isExifAPP1Segment(final GenericSegment segment) {
        return startsWith(segment.bytes, EXIF_IDENTIFIER_CODE);
    }

    private List<Segment> filterAPP1Segments(final List<Segment> v) {
        final List<Segment> result = new ArrayList<Segment>();

        for (int i = 0; i < v.size(); i++) {
            final GenericSegment segment = (GenericSegment) v.get(i);
            if (isExifAPP1Segment(segment)) {
                result.add(segment);
            }
        }

        return result;
    }

    public TiffImageMetadata getExifMetadata(final ByteSource byteSource, Map<String, Object> params)
            throws ImageReadException, IOException {
        final byte bytes[] = getExifRawData(byteSource);
        if (null == bytes) {
            return null;
        }

        if (params == null) {
            params = new HashMap<String, Object>();
        }
        if (!params.containsKey(PARAM_KEY_READ_THUMBNAILS)) {
            params.put(PARAM_KEY_READ_THUMBNAILS, Boolean.TRUE);
        }

        return (TiffImageMetadata) new TiffImageParser().getMetadata(bytes, params);
    }

    public byte[] getExifRawData(final ByteSource byteSource) throws ImageReadException, IOException {
        final List<Segment> segments = readSegments(byteSource, new int[] { JPEG_APP1_Marker, }, false);

        if ((segments == null) || (segments.size() < 1)) {
            return null;
        }

        final List<Segment> exifSegments = filterAPP1Segments(segments);
        if (debug) {
            System.out.println("exif_segments.size" + ": " + exifSegments.size());
        }

        // Debug.debug("segments", segments);
        // Debug.debug("exifSegments", exifSegments);

        // TODO: concatenate if multiple segments, need example.
        if (exifSegments.size() < 1) {
            return null;
        }
        if (exifSegments.size() > 1) {
            throw new ImageReadException(
                    "Sanselan currently can't parse EXIF metadata split across multiple APP1 segments.  "
                            + "Please send this image to the Sanselan project.");
        }

        final GenericSegment segment = (GenericSegment) exifSegments.get(0);
        final byte bytes[] = segment.bytes;

        // byte head[] = readBytearray("exif head", bytes, 0, 6);
        //
        // Debug.debug("head", head);

        return remainingBytes("trimmed exif bytes", bytes, 6);
    }

    public boolean hasExifSegment(final ByteSource byteSource) throws ImageReadException, IOException {
        final boolean result[] = { false, };

        final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
            // return false to exit before reading image data.
            public boolean beginSOS() {
                return false;
            }

            public void visitSOS(final int marker, final byte markerBytes[], final byte imageData[]) {
            }

            // return false to exit traversal.
            public boolean visitSegment(final int marker, final byte markerBytes[], final int markerLength,
                    final byte markerLengthBytes[], final byte segmentData[])
                    throws ImageReadException, IOException {
                if (marker == 0xffd9) {
                    return false;
                }

                if (marker == JPEG_APP1_Marker) {
                    if (startsWith(segmentData, EXIF_IDENTIFIER_CODE)) {
                        result[0] = true;
                        return false;
                    }
                }

                return true;
            }
        };

        new JpegUtils().traverseJFIF(byteSource, visitor);

        return result[0];
    }

    public boolean hasIptcSegment(final ByteSource byteSource) throws ImageReadException, IOException {
        final boolean result[] = { false, };

        final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
            // return false to exit before reading image data.
            public boolean beginSOS() {
                return false;
            }

            public void visitSOS(final int marker, final byte markerBytes[], final byte imageData[]) {
            }

            // return false to exit traversal.
            public boolean visitSegment(final int marker, final byte markerBytes[], final int markerLength,
                    final byte markerLengthBytes[], final byte segmentData[])
                    throws ImageReadException, IOException {
                if (marker == 0xffd9) {
                    return false;
                }

                if (marker == JPEG_APP13_Marker) {
                    if (new IptcParser().isPhotoshopJpegSegment(segmentData)) {
                        result[0] = true;
                        return false;
                    }
                }

                return true;
            }
        };

        new JpegUtils().traverseJFIF(byteSource, visitor);

        return result[0];
    }

    public boolean hasXmpSegment(final ByteSource byteSource) throws ImageReadException, IOException {
        final boolean result[] = { false, };

        final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
            // return false to exit before reading image data.
            public boolean beginSOS() {
                return false;
            }

            public void visitSOS(final int marker, final byte markerBytes[], final byte imageData[]) {
            }

            // return false to exit traversal.
            public boolean visitSegment(final int marker, final byte markerBytes[], final int markerLength,
                    final byte markerLengthBytes[], final byte segmentData[])
                    throws ImageReadException, IOException {
                if (marker == 0xffd9) {
                    return false;
                }

                if (marker == JPEG_APP1_Marker) {
                    if (new JpegXmpParser().isXmpJpegSegment(segmentData)) {
                        result[0] = true;
                        return false;
                    }
                }

                return true;
            }
        };
        new JpegUtils().traverseJFIF(byteSource, visitor);

        return result[0];
    }

    /**
     * Extracts embedded XML metadata as XML string.
     * <p>
     * 
     * @param byteSource
     *            File containing image data.
     * @param params
     *            Map of optional parameters, defined in SanselanConstants.
     * @return Xmp Xml as String, if present. Otherwise, returns null.
     */
    @Override
    public String getXmpXml(final ByteSource byteSource, final Map<String, Object> params)
            throws ImageReadException, IOException {

        final List<String> result = new ArrayList<String>();

        final JpegUtils.Visitor visitor = new JpegUtils.Visitor() {
            // return false to exit before reading image data.
            public boolean beginSOS() {
                return false;
            }

            public void visitSOS(final int marker, final byte markerBytes[], final byte imageData[]) {
            }

            // return false to exit traversal.
            public boolean visitSegment(final int marker, final byte markerBytes[], final int markerLength,
                    final byte markerLengthBytes[], final byte segmentData[])
                    throws ImageReadException, IOException {
                if (marker == 0xffd9) {
                    return false;
                }

                if (marker == JPEG_APP1_Marker) {
                    if (new JpegXmpParser().isXmpJpegSegment(segmentData)) {
                        result.add(new JpegXmpParser().parseXmpJpegSegment(segmentData));
                        return false;
                    }
                }

                return true;
            }
        };
        new JpegUtils().traverseJFIF(byteSource, visitor);

        if (result.size() < 1) {
            return null;
        }
        if (result.size() > 1) {
            throw new ImageReadException("Jpeg file contains more than one XMP segment.");
        }
        return result.get(0);
    }

    public JpegPhotoshopMetadata getPhotoshopMetadata(final ByteSource byteSource, final Map<String, Object> params)
            throws ImageReadException, IOException {
        final List<Segment> segments = readSegments(byteSource, new int[] { JPEG_APP13_Marker, }, false);

        if ((segments == null) || (segments.size() < 1)) {
            return null;
        }

        PhotoshopApp13Data photoshopApp13Data = null;

        for (int i = 0; i < segments.size(); i++) {
            final App13Segment segment = (App13Segment) segments.get(i);

            final PhotoshopApp13Data data = segment.parsePhotoshopSegment(params);
            if (data != null && photoshopApp13Data != null) {
                throw new ImageReadException("Jpeg contains more than one Photoshop App13 segment.");
            }

            photoshopApp13Data = data;
        }

        if (null == photoshopApp13Data) {
            return null;
        }
        return new JpegPhotoshopMetadata(photoshopApp13Data);
    }

    @Override
    public Dimension getImageSize(final ByteSource byteSource, final Map<String, Object> params)
            throws ImageReadException, IOException {
        final List<Segment> segments = readSegments(byteSource, new int[] {
                // kJFIFMarker,
                SOF0Marker,

                SOF1Marker, SOF2Marker, SOF3Marker, SOF5Marker, SOF6Marker, SOF7Marker, SOF9Marker, SOF10Marker,
                SOF11Marker, SOF13Marker, SOF14Marker, SOF15Marker,

        }, true);

        if ((segments == null) || (segments.size() < 1)) {
            throw new ImageReadException("No JFIF Data Found.");
        }

        if (segments.size() > 1) {
            throw new ImageReadException("Redundant JFIF Data Found.");
        }

        final SofnSegment fSOFNSegment = (SofnSegment) segments.get(0);

        return new Dimension(fSOFNSegment.width, fSOFNSegment.height);
    }

    public byte[] embedICCProfile(final byte image[], final byte profile[]) {
        return null;
    }

    @Override
    public boolean embedICCProfile(final File src, final File dst, final byte profile[]) {
        return false;
    }

    @Override
    public ImageInfo getImageInfo(final ByteSource byteSource, final Map<String, Object> params)
            throws ImageReadException, IOException {
        // List allSegments = readSegments(byteSource, null, false);

        final List<Segment> SOF_segments = readSegments(byteSource, new int[] {
                // kJFIFMarker,

                SOF0Marker, SOF1Marker, SOF2Marker, SOF3Marker, SOF5Marker, SOF6Marker, SOF7Marker, SOF9Marker,
                SOF10Marker, SOF11Marker, SOF13Marker, SOF14Marker, SOF15Marker,

        }, false);

        if (SOF_segments == null) {
            throw new ImageReadException("No SOFN Data Found.");
        }

        // if (SOF_segments.size() != 1)
        // System.out.println("Incoherent SOFN Data Found: "
        // + SOF_segments.size());

        final List<Segment> jfifSegments = readSegments(byteSource, new int[] { JFIFMarker, }, true);

        final SofnSegment fSOFNSegment = (SofnSegment) SOF_segments.get(0);
        // SofnSegment fSOFNSegment = (SofnSegment) findSegment(segments,
        // SOFNmarkers);

        if (fSOFNSegment == null) {
            throw new ImageReadException("No SOFN Data Found.");
        }

        final int Width = fSOFNSegment.width;
        final int Height = fSOFNSegment.height;

        JfifSegment jfifSegment = null;

        if ((jfifSegments != null) && (jfifSegments.size() > 0)) {
            jfifSegment = (JfifSegment) jfifSegments.get(0);
        }

        final List<Segment> app14Segments = readSegments(byteSource, new int[] { JPEG_APP14_Marker }, true);
        App14Segment app14Segment = null;
        if (app14Segments != null && !app14Segments.isEmpty()) {
            app14Segment = (App14Segment) app14Segments.get(0);
        }

        // JfifSegment fTheJFIFSegment = (JfifSegment) findSegment(segments,
        // kJFIFMarker);

        double x_density = -1.0;
        double y_density = -1.0;
        double units_per_inch = -1.0;
        // int JFIF_major_version;
        // int JFIF_minor_version;
        String FormatDetails;

        if (jfifSegment != null) {
            x_density = jfifSegment.xDensity;
            y_density = jfifSegment.yDensity;
            final int density_units = jfifSegment.densityUnits;
            // JFIF_major_version = fTheJFIFSegment.JFIF_major_version;
            // JFIF_minor_version = fTheJFIFSegment.JFIF_minor_version;

            FormatDetails = "Jpeg/JFIF v." + jfifSegment.jfifMajorVersion + "." + jfifSegment.jfifMinorVersion;

            switch (density_units) {
            case 0:
                break;
            case 1: // inches
                units_per_inch = 1.0;
                break;
            case 2: // cms
                units_per_inch = 2.54;
                break;
            default:
                break;
            }
        } else {
            final JpegImageMetadata metadata = (JpegImageMetadata) getMetadata(byteSource, params);

            if (metadata != null) {
                {
                    final TiffField field = metadata.findEXIFValue(TiffTagConstants.TIFF_TAG_XRESOLUTION);
                    if (field != null) {
                        x_density = ((Number) field.getValue()).doubleValue();
                    }
                }
                {
                    final TiffField field = metadata.findEXIFValue(TiffTagConstants.TIFF_TAG_YRESOLUTION);
                    if (field != null) {
                        y_density = ((Number) field.getValue()).doubleValue();
                    }
                }
                {
                    final TiffField field = metadata.findEXIFValue(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT);
                    if (field != null) {
                        final int density_units = ((Number) field.getValue()).intValue();

                        switch (density_units) {
                        case 1:
                            break;
                        case 2: // inches
                            units_per_inch = 1.0;
                            break;
                        case 3: // cms
                            units_per_inch = 2.54;
                            break;
                        default:
                            break;
                        }
                    }

                }
            }

            FormatDetails = "Jpeg/DCM";

        }

        int PhysicalHeightDpi = -1;
        float PhysicalHeightInch = -1;
        int PhysicalWidthDpi = -1;
        float PhysicalWidthInch = -1;

        if (units_per_inch > 0) {
            PhysicalWidthDpi = (int) Math.round(x_density * units_per_inch);
            PhysicalWidthInch = (float) (Width / (x_density * units_per_inch));
            PhysicalHeightDpi = (int) Math.round(y_density * units_per_inch);
            PhysicalHeightInch = (float) (Height / (y_density * units_per_inch));
        }

        final List<String> Comments = new ArrayList<String>();
        final List<Segment> commentSegments = readSegments(byteSource, new int[] { COMMarker }, false);
        for (int i = 0; i < commentSegments.size(); i++) {
            final ComSegment comSegment = (ComSegment) commentSegments.get(i);
            String comment = "";
            try {
                comment = new String(comSegment.comment, "UTF-8");
            } catch (final UnsupportedEncodingException cannotHappen) {
            }
            Comments.add(comment);
        }

        final int Number_of_components = fSOFNSegment.numberOfComponents;
        final int Precision = fSOFNSegment.precision;

        final int BitsPerPixel = Number_of_components * Precision;
        final ImageFormat Format = ImageFormat.IMAGE_FORMAT_JPEG;
        final String FormatName = "JPEG (Joint Photographic Experts Group) Format";
        final String MimeType = "image/jpeg";
        // we ought to count images, but don't yet.
        final int NumberOfImages = 1;
        // not accurate ... only reflects first
        final boolean isProgressive = fSOFNSegment.marker == SOF2Marker;

        boolean isTransparent = false;
        final boolean usesPalette = false; // TODO: inaccurate.

        // See http://docs.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#color
        int colorType = ImageInfo.COLOR_TYPE_UNKNOWN;
        // Some images have both JFIF/APP0 and APP14.
        // JFIF is meant to win but in them APP14 is clearly right, so make it win.
        if (app14Segment != null && app14Segment.isAdobeJpegSegment()) {
            final int colorTransform = app14Segment.getAdobeColorTransform();
            if (colorTransform == App14Segment.ADOBE_COLOR_TRANSFORM_UNKNOWN) {
                if (Number_of_components == 3) {
                    colorType = ImageInfo.COLOR_TYPE_RGB;
                } else if (Number_of_components == 4) {
                    colorType = ImageInfo.COLOR_TYPE_CMYK;
                }
            } else if (colorTransform == App14Segment.ADOBE_COLOR_TRANSFORM_YCbCr) {
                colorType = ImageInfo.COLOR_TYPE_YCbCr;
            } else if (colorTransform == App14Segment.ADOBE_COLOR_TRANSFORM_YCCK) {
                colorType = ImageInfo.COLOR_TYPE_YCCK;
            }
        } else if (jfifSegment != null) {
            if (Number_of_components == 1) {
                colorType = ImageInfo.COLOR_TYPE_GRAYSCALE;
            } else if (Number_of_components == 3) {
                colorType = ImageInfo.COLOR_TYPE_YCbCr;
            }
        } else {
            if (Number_of_components == 1) {
                colorType = ImageInfo.COLOR_TYPE_GRAYSCALE;
            } else if (Number_of_components == 2) {
                colorType = ImageInfo.COLOR_TYPE_GRAYSCALE;
                isTransparent = true;
            } else if (Number_of_components == 3 || Number_of_components == 4) {
                boolean have1 = false;
                boolean have2 = false;
                boolean have3 = false;
                boolean have4 = false;
                boolean haveOther = false;
                for (final SofnSegment.Component component : fSOFNSegment.components) {
                    final int id = component.componentIdentifier;
                    if (id == 1) {
                        have1 = true;
                    } else if (id == 2) {
                        have2 = true;
                    } else if (id == 3) {
                        have3 = true;
                    } else if (id == 4) {
                        have4 = true;
                    } else {
                        haveOther = true;
                    }
                }
                if (Number_of_components == 3 && have1 && have2 && have3 && !have4 && !haveOther) {
                    colorType = ImageInfo.COLOR_TYPE_YCbCr;
                } else if (Number_of_components == 4 && have1 && have2 && have3 && have4 && !haveOther) {
                    colorType = ImageInfo.COLOR_TYPE_YCbCr;
                    isTransparent = true;
                } else {
                    boolean haveR = false;
                    boolean haveG = false;
                    boolean haveB = false;
                    boolean haveA = false;
                    boolean haveC = false;
                    boolean havec = false;
                    boolean haveY = false;
                    for (final SofnSegment.Component component : fSOFNSegment.components) {
                        final int id = component.componentIdentifier;
                        if (id == 'R') {
                            haveR = true;
                        } else if (id == 'G') {
                            haveG = true;
                        } else if (id == 'B') {
                            haveB = true;
                        } else if (id == 'A') {
                            haveA = true;
                        } else if (id == 'C') {
                            haveC = true;
                        } else if (id == 'c') {
                            havec = true;
                        } else if (id == 'Y') {
                            haveY = true;
                        }
                    }
                    if (haveR && haveG && haveB && !haveA && !haveC && !havec && !haveY) {
                        colorType = ImageInfo.COLOR_TYPE_RGB;
                    } else if (haveR && haveG && haveB && haveA && !haveC && !havec && !haveY) {
                        colorType = ImageInfo.COLOR_TYPE_RGB;
                        isTransparent = true;
                    } else if (haveY && haveC && havec && !haveR && !haveG && !haveB && !haveA) {
                        colorType = ImageInfo.COLOR_TYPE_YCC;
                    } else if (haveY && haveC && havec && haveA && !haveR && !haveG && !haveB) {
                        colorType = ImageInfo.COLOR_TYPE_YCC;
                        isTransparent = true;
                    } else {
                        int minHorizontalSamplingFactor = Integer.MAX_VALUE;
                        int maxHorizontalSmaplingFactor = Integer.MIN_VALUE;
                        int minVerticalSamplingFactor = Integer.MAX_VALUE;
                        int maxVerticalSamplingFactor = Integer.MIN_VALUE;
                        for (final SofnSegment.Component component : fSOFNSegment.components) {
                            if (minHorizontalSamplingFactor > component.horizontalSamplingFactor) {
                                minHorizontalSamplingFactor = component.horizontalSamplingFactor;
                            }
                            if (maxHorizontalSmaplingFactor < component.horizontalSamplingFactor) {
                                maxHorizontalSmaplingFactor = component.horizontalSamplingFactor;
                            }
                            if (minVerticalSamplingFactor > component.verticalSamplingFactor) {
                                minVerticalSamplingFactor = component.verticalSamplingFactor;
                            }
                            if (maxVerticalSamplingFactor < component.verticalSamplingFactor) {
                                maxVerticalSamplingFactor = component.verticalSamplingFactor;
                            }
                        }
                        final boolean isSubsampled = (minHorizontalSamplingFactor != maxHorizontalSmaplingFactor)
                                || (minVerticalSamplingFactor != maxVerticalSamplingFactor);
                        if (Number_of_components == 3) {
                            if (isSubsampled) {
                                colorType = ImageInfo.COLOR_TYPE_YCbCr;
                            } else {
                                colorType = ImageInfo.COLOR_TYPE_RGB;
                            }
                        } else if (Number_of_components == 4) {
                            if (isSubsampled) {
                                colorType = ImageInfo.COLOR_TYPE_YCCK;
                            } else {
                                colorType = ImageInfo.COLOR_TYPE_CMYK;
                            }
                        }
                    }
                }
            }
        }

        final String compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_JPEG;

        final ImageInfo result = new ImageInfo(FormatDetails, BitsPerPixel, Comments, Format, FormatName, Height,
                MimeType, NumberOfImages, PhysicalHeightDpi, PhysicalHeightInch, PhysicalWidthDpi,
                PhysicalWidthInch, Width, isProgressive, isTransparent, usesPalette, colorType,
                compressionAlgorithm);

        return result;
    }

    // public ImageInfo getImageInfo(ByteSource byteSource, Map params)
    // throws ImageReadException, IOException
    // {
    //
    // List allSegments = readSegments(byteSource, null, false);
    //
    // final int SOF_MARKERS[] = new int[]{
    // SOF0Marker, SOF1Marker, SOF2Marker, SOF3Marker, SOF5Marker,
    // SOF6Marker, SOF7Marker, SOF9Marker, SOF10Marker, SOF11Marker,
    // SOF13Marker, SOF14Marker, SOF15Marker,
    // };
    //
    // List sofMarkers = new ArrayList();
    // for(int i=0;i<SOF_MARKERS.length;i++)
    // sofMarkers.add(new Integer(SOF_MARKERS[i]));
    // List SOFSegments = filterSegments(allSegments, sofMarkers);
    // if (SOFSegments == null || SOFSegments.size()<1)
    // throw new ImageReadException("No SOFN Data Found.");
    //
    // List jfifMarkers = new ArrayList();
    // jfifMarkers.add(new Integer(JFIFMarker));
    // List jfifSegments = filterSegments(allSegments, jfifMarkers);
    //
    // SofnSegment firstSOFNSegment = (SofnSegment) SOFSegments.get(0);
    //
    // int Width = firstSOFNSegment.width;
    // int Height = firstSOFNSegment.height;
    //
    // JfifSegment jfifSegment = null;
    //
    // if (jfifSegments != null && jfifSegments.size() > 0)
    // jfifSegment = (JfifSegment) jfifSegments.get(0);
    //
    // double x_density = -1.0;
    // double y_density = -1.0;
    // double units_per_inch = -1.0;
    // // int JFIF_major_version;
    // // int JFIF_minor_version;
    // String FormatDetails;
    //
    // if (jfifSegment != null)
    // {
    // x_density = jfifSegment.xDensity;
    // y_density = jfifSegment.yDensity;
    // int density_units = jfifSegment.densityUnits;
    // // JFIF_major_version = fTheJFIFSegment.JFIF_major_version;
    // // JFIF_minor_version = fTheJFIFSegment.JFIF_minor_version;
    //
    // FormatDetails = "Jpeg/JFIF v." + jfifSegment.jfifMajorVersion
    // + "." + jfifSegment.jfifMinorVersion;
    //
    // switch (density_units)
    // {
    // case 0 :
    // break;
    // case 1 : // inches
    // units_per_inch = 1.0;
    // break;
    // case 2 : // cms
    // units_per_inch = 2.54;
    // break;
    // default :
    // break;
    // }
    // }
    // else
    // {
    // JpegImageMetadata metadata = (JpegImageMetadata) getMetadata(byteSource,
    // params);
    //
    // {
    // TiffField field = metadata
    // .findEXIFValue(TiffField.TIFF_TAG_XRESOLUTION);
    // if (field == null)
    // throw new ImageReadException("No XResolution");
    //
    // x_density = ((Number) field.getValue()).doubleValue();
    // }
    // {
    // TiffField field = metadata
    // .findEXIFValue(TiffField.TIFF_TAG_YRESOLUTION);
    // if (field == null)
    // throw new ImageReadException("No YResolution");
    //
    // y_density = ((Number) field.getValue()).doubleValue();
    // }
    // {
    // TiffField field = metadata
    // .findEXIFValue(TiffField.TIFF_TAG_RESOLUTION_UNIT);
    // if (field == null)
    // throw new ImageReadException("No ResolutionUnits");
    //
    // int density_units = ((Number) field.getValue()).intValue();
    //
    // switch (density_units)
    // {
    // case 1 :
    // break;
    // case 2 : // inches
    // units_per_inch = 1.0;
    // break;
    // case 3 : // cms
    // units_per_inch = 2.54;
    // break;
    // default :
    // break;
    // }
    //
    // }
    //
    // FormatDetails = "Jpeg/DCM";
    //
    // }
    //
    // int PhysicalHeightDpi = -1;
    // float PhysicalHeightInch = -1;
    // int PhysicalWidthDpi = -1;
    // float PhysicalWidthInch = -1;
    //
    // if (units_per_inch > 0)
    // {
    // PhysicalWidthDpi = (int) Math.round((double) x_density
    // / units_per_inch);
    // PhysicalWidthInch = (float) ((double) Width / (x_density *
    // units_per_inch));
    // PhysicalHeightDpi = (int) Math.round((double) y_density
    // * units_per_inch);
    // PhysicalHeightInch = (float) ((double) Height / (y_density *
    // units_per_inch));
    // }
    //
    // List Comments = new ArrayList();
    // // TODO: comments...
    //
    // int Number_of_components = firstSOFNSegment.numberOfComponents;
    // int Precision = firstSOFNSegment.precision;
    //
    // int BitsPerPixel = Number_of_components * Precision;
    // ImageFormat Format = ImageFormat.IMAGE_FORMAT_JPEG;
    // String FormatName = "JPEG (Joint Photographic Experts Group) Format";
    // String MimeType = "image/jpeg";
    // // we ought to count images, but don't yet.
    // int NumberOfImages = -1;
    // // not accurate ... only reflects first
    // boolean isProgressive = firstSOFNSegment.marker == SOF2Marker;
    //
    // boolean isTransparent = false; // TODO: inaccurate.
    // boolean usesPalette = false; // TODO: inaccurate.
    // int ColorType;
    // if (Number_of_components == 1)
    // ColorType = ImageInfo.COLOR_TYPE_BW;
    // else if (Number_of_components == 3)
    // ColorType = ImageInfo.COLOR_TYPE_RGB;
    // else if (Number_of_components == 4)
    // ColorType = ImageInfo.COLOR_TYPE_CMYK;
    // else
    // ColorType = ImageInfo.COLOR_TYPE_UNKNOWN;
    //
    // String compressionAlgorithm = ImageInfo.COMPRESSION_ALGORITHM_JPEG;
    //
    // ImageInfo result = new ImageInfo(FormatDetails, BitsPerPixel, Comments,
    // Format, FormatName, Height, MimeType, NumberOfImages,
    // PhysicalHeightDpi, PhysicalHeightInch, PhysicalWidthDpi,
    // PhysicalWidthInch, Width, isProgressive, isTransparent,
    // usesPalette, ColorType, compressionAlgorithm);
    //
    // return result;
    // }

    @Override
    public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource)
            throws ImageReadException, IOException {
        pw.println("tiff.dumpImageFile");

        {
            final ImageInfo imageInfo = getImageInfo(byteSource);
            if (imageInfo == null) {
                return false;
            }

            imageInfo.toString(pw, "");
        }

        pw.println("");

        {
            final List<Segment> segments = readSegments(byteSource, null, false);

            if (segments == null) {
                throw new ImageReadException("No Segments Found.");
            }

            for (int d = 0; d < segments.size(); d++) {

                final Segment segment = segments.get(d);

                final NumberFormat nf = NumberFormat.getIntegerInstance();
                // this.debugNumber("found, marker: ", marker, 4);
                pw.println(d + ": marker: " + Integer.toHexString(segment.marker) + ", " + segment.getDescription()
                        + " (length: " + nf.format(segment.length) + ")");
                segment.dump(pw);
            }

            pw.println("");
        }

        return true;
    }

}