/* * 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 neededLines 1–500 of 1564. Load more
/* * 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