2416 lines · 96.4 KB · Java
RawDownload
/* * Copyright (c) 2001, 2004, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation.  Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.imageio.plugins.jpeg; import javax.imageio.ImageTypeSpecifier;import javax.imageio.ImageWriteParam;import javax.imageio.IIOException;import javax.imageio.stream.ImageInputStream;import javax.imageio.stream.ImageOutputStream;import javax.imageio.metadata.IIOMetadata;import javax.imageio.metadata.IIOMetadataNode;import javax.imageio.metadata.IIOMetadataFormat;import javax.imageio.metadata.IIOMetadataFormatImpl;import javax.imageio.metadata.IIOInvalidTreeException;import javax.imageio.plugins.jpeg.JPEGQTable;import javax.imageio.plugins.jpeg.JPEGHuffmanTable;import javax.imageio.plugins.jpeg.JPEGImageWriteParam; import org.w3c.dom.Node;import org.w3c.dom.NodeList;import org.w3c.dom.NamedNodeMap; import java.util.List;import java.util.ArrayList;import java.util.Arrays;import java.util.Iterator;import java.util.ListIterator;import java.io.IOException;import java.awt.color.ICC_Profile;import java.awt.color.ICC_ColorSpace;import java.awt.color.ColorSpace;import java.awt.image.ColorModel;import java.awt.Point; /** * Metadata for the JPEG plug-in. */public class JPEGMetadata extends IIOMetadata implements Cloneable {     //////// Private variables     private static final boolean debug = false;     /**     * A copy of <code>markerSequence</code>, created the first time the     * <code>markerSequence</code> is modified.  This is used by reset     * to restore the original state.     */    private List resetSequence = null;     /**     * Set to <code>true</code> when reading a thumbnail stored as     * JPEG.  This is used to enforce the prohibition of JFIF thumbnails     * containing any JFIF marker segments, and to ensure generation of     * a correct native subtree during <code>getAsTree</code>.     */    private boolean inThumb = false;     /**     * Set by the chroma node construction method to signal the     * presence or absence of an alpha channel to the transparency     * node construction method.  Used only when constructing a     * standard metadata tree.     */    private boolean hasAlpha;     //////// end of private variables     /////// Package-access variables     /**     * All data is a list of <code>MarkerSegment</code> objects.     * When accessing the list, use the tag to identify the particular     * subclass.  Any JFIF marker segment must be the first element     * of the list if it is present, and any JFXX or APP2ICC marker     * segments are subordinate to the JFIF marker segment.  This     * list is package visible so that the writer can access it.     * @see #MarkerSegment     */    List markerSequence = new ArrayList();     /**     * Indicates whether this object represents stream or image     * metadata.  Package-visible so the writer can see it.     */    final boolean isStream;     /////// End of package-access variables     /////// Constructors     /**     * Constructor containing code shared by other constructors.     */    JPEGMetadata(boolean isStream, boolean inThumb) {        super(true,  // Supports standard format              JPEG.nativeImageMetadataFormatName,  // and a native format              JPEG.nativeImageMetadataFormatClassName,              null, null);  // No other formats        this.inThumb = inThumb;        // But if we are stream metadata, adjust the variables        this.isStream = isStream;        if (isStream) {            nativeMetadataFormatName = JPEG.nativeStreamMetadataFormatName;            nativeMetadataFormatClassName =                JPEG.nativeStreamMetadataFormatClassName;        }    }     /*     * Constructs a <code>JPEGMetadata</code> object by reading the     * contents of an <code>ImageInputStream</code>.  Has package-only     * access.     *     * @param isStream A boolean indicating whether this object will be     * stream or image metadata.     * @param isThumb A boolean indicating whether this metadata object     * is for an image or for a thumbnail stored as JPEG.     * @param iis An <code>ImageInputStream</code> from which to read     * the metadata.     * @param reader The <code>JPEGImageReader</code> calling this     * constructor, to which warnings should be sent.     */    JPEGMetadata(boolean isStream,                 boolean isThumb,                 ImageInputStream iis,                 JPEGImageReader reader) throws IOException {        this(isStream, isThumb);         JPEGBuffer buffer = new JPEGBuffer(iis);         buffer.loadBuf(0);         // The first three bytes should be FF, SOI, FF        if (((buffer.buf[0] & 0xff) != 0xff)            || ((buffer.buf[1] & 0xff) != JPEG.SOI)            || ((buffer.buf[2] & 0xff) != 0xff)) {            throw new IIOException ("Image format error");        }         boolean done = false;        buffer.bufAvail -=2;  // Next byte should be the ff before a marker        buffer.bufPtr = 2;        MarkerSegment newGuy = null;        while (!done) {            byte [] buf;            int ptr;            buffer.loadBuf(1);            if (debug) {                System.out.println("top of loop");                buffer.print(10);            }            buffer.scanForFF(reader);            switch (buffer.buf[buffer.bufPtr] & 0xff) {            case 0:                if (debug) {                    System.out.println("Skipping 0");                }                buffer.bufAvail--;                buffer.bufPtr++;                break;            case JPEG.SOF0:            case JPEG.SOF1:            case JPEG.SOF2:                if (isStream) {                    throw new IIOException                        ("SOF not permitted in stream metadata");                }                newGuy = new SOFMarkerSegment(buffer);                break;            case JPEG.DQT:                newGuy = new DQTMarkerSegment(buffer);                break;            case JPEG.DHT:                newGuy = new DHTMarkerSegment(buffer);                break;            case JPEG.DRI:                newGuy = new DRIMarkerSegment(buffer);                break;            case JPEG.APP0:                // Either JFIF, JFXX, or unknown APP0                buffer.loadBuf(8); // tag, length, id                buf = buffer.buf;                ptr = buffer.bufPtr;                if ((buf[ptr+3] == 'J')                    && (buf[ptr+4] == 'F')                    && (buf[ptr+5] == 'I')                    && (buf[ptr+6] == 'F')                    && (buf[ptr+7] == 0)) {                    if (inThumb) {                        reader.warningOccurred                            (JPEGImageReader.WARNING_NO_JFIF_IN_THUMB);                        // Leave newGuy null                        // Read a dummy to skip the segment                        JFIFMarkerSegment dummy =                            new JFIFMarkerSegment(buffer);                    } else if (isStream) {                        throw new IIOException                            ("JFIF not permitted in stream metadata");                    } else if (markerSequence.isEmpty() == false) {                        throw new IIOException                            ("JFIF APP0 must be first marker after SOI");                    } else {                        newGuy = new JFIFMarkerSegment(buffer);                    }                } else if ((buf[ptr+3] == 'J')                           && (buf[ptr+4] == 'F')                           && (buf[ptr+5] == 'X')                           && (buf[ptr+6] == 'X')                           && (buf[ptr+7] == 0)) {                    if (isStream) {                        throw new IIOException                            ("JFXX not permitted in stream metadata");                    }                    if (inThumb) {                        throw new IIOException                          ("JFXX markers not allowed in JFIF JPEG thumbnail");                    }                    JFIFMarkerSegment jfif =                        (JFIFMarkerSegment) findMarkerSegment                               (JFIFMarkerSegment.class, true);                    if (jfif == null) {                        throw new IIOException                            ("JFXX encountered without prior JFIF!");                    }                    jfif.addJFXX(buffer, reader);                    // newGuy remains null                } else {                    newGuy = new MarkerSegment(buffer);                    newGuy.loadData(buffer);                }                break;            case JPEG.APP2:                // Either an ICC profile or unknown APP2                buffer.loadBuf(15); // tag, length, id                if ((buffer.buf[buffer.bufPtr+3] == 'I')                    && (buffer.buf[buffer.bufPtr+4] == 'C')                    && (buffer.buf[buffer.bufPtr+5] == 'C')                    && (buffer.buf[buffer.bufPtr+6] == '_')                    && (buffer.buf[buffer.bufPtr+7] == 'P')                    && (buffer.buf[buffer.bufPtr+8] == 'R')                    && (buffer.buf[buffer.bufPtr+9] == 'O')                    && (buffer.buf[buffer.bufPtr+10] == 'F')                    && (buffer.buf[buffer.bufPtr+11] == 'I')                    && (buffer.buf[buffer.bufPtr+12] == 'L')                    && (buffer.buf[buffer.bufPtr+13] == 'E')                    && (buffer.buf[buffer.bufPtr+14] == 0)                    ) {                    if (isStream) {                        throw new IIOException                            ("ICC profiles not permitted in stream metadata");                    }                     JFIFMarkerSegment jfif =                        (JFIFMarkerSegment) findMarkerSegment                        (JFIFMarkerSegment.class, true);                    if (jfif == null) {                        throw new IIOException                            ("ICC APP2 encountered without prior JFIF!");                    }                    jfif.addICC(buffer);                    // newGuy remains null                } else {                    newGuy = new MarkerSegment(buffer);                    newGuy.loadData(buffer);                }                break;            case JPEG.APP14:                // Either Adobe or unknown APP14                buffer.loadBuf(8); // tag, length, id                if ((buffer.buf[buffer.bufPtr+3] == 'A')                    && (buffer.buf[buffer.bufPtr+4] == 'd')                    && (buffer.buf[buffer.bufPtr+5] == 'o')                    && (buffer.buf[buffer.bufPtr+6] == 'b')                    && (buffer.buf[buffer.bufPtr+7] == 'e')) {                    if (isStream) {                        throw new IIOException                      ("Adobe APP14 markers not permitted in stream metadata");                    }                    newGuy = new AdobeMarkerSegment(buffer);                } else {                    newGuy = new MarkerSegment(buffer);                    newGuy.loadData(buffer);                }                 break;            case JPEG.COM:                newGuy = new COMMarkerSegment(buffer);                break;            case JPEG.SOS:                if (isStream) {                    throw new IIOException                        ("SOS not permitted in stream metadata");                }                newGuy = new SOSMarkerSegment(buffer);                break;            case JPEG.RST0:            case JPEG.RST1:            case JPEG.RST2:            case JPEG.RST3:            case JPEG.RST4:            case JPEG.RST5:            case JPEG.RST6:            case JPEG.RST7:                if (debug) {                    System.out.println("Restart Marker");                }                buffer.bufPtr++; // Just skip it                buffer.bufAvail--;                break;            case JPEG.EOI:                done = true;                buffer.bufPtr++;                buffer.bufAvail--;                break;            default:                newGuy = new MarkerSegment(buffer);                newGuy.loadData(buffer);                newGuy.unknown = true;                break;            }            if (newGuy != null) {                markerSequence.add(newGuy);                if (debug) {                    newGuy.print();                }                newGuy = null;            }        }         // Now that we've read up to the EOI, we need to push back        // whatever is left in the buffer, so that the next read        // in the native code will work.         buffer.pushBack();         if (!isConsistent()) {            throw new IIOException("Inconsistent metadata read from stream");        }    }     /**     * Constructs a default stream <code>JPEGMetadata</code> object appropriate     * for the given write parameters.     */    JPEGMetadata(ImageWriteParam param, JPEGImageWriter writer) {        this(true, false);         JPEGImageWriteParam jparam = null;         if ((param != null) && (param instanceof JPEGImageWriteParam)) {            jparam = (JPEGImageWriteParam) param;            if (!jparam.areTablesSet()) {                jparam = null;            }        }        if (jparam != null) {            markerSequence.add(new DQTMarkerSegment(jparam.getQTables()));            markerSequence.add                (new DHTMarkerSegment(jparam.getDCHuffmanTables(),                                      jparam.getACHuffmanTables()));        } else {            // default tables.            markerSequence.add(new DQTMarkerSegment(JPEG.getDefaultQTables()));            markerSequence.add(new DHTMarkerSegment(JPEG.getDefaultHuffmanTables(true),                                                    JPEG.getDefaultHuffmanTables(false)));        }         // Defensive programming        if (!isConsistent()) {            throw new InternalError("Default stream metadata is inconsistent");        }    }     /**     * Constructs a default image <code>JPEGMetadata</code> object appropriate     * for the given image type and write parameters.     */    JPEGMetadata(ImageTypeSpecifier imageType,                 ImageWriteParam param,                 JPEGImageWriter writer) {        this(false, false);         boolean wantJFIF = true;        boolean wantAdobe = false;        int transform = JPEG.ADOBE_UNKNOWN;        boolean willSubsample = true;        boolean wantICC = false;        boolean wantProg = false;        boolean wantOptimized = false;        boolean wantExtended = false;        boolean wantQTables = true;        boolean wantHTables = true;        float quality = JPEG.DEFAULT_QUALITY;        byte[] componentIDs = { 1, 2, 3, 4};        int numComponents = 0;         ImageTypeSpecifier destType = null;         if (param != null) {            destType = param.getDestinationType();            if (destType != null) {                if (imageType != null) {                    // Ignore the destination type.                    writer.warningOccurred                        (JPEGImageWriter.WARNING_DEST_IGNORED);                    destType = null;                }            }            // The only progressive mode that makes sense here is MODE_DEFAULT            if (param.canWriteProgressive()) {                // the param may not be one of ours, so it may return false.                // If so, the following would throw an exception                if (param.getProgressiveMode() == ImageWriteParam.MODE_DEFAULT) {                    wantProg = true;                    wantOptimized = true;                    wantHTables = false;                }            }             if (param instanceof JPEGImageWriteParam) {                JPEGImageWriteParam jparam = (JPEGImageWriteParam) param;                if (jparam.areTablesSet()) {                    wantQTables = false;  // If the param has them, metadata shouldn't                    wantHTables = false;                    if ((jparam.getDCHuffmanTables().length > 2)                            || (jparam.getACHuffmanTables().length > 2)) {                        wantExtended = true;                    }                }                // Progressive forces optimized, regardless of param setting                // so consult the param re optimized only if not progressive                if (!wantProg) {                    wantOptimized = jparam.getOptimizeHuffmanTables();                    if (wantOptimized) {                        wantHTables = false;                    }                }            }             // compression quality should determine the q tables.  Note that this            // will be ignored if we already decided not to create any.            // Again, the param may not be one of ours, so we must check that it            // supports compression settings            if (param.canWriteCompressed()) {                if (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {                    quality = param.getCompressionQuality();                }            }        }         // We are done with the param, now for the image types         ColorSpace cs = null;        if (destType != null) {            ColorModel cm = destType.getColorModel();            numComponents = cm.getNumComponents();            boolean hasExtraComponents = (cm.getNumColorComponents() != numComponents);            boolean hasAlpha = cm.hasAlpha();            cs = cm.getColorSpace();            int type = cs.getType();            switch(type) {            case ColorSpace.TYPE_GRAY:                willSubsample = false;                if (hasExtraComponents) {  // e.g. alpha                    wantJFIF = false;                }                break;            case ColorSpace.TYPE_3CLR:                if (cs == JPEG.JCS.getYCC()) {                    wantJFIF = false;                    componentIDs[0] = (byte) 'Y';                    componentIDs[1] = (byte) 'C';                    componentIDs[2] = (byte) 'c';                    if (hasAlpha) {                        componentIDs[3] = (byte) 'A';                    }

Lines 1–500 of 2416.