1564 lines · 58.2 KB · Java
RawDownload
/* * Copyright (c) 2001, 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.IIOException;import javax.imageio.IIOImage;import javax.imageio.ImageTypeSpecifier;import javax.imageio.ImageReader;import javax.imageio.metadata.IIOInvalidTreeException;import javax.imageio.metadata.IIOMetadataNode;import javax.imageio.metadata.IIOMetadata;import javax.imageio.stream.ImageInputStream;import javax.imageio.stream.ImageOutputStream;import javax.imageio.stream.MemoryCacheImageOutputStream;import javax.imageio.event.IIOReadProgressListener; import java.awt.Graphics;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.image.SampleModel;import java.awt.image.IndexColorModel;import java.awt.image.ComponentColorModel;import java.awt.image.BufferedImage;import java.awt.image.DataBuffer;import java.awt.image.DataBufferByte;import java.awt.image.Raster;import java.awt.image.WritableRaster;import java.io.IOException;import java.io.ByteArrayOutputStream;import java.util.List;import java.util.ArrayList;import java.util.Iterator; import org.w3c.dom.Node;import org.w3c.dom.NodeList;import org.w3c.dom.NamedNodeMap; /** * A JFIF (JPEG File Interchange Format) APP0 (Application-Specific) * marker segment.  Inner classes are included for JFXX extension * marker segments, for different varieties of thumbnails, and for * ICC Profile APP2 marker segments.  Any of these secondary types * that occur are kept as members of a single JFIFMarkerSegment object. */class JFIFMarkerSegment extends MarkerSegment {    int majorVersion;    int minorVersion;    int resUnits;    int Xdensity;    int Ydensity;    int thumbWidth;    int thumbHeight;    JFIFThumbRGB thumb = null;  // If present    ArrayList extSegments = new ArrayList();    ICCMarkerSegment iccSegment = null; // optional ICC    private static final int THUMB_JPEG = 0x10;    private static final int THUMB_PALETTE = 0x11;    private static final int THUMB_UNASSIGNED = 0x12;    private static final int THUMB_RGB = 0x13;    private static final int DATA_SIZE = 14;    private static final int ID_SIZE = 5;    private final int MAX_THUMB_WIDTH = 255;    private final int MAX_THUMB_HEIGHT = 255;     private final boolean debug = false;     /**     * Set to <code>true</code> when reading the chunks of an     * ICC profile.  All chunks are consolidated to create a single     * "segment" containing all the chunks.  This flag is a state     * variable identifying whether to construct a new segment or     * append to an old one.     */    private boolean inICC = false;     /**     * A placeholder for an ICC profile marker segment under     * construction.  The segment is not added to the list     * until all chunks have been read.     */    private ICCMarkerSegment tempICCSegment = null;      /**     * Default constructor.  Used to create a default JFIF header     */    JFIFMarkerSegment() {        super(JPEG.APP0);        majorVersion = 1;        minorVersion = 2;        resUnits = JPEG.DENSITY_UNIT_ASPECT_RATIO;        Xdensity = 1;        Ydensity = 1;        thumbWidth = 0;        thumbHeight = 0;    }     /**     * Constructs a JFIF header by reading from a stream wrapped     * in a JPEGBuffer.     */    JFIFMarkerSegment(JPEGBuffer buffer) throws IOException {        super(buffer);        buffer.bufPtr += ID_SIZE;  // skip the id, we already checked it         majorVersion = buffer.buf[buffer.bufPtr++];        minorVersion = buffer.buf[buffer.bufPtr++];        resUnits = buffer.buf[buffer.bufPtr++];        Xdensity = (buffer.buf[buffer.bufPtr++] & 0xff) << 8;        Xdensity |= buffer.buf[buffer.bufPtr++] & 0xff;        Ydensity = (buffer.buf[buffer.bufPtr++] & 0xff) << 8;        Ydensity |= buffer.buf[buffer.bufPtr++] & 0xff;        thumbWidth = buffer.buf[buffer.bufPtr++] & 0xff;        thumbHeight = buffer.buf[buffer.bufPtr++] & 0xff;        buffer.bufAvail -= DATA_SIZE;        if (thumbWidth > 0) {            thumb = new JFIFThumbRGB(buffer, thumbWidth, thumbHeight);        }    }     /**     * Constructs a JFIF header from a DOM Node.     */    JFIFMarkerSegment(Node node) throws IIOInvalidTreeException {        this();        updateFromNativeNode(node, true);    }     /**     * Returns a deep-copy clone of this object.     */    protected Object clone() {        JFIFMarkerSegment newGuy = (JFIFMarkerSegment) super.clone();        if (!extSegments.isEmpty()) { // Clone the list with a deep copy            newGuy.extSegments = new ArrayList();            for (Iterator iter = extSegments.iterator(); iter.hasNext();) {                JFIFExtensionMarkerSegment jfxx =                    (JFIFExtensionMarkerSegment) iter.next();                newGuy.extSegments.add(jfxx.clone());            }        }        if (iccSegment != null) {            newGuy.iccSegment = (ICCMarkerSegment) iccSegment.clone();        }        return newGuy;    }     /**     * Add an JFXX extension marker segment from the stream wrapped     * in the JPEGBuffer to the list of extension segments.     */    void addJFXX(JPEGBuffer buffer, JPEGImageReader reader)        throws IOException {        extSegments.add(new JFIFExtensionMarkerSegment(buffer, reader));    }     /**     * Adds an ICC Profile APP2 segment from the stream wrapped     * in the JPEGBuffer.     */    void addICC(JPEGBuffer buffer) throws IOException {        if (inICC == false) {            if (iccSegment != null) {                throw new IIOException                    ("> 1 ICC APP2 Marker Segment not supported");            }            tempICCSegment = new ICCMarkerSegment(buffer);            if (inICC == false) { // Just one chunk                iccSegment = tempICCSegment;                tempICCSegment = null;            }        } else {            if (tempICCSegment.addData(buffer) == true) {                iccSegment = tempICCSegment;                tempICCSegment = null;            }        }    }     /**     * Add an ICC Profile APP2 segment by constructing it from     * the given ICC_ColorSpace object.     */    void addICC(ICC_ColorSpace cs) throws IOException {        if (iccSegment != null) {            throw new IIOException                ("> 1 ICC APP2 Marker Segment not supported");        }        iccSegment = new ICCMarkerSegment(cs);    }     /**     * Returns a tree of DOM nodes representing this object and any     * subordinate JFXX extension or ICC Profile segments.     */    IIOMetadataNode getNativeNode() {        IIOMetadataNode node = new IIOMetadataNode("app0JFIF");        node.setAttribute("majorVersion", Integer.toString(majorVersion));        node.setAttribute("minorVersion", Integer.toString(minorVersion));        node.setAttribute("resUnits", Integer.toString(resUnits));        node.setAttribute("Xdensity", Integer.toString(Xdensity));        node.setAttribute("Ydensity", Integer.toString(Ydensity));        node.setAttribute("thumbWidth", Integer.toString(thumbWidth));        node.setAttribute("thumbHeight", Integer.toString(thumbHeight));        if (!extSegments.isEmpty()) {            IIOMetadataNode JFXXnode = new IIOMetadataNode("JFXX");            node.appendChild(JFXXnode);            for (Iterator iter = extSegments.iterator(); iter.hasNext();) {                JFIFExtensionMarkerSegment seg =                    (JFIFExtensionMarkerSegment) iter.next();                JFXXnode.appendChild(seg.getNativeNode());            }        }        if (iccSegment != null) {            node.appendChild(iccSegment.getNativeNode());        }         return node;    }     /**     * Updates the data in this object from the given DOM Node tree.     * If fromScratch is true, this object is being constructed.     * Otherwise an existing object is being modified.     * Throws an IIOInvalidTreeException if the tree is invalid in     * any way.     */    void updateFromNativeNode(Node node, boolean fromScratch)        throws IIOInvalidTreeException {        // none of the attributes are required        NamedNodeMap attrs = node.getAttributes();        if (attrs.getLength() > 0) {            int value = getAttributeValue(node, attrs, "majorVersion",                                          0, 255, false);            majorVersion = (value != -1) ? value : majorVersion;            value = getAttributeValue(node, attrs, "minorVersion",                                      0, 255, false);            minorVersion = (value != -1) ? value : minorVersion;            value = getAttributeValue(node, attrs, "resUnits", 0, 2, false);            resUnits = (value != -1) ? value : resUnits;            value = getAttributeValue(node, attrs, "Xdensity", 1, 65535, false);            Xdensity = (value != -1) ? value : Xdensity;            value = getAttributeValue(node, attrs, "Ydensity", 1, 65535, false);            Ydensity = (value != -1) ? value : Ydensity;            value = getAttributeValue(node, attrs, "thumbWidth", 0, 255, false);            thumbWidth = (value != -1) ? value : thumbWidth;            value = getAttributeValue(node, attrs, "thumbHeight", 0, 255, false);            thumbHeight = (value != -1) ? value : thumbHeight;        }        if (node.hasChildNodes()) {            NodeList children = node.getChildNodes();            int count = children.getLength();            if (count > 2) {                throw new IIOInvalidTreeException                    ("app0JFIF node cannot have > 2 children", node);            }            for (int i = 0; i < count; i++) {                Node child = children.item(i);                String name = child.getNodeName();                if (name.equals("JFXX")) {                    if ((!extSegments.isEmpty()) && fromScratch) {                        throw new IIOInvalidTreeException                            ("app0JFIF node cannot have > 1 JFXX node", node);                    }                    NodeList exts = child.getChildNodes();                    int extCount = exts.getLength();                    for (int j = 0; j < extCount; j++) {                        Node ext = exts.item(j);                        extSegments.add(new JFIFExtensionMarkerSegment(ext));                    }                }                if (name.equals("app2ICC")) {                    if ((iccSegment != null) && fromScratch) {                        throw new IIOInvalidTreeException                            ("> 1 ICC APP2 Marker Segment not supported", node);                    }                    iccSegment = new ICCMarkerSegment(child);                }            }        }    }     int getThumbnailWidth(int index) {        if (thumb != null) {            if (index == 0) {                return thumb.getWidth();            }            index--;        }        JFIFExtensionMarkerSegment jfxx =            (JFIFExtensionMarkerSegment) extSegments.get(index);        return jfxx.thumb.getWidth();    }     int getThumbnailHeight(int index) {        if (thumb != null) {            if (index == 0) {                return thumb.getHeight();            }            index--;        }        JFIFExtensionMarkerSegment jfxx =            (JFIFExtensionMarkerSegment) extSegments.get(index);        return jfxx.thumb.getHeight();    }     BufferedImage getThumbnail(ImageInputStream iis,                               int index,                               JPEGImageReader reader) throws IOException {        reader.thumbnailStarted(index);        BufferedImage ret = null;        if ((thumb != null) && (index == 0)) {                ret = thumb.getThumbnail(iis, reader);        } else {            if (thumb != null) {                index--;            }            JFIFExtensionMarkerSegment jfxx =                (JFIFExtensionMarkerSegment) extSegments.get(index);            ret = jfxx.thumb.getThumbnail(iis, reader);        }        reader.thumbnailComplete();        return ret;    }      /**     * Writes the data for this segment to the stream in     * valid JPEG format.  Assumes that there will be no thumbnail.     */    void write(ImageOutputStream ios,               JPEGImageWriter writer) throws IOException {        // No thumbnail        write(ios, null, writer);    }     /**     * Writes the data for this segment to the stream in     * valid JPEG format.  The length written takes the thumbnail     * width and height into account.  If necessary, the thumbnail     * is clipped to 255 x 255 and a warning is sent to the writer     * argument.  Progress updates are sent to the writer argument.     */    void write(ImageOutputStream ios,               BufferedImage thumb,               JPEGImageWriter writer) throws IOException {        int thumbWidth = 0;        int thumbHeight = 0;        int thumbLength = 0;        int [] thumbData = null;        if (thumb != null) {            // Clip if necessary and get the data in thumbData            thumbWidth = thumb.getWidth();            thumbHeight = thumb.getHeight();            if ((thumbWidth > MAX_THUMB_WIDTH)                || (thumbHeight > MAX_THUMB_HEIGHT)) {                writer.warningOccurred(JPEGImageWriter.WARNING_THUMB_CLIPPED);            }            thumbWidth = Math.min(thumbWidth, MAX_THUMB_WIDTH);            thumbHeight = Math.min(thumbHeight, MAX_THUMB_HEIGHT);            thumbData = thumb.getRaster().getPixels(0, 0,                                                    thumbWidth, thumbHeight,                                                    (int []) null);            thumbLength = thumbData.length;        }        length = DATA_SIZE + LENGTH_SIZE + thumbLength;        writeTag(ios);        byte [] id = {0x4A, 0x46, 0x49, 0x46, 0x00};        ios.write(id);        ios.write(majorVersion);        ios.write(minorVersion);        ios.write(resUnits);        write2bytes(ios, Xdensity);        write2bytes(ios, Ydensity);        ios.write(thumbWidth);        ios.write(thumbHeight);        if (thumbData != null) {            writer.thumbnailStarted(0);            writeThumbnailData(ios, thumbData, writer);            writer.thumbnailComplete();        }    }     /*     * Write out the values in the integer array as a sequence of bytes,     * reporting progress to the writer argument.     */    void writeThumbnailData(ImageOutputStream ios,                            int [] thumbData,                            JPEGImageWriter writer) throws IOException {        int progInterval = thumbData.length / 20;  // approx. every 5%        if (progInterval == 0) {            progInterval = 1;        }        for (int i = 0; i < thumbData.length; i++) {            ios.write(thumbData[i]);            if ((i > progInterval) && (i % progInterval == 0)) {                writer.thumbnailProgress                    (((float) i * 100) / ((float) thumbData.length));            }        }    }     /**     * Write out this JFIF Marker Segment, including a thumbnail or     * appending a series of JFXX Marker Segments, as appropriate.     * Warnings and progress reports are sent to the writer argument.     * The list of thumbnails is matched to the list of JFXX extension     * segments, if any, in order to determine how to encode the     * thumbnails.  If there are more thumbnails than metadata segments,     * default encoding is used for the extra thumbnails.     */    void writeWithThumbs(ImageOutputStream ios,                         List thumbnails,                         JPEGImageWriter writer) throws IOException {        if (thumbnails != null) {            JFIFExtensionMarkerSegment jfxx = null;            if (thumbnails.size() == 1) {                if (!extSegments.isEmpty()) {                    jfxx = (JFIFExtensionMarkerSegment) extSegments.get(0);                }                writeThumb(ios,                           (BufferedImage) thumbnails.get(0),                           jfxx,                           0,                           true,                           writer);            } else {                // All others write as separate JFXX segments                write(ios, writer);  // Just the header without any thumbnail                for (int i = 0; i < thumbnails.size(); i++) {                    jfxx = null;                    if (i < extSegments.size()) {                        jfxx = (JFIFExtensionMarkerSegment) extSegments.get(i);                    }                    writeThumb(ios,                               (BufferedImage) thumbnails.get(i),                               jfxx,                               i,                               false,                               writer);                }            }        } else {  // No thumbnails            write(ios, writer);        }     }     private void writeThumb(ImageOutputStream ios,                            BufferedImage thumb,                            JFIFExtensionMarkerSegment jfxx,                            int index,                            boolean onlyOne,                            JPEGImageWriter writer) throws IOException {        ColorModel cm = thumb.getColorModel();        ColorSpace cs = cm.getColorSpace();         if (cm instanceof IndexColorModel) {            // We never write a palette image into the header            // So if it's the only one, we need to write the header first            if (onlyOne) {                write(ios, writer);            }            if ((jfxx == null)                || (jfxx.code == THUMB_PALETTE)) {                writeJFXXSegment(index, thumb, ios, writer); // default            } else {                // Expand to RGB                BufferedImage thumbRGB =                    ((IndexColorModel) cm).convertToIntDiscrete                    (thumb.getRaster(), false);                jfxx.setThumbnail(thumbRGB);                writer.thumbnailStarted(index);                jfxx.write(ios, writer);  // Handles clipping if needed

Lines 1–500 of 1564.