# HG changeset patch # User bpb # Date 1448310372 28800 # Node ID 68c0d866db5d8e65a65768842cfeda9d2cb19128 # Parent 098d54b4051d9bd86c748ea0cdbe2affcdddf310 8143342: Integrate Java Image I/O support for TIFF per JEP 262 Summary: Port TIFF reader and writer plugins from JAI Image I/O Tools to JDK 9 Reviewed-by: prr, serb diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/ImageUtil.java --- a/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/ImageUtil.java Mon Nov 23 10:00:50 2015 -0800 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/ImageUtil.java Mon Nov 23 12:26:12 2015 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2015, 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 @@ -29,6 +29,7 @@ import java.awt.Rectangle; import java.awt.Transparency; import java.awt.color.ColorSpace; +import java.awt.color.ICC_ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; @@ -47,64 +48,15 @@ import java.awt.image.SinglePixelPackedSampleModel; import java.awt.image.WritableRaster; import java.util.Arrays; - -//import javax.imageio.ImageTypeSpecifier; - +import java.util.Iterator; import javax.imageio.IIOException; import javax.imageio.IIOImage; +import javax.imageio.ImageReadParam; import javax.imageio.ImageTypeSpecifier; import javax.imageio.ImageWriter; import javax.imageio.spi.ImageWriterSpi; public class ImageUtil { - /* XXX testing only - public static void main(String[] args) { - ImageTypeSpecifier bilevel = - ImageTypeSpecifier.createIndexed(new byte[] {(byte)0, (byte)255}, - new byte[] {(byte)0, (byte)255}, - new byte[] {(byte)0, (byte)255}, - null, 1, - DataBuffer.TYPE_BYTE); - ImageTypeSpecifier gray = - ImageTypeSpecifier.createGrayscale(8, DataBuffer.TYPE_BYTE, false); - ImageTypeSpecifier grayAlpha = - ImageTypeSpecifier.createGrayscale(8, DataBuffer.TYPE_BYTE, false, - false); - ImageTypeSpecifier rgb = - ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), - new int[] {0, 1, 2}, - DataBuffer.TYPE_BYTE, - false, - false); - ImageTypeSpecifier rgba = - ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), - new int[] {0, 1, 2, 3}, - DataBuffer.TYPE_BYTE, - true, - false); - ImageTypeSpecifier packed = - ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), - 0xff000000, - 0x00ff0000, - 0x0000ff00, - 0x000000ff, - DataBuffer.TYPE_BYTE, - false); - - SampleModel bandedSM = - new java.awt.image.BandedSampleModel(DataBuffer.TYPE_BYTE, - 1, 1, 15); - - System.out.println(createColorModel(bilevel.getSampleModel())); - System.out.println(createColorModel(gray.getSampleModel())); - System.out.println(createColorModel(grayAlpha.getSampleModel())); - System.out.println(createColorModel(rgb.getSampleModel())); - System.out.println(createColorModel(rgba.getSampleModel())); - System.out.println(createColorModel(packed.getSampleModel())); - System.out.println(createColorModel(bandedSM)); - } - */ - /** * Creates a ColorModel that may be used with the * specified SampleModel. If a suitable @@ -1162,4 +1114,78 @@ // pixel stride. return ImageUtil.isBinary(sm); } + + /** + * Gets the destination image type. + */ + public static final ImageTypeSpecifier + getDestinationType(ImageReadParam param, + Iterator imageTypes) throws IIOException { + + if (imageTypes == null || !imageTypes.hasNext()) { + throw new IllegalArgumentException("imageTypes null or empty!"); + } + + ImageTypeSpecifier imageType = null; + + // If param is non-null, use it + if (param != null) { + imageType = param.getDestinationType(); + } + + // No info from param, use fallback image type + if (imageType == null) { + Object o = imageTypes.next(); + if (!(o instanceof ImageTypeSpecifier)) { + throw new IllegalArgumentException + ("Non-ImageTypeSpecifier retrieved from imageTypes!"); + } + imageType = (ImageTypeSpecifier)o; + } else { + boolean foundIt = false; + while (imageTypes.hasNext()) { + ImageTypeSpecifier type = + imageTypes.next(); + if (type.equals(imageType)) { + foundIt = true; + break; + } + } + + if (!foundIt) { + throw new IIOException + ("Destination type from ImageReadParam does not match!"); + } + } + + return imageType; + } + + /** + * Returns true if the given ColorSpace object is + * an instance of ICC_ColorSpace but is not one of the standard + * ColorSpaces returned by ColorSpace.getInstance(). + * + * @param cs The ColorSpace to test. + */ + public static boolean isNonStandardICCColorSpace(ColorSpace cs) { + boolean retval = false; + + try { + // Check the standard ColorSpaces in decreasing order of + // likelihood except check CS_PYCC last as in some JREs + // PYCC.pf used not to be installed. + retval = + (cs instanceof ICC_ColorSpace) && + !(cs.isCS_sRGB() || + cs.equals(ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB)) || + cs.equals(ColorSpace.getInstance(ColorSpace.CS_GRAY)) || + cs.equals(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ)) || + cs.equals(ColorSpace.getInstance(ColorSpace.CS_PYCC))); + } catch(IllegalArgumentException e) { + // PYCC.pf not installed: ignore it - 'retval' is still 'false'. + } + + return retval; + } } diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleCMYKColorSpace.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleCMYKColorSpace.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2005, 2015, 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.common; + +import java.awt.color.ColorSpace; + +/** + * Singleton class representing a simple, mathematically defined CMYK + * color space. + */ +public final class SimpleCMYKColorSpace extends ColorSpace { + private static final long serialVersionUID = 666L; // XXX Revise UID value + + private static ColorSpace theInstance = null; + private ColorSpace csRGB; + + /** The exponent for gamma correction. */ + private static final double power1 = 1.0 / 2.4; + + public static final synchronized ColorSpace getInstance() { + if(theInstance == null) { + theInstance = new SimpleCMYKColorSpace(); + } + return theInstance; + } + + private SimpleCMYKColorSpace() { + super(TYPE_CMYK, 4); + csRGB = ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB); + } + + public boolean equals(Object o) { + return o != null && o instanceof SimpleCMYKColorSpace; + } + + public int hashCode() { + return theInstance.hashCode(); + } + + public float[] toRGB(float[] colorvalue) { + float C = colorvalue[0]; + float M = colorvalue[1]; + float Y = colorvalue[2]; + float K = colorvalue[3]; + + float K1 = 1.0F - K; + + // Convert from CMYK to linear RGB. + float[] rgbvalue = new float[] {K1*(1.0F - C), + K1*(1.0F - M), + K1*(1.0F - Y)}; + + // Convert from linear RGB to sRGB. + for (int i = 0; i < 3; i++) { + float v = rgbvalue[i]; + + if (v < 0.0F) v = 0.0F; + + if (v < 0.0031308F) { + rgbvalue[i] = 12.92F * v; + } else { + if (v > 1.0F) v = 1.0F; + + rgbvalue[i] = (float)(1.055 * Math.pow(v, power1) - 0.055); + } + } + + return rgbvalue; + } + + public float[] fromRGB(float[] rgbvalue) { + // Convert from sRGB to linear RGB. + for (int i = 0; i < 3; i++) { + if (rgbvalue[i] < 0.040449936F) { + rgbvalue[i] /= 12.92F; + } else { + rgbvalue[i] = + (float)(Math.pow((rgbvalue[i] + 0.055)/1.055, 2.4)); + } + } + + // Convert from linear RGB to CMYK. + float C = 1.0F - rgbvalue[0]; + float M = 1.0F - rgbvalue[1]; + float Y = 1.0F - rgbvalue[2]; + float K = Math.min(C, Math.min(M, Y)); + + // If K == 1.0F, then C = M = Y = 1.0F. + if(K != 1.0F) { + float K1 = 1.0F - K; + + C = (C - K)/K1; + M = (M - K)/K1; + Y = (Y - K)/K1; + } else { + C = M = Y = 0.0F; + } + + return new float[] {C, M, Y, K}; + } + + public float[] toCIEXYZ(float[] colorvalue) { + return csRGB.toCIEXYZ(toRGB(colorvalue)); + } + + public float[] fromCIEXYZ(float[] xyzvalue) { + return fromRGB(csRGB.fromCIEXYZ(xyzvalue)); + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleRenderedImage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleRenderedImage.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,571 @@ +/* + * Copyright (c) 2005, 2015, 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.common; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.image.RenderedImage; +import java.awt.image.ColorModel; +import java.awt.image.SampleModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Vector; + +public abstract class SimpleRenderedImage implements RenderedImage { + /** The X coordinate of the image's upper-left pixel. */ + protected int minX; + + /** The Y coordinate of the image's upper-left pixel. */ + protected int minY; + + /** The image's width in pixels. */ + protected int width; + + /** The image's height in pixels. */ + protected int height; + + /** The width of a tile. */ + protected int tileWidth; + + /** The height of a tile. */ + protected int tileHeight; + + /** The X coordinate of the upper-left pixel of tile (0, 0). */ + protected int tileGridXOffset = 0; + + /** The Y coordinate of the upper-left pixel of tile (0, 0). */ + protected int tileGridYOffset = 0; + + /** The image's SampleModel. */ + protected SampleModel sampleModel; + + /** The image's ColorModel. */ + protected ColorModel colorModel; + + /** The image's sources, stored in a Vector. */ + protected Vector sources = new Vector(); + + /** A Hashtable containing the image properties. */ + protected Hashtable properties = new Hashtable(); + + /** Returns the X coordinate of the leftmost column of the image. */ + public int getMinX() { + return minX; + } + + /** + * Returns the X coordinate of the column immediatetely to the + * right of the rightmost column of the image. getMaxX() is + * implemented in terms of getMinX() and getWidth() and so does + * not need to be implemented by subclasses. + */ + public final int getMaxX() { + return getMinX() + getWidth(); + } + + /** Returns the X coordinate of the uppermost row of the image. */ + public int getMinY() { + return minY; + } + + /** + * Returns the Y coordinate of the row immediately below the + * bottom row of the image. getMaxY() is implemented in terms of + * getMinY() and getHeight() and so does not need to be + * implemented by subclasses. + */ + public final int getMaxY() { + return getMinY() + getHeight(); + } + + /** Returns the width of the image. */ + public int getWidth() { + return width; + } + + /** Returns the height of the image. */ + public int getHeight() { + return height; + } + + /** Returns a Rectangle indicating the image bounds. */ + public Rectangle getBounds() { + return new Rectangle(getMinX(), getMinY(), getWidth(), getHeight()); + } + + /** Returns the width of a tile. */ + public int getTileWidth() { + return tileWidth; + } + + /** Returns the height of a tile. */ + public int getTileHeight() { + return tileHeight; + } + + /** + * Returns the X coordinate of the upper-left pixel of tile (0, 0). + */ + public int getTileGridXOffset() { + return tileGridXOffset; + } + + /** + * Returns the Y coordinate of the upper-left pixel of tile (0, 0). + */ + public int getTileGridYOffset() { + return tileGridYOffset; + } + + /** + * Returns the horizontal index of the leftmost column of tiles. + * getMinTileX() is implemented in terms of getMinX() + * and so does not need to be implemented by subclasses. + */ + public int getMinTileX() { + return XToTileX(getMinX()); + } + + /** + * Returns the horizontal index of the rightmost column of tiles. + * getMaxTileX() is implemented in terms of getMaxX() + * and so does not need to be implemented by subclasses. + */ + public int getMaxTileX() { + return XToTileX(getMaxX() - 1); + } + + /** + * Returns the number of tiles along the tile grid in the + * horizontal direction. getNumXTiles() is implemented in terms + * of getMinTileX() and getMaxTileX() and so does not need to be + * implemented by subclasses. + */ + public int getNumXTiles() { + return getMaxTileX() - getMinTileX() + 1; + } + + /** + * Returns the vertical index of the uppermost row of tiles. getMinTileY() + * is implemented in terms of getMinY() and so does not need to be + * implemented by subclasses. + */ + public int getMinTileY() { + return YToTileY(getMinY()); + } + + /** + * Returns the vertical index of the bottom row of tiles. getMaxTileY() + * is implemented in terms of getMaxY() and so does not need to + * be implemented by subclasses. + */ + public int getMaxTileY() { + return YToTileY(getMaxY() - 1); + } + + /** + * Returns the number of tiles along the tile grid in the vertical + * direction. getNumYTiles() is implemented in terms + * of getMinTileY() and getMaxTileY() and so does not need to be + * implemented by subclasses. + */ + public int getNumYTiles() { + return getMaxTileY() - getMinTileY() + 1; + } + + /** Returns the SampleModel of the image. */ + public SampleModel getSampleModel() { + return sampleModel; + } + + /** Returns the ColorModel of the image. */ + public ColorModel getColorModel() { + return colorModel; + } + + /** + * Gets a property from the property set of this image. If the + * property name is not recognized, + * java.awt.Image.UndefinedProperty will be returned. + * + * @param name the name of the property to get, as a + * String. @return a reference to the property + * Object, or the value + * java.awt.Image.UndefinedProperty. + */ + public Object getProperty(String name) { + name = name.toLowerCase(); + Object value = properties.get(name); + return value != null ? value : java.awt.Image.UndefinedProperty; + } + + /** + * Returns a list of the properties recognized by this image. If + * no properties are available, null will be + * returned. + * + * @return an array of Strings representing valid + * property names. + */ + public String[] getPropertyNames() { + String[] names = null; + + if(properties.size() > 0) { + names = new String[properties.size()]; + int index = 0; + + Enumeration e = properties.keys(); + while (e.hasMoreElements()) { + String name = e.nextElement(); + names[index++] = name; + } + } + + return names; + } + + /** + * Returns an array of Strings recognized as names by + * this property source that begin with the supplied prefix. If + * no property names match, null will be returned. + * The comparison is done in a case-independent manner. + * + *

The default implementation calls + * getPropertyNames() and searches the list of names + * for matches. + * + * @return an array of Strings giving the valid + * property names. + */ + public String[] getPropertyNames(String prefix) { + String propertyNames[] = getPropertyNames(); + if (propertyNames == null) { + return null; + } + + prefix = prefix.toLowerCase(); + + Vector names = new Vector(); + for (int i = 0; i < propertyNames.length; i++) { + if (propertyNames[i].startsWith(prefix)) { + names.addElement(propertyNames[i]); + } + } + + if (names.size() == 0) { + return null; + } + + // Copy the strings from the Vector over to a String array. + String prefixNames[] = new String[names.size()]; + int count = 0; + for (Iterator it = names.iterator(); it.hasNext(); ) { + prefixNames[count++] = it.next(); + } + + return prefixNames; + } + + // Utility methods. + + /** + * Converts a pixel's X coordinate into a horizontal tile index + * relative to a given tile grid layout specified by its X offset + * and tile width. + */ + public static int XToTileX(int x, int tileGridXOffset, int tileWidth) { + x -= tileGridXOffset; + if (x < 0) { + x += 1 - tileWidth; // Force round to -infinity + } + return x/tileWidth; + } + + /** + * Converts a pixel's Y coordinate into a vertical tile index + * relative to a given tile grid layout specified by its Y offset + * and tile height. + */ + public static int YToTileY(int y, int tileGridYOffset, int tileHeight) { + y -= tileGridYOffset; + if (y < 0) { + y += 1 - tileHeight; // Force round to -infinity + } + return y/tileHeight; + } + + /** + * Converts a pixel's X coordinate into a horizontal tile index. + * This is a convenience method. No attempt is made to detect + * out-of-range coordinates. + * + * @param x the X coordinate of a pixel. + * @return the X index of the tile containing the pixel. + */ + public int XToTileX(int x) { + return XToTileX(x, getTileGridXOffset(), getTileWidth()); + } + + /** + * Converts a pixel's Y coordinate into a vertical tile index. + * This is a convenience method. No attempt is made to detect + * out-of-range coordinates. + * + * @param y the Y coordinate of a pixel. + * @return the Y index of the tile containing the pixel. + */ + public int YToTileY(int y) { + return YToTileY(y, getTileGridYOffset(), getTileHeight()); + } + + /** + * Converts a horizontal tile index into the X coordinate of its + * upper left pixel relative to a given tile grid layout specified + * by its X offset and tile width. + */ + public static int tileXToX(int tx, int tileGridXOffset, int tileWidth) { + return tx*tileWidth + tileGridXOffset; + } + + /** + * Converts a vertical tile index into the Y coordinate of + * its upper left pixel relative to a given tile grid layout + * specified by its Y offset and tile height. + */ + public static int tileYToY(int ty, int tileGridYOffset, int tileHeight) { + return ty*tileHeight + tileGridYOffset; + } + + /** + * Converts a horizontal tile index into the X coordinate of its + * upper left pixel. This is a convenience method. No attempt is made + * to detect out-of-range indices. + * + * @param tx the horizontal index of a tile. + * @return the X coordinate of the tile's upper left pixel. + */ + public int tileXToX(int tx) { + return tx*tileWidth + tileGridXOffset; + } + + /** + * Converts a vertical tile index into the Y coordinate of its + * upper left pixel. This is a convenience method. No attempt is made + * to detect out-of-range indices. + * + * @param ty the vertical index of a tile. + * @return the Y coordinate of the tile's upper left pixel. + */ + public int tileYToY(int ty) { + return ty*tileHeight + tileGridYOffset; + } + + public Vector getSources() { + return null; + } + + /** + * Returns the entire image in a single Raster. For images with + * multiple tiles this will require making a copy. + * + *

The returned Raster is semantically a copy. This means + * that updates to the source image will not be reflected in the + * returned Raster. For non-writable (immutable) source images, + * the returned value may be a reference to the image's internal + * data. The returned Raster should be considered non-writable; + * any attempt to alter its pixel data (such as by casting it to + * WritableRaster or obtaining and modifying its DataBuffer) may + * result in undefined behavior. The copyData method should be + * used if the returned Raster is to be modified. + * + * @return a Raster containing a copy of this image's data. + */ + public Raster getData() { + Rectangle rect = new Rectangle(getMinX(), getMinY(), + getWidth(), getHeight()); + return getData(rect); + } + + /** + * Returns an arbitrary rectangular region of the RenderedImage + * in a Raster. The rectangle of interest will be clipped against + * the image bounds. + * + *

The returned Raster is semantically a copy. This means + * that updates to the source image will not be reflected in the + * returned Raster. For non-writable (immutable) source images, + * the returned value may be a reference to the image's internal + * data. The returned Raster should be considered non-writable; + * any attempt to alter its pixel data (such as by casting it to + * WritableRaster or obtaining and modifying its DataBuffer) may + * result in undefined behavior. The copyData method should be + * used if the returned Raster is to be modified. + * + * @param bounds the region of the RenderedImage to be returned. + */ + public Raster getData(Rectangle bounds) { + // Get the image bounds. + Rectangle imageBounds = getBounds(); + + // Check for parameter validity. + if(bounds == null) { + bounds = imageBounds; + } else if(!bounds.intersects(imageBounds)) { + throw new IllegalArgumentException("The provided region doesn't intersect with the image bounds."); + } + + // Determine tile limits for the prescribed bounds. + int startX = XToTileX(bounds.x); + int startY = YToTileY(bounds.y); + int endX = XToTileX(bounds.x + bounds.width - 1); + int endY = YToTileY(bounds.y + bounds.height - 1); + + // If the bounds are contained in a single tile, return a child + // of that tile's Raster. + if ((startX == endX) && (startY == endY)) { + Raster tile = getTile(startX, startY); + return tile.createChild(bounds.x, bounds.y, + bounds.width, bounds.height, + bounds.x, bounds.y, null); + } else { + // Recalculate the tile limits if the data bounds are not a + // subset of the image bounds. + if(!imageBounds.contains(bounds)) { + Rectangle xsect = bounds.intersection(imageBounds); + startX = XToTileX(xsect.x); + startY = YToTileY(xsect.y); + endX = XToTileX(xsect.x + xsect.width - 1); + endY = YToTileY(xsect.y + xsect.height - 1); + } + + // Create a WritableRaster of the desired size + SampleModel sm = + sampleModel.createCompatibleSampleModel(bounds.width, + bounds.height); + + // Translate it + WritableRaster dest = + Raster.createWritableRaster(sm, bounds.getLocation()); + + // Loop over the tiles in the intersection. + for (int j = startY; j <= endY; j++) { + for (int i = startX; i <= endX; i++) { + // Retrieve the tile. + Raster tile = getTile(i, j); + + // Create a child of the tile for the intersection of + // the tile bounds and the bounds of the requested area. + Rectangle tileRect = tile.getBounds(); + Rectangle intersectRect = + bounds.intersection(tile.getBounds()); + Raster liveRaster = tile.createChild(intersectRect.x, + intersectRect.y, + intersectRect.width, + intersectRect.height, + intersectRect.x, + intersectRect.y, + null); + + // Copy the data from the child. + dest.setRect(liveRaster); + } + } + + return dest; + } + } + + /** + * Copies an arbitrary rectangular region of the RenderedImage + * into a caller-supplied WritableRaster. The region to be + * computed is determined by clipping the bounds of the supplied + * WritableRaster against the bounds of the image. The supplied + * WritableRaster must have a SampleModel that is compatible with + * that of the image. + * + *

If the raster argument is null, the entire image will + * be copied into a newly-created WritableRaster with a SampleModel + * that is compatible with that of the image. + * + * @param dest a WritableRaster to hold the returned portion of + * the image. + * @return a reference to the supplied WritableRaster, or to a + * new WritableRaster if the supplied one was null. + */ + public WritableRaster copyData(WritableRaster dest) { + // Get the image bounds. + Rectangle imageBounds = getBounds(); + + Rectangle bounds; + if (dest == null) { + // Create a WritableRaster for the entire image. + bounds = imageBounds; + Point p = new Point(minX, minY); + SampleModel sm = + sampleModel.createCompatibleSampleModel(width, height); + dest = Raster.createWritableRaster(sm, p); + } else { + bounds = dest.getBounds(); + } + + // Determine tile limits for the intersection of the prescribed + // bounds with the image bounds. + Rectangle xsect = imageBounds.contains(bounds) ? + bounds : bounds.intersection(imageBounds); + int startX = XToTileX(xsect.x); + int startY = YToTileY(xsect.y); + int endX = XToTileX(xsect.x + xsect.width - 1); + int endY = YToTileY(xsect.y + xsect.height - 1); + + // Loop over the tiles in the intersection. + for (int j = startY; j <= endY; j++) { + for (int i = startX; i <= endX; i++) { + // Retrieve the tile. + Raster tile = getTile(i, j); + + // Create a child of the tile for the intersection of + // the tile bounds and the bounds of the requested area. + Rectangle tileRect = tile.getBounds(); + Rectangle intersectRect = + bounds.intersection(tile.getBounds()); + Raster liveRaster = tile.createChild(intersectRect.x, + intersectRect.y, + intersectRect.width, + intersectRect.height, + intersectRect.x, + intersectRect.y, + null); + + // Copy the data from the child. + dest.setRect(liveRaster); + } + } + + return dest; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SingleTileRenderedImage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SingleTileRenderedImage.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2005, 2015, 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.common; + +import java.awt.image.ColorModel; +import java.awt.image.Raster; + +/** + * A simple class that provides RenderedImage functionality + * given a Raster and a ColorModel. + */ +public class SingleTileRenderedImage extends SimpleRenderedImage { + + Raster ras; + + /** + * Constructs a SingleTileRenderedImage based on a Raster + * and a ColorModel. + * + * @param ras A Raster that will define tile (0, 0) of the image. + * @param cm A ColorModel that will serve as the image's + * ColorModel. + */ + public SingleTileRenderedImage(Raster ras, ColorModel colorModel) { + this.ras = ras; + + this.tileGridXOffset = this.minX = ras.getMinX(); + this.tileGridYOffset = this.minY = ras.getMinY(); + this.tileWidth = this.width = ras.getWidth(); + this.tileHeight = this.height = ras.getHeight(); + this.sampleModel = ras.getSampleModel(); + this.colorModel = colorModel; + } + + /** + * Returns the image's Raster as tile (0, 0). + */ + public Raster getTile(int tileX, int tileY) { + if (tileX != 0 || tileY != 0) { + throw new IllegalArgumentException("tileX != 0 || tileY != 0"); + } + return ras; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/iio-plugin.properties --- a/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/iio-plugin.properties Mon Nov 23 10:00:50 2015 -0800 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/iio-plugin.properties Mon Nov 23 12:26:12 2015 -0800 @@ -8,7 +8,7 @@ # Common properties ImageUtil0=The supplied Raster does not represent a binary data set. ImageUtil1=The provided sample model is null. -SimpleRenderedImage0=The provided region doesn't intersect with the image bounds. +ImageUtil2=The provided image cannot be encoded using: GetNumImages0=Input has not been set. GetNumImages1=seekForwardOnly and allowSearch cannot both be true. diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFAttrInfo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFAttrInfo.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.metadata.IIOMetadataFormat; + +public class TIFFAttrInfo { + int valueType = IIOMetadataFormat.VALUE_ARBITRARY; + int dataType; + boolean isRequired = false; + int listMinLength = 0; + int listMaxLength = Integer.MAX_VALUE; + + public TIFFAttrInfo() { } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFBaseJPEGCompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFBaseJPEGCompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,444 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.awt.Point; +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.Raster; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.io.IOException; +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Iterator; +import javax.imageio.IIOException; +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.plugins.jpeg.JPEGImageWriteParam; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.stream.MemoryCacheImageOutputStream; +import org.w3c.dom.Node; + +/** + * Base class for all possible forms of JPEG compression in TIFF. + */ +public abstract class TIFFBaseJPEGCompressor extends TIFFCompressor { + + // Stream metadata format. + protected static final String STREAM_METADATA_NAME = + "javax_imageio_jpeg_stream_1.0"; + + // Image metadata format. + protected static final String IMAGE_METADATA_NAME = + "javax_imageio_jpeg_image_1.0"; + + // ImageWriteParam passed in. + private ImageWriteParam param = null; + + /** + * ImageWriteParam for JPEG writer. + * May be initialized by {@link #initJPEGWriter()}. + */ + protected JPEGImageWriteParam JPEGParam = null; + + /** + * The JPEG writer. + * May be initialized by {@link #initJPEGWriter()}. + */ + protected ImageWriter JPEGWriter = null; + + /** + * Whether to write abbreviated JPEG streams (default == false). + * A subclass which sets this to true should also + * initialized {@link #JPEGStreamMetadata}. + */ + protected boolean writeAbbreviatedStream = false; + + /** + * Stream metadata equivalent to a tables-only stream such as in + * the JPEGTables. Default value is null. + * This should be set by any subclass which sets + * {@link writeAbbreviatedStream} to true. + */ + protected IIOMetadata JPEGStreamMetadata = null; + + // A pruned image metadata object containing only essential nodes. + private IIOMetadata JPEGImageMetadata = null; + + // Array-based output stream. + private IIOByteArrayOutputStream baos; + + /** + * Removes nonessential nodes from a JPEG native image metadata tree. + * All nodes derived from JPEG marker segments other than DHT, DQT, + * SOF, SOS segments are removed unless pruneTables is + * true in which case the nodes derived from the DHT and + * DQT marker segments are also removed. + * + * @param tree A javax_imageio_jpeg_image_1.0 tree. + * @param pruneTables Whether to prune Huffman and quantization tables. + * @throws NullPointerException if tree is + * null. + * @throws IllegalArgumentException if tree is not the root + * of a JPEG native image metadata tree. + */ + private static void pruneNodes(Node tree, boolean pruneTables) { + if(tree == null) { + throw new NullPointerException("tree == null!"); + } + if(!tree.getNodeName().equals(IMAGE_METADATA_NAME)) { + throw new IllegalArgumentException + ("root node name is not "+IMAGE_METADATA_NAME+"!"); + } + + // Create list of required nodes. + List wantedNodes = new ArrayList(); + wantedNodes.addAll(Arrays.asList(new String[] { + "JPEGvariety", "markerSequence", + "sof", "componentSpec", + "sos", "scanComponentSpec" + })); + + // Add Huffman and quantization table nodes if not pruning tables. + if(!pruneTables) { + wantedNodes.add("dht"); + wantedNodes.add("dhtable"); + wantedNodes.add("dqt"); + wantedNodes.add("dqtable"); + } + + IIOMetadataNode iioTree = (IIOMetadataNode)tree; + + List nodes = getAllNodes(iioTree, null); + int numNodes = nodes.size(); + + for(int i = 0; i < numNodes; i++) { + Node node = nodes.get(i); + if(!wantedNodes.contains(node.getNodeName())) { + node.getParentNode().removeChild(node); + } + } + } + + private static List getAllNodes(IIOMetadataNode root, List nodes) { + if(nodes == null) nodes = new ArrayList(); + + if(root.hasChildNodes()) { + Node sibling = root.getFirstChild(); + while(sibling != null) { + nodes.add(sibling); + nodes = getAllNodes((IIOMetadataNode)sibling, nodes); + sibling = sibling.getNextSibling(); + } + } + + return nodes; + } + + public TIFFBaseJPEGCompressor(String compressionType, + int compressionTagValue, + boolean isCompressionLossless, + ImageWriteParam param) { + super(compressionType, compressionTagValue, isCompressionLossless); + + this.param = param; + } + + /** + * A ByteArrayOutputStream which allows writing to an + * ImageOutputStream. + */ + private static class IIOByteArrayOutputStream extends ByteArrayOutputStream { + IIOByteArrayOutputStream() { + super(); + } + + IIOByteArrayOutputStream(int size) { + super(size); + } + + public synchronized void writeTo(ImageOutputStream ios) + throws IOException { + ios.write(buf, 0, count); + } + } + + /** + * Initializes the JPEGWriter and JPEGParam instance variables. + * This method must be called before encode() is invoked. + * + * @param supportsStreamMetadata Whether the JPEG writer must + * support JPEG native stream metadata, i.e., be capable of writing + * abbreviated streams. + * @param supportsImageMetadata Whether the JPEG writer must + * support JPEG native image metadata. + */ + protected void initJPEGWriter(boolean supportsStreamMetadata, + boolean supportsImageMetadata) { + // Reset the writer to null if it does not match preferences. + if(this.JPEGWriter != null && + (supportsStreamMetadata || supportsImageMetadata)) { + ImageWriterSpi spi = this.JPEGWriter.getOriginatingProvider(); + if(supportsStreamMetadata) { + String smName = spi.getNativeStreamMetadataFormatName(); + if(smName == null || !smName.equals(STREAM_METADATA_NAME)) { + this.JPEGWriter = null; + } + } + if(this.JPEGWriter != null && supportsImageMetadata) { + String imName = spi.getNativeImageMetadataFormatName(); + if(imName == null || !imName.equals(IMAGE_METADATA_NAME)) { + this.JPEGWriter = null; + } + } + } + + // Set the writer. + if(this.JPEGWriter == null) { + Iterator iter = ImageIO.getImageWritersByFormatName("jpeg"); + + while(iter.hasNext()) { + // Get a writer. + ImageWriter writer = iter.next(); + + // Verify its metadata support level. + if(supportsStreamMetadata || supportsImageMetadata) { + ImageWriterSpi spi = writer.getOriginatingProvider(); + if(supportsStreamMetadata) { + String smName = + spi.getNativeStreamMetadataFormatName(); + if(smName == null || + !smName.equals(STREAM_METADATA_NAME)) { + // Try the next one. + continue; + } + } + if(supportsImageMetadata) { + String imName = + spi.getNativeImageMetadataFormatName(); + if(imName == null || + !imName.equals(IMAGE_METADATA_NAME)) { + // Try the next one. + continue; + } + } + } + + // Set the writer. + this.JPEGWriter = writer; + break; + } + + if(this.JPEGWriter == null) { + throw new NullPointerException + ("No appropriate JPEG writers found!"); + } + } + + // Initialize the ImageWriteParam. + if(this.JPEGParam == null) { + if(param != null && param instanceof JPEGImageWriteParam) { + JPEGParam = (JPEGImageWriteParam)param; + } else { + JPEGParam = + new JPEGImageWriteParam(writer != null ? + writer.getLocale() : null); + if (param != null && param.getCompressionMode() + == ImageWriteParam.MODE_EXPLICIT) { + JPEGParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + JPEGParam.setCompressionQuality(param.getCompressionQuality()); + } + } + } + } + + /** + * Retrieves image metadata with non-core nodes removed. + */ + private IIOMetadata getImageMetadata(boolean pruneTables) + throws IIOException { + if(JPEGImageMetadata == null && + IMAGE_METADATA_NAME.equals(JPEGWriter.getOriginatingProvider().getNativeImageMetadataFormatName())) { + TIFFImageWriter tiffWriter = (TIFFImageWriter)this.writer; + + // Get default image metadata. + JPEGImageMetadata = + JPEGWriter.getDefaultImageMetadata(tiffWriter.getImageType(), + JPEGParam); + + // Get the DOM tree. + Node tree = JPEGImageMetadata.getAsTree(IMAGE_METADATA_NAME); + + // Remove unwanted marker segments. + try { + pruneNodes(tree, pruneTables); + } catch(IllegalArgumentException e) { + throw new IIOException("Error pruning unwanted nodes", e); + } + + // Set the DOM back into the metadata. + try { + JPEGImageMetadata.setFromTree(IMAGE_METADATA_NAME, tree); + } catch(IIOInvalidTreeException e) { + throw new IIOException + ("Cannot set pruned image metadata!", e); + } + } + + return JPEGImageMetadata; + } + + public final int encode(byte[] b, int off, + int width, int height, + int[] bitsPerSample, + int scanlineStride) throws IOException { + if (this.JPEGWriter == null) { + throw new IIOException("JPEG writer has not been initialized!"); + } + if (!((bitsPerSample.length == 3 + && bitsPerSample[0] == 8 + && bitsPerSample[1] == 8 + && bitsPerSample[2] == 8) + || (bitsPerSample.length == 1 + && bitsPerSample[0] == 8))) { + throw new IIOException("Can only JPEG compress 8- and 24-bit images!"); + } + + // Set the stream. + // The stream has to be wrapped as the Java Image I/O JPEG + // ImageWriter flushes the stream at the end of each write() + // and this causes problems for the TIFF writer. + if (baos == null) { + baos = new IIOByteArrayOutputStream(); + } else { + baos.reset(); + } + ImageOutputStream ios = new MemoryCacheImageOutputStream(baos); + JPEGWriter.setOutput(ios); + + // Create a DataBuffer. + DataBufferByte dbb; + if (off == 0) { + dbb = new DataBufferByte(b, b.length); + } else { + // + // Workaround for bug in core Java Image I/O JPEG + // ImageWriter which cannot handle non-zero offsets. + // + int bytesPerSegment = scanlineStride * height; + byte[] btmp = new byte[bytesPerSegment]; + System.arraycopy(b, off, btmp, 0, bytesPerSegment); + dbb = new DataBufferByte(btmp, bytesPerSegment); + off = 0; + } + + // Set up the ColorSpace. + int[] offsets; + ColorSpace cs; + if (bitsPerSample.length == 3) { + offsets = new int[]{off, off + 1, off + 2}; + cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); + } else { + offsets = new int[]{off}; + cs = ColorSpace.getInstance(ColorSpace.CS_GRAY); + } + + // Create the ColorModel. + ColorModel cm = new ComponentColorModel(cs, + false, + false, + Transparency.OPAQUE, + DataBuffer.TYPE_BYTE); + + // Create the SampleModel. + SampleModel sm + = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, + width, height, + bitsPerSample.length, + scanlineStride, + offsets); + + // Create the WritableRaster. + WritableRaster wras + = Raster.createWritableRaster(sm, dbb, new Point(0, 0)); + + // Create the BufferedImage. + BufferedImage bi = new BufferedImage(cm, wras, false, null); + + // Get the pruned JPEG image metadata (may be null). + IIOMetadata imageMetadata = getImageMetadata(writeAbbreviatedStream); + + // Compress the image into the output stream. + int compDataLength; + if (writeAbbreviatedStream) { + // Write abbreviated JPEG stream + + // First write the tables-only data. + JPEGWriter.prepareWriteSequence(JPEGStreamMetadata); + ios.flush(); + + // Rewind to the beginning of the byte array. + baos.reset(); + + // Write the abbreviated image data. + IIOImage image = new IIOImage(bi, null, imageMetadata); + JPEGWriter.writeToSequence(image, JPEGParam); + JPEGWriter.endWriteSequence(); + } else { + // Write complete JPEG stream + JPEGWriter.write(null, + new IIOImage(bi, null, imageMetadata), + JPEGParam); + } + + compDataLength = baos.size(); + baos.writeTo(stream); + baos.reset(); + + return compDataLength; + } + + protected void finalize() throws Throwable { + super.finalize(); + if(JPEGWriter != null) { + JPEGWriter.dispose(); + } + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFCIELabColorConverter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFCIELabColorConverter.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +public class TIFFCIELabColorConverter extends TIFFColorConverter { + + // XYZ coordinate or reference white (CIE D65) + private static final float Xn = 95.047f; + private static final float Yn = 100.0f; + private static final float Zn = 108.883f; + + private static final float THRESHOLD = (float)Math.pow(0.008856, 1.0/3.0); + + public TIFFCIELabColorConverter() {} + + + private float clamp(float x) { + if (x < 0.0f) { + return 0.0f; + } else if (x > 100.0f) { + return 255.0f; + } else { + return x*(255.0f/100.0f); + } + } + + private float clamp2(float x) { + if (x < 0.0f) { + return 0.0f; + } else if (x > 255.0f) { + return 255.0f; + } else { + return x; + } + } + + public void fromRGB(float r, float g, float b, float[] result) { + float X = 0.412453f*r + 0.357580f*g + 0.180423f*b; + float Y = 0.212671f*r + 0.715160f*g + 0.072169f*b; + float Z = 0.019334f*r + 0.119193f*g + 0.950227f*b; + + float YYn = Y/Yn; + float XXn = X/Xn; + float ZZn = Z/Zn; + + if (YYn < 0.008856f) { + YYn = 7.787f*YYn + 16.0f/116.0f; + } else { + YYn = (float)Math.pow(YYn, 1.0/3.0); + } + + if (XXn < 0.008856f) { + XXn = 7.787f*XXn + 16.0f/116.0f; + } else { + XXn = (float)Math.pow(XXn, 1.0/3.0); + } + + if (ZZn < 0.008856f) { + ZZn = 7.787f*ZZn + 16.0f/116.0f; + } else { + ZZn = (float)Math.pow(ZZn, 1.0/3.0); + } + + float LStar = 116.0f*YYn - 16.0f; + float aStar = 500.0f*(XXn - YYn); + float bStar = 200.0f*(YYn - ZZn); + + LStar *= 255.0f/100.0f; + if (aStar < 0.0f) { + aStar += 256.0f; + } + if (bStar < 0.0f) { + bStar += 256.0f; + } + + result[0] = clamp2(LStar); + result[1] = clamp2(aStar); + result[2] = clamp2(bStar); + } + + public void toRGB(float x0, float x1, float x2, float[] rgb) { + float LStar = x0*100.0f/255.0f; + float aStar = (x1 > 128.0f) ? (x1 - 256.0f) : x1; + float bStar = (x2 > 128.0f) ? (x2 - 256.0f) : x2; + + float YYn; // Y/Yn + float fY; // 'F' value for Y + + if (LStar < 8.0f) { + YYn = LStar/903.3f; + fY = 7.787f*YYn + 16.0f/116.0f; + } else { + float YYn_cubeRoot = (LStar + 16.0f)/116.0f; + YYn = YYn_cubeRoot*YYn_cubeRoot*YYn_cubeRoot; + fY = (float)Math.pow(YYn, 1.0/3.0); + } + float Y = YYn*Yn; + + float fX = fY + (aStar/500.0f); + float X; + if (fX <= THRESHOLD) { + X = Xn*(fX - 16.0f/116.0f)/7.787f; + } else { + X = Xn*fX*fX*fX; + } + + float fZ = fY - bStar/200.0f; + float Z; + if (fZ <= THRESHOLD) { + Z = Zn*(fZ - 16.0f/116.0f)/7.787f; + } else { + Z = Zn*fZ*fZ*fZ; + } + + float R = 3.240479f*X - 1.537150f*Y - 0.498535f*Z; + float G = -0.969256f*X + 1.875992f*Y + 0.041556f*Z; + float B = 0.055648f*X - 0.204043f*Y + 1.057311f*Z; + + rgb[0] = clamp(R); + rgb[1] = clamp(G); + rgb[2] = clamp(B); + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFColorConverter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFColorConverter.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +/** + * An abstract class that performs simple color conversion on 3-banded source + * images, for use with the TIFF Image I/O plug-in. + */ +public abstract class TIFFColorConverter { + + /** + * Constructs an instance of a TIFFColorConverter. + */ + public TIFFColorConverter() {} + + /** + * Converts an RGB triple into the native color space of this + * TIFFColorConverter, and stores the result in the first three + * entries of the result array. + * + * @param r the red value. + * @param g the green value. + * @param b the blue value. + * @param result an array of floats containing three elements. + * @throws NullPointerException if result is + * null. + * @throws ArrayIndexOutOfBoundsException if + * result.length < 3. + */ + public abstract void fromRGB(float r, float g, float b, float[] result); + + /** + * Converts a triple in the native color space of this + * TIFFColorConverter into an RGB triple, and stores the result in + * the first three entries of the rgb array. + * + * @param x0 the value of channel 0. + * @param x1 the value of channel 1. + * @param x2 the value of channel 2. + * @param rgb an array of floats containing three elements. + * @throws NullPointerException if rgb is + * null. + * @throws ArrayIndexOutOfBoundsException if + * rgb.length < 3. + */ + public abstract void toRGB(float x0, float x1, float x2, float[] rgb); +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFCompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFCompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.io.IOException; +import javax.imageio.ImageWriter; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.stream.ImageOutputStream; + +/** + * An abstract superclass for pluggable TIFF compressors. + */ +public abstract class TIFFCompressor { + + /** + * The ImageWriter calling this + * TIFFCompressor. + */ + protected ImageWriter writer; + + /** + * The IIOMetadata object containing metadata for the + * current image. + */ + protected IIOMetadata metadata; + + /** + * The name of the compression type supported by this compressor. + */ + protected String compressionType; + + /** + * The value to be assigned to the TIFF Compression tag in the + * TIFF image metadata. + */ + protected int compressionTagValue; + + /** + * Whether the compression is lossless. + */ + protected boolean isCompressionLossless; + + /** + * The ImageOutputStream to be written. + */ + protected ImageOutputStream stream; + + /** + * Creates a compressor object for use in compressing TIFF data. This + * object may be passed to the + * {@link TIFFImageWriteParam#setTIFFCompressor(TIFFCompressor)} + * method to override the compressor of a supported compression type or + * to provide the implementation of the compression algorithm of an + * unsupported compression type. + * + *

The parameters compressionTagValue and + * isCompressionLossless are provided to accomodate + * compression types which are unknown. A compression type is + * "known" if it is either among those already supported by the + * TIFF writer (see {@link TIFFImageWriteParam}), or is listed in + * the TIFF 6.0 specification but not supported. If the compression + * type is unknown, the compressionTagValue and + * isCompressionLossless parameters are ignored.

+ * + * @param compressionType The name of the compression type. + * @param compressionTagValue The value to be assigned to the TIFF + * Compression tag in the TIFF image metadata; ignored if + * compressionType is a known type. + * @param isCompressionLossless Whether the compression is lossless; + * ignored if compressionType is a known type. + * + * @throws NullPointerException if compressionType is + * null. + * @throws IllegalArgumentException if compressionTagValue is + * less 1. + */ + public TIFFCompressor(String compressionType, + int compressionTagValue, + boolean isCompressionLossless) { + if(compressionType == null) { + throw new NullPointerException("compressionType == null"); + } else if(compressionTagValue < 1) { + throw new IllegalArgumentException("compressionTagValue < 1"); + } + + // Set the compression type. + this.compressionType = compressionType; + + // Determine whether this type is either defined in the TIFF 6.0 + // specification or is already supported. + int compressionIndex = -1; + String[] compressionTypes = TIFFImageWriter.compressionTypes; + int len = compressionTypes.length; + for(int i = 0; i < len; i++) { + if(compressionTypes[i].equals(compressionType)) { + // Save the index of the supported type. + compressionIndex = i; + break; + } + } + + if(compressionIndex != -1) { + // Known compression type. + this.compressionTagValue = + TIFFImageWriter.compressionNumbers[compressionIndex]; + this.isCompressionLossless = + TIFFImageWriter.isCompressionLossless[compressionIndex]; + } else { + // Unknown compression type. + this.compressionTagValue = compressionTagValue; + this.isCompressionLossless = isCompressionLossless; + } + } + + /** + * Retrieve the name of the compression type supported by this compressor. + * + * @return The compression type name. + */ + public String getCompressionType() { + return compressionType; + } + + /** + * Retrieve the value to be assigned to the TIFF Compression tag + * in the TIFF image metadata. + * + * @return The Compression tag value. + */ + public int getCompressionTagValue() { + return compressionTagValue; + } + + /** + * Retrieves a value indicating whether the compression is lossless. + * + * @return Whether the compression is lossless. + */ + public boolean isCompressionLossless() { + return isCompressionLossless; + } + + /** + * Sets the ImageOutputStream to be written. + * + * @param stream an ImageOutputStream to be written. + * + * @see #getStream + */ + public void setStream(ImageOutputStream stream) { + this.stream = stream; + } + + /** + * Returns the ImageOutputStream that will be written. + * + * @return an ImageOutputStream. + * + * @see #setStream(ImageOutputStream) + */ + public ImageOutputStream getStream() { + return stream; + } + + /** + * Sets the value of the writer field. + * + * @param writer the current ImageWriter. + * + * @see #getWriter() + */ + public void setWriter(ImageWriter writer) { + this.writer = writer; + } + + /** + * Returns the current ImageWriter. + * + * @return an ImageWriter. + * + * @see #setWriter(ImageWriter) + */ + public ImageWriter getWriter() { + return this.writer; + } + + /** + * Sets the value of the metadata field. + * + * @param metadata the IIOMetadata object for the + * image being written. + * + * @see #getMetadata() + */ + public void setMetadata(IIOMetadata metadata) { + this.metadata = metadata; + } + + /** + * Returns the current IIOMetadata object. + * + * @return the IIOMetadata object for the image being + * written. + * + * @see #setMetadata(IIOMetadata) + */ + public IIOMetadata getMetadata() { + return this.metadata; + } + + /** + * Encodes the supplied image data, writing to the currently set + * ImageOutputStream. + * + * @param b an array of bytes containing the packed + * but uncompressed image data. + * @param off the starting offset of the data to be written in the + * array b. + * @param width the width of the rectangle of pixels to be written. + * @param height the height of the rectangle of pixels to be written. + * @param bitsPerSample an array of ints indicting + * the number of bits used to represent each image sample within + * a pixel. + * @param scanlineStride the number of bytes separating each + * row of the input data. + * + * @return the number of bytes written. + * + * @throws IOException if the supplied data cannot be encoded by + * this TIFFCompressor, or if any I/O error occurs + * during writing. + */ + public abstract int encode(byte[] b, int off, + int width, int height, + int[] bitsPerSample, + int scanlineStride) throws IOException; + +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDecompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDecompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,2816 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.awt.Rectangle; +import java.awt.Transparency; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferDouble; +import java.awt.image.DataBufferFloat; +import java.awt.image.DataBufferInt; +import java.awt.image.DataBufferShort; +import java.awt.image.DataBufferUShort; +import java.awt.image.MultiPixelPackedSampleModel; +import java.awt.image.PixelInterleavedSampleModel; +import java.awt.image.Raster; +import java.awt.image.SampleModel; +import java.awt.image.SinglePixelPackedSampleModel; +import java.awt.image.WritableRaster; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.ByteOrder; +import javax.imageio.IIOException; +import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.MemoryCacheImageInputStream; +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import com.sun.imageio.plugins.common.ImageUtil; +import com.sun.imageio.plugins.common.BogusColorSpace; +import com.sun.imageio.plugins.common.SimpleCMYKColorSpace; + +/** + * A class defining a pluggable TIFF decompressor. + * + *

The mapping between source and destination Y coordinates is + * given by the equations: + * + *

+ * dx = (sx - sourceXOffset)/subsampleX + dstXOffset;
+ * dy = (sy - sourceYOffset)/subsampleY + dstYOffset;
+ * 
+ * + * Note that the mapping from source coordinates to destination + * coordinates is not one-to-one if subsampling is being used, since + * only certain source pixels are to be copied to the + * destination. However, * the inverse mapping is always one-to-one: + * + *
+ * sx = (dx - dstXOffset)*subsampleX + sourceXOffset;
+ * sy = (dy - dstYOffset)*subsampleY + sourceYOffset;
+ * 
+ * + *

Decompressors may be written with various levels of complexity. + * The most complex decompressors will override the + * decode method, and will perform all the work of + * decoding, subsampling, offsetting, clipping, and format conversion. + * This approach may be the most efficient, since it is possible to + * avoid the use of extra image buffers, and it may be possible to + * avoid decoding portions of the image that will not be copied into + * the destination. + * + *

Less ambitious decompressors may override the + * decodeRaw method, which is responsible for + * decompressing the entire tile or strip into a byte array (or other + * appropriate datatype). The default implementation of + * decode will perform all necessary setup of buffers, + * call decodeRaw to perform the actual decoding, perform + * subsampling, and copy the results into the final destination image. + * Where possible, it will pass the real image buffer to + * decodeRaw in order to avoid making an extra copy. + * + *

Slightly more ambitious decompressors may override + * decodeRaw, but avoid writing pixels that will be + * discarded in the subsampling phase. + */ +public abstract class TIFFDecompressor { + + /** + * The ImageReader calling this + * TIFFDecompressor. + */ + protected ImageReader reader; + + /** + * The IIOMetadata object containing metadata for the + * current image. + */ + protected IIOMetadata metadata; + + /** + * The value of the PhotometricInterpretation tag. + * Legal values are {@link + * BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO }, + * {@link + * BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO}, + * {@link BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_RGB}, + * {@link + * BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR}, + * {@link + * BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_TRANSPARENCY_MASK}, + * {@link BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_Y_CB_CR}, + * {@link BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_CIELAB}, + * {@link BaselineTIFFTagSet#PHOTOMETRIC_INTERPRETATION_ICCLAB}, + * or other value defined by a TIFF extension. + */ + protected int photometricInterpretation; + + /** + * The value of the Compression tag. Legal values are + * {@link BaselineTIFFTagSet#COMPRESSION_NONE}, {@link + * BaselineTIFFTagSet#COMPRESSION_CCITT_RLE}, {@link + * BaselineTIFFTagSet#COMPRESSION_CCITT_T_4}, {@link + * BaselineTIFFTagSet#COMPRESSION_CCITT_T_6}, {@link + * BaselineTIFFTagSet#COMPRESSION_LZW}, {@link + * BaselineTIFFTagSet#COMPRESSION_OLD_JPEG}, {@link + * BaselineTIFFTagSet#COMPRESSION_JPEG}, {@link + * BaselineTIFFTagSet#COMPRESSION_ZLIB}, {@link + * BaselineTIFFTagSet#COMPRESSION_PACKBITS}, {@link + * BaselineTIFFTagSet#COMPRESSION_DEFLATE}, or other value + * defined by a TIFF extension. + */ + protected int compression; + + /** + * true if the image is encoded using separate planes. + */ + protected boolean planar; + + /** + * The value of the SamplesPerPixel tag. + */ + protected int samplesPerPixel; + + /** + * The value of the BitsPerSample tag. + * + */ + protected int[] bitsPerSample; + + /** + * The value of the SampleFormat tag. Legal values + * are {@link BaselineTIFFTagSet#SAMPLE_FORMAT_UNSIGNED_INTEGER}, + * {@link BaselineTIFFTagSet#SAMPLE_FORMAT_SIGNED_INTEGER}, {@link + * BaselineTIFFTagSet#SAMPLE_FORMAT_FLOATING_POINT}, {@link + * BaselineTIFFTagSet#SAMPLE_FORMAT_UNDEFINED}, or other value + * defined by a TIFF extension. + */ + protected int[] sampleFormat = + new int[] {BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER}; + + /** + * The value of the ExtraSamples tag. Legal values + * are {@link BaselineTIFFTagSet#EXTRA_SAMPLES_UNSPECIFIED}, + * {@link BaselineTIFFTagSet#EXTRA_SAMPLES_ASSOCIATED_ALPHA}, + * {@link BaselineTIFFTagSet#EXTRA_SAMPLES_UNASSOCIATED_ALPHA}, + * or other value defined by a TIFF extension. + */ + protected int[] extraSamples; + + /** + * The value of the ColorMap tag. + * + */ + protected char[] colorMap; + + // Region of input stream containing the data + + /** + * The ImageInputStream containing the TIFF source + * data. + */ + protected ImageInputStream stream; + + /** + * The offset in the source ImageInputStream of the + * start of the data to be decompressed. + */ + protected long offset; + + /** + * The number of bytes of data from the source + * ImageInputStream to be decompressed. + */ + protected int byteCount; + + // Region of the file image represented in the stream + // This is unaffected by subsampling + + /** + * The X coordinate of the upper-left pixel of the source region + * being decoded from the source stream. This value is not affected + * by source subsampling. + */ + protected int srcMinX; + + /** + * The Y coordinate of the upper-left pixel of the source region + * being decoded from the source stream. This value is not affected + * by source subsampling. + */ + protected int srcMinY; + + /** + * The width of the source region being decoded from the source + * stream. This value is not affected by source subsampling. + */ + protected int srcWidth; + + /** + * The height of the source region being decoded from the source + * stream. This value is not affected by source subsampling. + */ + protected int srcHeight; + + // Subsampling to be performed + + /** + * The source X offset used, along with dstXOffset + * and subsampleX, to map between horizontal source + * and destination pixel coordinates. + */ + protected int sourceXOffset; + + /** + * The horizontal destination offset used, along with + * sourceXOffset and subsampleX, to map + * between horizontal source and destination pixel coordinates. + * See the comment for {@link #sourceXOffset sourceXOffset} for + * the mapping equations. + */ + protected int dstXOffset; + + /** + * The source Y offset used, along with dstYOffset + * and subsampleY, to map between vertical source and + * destination pixel coordinates. + */ + protected int sourceYOffset; + + /** + * The vertical destination offset used, along with + * sourceYOffset and subsampleY, to map + * between horizontal source and destination pixel coordinates. + * See the comment for {@link #sourceYOffset sourceYOffset} for + * the mapping equations. + */ + protected int dstYOffset; + + /** + * The horizontal subsampling factor. A factor of 1 means that + * every column is copied to the destination; a factor of 2 means + * that every second column is copied, etc. + */ + protected int subsampleX; + + /** + * The vertical subsampling factor. A factor of 1 means that + * every row is copied to the destination; a factor of 2 means + * that every second row is copied, etc. + */ + protected int subsampleY; + + // Band subsetting/rearrangement + + /** + * The sequence of source bands that are to be copied into the + * destination. + */ + protected int[] sourceBands; + + /** + * The sequence of destination bands to receive the source data. + */ + protected int[] destinationBands; + + // Destination for decodeRaw + + /** + * A BufferedImage for the decodeRaw + * method to write into. + */ + protected BufferedImage rawImage; + + // Destination + + /** + * The final destination image. + */ + protected BufferedImage image; + + /** + * The X coordinate of the upper left pixel to be written in the + * destination image. + */ + protected int dstMinX; + + /** + * The Y coordinate of the upper left pixel to be written in the + * destination image. + */ + protected int dstMinY; + + /** + * The width of the region of the destination image to be written. + */ + protected int dstWidth; + + /** + * The height of the region of the destination image to be written. + */ + protected int dstHeight; + + // Region of source contributing to the destination + + /** + * The X coordinate of the upper-left source pixel that will + * actually be copied into the destination image, taking into + * account all subsampling, offsetting, and clipping. That is, + * the pixel at (activeSrcMinX, + * activeSrcMinY) is to be copied into the + * destination pixel at (dstMinX, + * dstMinY). + * + *

The pixels in the source region to be copied are + * those with X coordinates of the form activeSrcMinX + + * k*subsampleX, where k is an integer such + * that 0 ≤ k < dstWidth. + */ + protected int activeSrcMinX; + + /** + * The Y coordinate of the upper-left source pixel that will + * actually be copied into the destination image, taking into account + * all subsampling, offsetting, and clipping. + * + *

The pixels in the source region to be copied are + * those with Y coordinates of the form activeSrcMinY + + * k*subsampleY, where k is an integer such + * that 0 ≤ k < dstHeight. + */ + protected int activeSrcMinY; + + /** + * The width of the source region that will actually be copied + * into the destination image, taking into account all + * susbampling, offsetting, and clipping. + * + *

The active source width will always be equal to + * (dstWidth - 1)*subsampleX + 1. + */ + protected int activeSrcWidth; + + /** + * The height of the source region that will actually be copied + * into the destination image, taking into account all + * susbampling, offsetting, and clipping. + * + *

The active source height will always be equal to + * (dstHeight - 1)*subsampleY + 1. + */ + protected int activeSrcHeight; + + /** + * A TIFFColorConverter object describing the color space of + * the encoded pixel data, or null. + */ + protected TIFFColorConverter colorConverter; + + private boolean isBilevel; + private boolean isContiguous; + private boolean isImageSimple; + private boolean adjustBitDepths; + private int[][] bitDepthScale; + + // source pixel at (sx, sy) should map to dst pixel (dx, dy), where: + // + // dx = (sx - sourceXOffset)/subsampleX + dstXOffset; + // dy = (sy - sourceYOffset)/subsampleY + dstYOffset; + // + // Note that this mapping is many-to-one. Source pixels such that + // (sx - sourceXOffset) % subsampleX != 0 should not be copied + // (and similarly for y). + // + // The backwards mapping from dest to source is one-to-one: + // + // sx = (dx - dstXOffset)*subsampleX + sourceXOffset; + // sy = (dy - dstYOffset)*subsampleY + sourceYOffset; + // + // The reader will always hand us the full source region as it + // exists in the file. It will take care of clipping the dest region + // to exactly those dest pixels that are present in the source region. + + /** + * Create a PixelInterleavedSampleModel for use in creating + * an ImageTypeSpecifier. Its dimensions will be 1x1 and + * it will have ascending band offsets as {0, 1, 2, ..., numBands}. + * + * @param dataType The data type (DataBuffer.TYPE_*). + * @param numBands The number of bands. + * @return A PixelInterleavedSampleModel. + */ + static SampleModel createInterleavedSM(int dataType, + int numBands) { + int[] bandOffsets = new int[numBands]; + for(int i = 0; i < numBands; i++) { + bandOffsets[i] = i; + } + return new PixelInterleavedSampleModel(dataType, + 1, // width + 1, // height + numBands, // pixelStride, + numBands, // scanlineStride + bandOffsets); + } + + /** + * Create a ComponentColorModel for use in creating + * an ImageTypeSpecifier. + */ + // This code was copied from javax.imageio.ImageTypeSpecifier. + static ColorModel createComponentCM(ColorSpace colorSpace, + int numBands, + int dataType, + boolean hasAlpha, + boolean isAlphaPremultiplied) { + int transparency = + hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE; + + int[] numBits = new int[numBands]; + int bits = DataBuffer.getDataTypeSize(dataType); + + for (int i = 0; i < numBands; i++) { + numBits[i] = bits; + } + + return new ComponentColorModel(colorSpace, + numBits, + hasAlpha, + isAlphaPremultiplied, + transparency, + dataType); + } + + private static int createMask(int[] bitsPerSample, int band) { + int mask = (1 << bitsPerSample[band]) - 1; + for (int i = band + 1; i < bitsPerSample.length; i++) { + mask <<= bitsPerSample[i]; + } + + return mask; + } + + private static int getDataTypeFromNumBits(int numBits, boolean isSigned) { + int dataType; + + if (numBits <= 8) { + dataType = DataBuffer.TYPE_BYTE; + } else if (numBits <= 16) { + dataType = isSigned ? + DataBuffer.TYPE_SHORT : DataBuffer.TYPE_USHORT; + } else { + dataType = DataBuffer.TYPE_INT; + } + + return dataType; + } + + private static boolean areIntArraysEqual(int[] a, int[] b) { + if(a == null || b == null) { + if(a == null && b == null) { + return true; + } else { // one is null and one is not + return false; + } + } + + if(a.length != b.length) { + return false; + } + + int length = a.length; + for(int i = 0; i < length; i++) { + if(a[i] != b[i]) { + return false; + } + } + + return true; + } + + /** + * Return the number of bits occupied by dataType + * which must be one of the DataBuffer TYPEs. + */ + private static int getDataTypeSize(int dataType) throws IIOException { + int dataTypeSize = 0; + switch(dataType) { + case DataBuffer.TYPE_BYTE: + dataTypeSize = 8; + break; + case DataBuffer.TYPE_SHORT: + case DataBuffer.TYPE_USHORT: + dataTypeSize = 16; + break; + case DataBuffer.TYPE_INT: + case DataBuffer.TYPE_FLOAT: + dataTypeSize = 32; + break; + case DataBuffer.TYPE_DOUBLE: + dataTypeSize = 64; + break; + default: + throw new IIOException("Unknown data type "+dataType); + } + + return dataTypeSize; + } + + /** + * Returns the number of bits per pixel. + */ + private static int getBitsPerPixel(SampleModel sm) { + int bitsPerPixel = 0; + int[] sampleSize = sm.getSampleSize(); + int numBands = sampleSize.length; + for(int i = 0; i < numBands; i++) { + bitsPerPixel += sampleSize[i]; + } + return bitsPerPixel; + } + + /** + * Returns whether all samples have the same number of bits. + */ + private static boolean areSampleSizesEqual(SampleModel sm) { + boolean allSameSize = true; + int[] sampleSize = sm.getSampleSize(); + int sampleSize0 = sampleSize[0]; + int numBands = sampleSize.length; + + for(int i = 1; i < numBands; i++) { + if(sampleSize[i] != sampleSize0) { + allSameSize = false; + break; + } + } + + return allSameSize; + } + + /** + * Determines whether the DataBuffer is filled without + * any interspersed padding bits. + */ + private static boolean isDataBufferBitContiguous(SampleModel sm) + throws IIOException { + int dataTypeSize = getDataTypeSize(sm.getDataType()); + + if(sm instanceof ComponentSampleModel) { + int numBands = sm.getNumBands(); + for(int i = 0; i < numBands; i++) { + if(sm.getSampleSize(i) != dataTypeSize) { + // Sample does not fill data element. + return false; + } + } + } else if(sm instanceof MultiPixelPackedSampleModel) { + MultiPixelPackedSampleModel mppsm = + (MultiPixelPackedSampleModel)sm; + if(dataTypeSize % mppsm.getPixelBitStride() != 0) { + // Pixels do not fill the data element. + return false; + } + } else if(sm instanceof SinglePixelPackedSampleModel) { + SinglePixelPackedSampleModel sppsm = + (SinglePixelPackedSampleModel)sm; + int numBands = sm.getNumBands(); + int numBits = 0; + for(int i = 0; i < numBands; i++) { + numBits += sm.getSampleSize(i); + } + if(numBits != dataTypeSize) { + // Pixel does not fill the data element. + return false; + } + } else { + // Unknown SampleModel class. + return false; + } + + return true; + } + + /** + * Reformats data read as bytes into a short or int buffer. + */ + private static void reformatData(byte[] buf, + int bytesPerRow, + int numRows, + short[] shortData, + int[] intData, + int outOffset, + int outStride) + throws IIOException { + + if(shortData != null) { + int inOffset = 0; + int shortsPerRow = bytesPerRow/2; + int numExtraBytes = bytesPerRow % 2; + for(int j = 0; j < numRows; j++) { + int k = outOffset; + for(int i = 0; i < shortsPerRow; i++) { + shortData[k++] = + (short)(((buf[inOffset++]&0xff) << 8) | + (buf[inOffset++]&0xff)); + } + if(numExtraBytes != 0) { + shortData[k++] = (short)((buf[inOffset++]&0xff) << 8); + } + outOffset += outStride; + } + } else if(intData != null) { + int inOffset = 0; + int intsPerRow = bytesPerRow/4; + int numExtraBytes = bytesPerRow % 4; + for(int j = 0; j < numRows; j++) { + int k = outOffset; + for(int i = 0; i < intsPerRow; i++) { + intData[k++] = + ((buf[inOffset++]&0xff) << 24) | + ((buf[inOffset++]&0xff) << 16) | + ((buf[inOffset++]&0xff) << 8) | + (buf[inOffset++]&0xff); + } + if(numExtraBytes != 0) { + int shift = 24; + int ival = 0; + for(int b = 0; b < numExtraBytes; b++) { + ival |= (buf[inOffset++]&0xff) << shift; + shift -= 8; + } + intData[k++] = ival; + } + outOffset += outStride; + } + } else { + throw new IIOException("shortData == null && intData == null!"); + } + } + + /** + * Reformats bit-discontiguous data into the DataBuffer + * of the supplied WritableRaster. + */ + private static void reformatDiscontiguousData(byte[] buf, + int stride, + int w, + int h, + WritableRaster raster) + throws IOException { + + // Get SampleModel info. + SampleModel sm = raster.getSampleModel(); + int numBands = sm.getNumBands(); + int[] sampleSize = sm.getSampleSize(); + + // Initialize input stream. + ByteArrayInputStream is = new ByteArrayInputStream(buf); + ImageInputStream iis = new MemoryCacheImageInputStream(is); + + // Reformat. + long iisPosition = 0L; + int y = raster.getMinY(); + for(int j = 0; j < h; j++, y++) { + iis.seek(iisPosition); + int x = raster.getMinX(); + for(int i = 0; i < w; i++, x++) { + for(int b = 0; b < numBands; b++) { + long bits = iis.readBits(sampleSize[b]); + raster.setSample(x, y, b, (int)bits); + } + } + iisPosition += stride; + } + } + + /** + * A utility method that returns an + * ImageTypeSpecifier suitable for decoding an image + * with the given parameters. + * + * @param photometricInterpretation the value of the + * PhotometricInterpretation field. + * @param compression the value of the Compression field. + * @param samplesPerPixel the value of the + * SamplesPerPixel field. + * @param bitsPerSample the value of the BitsPerSample field. + * @param sampleFormat the value of the SampleFormat field. + * @param extraSamples the value of the ExtraSamples field. + * @param colorMap the value of the ColorMap field. + * + * @return a suitable ImageTypeSpecifier, or + * null if it is not possible to create one. + */ + public static ImageTypeSpecifier + getRawImageTypeSpecifier(int photometricInterpretation, + int compression, + int samplesPerPixel, + int[] bitsPerSample, + int[] sampleFormat, + int[] extraSamples, + char[] colorMap) { + + // + // Types to support: + // + // 1, 2, 4, 8, or 16 bit grayscale or indexed + // 8,8-bit gray+alpha + // 16,16-bit gray+alpha + // 8,8,8-bit RGB + // 8,8,8,8-bit RGB+alpha + // 16,16,16-bit RGB + // 16,16,16,16-bit RGB+alpha + // R+G+B = 8-bit RGB + // R+G+B+A = 8-bit RGB + // R+G+B = 16-bit RGB + // R+G+B+A = 16-bit RGB + // 8X-bits/sample, arbitrary numBands. + // Arbitrary non-indexed, non-float layouts (discontiguous). + // + + // Band-sequential + + // 1, 2, 4, 8, or 16 bit grayscale or indexed images + if (samplesPerPixel == 1 && + (bitsPerSample[0] == 1 || + bitsPerSample[0] == 2 || + bitsPerSample[0] == 4 || + bitsPerSample[0] == 8 || + bitsPerSample[0] == 16)) { + + // 2 and 16 bits images are not in the baseline + // specification, but we will allow them anyway + // since they fit well into Java2D + // + // this raises the issue of how to write such images... + + if (colorMap == null) { + // Grayscale + boolean isSigned = (sampleFormat[0] == + BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER); + int dataType; + if (bitsPerSample[0] <= 8) { + dataType = DataBuffer.TYPE_BYTE; + } else { + dataType = sampleFormat[0] == + BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER ? + DataBuffer.TYPE_SHORT : + DataBuffer.TYPE_USHORT; + } + + return ImageTypeSpecifier.createGrayscale(bitsPerSample[0], + dataType, + isSigned); + } else { + // Indexed + int mapSize = 1 << bitsPerSample[0]; + byte[] redLut = new byte[mapSize]; + byte[] greenLut = new byte[mapSize]; + byte[] blueLut = new byte[mapSize]; + byte[] alphaLut = null; + + int idx = 0; + for (int i = 0; i < mapSize; i++) { + redLut[i] = (byte)((colorMap[i]*255)/65535); + greenLut[i] = (byte)((colorMap[mapSize + i]*255)/65535); + blueLut[i] = (byte)((colorMap[2*mapSize + i]*255)/65535); + } + + int dataType = bitsPerSample[0] == 8 ? + DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT; + return ImageTypeSpecifier.createIndexed(redLut, + greenLut, + blueLut, + alphaLut, + bitsPerSample[0], + dataType); + } + } + + // 8-bit gray-alpha + if (samplesPerPixel == 2 && + bitsPerSample[0] == 8 && + bitsPerSample[1] == 8) { + int dataType = DataBuffer.TYPE_BYTE; + boolean alphaPremultiplied = false; + if (extraSamples != null && + extraSamples[0] == + BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) { + alphaPremultiplied = true; + } + return ImageTypeSpecifier.createGrayscale(8, + dataType, + false, + alphaPremultiplied); + } + + // 16-bit gray-alpha + if (samplesPerPixel == 2 && + bitsPerSample[0] == 16 && + bitsPerSample[1] == 16) { + int dataType = sampleFormat[0] == + BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER ? + DataBuffer.TYPE_SHORT : + DataBuffer.TYPE_USHORT; + boolean alphaPremultiplied = false; + if (extraSamples != null && + extraSamples[0] == + BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) { + alphaPremultiplied = true; + } + boolean isSigned = dataType == DataBuffer.TYPE_SHORT; + return ImageTypeSpecifier.createGrayscale(16, + dataType, + isSigned, + alphaPremultiplied); + } + + ColorSpace rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB); + + // 8-bit RGB + if (samplesPerPixel == 3 && + bitsPerSample[0] == 8 && + bitsPerSample[1] == 8 && + bitsPerSample[2] == 8) { + int[] bandOffsets = new int[3]; + bandOffsets[0] = 0; + bandOffsets[1] = 1; + bandOffsets[2] = 2; + int dataType = DataBuffer.TYPE_BYTE; + ColorSpace theColorSpace; + if((photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR && + compression != BaselineTIFFTagSet.COMPRESSION_JPEG && + compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) || + photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB) { + theColorSpace = + ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB); + } else { + theColorSpace = rgb; + } + return ImageTypeSpecifier.createInterleaved(theColorSpace, + bandOffsets, + dataType, + false, + false); + } + + // 8-bit RGBA + if (samplesPerPixel == 4 && + bitsPerSample[0] == 8 && + bitsPerSample[1] == 8 && + bitsPerSample[2] == 8 && + bitsPerSample[3] == 8) { + int[] bandOffsets = new int[4]; + bandOffsets[0] = 0; + bandOffsets[1] = 1; + bandOffsets[2] = 2; + bandOffsets[3] = 3; + int dataType = DataBuffer.TYPE_BYTE; + + ColorSpace theColorSpace; + boolean hasAlpha; + boolean alphaPremultiplied = false; + if(photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK) { + theColorSpace = SimpleCMYKColorSpace.getInstance(); + hasAlpha = false; + } else { + theColorSpace = rgb; + hasAlpha = true; + if (extraSamples != null && + extraSamples[0] == + BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) { + alphaPremultiplied = true; + } + } + + return ImageTypeSpecifier.createInterleaved(theColorSpace, + bandOffsets, + dataType, + hasAlpha, + alphaPremultiplied); + } + + // 16-bit RGB + if (samplesPerPixel == 3 && + bitsPerSample[0] == 16 && + bitsPerSample[1] == 16 && + bitsPerSample[2] == 16) { + int[] bandOffsets = new int[3]; + bandOffsets[0] = 0; + bandOffsets[1] = 1; + bandOffsets[2] = 2; + int dataType = sampleFormat[0] == + BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER ? + DataBuffer.TYPE_SHORT : + DataBuffer.TYPE_USHORT; + return ImageTypeSpecifier.createInterleaved(rgb, + bandOffsets, + dataType, + false, + false); + } + + // 16-bit RGBA + if (samplesPerPixel == 4 && + bitsPerSample[0] == 16 && + bitsPerSample[1] == 16 && + bitsPerSample[2] == 16 && + bitsPerSample[3] == 16) { + int[] bandOffsets = new int[4]; + bandOffsets[0] = 0; + bandOffsets[1] = 1; + bandOffsets[2] = 2; + bandOffsets[3] = 3; + int dataType = sampleFormat[0] == + BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER ? + DataBuffer.TYPE_SHORT : + DataBuffer.TYPE_USHORT; + + boolean alphaPremultiplied = false; + if (extraSamples != null && + extraSamples[0] == + BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) { + alphaPremultiplied = true; + } + return ImageTypeSpecifier.createInterleaved(rgb, + bandOffsets, + dataType, + true, + alphaPremultiplied); + } + + // Compute bits per pixel. + int totalBits = 0; + for (int i = 0; i < bitsPerSample.length; i++) { + totalBits += bitsPerSample[i]; + } + + // Packed: 3- or 4-band, 8- or 16-bit. + if ((samplesPerPixel == 3 || samplesPerPixel == 4) && + (totalBits == 8 || totalBits == 16)) { + int redMask = createMask(bitsPerSample, 0); + int greenMask = createMask(bitsPerSample, 1); + int blueMask = createMask(bitsPerSample, 2); + int alphaMask = (samplesPerPixel == 4) ? + createMask(bitsPerSample, 3) : 0; + int transferType = totalBits == 8 ? + DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT; + boolean alphaPremultiplied = false; + if (extraSamples != null && + extraSamples[0] == + BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) { + alphaPremultiplied = true; + } + return ImageTypeSpecifier.createPacked(rgb, + redMask, + greenMask, + blueMask, + alphaMask, + transferType, + alphaPremultiplied); + } + + // Generic components with 8X bits per sample. + if(bitsPerSample[0] % 8 == 0) { + // Check whether all bands have same bit depth. + boolean allSameBitDepth = true; + for(int i = 1; i < bitsPerSample.length; i++) { + if(bitsPerSample[i] != bitsPerSample[i-1]) { + allSameBitDepth = false; + break; + } + } + + // Proceed if all bands have same bit depth. + if(allSameBitDepth) { + // Determine the data type. + int dataType = -1; + boolean isDataTypeSet = false; + switch(bitsPerSample[0]) { + case 8: + if(sampleFormat[0] != + BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { + // Ignore whether signed or unsigned: + // treat all as unsigned. + dataType = DataBuffer.TYPE_BYTE; + isDataTypeSet = true; + } + break; + case 16: + if(sampleFormat[0] != + BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { + if(sampleFormat[0] == + BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER) { + dataType = DataBuffer.TYPE_SHORT; + } else { + dataType = DataBuffer.TYPE_USHORT; + } + isDataTypeSet = true; + } + break; + case 32: + if(sampleFormat[0] == + BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { + dataType = DataBuffer.TYPE_FLOAT; + } else { + dataType = DataBuffer.TYPE_INT; + } + isDataTypeSet = true; + break; + case 64: + if(sampleFormat[0] == + BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { + dataType = DataBuffer.TYPE_DOUBLE; + isDataTypeSet = true; + } + break; + } + + if(isDataTypeSet) { + // Create the SampleModel. + SampleModel sm = createInterleavedSM(dataType, + samplesPerPixel); + + // Create the ColorModel. + ColorModel cm; + if(samplesPerPixel >= 1 && samplesPerPixel <= 4 && + (dataType == DataBuffer.TYPE_INT || + dataType == DataBuffer.TYPE_FLOAT)) { + // Handle the 32-bit cases for 1-4 bands. + ColorSpace cs = samplesPerPixel <= 2 ? + ColorSpace.getInstance(ColorSpace.CS_GRAY) : rgb; + boolean hasAlpha = ((samplesPerPixel % 2) == 0); + boolean alphaPremultiplied = false; + if(hasAlpha && extraSamples != null && + extraSamples[0] == + BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) { + alphaPremultiplied = true; + } + + cm = createComponentCM(cs, + samplesPerPixel, + dataType, + hasAlpha, + alphaPremultiplied); + } else { + ColorSpace cs = new BogusColorSpace(samplesPerPixel); + cm = createComponentCM(cs, + samplesPerPixel, + dataType, + false, // hasAlpha + false); // alphaPremultiplied + } + return new ImageTypeSpecifier(cm, sm); + } + } + } + + // Other more bizarre cases including discontiguous DataBuffers + // such as for the image in bug 4918959. + + if(colorMap == null && + sampleFormat[0] != + BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { + + // Determine size of largest sample. + int maxBitsPerSample = 0; + for(int i = 0; i < bitsPerSample.length; i++) { + if(bitsPerSample[i] > maxBitsPerSample) { + maxBitsPerSample = bitsPerSample[i]; + } + } + + // Determine whether data are signed. + boolean isSigned = + (sampleFormat[0] == + BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER); + + // Grayscale + if(samplesPerPixel == 1) { + int dataType = + getDataTypeFromNumBits(maxBitsPerSample, isSigned); + + return ImageTypeSpecifier.createGrayscale(maxBitsPerSample, + dataType, + isSigned); + } + + // Gray-alpha + if (samplesPerPixel == 2) { + boolean alphaPremultiplied = false; + if (extraSamples != null && + extraSamples[0] == + BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) { + alphaPremultiplied = true; + } + + int dataType = + getDataTypeFromNumBits(maxBitsPerSample, isSigned); + + return ImageTypeSpecifier.createGrayscale(maxBitsPerSample, + dataType, + false, + alphaPremultiplied); + } + + if (samplesPerPixel == 3 || samplesPerPixel == 4) { + if(totalBits <= 32 && !isSigned) { + // Packed RGB or RGBA + int redMask = createMask(bitsPerSample, 0); + int greenMask = createMask(bitsPerSample, 1); + int blueMask = createMask(bitsPerSample, 2); + int alphaMask = (samplesPerPixel == 4) ? + createMask(bitsPerSample, 3) : 0; + int transferType = + getDataTypeFromNumBits(totalBits, false); + boolean alphaPremultiplied = false; + if (extraSamples != null && + extraSamples[0] == + BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) { + alphaPremultiplied = true; + } + return ImageTypeSpecifier.createPacked(rgb, + redMask, + greenMask, + blueMask, + alphaMask, + transferType, + alphaPremultiplied); + } else if(samplesPerPixel == 3) { + // Interleaved RGB + int[] bandOffsets = new int[] {0, 1, 2}; + int dataType = + getDataTypeFromNumBits(maxBitsPerSample, isSigned); + return ImageTypeSpecifier.createInterleaved(rgb, + bandOffsets, + dataType, + false, + false); + } else if(samplesPerPixel == 4) { + // Interleaved RGBA + int[] bandOffsets = new int[] {0, 1, 2, 3}; + int dataType = + getDataTypeFromNumBits(maxBitsPerSample, isSigned); + boolean alphaPremultiplied = false; + if (extraSamples != null && + extraSamples[0] == + BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) { + alphaPremultiplied = true; + } + return ImageTypeSpecifier.createInterleaved(rgb, + bandOffsets, + dataType, + true, + alphaPremultiplied); + } + } else { + // Arbitrary Interleaved. + int dataType = + getDataTypeFromNumBits(maxBitsPerSample, isSigned); + SampleModel sm = createInterleavedSM(dataType, + samplesPerPixel); + ColorSpace cs = new BogusColorSpace(samplesPerPixel); + ColorModel cm = createComponentCM(cs, + samplesPerPixel, + dataType, + false, // hasAlpha + false); // alphaPremultiplied + return new ImageTypeSpecifier(cm, sm); + } + } + + return null; + } + + /** + * Sets the value of the reader field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param reader the current ImageReader. + */ + public void setReader(ImageReader reader) { + this.reader = reader; + } + + /** + * Sets the value of the metadata field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param metadata the IIOMetadata object for the + * image being read. + */ + public void setMetadata(IIOMetadata metadata) { + this.metadata = metadata; + } + + /** + * Sets the value of the photometricInterpretation + * field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param photometricInterpretation the photometric interpretation + * value. + */ + public void setPhotometricInterpretation(int photometricInterpretation) { + this.photometricInterpretation = photometricInterpretation; + } + + /** + * Sets the value of the compression field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param compression the compression type. + */ + public void setCompression(int compression) { + this.compression = compression; + } + + /** + * Sets the value of the planar field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param planar true if the image to be decoded is + * stored in planar format. + */ + public void setPlanar(boolean planar) { + this.planar = planar; + } + + /** + * Sets the value of the samplesPerPixel field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param samplesPerPixel the number of samples in each source + * pixel. + */ + public void setSamplesPerPixel(int samplesPerPixel) { + this.samplesPerPixel = samplesPerPixel; + } + + /** + * Sets the value of the bitsPerSample field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param bitsPerSample the number of bits for each source image + * sample. + */ + public void setBitsPerSample(int[] bitsPerSample) { + this.bitsPerSample = bitsPerSample == null ? + null : bitsPerSample.clone(); + } + + /** + * Sets the value of the sampleFormat field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param sampleFormat the format of the source image data, + * for example unsigned integer or floating-point. + */ + public void setSampleFormat(int[] sampleFormat) { + this.sampleFormat = sampleFormat == null ? + new int[] {BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER} : + sampleFormat.clone(); + } + + /** + * Sets the value of the extraSamples field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param extraSamples the interpretation of any samples in the + * source file beyond those used for basic color or grayscale + * information. + */ + public void setExtraSamples(int[] extraSamples) { + this.extraSamples = extraSamples == null ? + null : extraSamples.clone(); + } + + /** + * Sets the value of the colorMap field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param colorMap the color map to apply to the source data, + * as an array of chars. + */ + public void setColorMap(char[] colorMap) { + this.colorMap = colorMap == null ? + null : colorMap.clone(); + } + + /** + * Sets the value of the stream field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param stream the ImageInputStream to be read. + */ + public void setStream(ImageInputStream stream) { + this.stream = stream; + } + + /** + * Sets the value of the offset field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param offset the offset of the beginning of the compressed + * data. + */ + public void setOffset(long offset) { + this.offset = offset; + } + + /** + * Sets the value of the byteCount field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param byteCount the number of bytes of compressed data. + */ + public void setByteCount(int byteCount) { + this.byteCount = byteCount; + } + + // Region of the file image represented in the stream + + /** + * Sets the value of the srcMinX field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param srcMinX the minimum X coordinate of the source region + * being decoded, irrespective of how it will be copied into the + * destination. + */ + public void setSrcMinX(int srcMinX) { + this.srcMinX = srcMinX; + } + + /** + * Sets the value of the srcMinY field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param srcMinY the minimum Y coordinate of the source region + * being decoded, irrespective of how it will be copied into the + * destination. + */ + public void setSrcMinY(int srcMinY) { + this.srcMinY = srcMinY; + } + + /** + * Sets the value of the srcWidth field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param srcWidth the width of the source region being decoded, + * irrespective of how it will be copied into the destination. + */ + public void setSrcWidth(int srcWidth) { + this.srcWidth = srcWidth; + } + + /** + * Sets the value of the srcHeight field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param srcHeight the height of the source region being decoded, + * irrespective of how it will be copied into the destination. + */ + public void setSrcHeight(int srcHeight) { + this.srcHeight = srcHeight; + } + + // First source pixel to be read + + /** + * Sets the value of the sourceXOffset field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param sourceXOffset the horizontal source offset to be used when + * mapping between source and destination coordinates. + */ + public void setSourceXOffset(int sourceXOffset) { + this.sourceXOffset = sourceXOffset; + } + + /** + * Sets the value of the dstXOffset field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param dstXOffset the horizontal destination offset to be + * used when mapping between source and destination coordinates. + */ + public void setDstXOffset(int dstXOffset) { + this.dstXOffset = dstXOffset; + } + + /** + * Sets the value of the sourceYOffset. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param sourceYOffset the vertical source offset to be used when + * mapping between source and destination coordinates. + */ + public void setSourceYOffset(int sourceYOffset) { + this.sourceYOffset = sourceYOffset; + } + + /** + * Sets the value of the dstYOffset field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param dstYOffset the vertical destination offset to be + * used when mapping between source and destination coordinates. + */ + public void setDstYOffset(int dstYOffset) { + this.dstYOffset = dstYOffset; + } + + // Subsampling to be performed + + /** + * Sets the value of the subsampleX field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param subsampleX the horizontal subsampling factor. + * + * @throws IllegalArgumentException if subsampleX is + * less than or equal to 0. + */ + public void setSubsampleX(int subsampleX) { + if (subsampleX <= 0) { + throw new IllegalArgumentException("subsampleX <= 0!"); + } + this.subsampleX = subsampleX; + } + + /** + * Sets the value of the subsampleY field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param subsampleY the vertical subsampling factor. + * + * @throws IllegalArgumentException if subsampleY is + * less than or equal to 0. + */ + public void setSubsampleY(int subsampleY) { + if (subsampleY <= 0) { + throw new IllegalArgumentException("subsampleY <= 0!"); + } + this.subsampleY = subsampleY; + } + + // Band subsetting/rearrangement + + /** + * Sets the value of the sourceBands field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param sourceBands an array of ints + * specifying the source bands to be read. + */ + public void setSourceBands(int[] sourceBands) { + this.sourceBands = sourceBands == null ? + null : sourceBands.clone(); + } + + /** + * Sets the value of the destinationBands field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param destinationBands an array of ints + * specifying the destination bands to be written. + */ + public void setDestinationBands(int[] destinationBands) { + this.destinationBands = destinationBands == null ? + null : destinationBands.clone(); + } + + // Destination image and region + + /** + * Sets the value of the image field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param image the destination BufferedImage. + */ + public void setImage(BufferedImage image) { + this.image = image; + } + + /** + * Sets the value of the dstMinX field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param dstMinX the minimum X coordinate of the destination + * region. + */ + public void setDstMinX(int dstMinX) { + this.dstMinX = dstMinX; + } + + /** + * Sets the value of the dstMinY field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param dstMinY the minimum Y coordinate of the destination + * region. + */ + public void setDstMinY(int dstMinY) { + this.dstMinY = dstMinY; + } + + /** + * Sets the value of the dstWidth field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param dstWidth the width of the destination region. + */ + public void setDstWidth(int dstWidth) { + this.dstWidth = dstWidth; + } + + /** + * Sets the value of the dstHeight field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param dstHeight the height of the destination region. + */ + public void setDstHeight(int dstHeight) { + this.dstHeight = dstHeight; + } + + // Active source region + + /** + * Sets the value of the activeSrcMinX field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param activeSrcMinX the minimum X coordinate of the active + * source region. + */ + public void setActiveSrcMinX(int activeSrcMinX) { + this.activeSrcMinX = activeSrcMinX; + } + + /** + * Sets the value of the activeSrcMinY field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param activeSrcMinY the minimum Y coordinate of the active + * source region. + */ + public void setActiveSrcMinY(int activeSrcMinY) { + this.activeSrcMinY = activeSrcMinY; + } + + /** + * Sets the value of the activeSrcWidth field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param activeSrcWidth the width of the active source region. + */ + public void setActiveSrcWidth(int activeSrcWidth) { + this.activeSrcWidth = activeSrcWidth; + } + + /** + * Sets the value of the activeSrcHeight field. + * + *

If this method is called, the beginDecoding + * method must be called prior to calling any of the decode + * methods. + * + * @param activeSrcHeight the height of the active source region. + */ + public void setActiveSrcHeight(int activeSrcHeight) { + this.activeSrcHeight = activeSrcHeight; + } + + /** + * Sets the TIFFColorConverter object describing the color + * space of the encoded data in the input stream. If no + * TIFFColorConverter is set, no conversion will be performed. + * + * @param colorConverter a TIFFColorConverter object, or + * null. + */ + public void setColorConverter(TIFFColorConverter colorConverter) { + this.colorConverter = colorConverter; + } + + /** + * Returns an ImageTypeSpecifier describing an image + * whose underlying data array has the same format as the raw + * source pixel data. + * + * @return an ImageTypeSpecifier. + */ + public ImageTypeSpecifier getRawImageType() { + ImageTypeSpecifier its = + getRawImageTypeSpecifier(photometricInterpretation, + compression, + samplesPerPixel, + bitsPerSample, + sampleFormat, + extraSamples, + colorMap); + return its; + } + + /** + * Creates a BufferedImage whose underlying data + * array will be suitable for holding the raw decoded output of + * the decodeRaw method. + * + *

The default implementation calls + * getRawImageType, and calls the resulting + * ImageTypeSpecifier's + * createBufferedImage method. + * + * @return a BufferedImage whose underlying data + * array has the same format as the raw source pixel data, or + * null if it is not possible to create such an + * image. + */ + public BufferedImage createRawImage() { + if (planar) { + // Create a single-banded image of the appropriate data type. + + // Get the number of bits per sample. + int bps = bitsPerSample[sourceBands[0]]; + + // Determine the data type. + int dataType; + if(sampleFormat[0] == + BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { + if(bps <= 32) { + dataType = DataBuffer.TYPE_FLOAT; + } else { + dataType = DataBuffer.TYPE_DOUBLE; + } + } else if(bps <= 8) { + dataType = DataBuffer.TYPE_BYTE; + } else if(bps <= 16) { + if(sampleFormat[0] == + BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER) { + dataType = DataBuffer.TYPE_SHORT; + } else { + dataType = DataBuffer.TYPE_USHORT; + } + } else { + dataType = DataBuffer.TYPE_INT; + } + + ColorSpace csGray = ColorSpace.getInstance(ColorSpace.CS_GRAY); + ImageTypeSpecifier its = + ImageTypeSpecifier.createInterleaved(csGray, + new int[] {0}, + dataType, + false, + false); + + return its.createBufferedImage(srcWidth, srcHeight); + } else { + ImageTypeSpecifier its = getRawImageType(); + if (its == null) { + return null; + } + + BufferedImage bi = its.createBufferedImage(srcWidth, srcHeight); + return bi; + } + } + + /** + * Decodes the source data into the provided byte + * array b, starting at the offset given by + * dstOffset. Each pixel occupies + * bitsPerPixel bits, with no padding between pixels. + * Scanlines are separated by scanlineStride + * bytes. + * + * @param b a byte array to be written. + * @param dstOffset the starting offset in b to be + * written. + * @param bitsPerPixel the number of bits for each pixel. + * @param scanlineStride the number of bytes to + * advance between that starting pixels of each scanline. + * + * @throws IOException if an error occurs reading from the source + * ImageInputStream. + */ + public abstract void decodeRaw(byte[] b, + int dstOffset, + int bitsPerPixel, + int scanlineStride) throws IOException; + + /** + * Decodes the source data into the provided short + * array s, starting at the offset given by + * dstOffset. Each pixel occupies + * bitsPerPixel bits, with no padding between pixels. + * Scanlines are separated by scanlineStride + * shorts + * + *

The default implementation calls decodeRaw(byte[] b, + * ...) and copies the resulting data into s. + * + * @param s a short array to be written. + * @param dstOffset the starting offset in s to be + * written. + * @param bitsPerPixel the number of bits for each pixel. + * @param scanlineStride the number of shorts to + * advance between that starting pixels of each scanline. + * + * @throws IOException if an error occurs reading from the source + * ImageInputStream. + */ + public void decodeRaw(short[] s, + int dstOffset, + int bitsPerPixel, + int scanlineStride) throws IOException { + int bytesPerRow = (srcWidth*bitsPerPixel + 7)/8; + int shortsPerRow = bytesPerRow/2; + + byte[] b = new byte[bytesPerRow*srcHeight]; + decodeRaw(b, 0, bitsPerPixel, bytesPerRow); + + int bOffset = 0; + if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { + for (int j = 0; j < srcHeight; j++) { + for (int i = 0; i < shortsPerRow; i++) { + short hiVal = b[bOffset++]; + short loVal = b[bOffset++]; + short sval = (short)((hiVal << 8) | (loVal & 0xff)); + s[dstOffset + i] = sval; + } + + dstOffset += scanlineStride; + } + } else { // ByteOrder.LITLE_ENDIAN + for (int j = 0; j < srcHeight; j++) { + for (int i = 0; i < shortsPerRow; i++) { + short loVal = b[bOffset++]; + short hiVal = b[bOffset++]; + short sval = (short)((hiVal << 8) | (loVal & 0xff)); + s[dstOffset + i] = sval; + } + + dstOffset += scanlineStride; + } + } + } + + /** + * Decodes the source data into the provided int + * array i, starting at the offset given by + * dstOffset. Each pixel occupies + * bitsPerPixel bits, with no padding between pixels. + * Scanlines are separated by scanlineStride + * ints. + * + *

The default implementation calls decodeRaw(byte[] b, + * ...) and copies the resulting data into i. + * + * @param i an int array to be written. + * @param dstOffset the starting offset in i to be + * written. + * @param bitsPerPixel the number of bits for each pixel. + * @param scanlineStride the number of ints to + * advance between that starting pixels of each scanline. + * + * @throws IOException if an error occurs reading from the source + * ImageInputStream. + */ + public void decodeRaw(int[] i, + int dstOffset, + int bitsPerPixel, + int scanlineStride) throws IOException { + int numBands = bitsPerPixel/32; + int intsPerRow = srcWidth*numBands; + int bytesPerRow = intsPerRow*4; + + byte[] b = new byte[bytesPerRow*srcHeight]; + decodeRaw(b, 0, bitsPerPixel, bytesPerRow); + + int bOffset = 0; + if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { + for (int j = 0; j < srcHeight; j++) { + for (int k = 0; k < intsPerRow; k++) { + int v0 = b[bOffset++] & 0xff; + int v1 = b[bOffset++] & 0xff; + int v2 = b[bOffset++] & 0xff; + int v3 = b[bOffset++] & 0xff; + int ival = (v0 << 24) | (v1 << 16) | (v2 << 8) | v3; + i[dstOffset + k] = ival; + } + + dstOffset += scanlineStride; + } + } else { // ByteOrder.LITLE_ENDIAN + for (int j = 0; j < srcHeight; j++) { + for (int k = 0; k < intsPerRow; k++) { + int v3 = b[bOffset++] & 0xff; + int v2 = b[bOffset++] & 0xff; + int v1 = b[bOffset++] & 0xff; + int v0 = b[bOffset++] & 0xff; + int ival = (v0 << 24) | (v1 << 16) | (v2 << 8) | v3; + i[dstOffset + k] = ival; + } + + dstOffset += scanlineStride; + } + } + } + + /** + * Decodes the source data into the provided float + * array f, starting at the offset given by + * dstOffset. Each pixel occupies + * bitsPerPixel bits, with no padding between pixels. + * Scanlines are separated by scanlineStride + * floats. + * + *

The default implementation calls decodeRaw(byte[] b, + * ...) and copies the resulting data into f. + * + * @param f a float array to be written. + * @param dstOffset the starting offset in f to be + * written. + * @param bitsPerPixel the number of bits for each pixel. + * @param scanlineStride the number of floats to + * advance between that starting pixels of each scanline. + * + * @throws IOException if an error occurs reading from the source + * ImageInputStream. + */ + public void decodeRaw(float[] f, + int dstOffset, + int bitsPerPixel, + int scanlineStride) throws IOException { + int numBands = bitsPerPixel/32; + int floatsPerRow = srcWidth*numBands; + int bytesPerRow = floatsPerRow*4; + + byte[] b = new byte[bytesPerRow*srcHeight]; + decodeRaw(b, 0, bitsPerPixel, bytesPerRow); + + int bOffset = 0; + if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { + for (int j = 0; j < srcHeight; j++) { + for (int i = 0; i < floatsPerRow; i++) { + int v0 = b[bOffset++] & 0xff; + int v1 = b[bOffset++] & 0xff; + int v2 = b[bOffset++] & 0xff; + int v3 = b[bOffset++] & 0xff; + int ival = (v0 << 24) | (v1 << 16) | (v2 << 8) | v3; + float fval = Float.intBitsToFloat(ival); + f[dstOffset + i] = fval; + } + + dstOffset += scanlineStride; + } + } else { // ByteOrder.LITLE_ENDIAN + for (int j = 0; j < srcHeight; j++) { + for (int i = 0; i < floatsPerRow; i++) { + int v3 = b[bOffset++] & 0xff; + int v2 = b[bOffset++] & 0xff; + int v1 = b[bOffset++] & 0xff; + int v0 = b[bOffset++] & 0xff; + int ival = (v0 << 24) | (v1 << 16) | (v2 << 8) | v3; + float fval = Float.intBitsToFloat(ival); + f[dstOffset + i] = fval; + } + + dstOffset += scanlineStride; + } + } + } + + /** + * Decodes the source data into the provided double + * array f, starting at the offset given by + * dstOffset. Each pixel occupies + * bitsPerPixel bits, with no padding between pixels. + * Scanlines are separated by scanlineStride + * doubles. + * + *

The default implementation calls decodeRaw(byte[] b, + * ...) and copies the resulting data into f. + * + * @param f a double array to be written. + * @param dstOffset the starting offset in f to be + * written. + * @param bitsPerPixel the number of bits for each pixel. + * @param scanlineStride the number of doubles to + * advance between that starting pixels of each scanline. + * + * @throws IOException if an error occurs reading from the source + * ImageInputStream. + */ + public void decodeRaw(double[] d, + int dstOffset, + int bitsPerPixel, + int scanlineStride) throws IOException { + int numBands = bitsPerPixel/64; + int doublesPerRow = srcWidth*numBands; + int bytesPerRow = doublesPerRow*8; + + byte[] b = new byte[bytesPerRow*srcHeight]; + decodeRaw(b, 0, bitsPerPixel, bytesPerRow); + + int bOffset = 0; + if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { + for (int j = 0; j < srcHeight; j++) { + for (int i = 0; i < doublesPerRow; i++) { + long v0 = b[bOffset++] & 0xff; + long v1 = b[bOffset++] & 0xff; + long v2 = b[bOffset++] & 0xff; + long v3 = b[bOffset++] & 0xff; + long v4 = b[bOffset++] & 0xff; + long v5 = b[bOffset++] & 0xff; + long v6 = b[bOffset++] & 0xff; + long v7 = b[bOffset++] & 0xff; + long lval = + (v0 << 56) | (v1 << 48) | (v2 << 40) | (v3 << 32) + | (v4 << 24) | (v5 << 16) | (v6 << 8) | v7; + double dval = Double.longBitsToDouble(lval); + d[dstOffset + i] = dval; + } + + dstOffset += scanlineStride; + } + } else { // ByteOrder.LITLE_ENDIAN + for (int j = 0; j < srcHeight; j++) { + for (int i = 0; i < doublesPerRow; i++) { + long v7 = b[bOffset++] & 0xff; + long v6 = b[bOffset++] & 0xff; + long v5 = b[bOffset++] & 0xff; + long v4 = b[bOffset++] & 0xff; + long v3 = b[bOffset++] & 0xff; + long v2 = b[bOffset++] & 0xff; + long v1 = b[bOffset++] & 0xff; + long v0 = b[bOffset++] & 0xff; + long lval = + (v0 << 56) | (v1 << 48) | (v2 << 40) | (v3 << 32) + | (v4 << 24) | (v5 << 16) | (v6 << 8) | v7; + double dval = Double.longBitsToDouble(lval); + d[dstOffset + i] = dval; + } + + dstOffset += scanlineStride; + } + } + } + + // + // Values used to prevent unneeded recalculation of bit adjustment table. + // + private boolean isFirstBitDepthTable = true; + private boolean planarCache = false; + private int[] destBitsPerSampleCache = null; + private int[] sourceBandsCache = null; + private int[] bitsPerSampleCache = null; + private int[] destinationBandsCache = null; + + /** + * This routine is called prior to a sequence of calls to the + * decode method, in order to allow any necessary + * tables or other structures to be initialized based on metadata + * values. This routine is guaranteed to be called any time the + * metadata values have changed. + * + *

The default implementation computes tables used by the + * decode method to rescale components to different + * bit depths. Thus, if this method is overridden, it is + * important for the subclass method to call super(), + * unless it overrides decode as well. + */ + public void beginDecoding() { + // Note: This method assumes that sourceBands, destinationBands, + // and bitsPerSample are all non-null which is true as they are + // set up that way in TIFFImageReader. Also the lengths and content + // of sourceBands and destinationBands are checked in TIFFImageReader + // before the present method is invoked. + + // Determine if all of the relevant output bands have the + // same bit depth as the source data + this.adjustBitDepths = false; + int numBands = destinationBands.length; + int[] destBitsPerSample = null; + if(planar) { + int totalNumBands = bitsPerSample.length; + destBitsPerSample = new int[totalNumBands]; + int dbps = image.getSampleModel().getSampleSize(0); + for(int b = 0; b < totalNumBands; b++) { + destBitsPerSample[b] = dbps; + } + } else { + destBitsPerSample = image.getSampleModel().getSampleSize(); + } + + for (int b = 0; b < numBands; b++) { + if (destBitsPerSample[destinationBands[b]] != + bitsPerSample[sourceBands[b]]) { + adjustBitDepths = true; + break; + } + } + + // If the bit depths differ, create a lookup table + // per band to perform the conversion + if(adjustBitDepths) { + // Compute the table only if this is the first time one is + // being computed or if any of the variables on which the + // table is based have changed. + if(this.isFirstBitDepthTable || + planar != planarCache || + !areIntArraysEqual(destBitsPerSample, + destBitsPerSampleCache) || + !areIntArraysEqual(sourceBands, + sourceBandsCache) || + !areIntArraysEqual(bitsPerSample, + bitsPerSampleCache) || + !areIntArraysEqual(destinationBands, + destinationBandsCache)) { + + this.isFirstBitDepthTable = false; + + // Cache some variables. + this.planarCache = planar; + this.destBitsPerSampleCache = + destBitsPerSample.clone(); // never null ... + this.sourceBandsCache = sourceBands == null ? + null : sourceBands.clone(); + this.bitsPerSampleCache = bitsPerSample == null ? + null : bitsPerSample.clone(); + this.destinationBandsCache = destinationBands.clone(); + + // Allocate and fill the table. + bitDepthScale = new int[numBands][]; + for (int b = 0; b < numBands; b++) { + int maxInSample = (1 << bitsPerSample[sourceBands[b]]) - 1; + int halfMaxInSample = maxInSample/2; + + int maxOutSample = + (1 << destBitsPerSample[destinationBands[b]]) - 1; + + bitDepthScale[b] = new int[maxInSample + 1]; + for (int s = 0; s <= maxInSample; s++) { + bitDepthScale[b][s] = + (s*maxOutSample + halfMaxInSample)/ + maxInSample; + } + } + } + } else { // !adjustBitDepths + // Clear any prior table. + this.bitDepthScale = null; + } + + // Determine whether source and destination band lists are ramps. + // Note that these conditions will be true for planar images if + // and only if samplesPerPixel == 1, sourceBands[0] == 0, and + // destinationBands[0] == 0. For the purposes of this method, the + // only difference between such a planar image and a chunky image + // is the setting of the PlanarConfiguration field. + boolean sourceBandsNormal = false; + boolean destinationBandsNormal = false; + if (numBands == samplesPerPixel) { + sourceBandsNormal = true; + destinationBandsNormal = true; + for (int i = 0; i < numBands; i++) { + if (sourceBands[i] != i) { + sourceBandsNormal = false; + } + if (destinationBands[i] != i) { + destinationBandsNormal = false; + } + } + } + + // Determine whether the image is bilevel and/or contiguous. + // Note that a planar image could be bilevel but it will not + // be contiguous unless it has a single component band stored + // in a single bank. + this.isBilevel = + ImageUtil.isBinary(this.image.getRaster().getSampleModel()); + this.isContiguous = this.isBilevel ? + true : ImageUtil.imageIsContiguous(this.image); + + // Analyze destination image to see if we can copy into it + // directly + + this.isImageSimple = + (colorConverter == null) && + (subsampleX == 1) && (subsampleY == 1) && + (srcWidth == dstWidth) && (srcHeight == dstHeight) && + ((dstMinX + dstWidth) <= image.getWidth()) && + ((dstMinY + dstHeight) <= image.getHeight()) && + sourceBandsNormal && destinationBandsNormal && + !adjustBitDepths; + } + + /** + * Decodes the input bit stream (located in the + * ImageInputStream stream, at offset + * offset, and continuing for byteCount + * bytes) into the output BufferedImage + * image. + * + *

The default implementation analyzes the destination image + * to determine if it is suitable as the destination for the + * decodeRaw method. If not, a suitable image is + * created. Next, decodeRaw is called to perform the + * actual decoding, and the results are copied into the + * destination image if necessary. Subsampling and offsetting are + * performed automatically. + * + *

The precise responsibilities of this routine are as + * follows. The input bit stream is defined by the instance + * variables stream, offset, and + * byteCount. These bits contain the data for the + * region of the source image defined by srcMinX, + * srcMinY, srcWidth, and + * srcHeight. + * + *

The source data is required to be subsampling, starting at + * the sourceXOffsetth column and including + * every subsampleXth pixel thereafter (and similarly + * for sourceYOffset and + * subsampleY). + * + *

Pixels are copied into the destination with an addition shift of + * (dstXOffset, dstYOffset). The complete + * set of formulas relating the source and destination coordinate spaces + * are: + * + *

+     * dx = (sx - sourceXOffset)/subsampleX + dstXOffset;
+     * dy = (sy - sourceYOffset)/subsampleY + dstYOffset;
+     * 
+ * + * Only source pixels such that (sx - sourceXOffset) % + * subsampleX == 0 and (sy - sourceYOffset) % + * subsampleY == 0 are copied. + * + *

The inverse mapping, from destination to source coordinates, + * is one-to-one: + * + *

+     * sx = (dx - dstXOffset)*subsampleX + sourceXOffset;
+     * sy = (dy - dstYOffset)*subsampleY + sourceYOffset;
+     * 
+ * + *

The region of the destination image to be updated is given + * by the instance variables dstMinX, + * dstMinY, dstWidth, and + * dstHeight. + * + *

It is possible that not all of the source data being read + * will contribute to the destination image. For example, the + * destination offsets could be set such that some of the source + * pixels land outside of the bounds of the image. As a + * convenience, the bounds of the active source region (that is, + * the region of the strip or tile being read that actually + * contributes to the destination image, taking clipping into + * account) are available as activeSrcMinX, + * activeSrcMinY, activeSrcWidth and + * activeSrcHeight. Thus, the source pixel at + * (activeSrcMinX, activeSrcMinY) will + * map to the destination pixel (dstMinX, + * dstMinY). + * + *

The sequence of source bands given by + * sourceBands are to be copied into the sequence of + * bands in the destination given by + * destinationBands. + * + *

Some standard tag information is provided the instance + * variables photometricInterpretation, + * compression, samplesPerPixel, + * bitsPerSample, sampleFormat, + * extraSamples, and colorMap. + * + *

In practice, unless there is a significant performance + * advantage to be gained by overriding this routine, most users + * will prefer to use the default implementation of this routine, + * and instead override the decodeRaw and/or + * getRawImageType methods. + * + * @exception IOException if an error occurs in + * decodeRaw. + */ + public void decode() throws IOException { + byte[] byteData = null; + short[] shortData = null; + int[] intData = null; + float[] floatData = null; + double[] doubleData = null; + + int dstOffset = 0; + int pixelBitStride = 1; + int scanlineStride = 0; + + // Analyze raw image + + this.rawImage = null; + if(isImageSimple) { + if(isBilevel) { + rawImage = this.image; + } else if (isContiguous) { + rawImage = + image.getSubimage(dstMinX, dstMinY, dstWidth, dstHeight); + } + } + + boolean isDirectCopy = rawImage != null; + + if(rawImage == null) { + rawImage = createRawImage(); + if (rawImage == null) { + throw new IIOException("Couldn't create image buffer!"); + } + } + + WritableRaster ras = rawImage.getRaster(); + + if(isBilevel) { + Rectangle rect = isImageSimple ? + new Rectangle(dstMinX, dstMinY, dstWidth, dstHeight) : + ras.getBounds(); + byteData = ImageUtil.getPackedBinaryData(ras, rect); + dstOffset = 0; + pixelBitStride = 1; + scanlineStride = (rect.width + 7)/8; + } else { + SampleModel sm = ras.getSampleModel(); + DataBuffer db = ras.getDataBuffer(); + + boolean isSupportedType = false; + + if (sm instanceof ComponentSampleModel) { + ComponentSampleModel csm = (ComponentSampleModel)sm; + dstOffset = csm.getOffset(-ras.getSampleModelTranslateX(), + -ras.getSampleModelTranslateY()); + scanlineStride = csm.getScanlineStride(); + if(db instanceof DataBufferByte) { + DataBufferByte dbb = (DataBufferByte)db; + + byteData = dbb.getData(); + pixelBitStride = csm.getPixelStride()*8; + isSupportedType = true; + } else if(db instanceof DataBufferUShort) { + DataBufferUShort dbus = (DataBufferUShort)db; + + shortData = dbus.getData(); + pixelBitStride = csm.getPixelStride()*16; + isSupportedType = true; + } else if(db instanceof DataBufferShort) { + DataBufferShort dbs = (DataBufferShort)db; + + shortData = dbs.getData(); + pixelBitStride = csm.getPixelStride()*16; + isSupportedType = true; + } else if(db instanceof DataBufferInt) { + DataBufferInt dbi = (DataBufferInt)db; + + intData = dbi.getData(); + pixelBitStride = csm.getPixelStride()*32; + isSupportedType = true; + } else if(db instanceof DataBufferFloat) { + DataBufferFloat dbf = (DataBufferFloat)db; + + floatData = dbf.getData(); + pixelBitStride = csm.getPixelStride()*32; + isSupportedType = true; + } else if(db instanceof DataBufferDouble) { + DataBufferDouble dbd = (DataBufferDouble)db; + + doubleData = dbd.getData(); + pixelBitStride = csm.getPixelStride()*64; + isSupportedType = true; + } + } else if (sm instanceof MultiPixelPackedSampleModel) { + MultiPixelPackedSampleModel mppsm = + (MultiPixelPackedSampleModel)sm; + dstOffset = + mppsm.getOffset(-ras.getSampleModelTranslateX(), + -ras.getSampleModelTranslateY()); + pixelBitStride = mppsm.getPixelBitStride(); + scanlineStride = mppsm.getScanlineStride(); + if(db instanceof DataBufferByte) { + DataBufferByte dbb = (DataBufferByte)db; + + byteData = dbb.getData(); + isSupportedType = true; + } else if(db instanceof DataBufferUShort) { + DataBufferUShort dbus = (DataBufferUShort)db; + + shortData = dbus.getData(); + isSupportedType = true; + } else if(db instanceof DataBufferInt) { + DataBufferInt dbi = (DataBufferInt)db; + + intData = dbi.getData(); + isSupportedType = true; + } + } else if (sm instanceof SinglePixelPackedSampleModel) { + SinglePixelPackedSampleModel sppsm = + (SinglePixelPackedSampleModel)sm; + dstOffset = + sppsm.getOffset(-ras.getSampleModelTranslateX(), + -ras.getSampleModelTranslateY()); + scanlineStride = sppsm.getScanlineStride(); + if(db instanceof DataBufferByte) { + DataBufferByte dbb = (DataBufferByte)db; + + byteData = dbb.getData(); + pixelBitStride = 8; + isSupportedType = true; + } else if(db instanceof DataBufferUShort) { + DataBufferUShort dbus = (DataBufferUShort)db; + + shortData = dbus.getData(); + pixelBitStride = 16; + isSupportedType = true; + } else if(db instanceof DataBufferInt) { + DataBufferInt dbi = (DataBufferInt)db; + + intData = dbi.getData(); + pixelBitStride = 32; + isSupportedType = true; + } + } + + if(!isSupportedType) { + throw new IIOException + ("Unsupported raw image type: SampleModel = "+sm+ + "; DataBuffer = "+db); + } + } + + if(isBilevel) { + // Bilevel data are always in a contiguous byte buffer. + decodeRaw(byteData, dstOffset, pixelBitStride, scanlineStride); + } else { + SampleModel sm = ras.getSampleModel(); + + // Branch based on whether data are bit-contiguous, i.e., + // data are packaed as tightly as possible leaving no unused + // bits except at the end of a row. + if(isDataBufferBitContiguous(sm)) { + // Use byte or float data directly. + if (byteData != null) { + decodeRaw(byteData, dstOffset, + pixelBitStride, scanlineStride); + } else if (floatData != null) { + decodeRaw(floatData, dstOffset, + pixelBitStride, scanlineStride); + } else if (doubleData != null) { + decodeRaw(doubleData, dstOffset, + pixelBitStride, scanlineStride); + } else { + if (shortData != null) { + if(areSampleSizesEqual(sm) && + sm.getSampleSize(0) == 16) { + // Decode directly into short data. + decodeRaw(shortData, dstOffset, + pixelBitStride, scanlineStride); + } else { + // Decode into bytes and reformat into shorts. + int bpp = getBitsPerPixel(sm); + int bytesPerRow = (bpp*srcWidth + 7)/8; + byte[] buf = new byte[bytesPerRow*srcHeight]; + decodeRaw(buf, 0, bpp, bytesPerRow); + reformatData(buf, bytesPerRow, srcHeight, + shortData, null, + dstOffset, scanlineStride); + } + } else if (intData != null) { + if(areSampleSizesEqual(sm) && + sm.getSampleSize(0) == 32) { + // Decode directly into int data. + decodeRaw(intData, dstOffset, + pixelBitStride, scanlineStride); + } else { + // Decode into bytes and reformat into ints. + int bpp = getBitsPerPixel(sm); + int bytesPerRow = (bpp*srcWidth + 7)/8; + byte[] buf = new byte[bytesPerRow*srcHeight]; + decodeRaw(buf, 0, bpp, bytesPerRow); + reformatData(buf, bytesPerRow, srcHeight, + null, intData, + dstOffset, scanlineStride); + } + } + } + } else { + // Read discontiguous data into bytes and set the samples + // into the Raster. + int bpp = getBitsPerPixel(sm); + int bytesPerRow = (bpp*srcWidth + 7)/8; + byte[] buf = new byte[bytesPerRow*srcHeight]; + decodeRaw(buf, 0, bpp, bytesPerRow); + reformatDiscontiguousData(buf, bytesPerRow, + srcWidth, srcHeight, + ras); + } + } + + if (colorConverter != null) { + float[] rgb = new float[3]; + + if(byteData != null) { + for (int j = 0; j < dstHeight; j++) { + int idx = dstOffset; + for (int i = 0; i < dstWidth; i++) { + float x0 = (float)(byteData[idx] & 0xff); + float x1 = (float)(byteData[idx + 1] & 0xff); + float x2 = (float)(byteData[idx + 2] & 0xff); + + colorConverter.toRGB(x0, x1, x2, rgb); + + byteData[idx] = (byte)(rgb[0]); + byteData[idx + 1] = (byte)(rgb[1]); + byteData[idx + 2] = (byte)(rgb[2]); + + idx += 3; + } + + dstOffset += scanlineStride; + } + } else if(shortData != null) { + if(sampleFormat[0] == + BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER) { + for (int j = 0; j < dstHeight; j++) { + int idx = dstOffset; + for (int i = 0; i < dstWidth; i++) { + float x0 = (float)shortData[idx]; + float x1 = (float)shortData[idx + 1]; + float x2 = (float)shortData[idx + 2]; + + colorConverter.toRGB(x0, x1, x2, rgb); + + shortData[idx] = (short)(rgb[0]); + shortData[idx + 1] = (short)(rgb[1]); + shortData[idx + 2] = (short)(rgb[2]); + + idx += 3; + } + + dstOffset += scanlineStride; + } + } else { + for (int j = 0; j < dstHeight; j++) { + int idx = dstOffset; + for (int i = 0; i < dstWidth; i++) { + float x0 = (float)(shortData[idx] & 0xffff); + float x1 = (float)(shortData[idx + 1] & 0xffff); + float x2 = (float)(shortData[idx + 2] & 0xffff); + + colorConverter.toRGB(x0, x1, x2, rgb); + + shortData[idx] = (short)(rgb[0]); + shortData[idx + 1] = (short)(rgb[1]); + shortData[idx + 2] = (short)(rgb[2]); + + idx += 3; + } + + dstOffset += scanlineStride; + } + } + } else if(intData != null) { + for (int j = 0; j < dstHeight; j++) { + int idx = dstOffset; + for (int i = 0; i < dstWidth; i++) { + float x0 = (float)intData[idx]; + float x1 = (float)intData[idx + 1]; + float x2 = (float)intData[idx + 2]; + + colorConverter.toRGB(x0, x1, x2, rgb); + + intData[idx] = (int)(rgb[0]); + intData[idx + 1] = (int)(rgb[1]); + intData[idx + 2] = (int)(rgb[2]); + + idx += 3; + } + + dstOffset += scanlineStride; + } + } else if(floatData != null) { + for (int j = 0; j < dstHeight; j++) { + int idx = dstOffset; + for (int i = 0; i < dstWidth; i++) { + float x0 = floatData[idx]; + float x1 = floatData[idx + 1]; + float x2 = floatData[idx + 2]; + + colorConverter.toRGB(x0, x1, x2, rgb); + + floatData[idx] = rgb[0]; + floatData[idx + 1] = rgb[1]; + floatData[idx + 2] = rgb[2]; + + idx += 3; + } + + dstOffset += scanlineStride; + } + } else if(doubleData != null) { + for (int j = 0; j < dstHeight; j++) { + int idx = dstOffset; + for (int i = 0; i < dstWidth; i++) { + // Note: Possible loss of precision. + float x0 = (float)doubleData[idx]; + float x1 = (float)doubleData[idx + 1]; + float x2 = (float)doubleData[idx + 2]; + + colorConverter.toRGB(x0, x1, x2, rgb); + + doubleData[idx] = rgb[0]; + doubleData[idx + 1] = rgb[1]; + doubleData[idx + 2] = rgb[2]; + + idx += 3; + } + + dstOffset += scanlineStride; + } + } + } + + if (photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) { + if(byteData != null) { + int bytesPerRow = (srcWidth*pixelBitStride + 7)/8; + for (int y = 0; y < srcHeight; y++) { + int offset = dstOffset + y*scanlineStride; + for (int i = 0; i < bytesPerRow; i++) { + byteData[offset + i] ^= 0xff; + } + } + } else if(shortData != null) { + int shortsPerRow = (srcWidth*pixelBitStride + 15)/16; + if(sampleFormat[0] == + BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER) { + for (int y = 0; y < srcHeight; y++) { + int offset = dstOffset + y*scanlineStride; + for (int i = 0; i < shortsPerRow; i++) { + int shortOffset = offset + i; + shortData[shortOffset] = + (short)(Short.MAX_VALUE - + shortData[shortOffset]); + } + } + } else { + for (int y = 0; y < srcHeight; y++) { + int offset = dstOffset + y*scanlineStride; + for (int i = 0; i < shortsPerRow; i++) { + shortData[offset + i] ^= 0xffff; + } + } + } + } else if(intData != null) { + int intsPerRow = (srcWidth*pixelBitStride + 31)/32; + for (int y = 0; y < srcHeight; y++) { + int offset = dstOffset + y*scanlineStride; + for (int i = 0; i < intsPerRow; i++) { + int intOffset = offset + i; + intData[intOffset] = + Integer.MAX_VALUE - intData[intOffset]; + } + } + } else if(floatData != null) { + int floatsPerRow = (srcWidth*pixelBitStride + 31)/32; + for (int y = 0; y < srcHeight; y++) { + int offset = dstOffset + y*scanlineStride; + for (int i = 0; i < floatsPerRow; i++) { + int floatOffset = offset + i; + floatData[floatOffset] = + 1.0F - floatData[floatOffset]; + } + } + } else if(doubleData != null) { + int doublesPerRow = (srcWidth*pixelBitStride + 63)/64; + for (int y = 0; y < srcHeight; y++) { + int offset = dstOffset + y*scanlineStride; + for (int i = 0; i < doublesPerRow; i++) { + int doubleOffset = offset + i; + doubleData[doubleOffset] = + 1.0F - doubleData[doubleOffset]; + } + } + } + } + + if(isBilevel) { + Rectangle rect = isImageSimple ? + new Rectangle(dstMinX, dstMinY, dstWidth, dstHeight) : + ras.getBounds(); + ImageUtil.setPackedBinaryData(byteData, ras, rect); + } + + if (isDirectCopy) { // rawImage == image) { + return; + } + + // Copy the raw image data into the true destination image + Raster src = rawImage.getRaster(); + + // Create band child of source + Raster srcChild = src.createChild(0, 0, + srcWidth, srcHeight, + srcMinX, srcMinY, + planar ? null : sourceBands); + + WritableRaster dst = image.getRaster(); + + // Create dst child covering area and bands to be written + WritableRaster dstChild = dst.createWritableChild(dstMinX, dstMinY, + dstWidth, dstHeight, + dstMinX, dstMinY, + destinationBands); + + if (subsampleX == 1 && subsampleY == 1 && !adjustBitDepths) { + srcChild = srcChild.createChild(activeSrcMinX, + activeSrcMinY, + activeSrcWidth, activeSrcHeight, + dstMinX, dstMinY, + null); + + dstChild.setRect(srcChild); + } else if (subsampleX == 1 && !adjustBitDepths) { + int sy = activeSrcMinY; + int dy = dstMinY; + while (sy < srcMinY + srcHeight) { + Raster srcRow = srcChild.createChild(activeSrcMinX, sy, + activeSrcWidth, 1, + dstMinX, dy, + null); + dstChild.setRect(srcRow); + + sy += subsampleY; + ++dy; + } + } else { + int[] p = srcChild.getPixel(srcMinX, srcMinY, (int[])null); + int numBands = p.length; + + int sy = activeSrcMinY; + int dy = dstMinY; + + while (sy < activeSrcMinY + activeSrcHeight) { + int sx = activeSrcMinX; + int dx = dstMinX; + + while (sx < activeSrcMinX + activeSrcWidth) { + srcChild.getPixel(sx, sy, p); + if (adjustBitDepths) { + for (int band = 0; band < numBands; band++) { + p[band] = bitDepthScale[band][p[band]]; + } + } + dstChild.setPixel(dx, dy, p); + + sx += subsampleX; + ++dx; + } + + sy += subsampleY; + ++dy; + } + } + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflateCompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflateCompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.ImageWriteParam; + +/** + * Compressor for Deflate compression. + */ +public class TIFFDeflateCompressor extends TIFFDeflater { + public TIFFDeflateCompressor(ImageWriteParam param, int predictor) { + super("Deflate", BaselineTIFFTagSet.COMPRESSION_DEFLATE, param, + predictor); + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflateDecompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflateDecompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.io.IOException; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; +import javax.imageio.IIOException; +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; + +public class TIFFDeflateDecompressor extends TIFFDecompressor { + + Inflater inflater = null; + int predictor; + + public TIFFDeflateDecompressor(int predictor) throws IIOException { + inflater = new Inflater(); + + if (predictor != BaselineTIFFTagSet.PREDICTOR_NONE && + predictor != + BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) { + throw new IIOException("Illegal value for Predictor in " + + "TIFF file"); + } + + this.predictor = predictor; + } + + public synchronized void decodeRaw(byte[] b, + int dstOffset, + int bitsPerPixel, + int scanlineStride) throws IOException { + + // Check bitsPerSample. + if (predictor == + BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) { + int len = bitsPerSample.length; + for(int i = 0; i < len; i++) { + if(bitsPerSample[i] != 8) { + throw new IIOException + (bitsPerSample[i] + "-bit samples "+ + "are not supported for Horizontal "+ + "differencing Predictor"); + } + } + } + + // Seek to current tile data offset. + stream.seek(offset); + + // Read the deflated data. + byte[] srcData = new byte[byteCount]; + stream.readFully(srcData); + + int bytesPerRow = (srcWidth*bitsPerPixel + 7)/8; + byte[] buf; + int bufOffset; + if(bytesPerRow == scanlineStride) { + buf = b; + bufOffset = dstOffset; + } else { + buf = new byte[bytesPerRow*srcHeight]; + bufOffset = 0; + } + + // Set the input to the Inflater. + inflater.setInput(srcData); + + // Inflate the data. + try { + inflater.inflate(buf, bufOffset, bytesPerRow*srcHeight); + } catch(DataFormatException dfe) { + throw new IIOException("Error inflating data", + dfe); + } + + // Reset the Inflater. + inflater.reset(); + + if (predictor == + BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) { + + for (int j = 0; j < srcHeight; j++) { + int count = bufOffset + samplesPerPixel * (j * srcWidth + 1); + for (int i=samplesPerPixel; i= samplesPerPixel; j--) { + rowBuf[j] -= rowBuf[j - samplesPerPixel]; + } + + deflater.setInput(rowBuf); + if(i == maxRow) { + deflater.finish(); + } + + int numBytes = 0; + while((numBytes = deflater.deflate(compData, + numCompressedBytes, + compData.length - + numCompressedBytes)) != 0) { + numCompressedBytes += numBytes; + } + + off += scanlineStride; + } + } else { + deflater.setInput(b, off, height*scanlineStride); + deflater.finish(); + + numCompressedBytes = deflater.deflate(compData); + } + + deflater.reset(); + + stream.write(compData, 0, numCompressedBytes); + + return numCompressedBytes; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFElementInfo.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFElementInfo.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.metadata.IIOMetadataFormat; + +class TIFFElementInfo { + String[] childNames; + String[] attributeNames; + int childPolicy; + + int minChildren = 0; + int maxChildren = Integer.MAX_VALUE; + + int objectValueType = IIOMetadataFormat.VALUE_NONE; + Class objectClass = null; + Object objectDefaultValue = null; + Object[] objectEnumerations = null; + Comparable objectMinValue = null; + Comparable objectMaxValue = null; + int objectArrayMinLength = 0; + int objectArrayMaxLength = 0; + + public TIFFElementInfo(String[] childNames, + String[] attributeNames, + int childPolicy) { + this.childNames = childNames; + this.attributeNames = attributeNames; + this.childPolicy = childPolicy; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFExifJPEGCompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFExifJPEGCompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.ImageWriteParam; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; + +/** + * A TIFFCompressor for the JPEG variant of Exif. + */ +public class TIFFExifJPEGCompressor extends TIFFBaseJPEGCompressor { + public TIFFExifJPEGCompressor(ImageWriteParam param) { + super(TIFFImageWriter.EXIF_JPEG_COMPRESSION_TYPE, + BaselineTIFFTagSet.COMPRESSION_OLD_JPEG, + false, + param); + } + + public void setMetadata(IIOMetadata metadata) { + // Set the metadata. + super.setMetadata(metadata); + + // Initialize the JPEG writer and writeparam. + initJPEGWriter(false, // No stream metadata (not writing abbreviated) + true); // Yes image metadata (remove APPn markers) + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxCompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxCompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,513 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.plugins.tiff.TIFFField; + +/** + * + */ +abstract class TIFFFaxCompressor extends TIFFCompressor { + + /** + * The CCITT numerical definition of white. + */ + protected static final int WHITE = 0; + + /** + * The CCITT numerical definition of black. + */ + protected static final int BLACK = 1; + + // --- Begin tables for CCITT compression --- + + protected static final byte[] byteTable = new byte[] { + 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, // 0 to 15 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 16 to 31 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 32 to 47 + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 48 to 63 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 to 79 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 to 95 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 to 111 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 112 to 127 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128 to 143 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 144 to 159 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 160 to 175 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 176 to 191 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 192 to 207 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 208 to 223 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 224 to 239 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 240 to 255 + }; + + /** + * Terminating codes for black runs. + */ + protected static final int[] termCodesBlack = new int[] { + /* 0 0x0000 */ 0x0dc0000a, 0x40000003, 0xc0000002, 0x80000002, + /* 4 0x0004 */ 0x60000003, 0x30000004, 0x20000004, 0x18000005, + /* 8 0x0008 */ 0x14000006, 0x10000006, 0x08000007, 0x0a000007, + /* 12 0x000c */ 0x0e000007, 0x04000008, 0x07000008, 0x0c000009, + /* 16 0x0010 */ 0x05c0000a, 0x0600000a, 0x0200000a, 0x0ce0000b, + /* 20 0x0014 */ 0x0d00000b, 0x0d80000b, 0x06e0000b, 0x0500000b, + /* 24 0x0018 */ 0x02e0000b, 0x0300000b, 0x0ca0000c, 0x0cb0000c, + /* 28 0x001c */ 0x0cc0000c, 0x0cd0000c, 0x0680000c, 0x0690000c, + /* 32 0x0020 */ 0x06a0000c, 0x06b0000c, 0x0d20000c, 0x0d30000c, + /* 36 0x0024 */ 0x0d40000c, 0x0d50000c, 0x0d60000c, 0x0d70000c, + /* 40 0x0028 */ 0x06c0000c, 0x06d0000c, 0x0da0000c, 0x0db0000c, + /* 44 0x002c */ 0x0540000c, 0x0550000c, 0x0560000c, 0x0570000c, + /* 48 0x0030 */ 0x0640000c, 0x0650000c, 0x0520000c, 0x0530000c, + /* 52 0x0034 */ 0x0240000c, 0x0370000c, 0x0380000c, 0x0270000c, + /* 56 0x0038 */ 0x0280000c, 0x0580000c, 0x0590000c, 0x02b0000c, + /* 60 0x003c */ 0x02c0000c, 0x05a0000c, 0x0660000c, 0x0670000c + }; + + /** + * Terminating codes for white runs. + */ + protected static final int[] termCodesWhite = new int[] { + /* 0 0x0000 */ 0x35000008, 0x1c000006, 0x70000004, 0x80000004, + /* 4 0x0004 */ 0xb0000004, 0xc0000004, 0xe0000004, 0xf0000004, + /* 8 0x0008 */ 0x98000005, 0xa0000005, 0x38000005, 0x40000005, + /* 12 0x000c */ 0x20000006, 0x0c000006, 0xd0000006, 0xd4000006, + /* 16 0x0010 */ 0xa8000006, 0xac000006, 0x4e000007, 0x18000007, + /* 20 0x0014 */ 0x10000007, 0x2e000007, 0x06000007, 0x08000007, + /* 24 0x0018 */ 0x50000007, 0x56000007, 0x26000007, 0x48000007, + /* 28 0x001c */ 0x30000007, 0x02000008, 0x03000008, 0x1a000008, + /* 32 0x0020 */ 0x1b000008, 0x12000008, 0x13000008, 0x14000008, + /* 36 0x0024 */ 0x15000008, 0x16000008, 0x17000008, 0x28000008, + /* 40 0x0028 */ 0x29000008, 0x2a000008, 0x2b000008, 0x2c000008, + /* 44 0x002c */ 0x2d000008, 0x04000008, 0x05000008, 0x0a000008, + /* 48 0x0030 */ 0x0b000008, 0x52000008, 0x53000008, 0x54000008, + /* 52 0x0034 */ 0x55000008, 0x24000008, 0x25000008, 0x58000008, + /* 56 0x0038 */ 0x59000008, 0x5a000008, 0x5b000008, 0x4a000008, + /* 60 0x003c */ 0x4b000008, 0x32000008, 0x33000008, 0x34000008 + }; + + /** + * Make-up codes for black runs. + */ + protected static final int[] makeupCodesBlack = new int[] { + /* 0 0x0000 */ 0x00000000, 0x03c0000a, 0x0c80000c, 0x0c90000c, + /* 4 0x0004 */ 0x05b0000c, 0x0330000c, 0x0340000c, 0x0350000c, + /* 8 0x0008 */ 0x0360000d, 0x0368000d, 0x0250000d, 0x0258000d, + /* 12 0x000c */ 0x0260000d, 0x0268000d, 0x0390000d, 0x0398000d, + /* 16 0x0010 */ 0x03a0000d, 0x03a8000d, 0x03b0000d, 0x03b8000d, + /* 20 0x0014 */ 0x0290000d, 0x0298000d, 0x02a0000d, 0x02a8000d, + /* 24 0x0018 */ 0x02d0000d, 0x02d8000d, 0x0320000d, 0x0328000d, + /* 28 0x001c */ 0x0100000b, 0x0180000b, 0x01a0000b, 0x0120000c, + /* 32 0x0020 */ 0x0130000c, 0x0140000c, 0x0150000c, 0x0160000c, + /* 36 0x0024 */ 0x0170000c, 0x01c0000c, 0x01d0000c, 0x01e0000c, + /* 40 0x0028 */ 0x01f0000c, 0x00000000, 0x00000000, 0x00000000, + /* 44 0x002c */ 0x00000000, 0x00000000, 0x00000000, 0x00000000, + /* 48 0x0030 */ 0x00000000, 0x00000000, 0x00000000, 0x00000000, + /* 52 0x0034 */ 0x00000000, 0x00000000, 0x00000000, 0x00000000, + /* 56 0x0038 */ 0x00000000, 0x00000000, 0x00000000, 0x00000000 + }; + + /** + * Make-up codes for white runs. + */ + protected static final int[] makeupCodesWhite = new int[] { + /* 0 0x0000 */ 0x00000000, 0xd8000005, 0x90000005, 0x5c000006, + /* 4 0x0004 */ 0x6e000007, 0x36000008, 0x37000008, 0x64000008, + /* 8 0x0008 */ 0x65000008, 0x68000008, 0x67000008, 0x66000009, + /* 12 0x000c */ 0x66800009, 0x69000009, 0x69800009, 0x6a000009, + /* 16 0x0010 */ 0x6a800009, 0x6b000009, 0x6b800009, 0x6c000009, + /* 20 0x0014 */ 0x6c800009, 0x6d000009, 0x6d800009, 0x4c000009, + /* 24 0x0018 */ 0x4c800009, 0x4d000009, 0x60000006, 0x4d800009, + /* 28 0x001c */ 0x0100000b, 0x0180000b, 0x01a0000b, 0x0120000c, + /* 32 0x0020 */ 0x0130000c, 0x0140000c, 0x0150000c, 0x0160000c, + /* 36 0x0024 */ 0x0170000c, 0x01c0000c, 0x01d0000c, 0x01e0000c, + /* 40 0x0028 */ 0x01f0000c, 0x00000000, 0x00000000, 0x00000000, + /* 44 0x002c */ 0x00000000, 0x00000000, 0x00000000, 0x00000000, + /* 48 0x0030 */ 0x00000000, 0x00000000, 0x00000000, 0x00000000, + /* 52 0x0034 */ 0x00000000, 0x00000000, 0x00000000, 0x00000000, + /* 56 0x0038 */ 0x00000000, 0x00000000, 0x00000000, 0x00000000 + }; + + /** + * Pass mode table. + */ + protected static final int[] passMode = new int[] { + 0x10000004 // 0001 + }; + + /** + * Vertical mode table. + */ + protected static final int[] vertMode = new int[] { + 0x06000007, // 0000011 + 0x0c000006, // 000011 + 0x60000003, // 011 + 0x80000001, // 1 + 0x40000003, // 010 + 0x08000006, // 000010 + 0x04000007 // 0000010 + }; + + /** + * Horizontal mode table. + */ + protected static final int[] horzMode = new int[] { + 0x20000003 // 001 + }; + + /** + * Black and white terminating code table. + */ + protected static final int[][] termCodes = + new int[][] {termCodesWhite, termCodesBlack}; + + /** + * Black and white make-up code table. + */ + protected static final int[][] makeupCodes = + new int[][] {makeupCodesWhite, makeupCodesBlack}; + + /** + * Black and white pass mode table. + */ + protected static final int[][] pass = new int[][] {passMode, passMode}; + + /** + * Black and white vertical mode table. + */ + protected static final int[][] vert = new int[][] {vertMode, vertMode}; + + /** + * Black and white horizontal mode table. + */ + protected static final int[][] horz = new int[][] {horzMode, horzMode}; + + // --- End tables for CCITT compression --- + + /** + * Whether bits are inserted in reverse order (TIFF FillOrder 2). + */ + protected boolean inverseFill = false; + + /** + * Output bit buffer. + */ + protected int bits; + + /** + * Number of bits in the output bit buffer. + */ + protected int ndex; + + /** + * Constructor. The superclass constructor is merely invoked with the + * same parameters. + */ + protected TIFFFaxCompressor(String compressionType, + int compressionTagValue, + boolean isCompressionLossless) { + super(compressionType, compressionTagValue, isCompressionLossless); + } + + /** + * Sets the value of the metadata field. + * + *

The implementation in this class also sets local options + * from the FILL_ORDER field if it exists.

+ * + * @param metadata the IIOMetadata object for the + * image being written. + * + * @see #getMetadata() + */ + public void setMetadata(IIOMetadata metadata) { + super.setMetadata(metadata); + + if (metadata instanceof TIFFImageMetadata) { + TIFFImageMetadata tim = (TIFFImageMetadata)metadata; + TIFFField f = tim.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER); + inverseFill = (f != null && f.getAsInt(0) == 2); + } + } + + /** + * Return min of maxOffset or offset of first pixel + * different from pixel at bitOffset. + */ + public int nextState(byte[] data, + int base, + int bitOffset, + int maxOffset) + { + if(data == null) { + return maxOffset; + } + + int next = base + (bitOffset>>>3); + // If the offset is beyond the data already then the minimum of the + // current offset and maxOffset must be maxOffset. + if(next >= data.length) { + return maxOffset; + } + int end = base + (maxOffset>>>3); + if(end == data.length) { // Prevents out of bounds exception below + end--; + } + int extra = bitOffset & 0x7; + + int testbyte; + + if((data[next] & (0x80 >>> extra)) != 0) { // look for "0" + testbyte = ~(data[next]) & (0xff >>> extra); + while (next < end) { + if (testbyte != 0) { + break; + } + testbyte = ~(data[++next]) & 0xff; + } + } else { // look for "1" + if ((testbyte = (data[next] & (0xff >>> extra))) != 0) { + bitOffset = (next-base)*8 + byteTable[testbyte]; + return ((bitOffset < maxOffset) ? bitOffset : maxOffset); + } + while (next < end) { + if ((testbyte = data[++next]&0xff) != 0) { + // "1" is in current byte + bitOffset = (next-base)*8 + byteTable[testbyte]; + return ((bitOffset < maxOffset) ? bitOffset : maxOffset); + } + } + } + bitOffset = (next-base)*8 + byteTable[testbyte]; + return ((bitOffset < maxOffset) ? bitOffset : maxOffset); + } + + /** + * Initialize bit buffer machinery. + */ + public void initBitBuf() + { + ndex = 0; + bits = 0x00000000; + } + + /** + * Get code for run and add to compressed bitstream. + */ + public int add1DBits(byte[] buf, + int where, // byte offs + int count, // #pixels in run + int color) // color of run + { + int sixtyfours; + int mask; + int len = where; + + sixtyfours = count >>> 6; // count / 64; + count = count & 0x3f; // count % 64 + if (sixtyfours != 0) { + for ( ; sixtyfours > 40; sixtyfours -= 40) { + mask = makeupCodes[color][40]; + bits |= (mask & 0xfff80000) >>> ndex; + ndex += (mask & 0x0000ffff); + while (ndex > 7) { + buf[len++] = (byte)(bits >>> 24); + bits <<= 8; + ndex -= 8; + } + } + + mask = makeupCodes[color][sixtyfours]; + bits |= (mask & 0xfff80000) >>> ndex; + ndex += (mask & 0x0000ffff); + while (ndex > 7) { + buf[len++] = (byte)(bits >>> 24); + bits <<= 8; + ndex -= 8; + } + } + + mask = termCodes[color][count]; + bits |= (mask & 0xfff80000) >>> ndex; + ndex += (mask & 0x0000ffff); + while (ndex > 7) { + buf[len++] = (byte)(bits >>> 24); + bits <<= 8; + ndex -= 8; + } + + return(len - where); + } + + /** + * Place entry from mode table into compressed bitstream. + */ + public int add2DBits(byte[] buf, // compressed buffer + int where, // byte offset into compressed buffer + int[][] mode, // 2-D mode to be encoded + int entry) // mode entry (0 unless vertical) + { + int mask; + int len = where; + int color = 0; + + mask = mode[color][entry]; + bits |= (mask & 0xfff80000) >>> ndex; + ndex += (mask & 0x0000ffff); + while (ndex > 7) { + buf[len++] = (byte)(bits >>> 24); + bits <<= 8; + ndex -= 8; + } + + return(len - where); + } + + /** + * Add an End-of-Line (EOL == 0x001) to the compressed bitstream + * with optional byte alignment. + */ + public int addEOL(boolean is1DMode,// 1D encoding + boolean addFill, // byte aligned EOLs + boolean add1, // add1 ? EOL+1 : EOL+0 + byte[] buf, // compressed buffer address + int where) // current byte offset into buffer + { + int len = where; + + // + // Add zero-valued fill bits such that the EOL is aligned as + // + // xxxx 0000 0000 0001 + // + if(addFill) { + // + // Simply increment the bit count. No need to feed bits into + // the output buffer at this point as there are at most 7 bits + // in the bit buffer, at most 7 are added here, and at most + // 13 below making the total 7+7+13 = 27 before the bit feed + // at the end of this routine. + // + ndex += ((ndex <= 4) ? 4 - ndex : 12 - ndex); + } + + // + // Write EOL into buffer + // + if(is1DMode) { + bits |= 0x00100000 >>> ndex; + ndex += 12; + } else { + bits |= (add1 ? 0x00180000 : 0x00100000) >>> ndex; + ndex += 13; + } + + while (ndex > 7) { + buf[len++] = (byte)(bits >>> 24); + bits <<= 8; + ndex -= 8; + } + + return(len - where); + } + + /** + * Add an End-of-Facsimile-Block (EOFB == 0x001001) to the compressed + * bitstream. + */ + public int addEOFB(byte[] buf, // compressed buffer + int where) // byte offset into compressed buffer + { + int len = where; + + // + // eofb code + // + bits |= 0x00100100 >>> ndex; + + // + // eofb code length + // + ndex += 24; + + // + // flush all pending bits + // + while(ndex > 0) { + buf[len++] = (byte)(bits >>> 24); + bits <<= 8; + ndex -= 8; + } + + return(len - where); + } + + /** + * One-dimensionally encode a row of data using CCITT Huffman compression. + * The bit buffer should be initialized as required before invoking this + * method and should be flushed after the method returns. The fill order + * is always highest-order to lowest-order bit so the calling routine + * should handle bit inversion. + */ + public int encode1D(byte[] data, + int rowOffset, + int colOffset, + int rowLength, + byte[] compData, + int compOffset) { + int lineAddr = rowOffset; + int bitIndex = colOffset; + + int last = bitIndex + rowLength; + int outIndex = compOffset; + + // + // Is first pixel black + // + int testbit = + ((data[lineAddr + (bitIndex>>>3)]&0xff) >>> + (7-(bitIndex & 0x7))) & 0x1; + int currentColor = BLACK; + if (testbit != 0) { + outIndex += add1DBits(compData, outIndex, 0, WHITE); + } else { + currentColor = WHITE; + } + + // + // Run-length encode line + // + while (bitIndex < last) { + int bitCount = + nextState(data, lineAddr, bitIndex, last) - bitIndex; + outIndex += + add1DBits(compData, outIndex, bitCount, currentColor); + bitIndex += bitCount; + currentColor ^= 0x00000001; + } + + return outIndex - compOffset; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxDecompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxDecompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,1594 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.io.IOException; +import java.io.EOFException; +import javax.imageio.IIOException; +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.plugins.tiff.TIFFField; + +class TIFFFaxDecompressor extends TIFFDecompressor { + + /** + * The logical order of bits within a byte. + *
+     * 1 = MSB-to-LSB
+     * 2 = LSB-to-MSB (flipped)
+     * 
+ */ + private int fillOrder; + private int t4Options; + private int t6Options; + + // Variables set by T4Options + /** + * Uncompressed mode flag: 1 if uncompressed, 0 if not. + */ + private int uncompressedMode = 0; + + /** + * EOL padding flag: 1 if fill bits have been added before an EOL such + * that the EOL ends on a byte boundary, 0 otherwise. + */ + private int fillBits = 0; + + /** + * Coding dimensionality: 1 for 2-dimensional, 0 for 1-dimensional. + */ + private int oneD; + + private byte[] data; + private int bitPointer, bytePointer; + + // Output image buffer + private byte[] buffer; + private int w, h, bitsPerScanline; + private int lineBitNum; + + // Data structures needed to store changing elements for the previous + // and the current scanline + private int changingElemSize = 0; + private int prevChangingElems[]; + private int currChangingElems[]; + + // Element at which to start search in getNextChangingElement + private int lastChangingElement = 0; + + private static int table1[] = { + 0x00, // 0 bits are left in first byte - SHOULD NOT HAPPEN + 0x01, // 1 bits are left in first byte + 0x03, // 2 bits are left in first byte + 0x07, // 3 bits are left in first byte + 0x0f, // 4 bits are left in first byte + 0x1f, // 5 bits are left in first byte + 0x3f, // 6 bits are left in first byte + 0x7f, // 7 bits are left in first byte + 0xff // 8 bits are left in first byte + }; + + private static int table2[] = { + 0x00, // 0 + 0x80, // 1 + 0xc0, // 2 + 0xe0, // 3 + 0xf0, // 4 + 0xf8, // 5 + 0xfc, // 6 + 0xfe, // 7 + 0xff // 8 + }; + + // Table to be used when fillOrder = 2, for flipping bytes. + static byte flipTable[] = { + 0, -128, 64, -64, 32, -96, 96, -32, + 16, -112, 80, -48, 48, -80, 112, -16, + 8, -120, 72, -56, 40, -88, 104, -24, + 24, -104, 88, -40, 56, -72, 120, -8, + 4, -124, 68, -60, 36, -92, 100, -28, + 20, -108, 84, -44, 52, -76, 116, -12, + 12, -116, 76, -52, 44, -84, 108, -20, + 28, -100, 92, -36, 60, -68, 124, -4, + 2, -126, 66, -62, 34, -94, 98, -30, + 18, -110, 82, -46, 50, -78, 114, -14, + 10, -118, 74, -54, 42, -86, 106, -22, + 26, -102, 90, -38, 58, -70, 122, -6, + 6, -122, 70, -58, 38, -90, 102, -26, + 22, -106, 86, -42, 54, -74, 118, -10, + 14, -114, 78, -50, 46, -82, 110, -18, + 30, -98, 94, -34, 62, -66, 126, -2, + 1, -127, 65, -63, 33, -95, 97, -31, + 17, -111, 81, -47, 49, -79, 113, -15, + 9, -119, 73, -55, 41, -87, 105, -23, + 25, -103, 89, -39, 57, -71, 121, -7, + 5, -123, 69, -59, 37, -91, 101, -27, + 21, -107, 85, -43, 53, -75, 117, -11, + 13, -115, 77, -51, 45, -83, 109, -19, + 29, -99, 93, -35, 61, -67, 125, -3, + 3, -125, 67, -61, 35, -93, 99, -29, + 19, -109, 83, -45, 51, -77, 115, -13, + 11, -117, 75, -53, 43, -85, 107, -21, + 27, -101, 91, -37, 59, -69, 123, -5, + 7, -121, 71, -57, 39, -89, 103, -25, + 23, -105, 87, -41, 55, -73, 119, -9, + 15, -113, 79, -49, 47, -81, 111, -17, + 31, -97, 95, -33, 63, -65, 127, -1, + }; + + // The main 10 bit white runs lookup table + private static short white[] = { + // 0 - 7 + 6430, 6400, 6400, 6400, 3225, 3225, 3225, 3225, + // 8 - 15 + 944, 944, 944, 944, 976, 976, 976, 976, + // 16 - 23 + 1456, 1456, 1456, 1456, 1488, 1488, 1488, 1488, + // 24 - 31 + 718, 718, 718, 718, 718, 718, 718, 718, + // 32 - 39 + 750, 750, 750, 750, 750, 750, 750, 750, + // 40 - 47 + 1520, 1520, 1520, 1520, 1552, 1552, 1552, 1552, + // 48 - 55 + 428, 428, 428, 428, 428, 428, 428, 428, + // 56 - 63 + 428, 428, 428, 428, 428, 428, 428, 428, + // 64 - 71 + 654, 654, 654, 654, 654, 654, 654, 654, + // 72 - 79 + 1072, 1072, 1072, 1072, 1104, 1104, 1104, 1104, + // 80 - 87 + 1136, 1136, 1136, 1136, 1168, 1168, 1168, 1168, + // 88 - 95 + 1200, 1200, 1200, 1200, 1232, 1232, 1232, 1232, + // 96 - 103 + 622, 622, 622, 622, 622, 622, 622, 622, + // 104 - 111 + 1008, 1008, 1008, 1008, 1040, 1040, 1040, 1040, + // 112 - 119 + 44, 44, 44, 44, 44, 44, 44, 44, + // 120 - 127 + 44, 44, 44, 44, 44, 44, 44, 44, + // 128 - 135 + 396, 396, 396, 396, 396, 396, 396, 396, + // 136 - 143 + 396, 396, 396, 396, 396, 396, 396, 396, + // 144 - 151 + 1712, 1712, 1712, 1712, 1744, 1744, 1744, 1744, + // 152 - 159 + 846, 846, 846, 846, 846, 846, 846, 846, + // 160 - 167 + 1264, 1264, 1264, 1264, 1296, 1296, 1296, 1296, + // 168 - 175 + 1328, 1328, 1328, 1328, 1360, 1360, 1360, 1360, + // 176 - 183 + 1392, 1392, 1392, 1392, 1424, 1424, 1424, 1424, + // 184 - 191 + 686, 686, 686, 686, 686, 686, 686, 686, + // 192 - 199 + 910, 910, 910, 910, 910, 910, 910, 910, + // 200 - 207 + 1968, 1968, 1968, 1968, 2000, 2000, 2000, 2000, + // 208 - 215 + 2032, 2032, 2032, 2032, 16, 16, 16, 16, + // 216 - 223 + 10257, 10257, 10257, 10257, 12305, 12305, 12305, 12305, + // 224 - 231 + 330, 330, 330, 330, 330, 330, 330, 330, + // 232 - 239 + 330, 330, 330, 330, 330, 330, 330, 330, + // 240 - 247 + 330, 330, 330, 330, 330, 330, 330, 330, + // 248 - 255 + 330, 330, 330, 330, 330, 330, 330, 330, + // 256 - 263 + 362, 362, 362, 362, 362, 362, 362, 362, + // 264 - 271 + 362, 362, 362, 362, 362, 362, 362, 362, + // 272 - 279 + 362, 362, 362, 362, 362, 362, 362, 362, + // 280 - 287 + 362, 362, 362, 362, 362, 362, 362, 362, + // 288 - 295 + 878, 878, 878, 878, 878, 878, 878, 878, + // 296 - 303 + 1904, 1904, 1904, 1904, 1936, 1936, 1936, 1936, + // 304 - 311 + -18413, -18413, -16365, -16365, -14317, -14317, -10221, -10221, + // 312 - 319 + 590, 590, 590, 590, 590, 590, 590, 590, + // 320 - 327 + 782, 782, 782, 782, 782, 782, 782, 782, + // 328 - 335 + 1584, 1584, 1584, 1584, 1616, 1616, 1616, 1616, + // 336 - 343 + 1648, 1648, 1648, 1648, 1680, 1680, 1680, 1680, + // 344 - 351 + 814, 814, 814, 814, 814, 814, 814, 814, + // 352 - 359 + 1776, 1776, 1776, 1776, 1808, 1808, 1808, 1808, + // 360 - 367 + 1840, 1840, 1840, 1840, 1872, 1872, 1872, 1872, + // 368 - 375 + 6157, 6157, 6157, 6157, 6157, 6157, 6157, 6157, + // 376 - 383 + 6157, 6157, 6157, 6157, 6157, 6157, 6157, 6157, + // 384 - 391 + -12275, -12275, -12275, -12275, -12275, -12275, -12275, -12275, + // 392 - 399 + -12275, -12275, -12275, -12275, -12275, -12275, -12275, -12275, + // 400 - 407 + 14353, 14353, 14353, 14353, 16401, 16401, 16401, 16401, + // 408 - 415 + 22547, 22547, 24595, 24595, 20497, 20497, 20497, 20497, + // 416 - 423 + 18449, 18449, 18449, 18449, 26643, 26643, 28691, 28691, + // 424 - 431 + 30739, 30739, -32749, -32749, -30701, -30701, -28653, -28653, + // 432 - 439 + -26605, -26605, -24557, -24557, -22509, -22509, -20461, -20461, + // 440 - 447 + 8207, 8207, 8207, 8207, 8207, 8207, 8207, 8207, + // 448 - 455 + 72, 72, 72, 72, 72, 72, 72, 72, + // 456 - 463 + 72, 72, 72, 72, 72, 72, 72, 72, + // 464 - 471 + 72, 72, 72, 72, 72, 72, 72, 72, + // 472 - 479 + 72, 72, 72, 72, 72, 72, 72, 72, + // 480 - 487 + 72, 72, 72, 72, 72, 72, 72, 72, + // 488 - 495 + 72, 72, 72, 72, 72, 72, 72, 72, + // 496 - 503 + 72, 72, 72, 72, 72, 72, 72, 72, + // 504 - 511 + 72, 72, 72, 72, 72, 72, 72, 72, + // 512 - 519 + 104, 104, 104, 104, 104, 104, 104, 104, + // 520 - 527 + 104, 104, 104, 104, 104, 104, 104, 104, + // 528 - 535 + 104, 104, 104, 104, 104, 104, 104, 104, + // 536 - 543 + 104, 104, 104, 104, 104, 104, 104, 104, + // 544 - 551 + 104, 104, 104, 104, 104, 104, 104, 104, + // 552 - 559 + 104, 104, 104, 104, 104, 104, 104, 104, + // 560 - 567 + 104, 104, 104, 104, 104, 104, 104, 104, + // 568 - 575 + 104, 104, 104, 104, 104, 104, 104, 104, + // 576 - 583 + 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107, + // 584 - 591 + 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107, + // 592 - 599 + 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107, + // 600 - 607 + 4107, 4107, 4107, 4107, 4107, 4107, 4107, 4107, + // 608 - 615 + 266, 266, 266, 266, 266, 266, 266, 266, + // 616 - 623 + 266, 266, 266, 266, 266, 266, 266, 266, + // 624 - 631 + 266, 266, 266, 266, 266, 266, 266, 266, + // 632 - 639 + 266, 266, 266, 266, 266, 266, 266, 266, + // 640 - 647 + 298, 298, 298, 298, 298, 298, 298, 298, + // 648 - 655 + 298, 298, 298, 298, 298, 298, 298, 298, + // 656 - 663 + 298, 298, 298, 298, 298, 298, 298, 298, + // 664 - 671 + 298, 298, 298, 298, 298, 298, 298, 298, + // 672 - 679 + 524, 524, 524, 524, 524, 524, 524, 524, + // 680 - 687 + 524, 524, 524, 524, 524, 524, 524, 524, + // 688 - 695 + 556, 556, 556, 556, 556, 556, 556, 556, + // 696 - 703 + 556, 556, 556, 556, 556, 556, 556, 556, + // 704 - 711 + 136, 136, 136, 136, 136, 136, 136, 136, + // 712 - 719 + 136, 136, 136, 136, 136, 136, 136, 136, + // 720 - 727 + 136, 136, 136, 136, 136, 136, 136, 136, + // 728 - 735 + 136, 136, 136, 136, 136, 136, 136, 136, + // 736 - 743 + 136, 136, 136, 136, 136, 136, 136, 136, + // 744 - 751 + 136, 136, 136, 136, 136, 136, 136, 136, + // 752 - 759 + 136, 136, 136, 136, 136, 136, 136, 136, + // 760 - 767 + 136, 136, 136, 136, 136, 136, 136, 136, + // 768 - 775 + 168, 168, 168, 168, 168, 168, 168, 168, + // 776 - 783 + 168, 168, 168, 168, 168, 168, 168, 168, + // 784 - 791 + 168, 168, 168, 168, 168, 168, 168, 168, + // 792 - 799 + 168, 168, 168, 168, 168, 168, 168, 168, + // 800 - 807 + 168, 168, 168, 168, 168, 168, 168, 168, + // 808 - 815 + 168, 168, 168, 168, 168, 168, 168, 168, + // 816 - 823 + 168, 168, 168, 168, 168, 168, 168, 168, + // 824 - 831 + 168, 168, 168, 168, 168, 168, 168, 168, + // 832 - 839 + 460, 460, 460, 460, 460, 460, 460, 460, + // 840 - 847 + 460, 460, 460, 460, 460, 460, 460, 460, + // 848 - 855 + 492, 492, 492, 492, 492, 492, 492, 492, + // 856 - 863 + 492, 492, 492, 492, 492, 492, 492, 492, + // 864 - 871 + 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, + // 872 - 879 + 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, + // 880 - 887 + 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, + // 888 - 895 + 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, + // 896 - 903 + 200, 200, 200, 200, 200, 200, 200, 200, + // 904 - 911 + 200, 200, 200, 200, 200, 200, 200, 200, + // 912 - 919 + 200, 200, 200, 200, 200, 200, 200, 200, + // 920 - 927 + 200, 200, 200, 200, 200, 200, 200, 200, + // 928 - 935 + 200, 200, 200, 200, 200, 200, 200, 200, + // 936 - 943 + 200, 200, 200, 200, 200, 200, 200, 200, + // 944 - 951 + 200, 200, 200, 200, 200, 200, 200, 200, + // 952 - 959 + 200, 200, 200, 200, 200, 200, 200, 200, + // 960 - 967 + 232, 232, 232, 232, 232, 232, 232, 232, + // 968 - 975 + 232, 232, 232, 232, 232, 232, 232, 232, + // 976 - 983 + 232, 232, 232, 232, 232, 232, 232, 232, + // 984 - 991 + 232, 232, 232, 232, 232, 232, 232, 232, + // 992 - 999 + 232, 232, 232, 232, 232, 232, 232, 232, + // 1000 - 1007 + 232, 232, 232, 232, 232, 232, 232, 232, + // 1008 - 1015 + 232, 232, 232, 232, 232, 232, 232, 232, + // 1016 - 1023 + 232, 232, 232, 232, 232, 232, 232, 232, + }; + + // Additional make up codes for both White and Black runs + private static short additionalMakeup[] = { + 28679, 28679, 31752, (short)32777, + (short)33801, (short)34825, (short)35849, (short)36873, + (short)29703, (short)29703, (short)30727, (short)30727, + (short)37897, (short)38921, (short)39945, (short)40969 + }; + + // Initial black run look up table, uses the first 4 bits of a code + private static short initBlack[] = { + // 0 - 7 + 3226, 6412, 200, 168, 38, 38, 134, 134, + // 8 - 15 + 100, 100, 100, 100, 68, 68, 68, 68 + }; + + // + private static short twoBitBlack[] = {292, 260, 226, 226}; // 0 - 3 + + // Main black run table, using the last 9 bits of possible 13 bit code + private static short black[] = { + // 0 - 7 + 62, 62, 30, 30, 0, 0, 0, 0, + // 8 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, + // 16 - 23 + 0, 0, 0, 0, 0, 0, 0, 0, + // 24 - 31 + 0, 0, 0, 0, 0, 0, 0, 0, + // 32 - 39 + 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225, + // 40 - 47 + 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225, + // 48 - 55 + 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225, + // 56 - 63 + 3225, 3225, 3225, 3225, 3225, 3225, 3225, 3225, + // 64 - 71 + 588, 588, 588, 588, 588, 588, 588, 588, + // 72 - 79 + 1680, 1680, 20499, 22547, 24595, 26643, 1776, 1776, + // 80 - 87 + 1808, 1808, -24557, -22509, -20461, -18413, 1904, 1904, + // 88 - 95 + 1936, 1936, -16365, -14317, 782, 782, 782, 782, + // 96 - 103 + 814, 814, 814, 814, -12269, -10221, 10257, 10257, + // 104 - 111 + 12305, 12305, 14353, 14353, 16403, 18451, 1712, 1712, + // 112 - 119 + 1744, 1744, 28691, 30739, -32749, -30701, -28653, -26605, + // 120 - 127 + 2061, 2061, 2061, 2061, 2061, 2061, 2061, 2061, + // 128 - 135 + 424, 424, 424, 424, 424, 424, 424, 424, + // 136 - 143 + 424, 424, 424, 424, 424, 424, 424, 424, + // 144 - 151 + 424, 424, 424, 424, 424, 424, 424, 424, + // 152 - 159 + 424, 424, 424, 424, 424, 424, 424, 424, + // 160 - 167 + 750, 750, 750, 750, 1616, 1616, 1648, 1648, + // 168 - 175 + 1424, 1424, 1456, 1456, 1488, 1488, 1520, 1520, + // 176 - 183 + 1840, 1840, 1872, 1872, 1968, 1968, 8209, 8209, + // 184 - 191 + 524, 524, 524, 524, 524, 524, 524, 524, + // 192 - 199 + 556, 556, 556, 556, 556, 556, 556, 556, + // 200 - 207 + 1552, 1552, 1584, 1584, 2000, 2000, 2032, 2032, + // 208 - 215 + 976, 976, 1008, 1008, 1040, 1040, 1072, 1072, + // 216 - 223 + 1296, 1296, 1328, 1328, 718, 718, 718, 718, + // 224 - 231 + 456, 456, 456, 456, 456, 456, 456, 456, + // 232 - 239 + 456, 456, 456, 456, 456, 456, 456, 456, + // 240 - 247 + 456, 456, 456, 456, 456, 456, 456, 456, + // 248 - 255 + 456, 456, 456, 456, 456, 456, 456, 456, + // 256 - 263 + 326, 326, 326, 326, 326, 326, 326, 326, + // 264 - 271 + 326, 326, 326, 326, 326, 326, 326, 326, + // 272 - 279 + 326, 326, 326, 326, 326, 326, 326, 326, + // 280 - 287 + 326, 326, 326, 326, 326, 326, 326, 326, + // 288 - 295 + 326, 326, 326, 326, 326, 326, 326, 326, + // 296 - 303 + 326, 326, 326, 326, 326, 326, 326, 326, + // 304 - 311 + 326, 326, 326, 326, 326, 326, 326, 326, + // 312 - 319 + 326, 326, 326, 326, 326, 326, 326, 326, + // 320 - 327 + 358, 358, 358, 358, 358, 358, 358, 358, + // 328 - 335 + 358, 358, 358, 358, 358, 358, 358, 358, + // 336 - 343 + 358, 358, 358, 358, 358, 358, 358, 358, + // 344 - 351 + 358, 358, 358, 358, 358, 358, 358, 358, + // 352 - 359 + 358, 358, 358, 358, 358, 358, 358, 358, + // 360 - 367 + 358, 358, 358, 358, 358, 358, 358, 358, + // 368 - 375 + 358, 358, 358, 358, 358, 358, 358, 358, + // 376 - 383 + 358, 358, 358, 358, 358, 358, 358, 358, + // 384 - 391 + 490, 490, 490, 490, 490, 490, 490, 490, + // 392 - 399 + 490, 490, 490, 490, 490, 490, 490, 490, + // 400 - 407 + 4113, 4113, 6161, 6161, 848, 848, 880, 880, + // 408 - 415 + 912, 912, 944, 944, 622, 622, 622, 622, + // 416 - 423 + 654, 654, 654, 654, 1104, 1104, 1136, 1136, + // 424 - 431 + 1168, 1168, 1200, 1200, 1232, 1232, 1264, 1264, + // 432 - 439 + 686, 686, 686, 686, 1360, 1360, 1392, 1392, + // 440 - 447 + 12, 12, 12, 12, 12, 12, 12, 12, + // 448 - 455 + 390, 390, 390, 390, 390, 390, 390, 390, + // 456 - 463 + 390, 390, 390, 390, 390, 390, 390, 390, + // 464 - 471 + 390, 390, 390, 390, 390, 390, 390, 390, + // 472 - 479 + 390, 390, 390, 390, 390, 390, 390, 390, + // 480 - 487 + 390, 390, 390, 390, 390, 390, 390, 390, + // 488 - 495 + 390, 390, 390, 390, 390, 390, 390, 390, + // 496 - 503 + 390, 390, 390, 390, 390, 390, 390, 390, + // 504 - 511 + 390, 390, 390, 390, 390, 390, 390, 390, + }; + + private static byte twoDCodes[] = { + // 0 - 7 + 80, 88, 23, 71, 30, 30, 62, 62, + // 8 - 15 + 4, 4, 4, 4, 4, 4, 4, 4, + // 16 - 23 + 11, 11, 11, 11, 11, 11, 11, 11, + // 24 - 31 + 11, 11, 11, 11, 11, 11, 11, 11, + // 32 - 39 + 35, 35, 35, 35, 35, 35, 35, 35, + // 40 - 47 + 35, 35, 35, 35, 35, 35, 35, 35, + // 48 - 55 + 51, 51, 51, 51, 51, 51, 51, 51, + // 56 - 63 + 51, 51, 51, 51, 51, 51, 51, 51, + // 64 - 71 + 41, 41, 41, 41, 41, 41, 41, 41, + // 72 - 79 + 41, 41, 41, 41, 41, 41, 41, 41, + // 80 - 87 + 41, 41, 41, 41, 41, 41, 41, 41, + // 88 - 95 + 41, 41, 41, 41, 41, 41, 41, 41, + // 96 - 103 + 41, 41, 41, 41, 41, 41, 41, 41, + // 104 - 111 + 41, 41, 41, 41, 41, 41, 41, 41, + // 112 - 119 + 41, 41, 41, 41, 41, 41, 41, 41, + // 120 - 127 + 41, 41, 41, 41, 41, 41, 41, 41, + }; + + public TIFFFaxDecompressor() {} + + /** + * Invokes the superclass method and then sets instance variables on + * the basis of the metadata set on this decompressor. + */ + public void beginDecoding() { + super.beginDecoding(); + + if(metadata instanceof TIFFImageMetadata) { + TIFFImageMetadata tmetadata = (TIFFImageMetadata)metadata; + TIFFField f; + + f = tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER); + this.fillOrder = f == null ? 1 : f.getAsInt(0); + + f = tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION); + this.compression = f == null ? + BaselineTIFFTagSet.COMPRESSION_CCITT_RLE : f.getAsInt(0); + + f = tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_T4_OPTIONS); + this.t4Options = f == null ? 0 : f.getAsInt(0); + this.oneD = (t4Options & 0x01); + // uncompressedMode - haven't dealt with this yet. + this.uncompressedMode = ((t4Options & 0x02) >> 1); + this.fillBits = ((t4Options & 0x04) >> 2); + f = tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_T6_OPTIONS); + this.t6Options = f == null ? 0 : f.getAsInt(0); + } else { + this.fillOrder = 1; // MSB-to-LSB + + this.compression = BaselineTIFFTagSet.COMPRESSION_CCITT_RLE; // RLE + + this.t4Options = 0; // Irrelevant as applies to T.4 only + this.oneD = 0; // One-dimensional + this.uncompressedMode = 0; // Not uncompressed mode + this.fillBits = 0; // No fill bits + this.t6Options = 0; + } + } + + public void decodeRaw(byte[] b, int dstOffset, + int pixelBitStride, // will always be 1 + int scanlineStride) throws IOException { + + this.buffer = b; + + this.w = srcWidth; + this.h = srcHeight; + this.bitsPerScanline = scanlineStride*8; + this.lineBitNum = 8*dstOffset; + + this.data = new byte[byteCount]; + this.bitPointer = 0; + this.bytePointer = 0; + this.prevChangingElems = new int[w + 1]; + this.currChangingElems = new int[w + 1]; + + stream.seek(offset); + stream.readFully(data); + + if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) { + decodeRLE(); + } else if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) { + decodeT4(); + } else if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) { + this.uncompressedMode = ((t6Options & 0x02) >> 1); + decodeT6(); + } else { + throw new IIOException("Unknown compression type " + compression); + } + } + + public void decodeRLE() throws IIOException { + for (int i = 0; i < h; i++) { + // Decode the line. + decodeNextScanline(srcMinY + i); + + // Advance to the next byte boundary if not already there. + if (bitPointer != 0) { + bytePointer++; + bitPointer = 0; + } + + // Update the total number of bits. + lineBitNum += bitsPerScanline; + } + } + + public void decodeNextScanline(int lineIndex) throws IIOException { + int bits = 0, code = 0, isT = 0; + int current, entry, twoBits; + boolean isWhite = true; + int dstEnd = 0; + + int bitOffset = 0; + + // Initialize starting of the changing elements array + changingElemSize = 0; + + // While scanline not complete + while (bitOffset < w) { + + // Mark start of white run. + int runOffset = bitOffset; + + while (isWhite && bitOffset < w) { + // White run + current = nextNBits(10); + entry = white[current]; + + // Get the 3 fields from the entry + isT = entry & 0x0001; + bits = (entry >>> 1) & 0x0f; + + if (bits == 12) { // Additional Make up code + // Get the next 2 bits + twoBits = nextLesserThan8Bits(2); + // Consolidate the 2 new bits and last 2 bits into 4 bits + current = ((current << 2) & 0x000c) | twoBits; + entry = additionalMakeup[current]; + bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111 + code = (entry >>> 4) & 0x0fff; // 12 bits + bitOffset += code; // Skip white run + + updatePointer(4 - bits); + } else if (bits == 0) { // ERROR + warning("Error 0"); + } else if (bits == 15) { // EOL + // + // Instead of throwing an exception, assume that the + // EOL was premature; emit a warning and return. + // + warning("Premature EOL in white run of line "+lineIndex+ + ": read "+bitOffset+" of "+w+" expected pixels."); + return; + } else { + // 11 bits - 0000 0111 1111 1111 = 0x07ff + code = (entry >>> 5) & 0x07ff; + bitOffset += code; + + updatePointer(10 - bits); + if (isT == 0) { + isWhite = false; + currChangingElems[changingElemSize++] = bitOffset; + } + } + } + + // Check whether this run completed one width + if (bitOffset == w) { + // If the white run has not been terminated then ensure that + // the next code word is a terminating code for a white run + // of length zero. + int runLength = bitOffset - runOffset; + if(isWhite && + runLength != 0 && runLength % 64 == 0 && + nextNBits(8) != 0x35) { + warning("Missing zero white run length terminating code!"); + updatePointer(8); + } + break; + } + + // Mark start of black run. + runOffset = bitOffset; + + while (isWhite == false && bitOffset < w) { + // Black run + current = nextLesserThan8Bits(4); + entry = initBlack[current]; + + // Get the 3 fields from the entry + isT = entry & 0x0001; + bits = (entry >>> 1) & 0x000f; + code = (entry >>> 5) & 0x07ff; + + if (code == 100) { + current = nextNBits(9); + entry = black[current]; + + // Get the 3 fields from the entry + isT = entry & 0x0001; + bits = (entry >>> 1) & 0x000f; + code = (entry >>> 5) & 0x07ff; + + if (bits == 12) { + // Additional makeup codes + updatePointer(5); + current = nextLesserThan8Bits(4); + entry = additionalMakeup[current]; + bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111 + code = (entry >>> 4) & 0x0fff; // 12 bits + + setToBlack(bitOffset, code); + bitOffset += code; + + updatePointer(4 - bits); + } else if (bits == 15) { + // + // Instead of throwing an exception, assume that the + // EOL was premature; emit a warning and return. + // + warning("Premature EOL in black run of line "+ + lineIndex+": read "+bitOffset+" of "+w+ + " expected pixels."); + return; + } else { + setToBlack(bitOffset, code); + bitOffset += code; + + updatePointer(9 - bits); + if (isT == 0) { + isWhite = true; + currChangingElems[changingElemSize++] = bitOffset; + } + } + } else if (code == 200) { + // Is a Terminating code + current = nextLesserThan8Bits(2); + entry = twoBitBlack[current]; + code = (entry >>> 5) & 0x07ff; + bits = (entry >>> 1) & 0x0f; + + setToBlack(bitOffset, code); + bitOffset += code; + + updatePointer(2 - bits); + isWhite = true; + currChangingElems[changingElemSize++] = bitOffset; + } else { + // Is a Terminating code + setToBlack(bitOffset, code); + bitOffset += code; + + updatePointer(4 - bits); + isWhite = true; + currChangingElems[changingElemSize++] = bitOffset; + } + } + + // Check whether this run completed one width + if (bitOffset == w) { + // If the black run has not been terminated then ensure that + // the next code word is a terminating code for a black run + // of length zero. + int runLength = bitOffset - runOffset; + if(!isWhite && + runLength != 0 && runLength % 64 == 0 && + nextNBits(10) != 0x37) { + warning("Missing zero black run length terminating code!"); + updatePointer(10); + } + break; + } + } + + currChangingElems[changingElemSize++] = bitOffset; + } + + public void decodeT4() throws IIOException { + int height = h; + + int a0, a1, b1, b2; + int[] b = new int[2]; + int entry, code, bits, color; + boolean isWhite; + int currIndex = 0; + int temp[]; + + if(data.length < 2) { + throw new IIOException("Insufficient data to read initial EOL."); + } + + // The data should start with an EOL code + int next12 = nextNBits(12); + if(next12 != 1) { + warning("T.4 compressed data should begin with EOL."); + } + updatePointer(12); + + // Find the first one-dimensionally encoded line. + int modeFlag = 0; + int lines = -1; // indicates imaginary line before first actual line. + while(modeFlag != 1) { + try { + modeFlag = findNextLine(); + lines++; // Normally 'lines' will be 0 on exiting loop. + } catch(EOFException eofe) { + throw new IIOException("No reference line present."); + } + } + + int bitOffset; + + // Then the 1D encoded scanline data will occur, changing elements + // array gets set. + decodeNextScanline(srcMinY); + lines++; + lineBitNum += bitsPerScanline; + + while(lines < height) { + + // Every line must begin with an EOL followed by a bit which + // indicates whether the following scanline is 1D or 2D encoded. + try { + modeFlag = findNextLine(); + } catch(EOFException eofe) { + warning("Input exhausted before EOL found at line "+ + (srcMinY+lines)+": read 0 of "+w+" expected pixels."); + break; + } + if(modeFlag == 0) { + // 2D encoded scanline follows + + // Initialize previous scanlines changing elements, and + // initialize current scanline's changing elements array + temp = prevChangingElems; + prevChangingElems = currChangingElems; + currChangingElems = temp; + currIndex = 0; + + // a0 has to be set just before the start of this scanline. + a0 = -1; + isWhite = true; + bitOffset = 0; + + lastChangingElement = 0; + + while (bitOffset < w) { + // Get the next changing element + getNextChangingElement(a0, isWhite, b); + + b1 = b[0]; + b2 = b[1]; + + // Get the next seven bits + entry = nextLesserThan8Bits(7); + + // Run these through the 2DCodes table + entry = (twoDCodes[entry] & 0xff); + + // Get the code and the number of bits used up + code = (entry & 0x78) >>> 3; + bits = entry & 0x07; + + if (code == 0) { + if (!isWhite) { + setToBlack(bitOffset, b2 - bitOffset); + } + bitOffset = a0 = b2; + + // Set pointer to consume the correct number of bits. + updatePointer(7 - bits); + } else if (code == 1) { + // Horizontal + updatePointer(7 - bits); + + // identify the next 2 codes. + int number; + if (isWhite) { + number = decodeWhiteCodeWord(); + bitOffset += number; + currChangingElems[currIndex++] = bitOffset; + + number = decodeBlackCodeWord(); + setToBlack(bitOffset, number); + bitOffset += number; + currChangingElems[currIndex++] = bitOffset; + } else { + number = decodeBlackCodeWord(); + setToBlack(bitOffset, number); + bitOffset += number; + currChangingElems[currIndex++] = bitOffset; + + number = decodeWhiteCodeWord(); + bitOffset += number; + currChangingElems[currIndex++] = bitOffset; + } + + a0 = bitOffset; + } else if (code <= 8) { + // Vertical + a1 = b1 + (code - 5); + + currChangingElems[currIndex++] = a1; + + // We write the current color till a1 - 1 pos, + // since a1 is where the next color starts + if (!isWhite) { + setToBlack(bitOffset, a1 - bitOffset); + } + bitOffset = a0 = a1; + isWhite = !isWhite; + + updatePointer(7 - bits); + } else { + warning("Unknown coding mode encountered at line "+ + (srcMinY+lines)+": read "+bitOffset+" of "+w+ + " expected pixels."); + + // Find the next one-dimensionally encoded line. + int numLinesTested = 0; + while(modeFlag != 1) { + try { + modeFlag = findNextLine(); + numLinesTested++; + } catch(EOFException eofe) { + warning("Sync loss at line "+ + (srcMinY+lines)+": read "+ + lines+" of "+height+" lines."); + return; + } + } + lines += numLinesTested - 1; + updatePointer(13); + break; + } + } + + // Add the changing element beyond the current scanline for the + // other color too + currChangingElems[currIndex++] = bitOffset; + changingElemSize = currIndex; + } else { // modeFlag == 1 + // 1D encoded scanline follows + decodeNextScanline(srcMinY+lines); + } + + lineBitNum += bitsPerScanline; + lines++; + } // while(lines < height) + } + + public synchronized void decodeT6() throws IIOException { + int height = h; + + int bufferOffset = 0; + + int a0, a1, b1, b2; + int entry, code, bits; + byte color; + boolean isWhite; + int currIndex; + int temp[]; + + // Return values from getNextChangingElement + int[] b = new int[2]; + + // uncompressedMode - have written some code for this, but this + // has not been tested due to lack of test images using this optional + // extension. This code is when code == 11. aastha 03/03/1999 + + // Local cached reference + int[] cce = currChangingElems; + + // Assume invisible preceding row of all white pixels and insert + // both black and white changing elements beyond the end of this + // imaginary scanline. + changingElemSize = 0; + cce[changingElemSize++] = w; + cce[changingElemSize++] = w; + + int bitOffset; + + for (int lines = 0; lines < height; lines++) { + // a0 has to be set just before the start of the scanline. + a0 = -1; + isWhite = true; + + // Assign the changing elements of the previous scanline to + // prevChangingElems and start putting this new scanline's + // changing elements into the currChangingElems. + temp = prevChangingElems; + prevChangingElems = currChangingElems; + cce = currChangingElems = temp; + currIndex = 0; + + // Start decoding the scanline + bitOffset = 0; + + // Reset search start position for getNextChangingElement + lastChangingElement = 0; + + // Till one whole scanline is decoded + while (bitOffset < w) { + // Get the next changing element + getNextChangingElement(a0, isWhite, b); + b1 = b[0]; + b2 = b[1]; + + // Get the next seven bits + entry = nextLesserThan8Bits(7); + // Run these through the 2DCodes table + entry = (twoDCodes[entry] & 0xff); + + // Get the code and the number of bits used up + code = (entry & 0x78) >>> 3; + bits = entry & 0x07; + + if (code == 0) { // Pass + // We always assume WhiteIsZero format for fax. + if (!isWhite) { + if(b2 > w) { + b2 = w; + warning("Decoded row "+(srcMinY+lines)+ + " too long; ignoring extra samples."); + } + setToBlack(bitOffset, b2 - bitOffset); + } + bitOffset = a0 = b2; + + // Set pointer to only consume the correct number of bits. + updatePointer(7 - bits); + } else if (code == 1) { // Horizontal + // Set pointer to only consume the correct number of bits. + updatePointer(7 - bits); + + // identify the next 2 alternating color codes. + int number; + if (isWhite) { + // Following are white and black runs + number = decodeWhiteCodeWord(); + bitOffset += number; + cce[currIndex++] = bitOffset; + + number = decodeBlackCodeWord(); + if(number > w - bitOffset) { + number = w - bitOffset; + warning("Decoded row "+(srcMinY+lines)+ + " too long; ignoring extra samples."); + } + setToBlack(bitOffset, number); + bitOffset += number; + cce[currIndex++] = bitOffset; + } else { + // First a black run and then a white run follows + number = decodeBlackCodeWord(); + if(number > w - bitOffset) { + number = w - bitOffset; + warning("Decoded row "+(srcMinY+lines)+ + " too long; ignoring extra samples."); + } + setToBlack(bitOffset, number); + bitOffset += number; + cce[currIndex++] = bitOffset; + + number = decodeWhiteCodeWord(); + bitOffset += number; + cce[currIndex++] = bitOffset; + } + + a0 = bitOffset; + } else if (code <= 8) { // Vertical + a1 = b1 + (code - 5); + cce[currIndex++] = a1; + + // We write the current color till a1 - 1 pos, + // since a1 is where the next color starts + if (!isWhite) { + if(a1 > w) { + a1 = w; + warning("Decoded row "+(srcMinY+lines)+ + " too long; ignoring extra samples."); + } + setToBlack(bitOffset, a1 - bitOffset); + } + bitOffset = a0 = a1; + isWhite = !isWhite; + + updatePointer(7 - bits); + } else if (code == 11) { + int entranceCode = nextLesserThan8Bits(3); + if (entranceCode != 7) { + String msg = + "Unsupported entrance code "+entranceCode+ + " for extension mode at line "+(srcMinY+lines)+"."; + warning(msg); + } + + int zeros = 0; + boolean exit = false; + + while (!exit) { + while (nextLesserThan8Bits(1) != 1) { + zeros++; + } + + if (zeros > 5) { + // Exit code + + // Zeros before exit code + zeros = zeros - 6; + + if (!isWhite && (zeros > 0)) { + cce[currIndex++] = bitOffset; + } + + // Zeros before the exit code + bitOffset += zeros; + if (zeros > 0) { + // Some zeros have been written + isWhite = true; + } + + // Read in the bit which specifies the color of + // the following run + if (nextLesserThan8Bits(1) == 0) { + if (!isWhite) { + cce[currIndex++] = bitOffset; + } + isWhite = true; + } else { + if (isWhite) { + cce[currIndex++] = bitOffset; + } + isWhite = false; + } + + exit = true; + } + + if (zeros == 5) { + if (!isWhite) { + cce[currIndex++] = bitOffset; + } + bitOffset += zeros; + + // Last thing written was white + isWhite = true; + } else { + bitOffset += zeros; + + cce[currIndex++] = bitOffset; + setToBlack(bitOffset, 1); + ++bitOffset; + + // Last thing written was black + isWhite = false; + } + + } + } else { + String msg = + "Unknown coding mode encountered at line "+ + (srcMinY+lines)+"."; + warning(msg); + } + } // while bitOffset < w + + // Add the changing element beyond the current scanline for the + // other color too, if not already added previously + if (currIndex <= w) + cce[currIndex++] = bitOffset; + + // Number of changing elements in this scanline. + changingElemSize = currIndex; + + lineBitNum += bitsPerScanline; + } // for lines < height + } + + private void setToBlack(int bitNum, int numBits) { + // bitNum is relative to current scanline so bump it by lineBitNum + bitNum += lineBitNum; + + int lastBit = bitNum + numBits; + int byteNum = bitNum >> 3; + + // Handle bits in first byte + int shift = bitNum & 0x7; + if (shift > 0) { + int maskVal = 1 << (7 - shift); + byte val = buffer[byteNum]; + while (maskVal > 0 && bitNum < lastBit) { + val |= maskVal; + maskVal >>= 1; + ++bitNum; + } + buffer[byteNum] = val; + } + + // Fill in 8 bits at a time + byteNum = bitNum >> 3; + while (bitNum < lastBit - 7) { + buffer[byteNum++] = (byte)255; + bitNum += 8; + } + + // Fill in remaining bits + while (bitNum < lastBit) { + byteNum = bitNum >> 3; + buffer[byteNum] |= 1 << (7 - (bitNum & 0x7)); + ++bitNum; + } + } + + // Returns run length + private int decodeWhiteCodeWord() throws IIOException { + int current, entry, bits, isT, twoBits, code = -1; + int runLength = 0; + boolean isWhite = true; + + while (isWhite) { + current = nextNBits(10); + entry = white[current]; + + // Get the 3 fields from the entry + isT = entry & 0x0001; + bits = (entry >>> 1) & 0x0f; + + if (bits == 12) { // Additional Make up code + // Get the next 2 bits + twoBits = nextLesserThan8Bits(2); + // Consolidate the 2 new bits and last 2 bits into 4 bits + current = ((current << 2) & 0x000c) | twoBits; + entry = additionalMakeup[current]; + bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111 + code = (entry >>> 4) & 0x0fff; // 12 bits + runLength += code; + updatePointer(4 - bits); + } else if (bits == 0) { // ERROR + throw new IIOException("Error 0"); + } else if (bits == 15) { // EOL + throw new IIOException("Error 1"); + } else { + // 11 bits - 0000 0111 1111 1111 = 0x07ff + code = (entry >>> 5) & 0x07ff; + runLength += code; + updatePointer(10 - bits); + if (isT == 0) { + isWhite = false; + } + } + } + + return runLength; + } + + // Returns run length + private int decodeBlackCodeWord() throws IIOException { + int current, entry, bits, isT, twoBits, code = -1; + int runLength = 0; + boolean isWhite = false; + + while (!isWhite) { + current = nextLesserThan8Bits(4); + entry = initBlack[current]; + + // Get the 3 fields from the entry + isT = entry & 0x0001; + bits = (entry >>> 1) & 0x000f; + code = (entry >>> 5) & 0x07ff; + + if (code == 100) { + current = nextNBits(9); + entry = black[current]; + + // Get the 3 fields from the entry + isT = entry & 0x0001; + bits = (entry >>> 1) & 0x000f; + code = (entry >>> 5) & 0x07ff; + + if (bits == 12) { + // Additional makeup codes + updatePointer(5); + current = nextLesserThan8Bits(4); + entry = additionalMakeup[current]; + bits = (entry >>> 1) & 0x07; // 3 bits 0000 0111 + code = (entry >>> 4) & 0x0fff; // 12 bits + runLength += code; + + updatePointer(4 - bits); + } else if (bits == 15) { + // EOL code + throw new IIOException("Error 2"); + } else { + runLength += code; + updatePointer(9 - bits); + if (isT == 0) { + isWhite = true; + } + } + } else if (code == 200) { + // Is a Terminating code + current = nextLesserThan8Bits(2); + entry = twoBitBlack[current]; + code = (entry >>> 5) & 0x07ff; + runLength += code; + bits = (entry >>> 1) & 0x0f; + updatePointer(2 - bits); + isWhite = true; + } else { + // Is a Terminating code + runLength += code; + updatePointer(4 - bits); + isWhite = true; + } + } + + return runLength; + } + + private int findNextLine() throws IIOException, EOFException { + // Set maximum and current bit index into the compressed data. + int bitIndexMax = data.length*8 - 1; + int bitIndexMax12 = bitIndexMax - 12; + int bitIndex = bytePointer*8 + bitPointer; + + // Loop while at least 12 bits are available. + while(bitIndex <= bitIndexMax12) { + // Get the next 12 bits. + int next12Bits = nextNBits(12); + bitIndex += 12; + + // Loop while the 12 bits are not unity, i.e., while the EOL + // has not been reached, and there is at least one bit left. + while(next12Bits != 1 && bitIndex < bitIndexMax) { + next12Bits = + ((next12Bits & 0x000007ff) << 1) | + (nextLesserThan8Bits(1) & 0x00000001); + bitIndex++; + } + + if(next12Bits == 1) { // now positioned just after EOL + if(oneD == 1) { // two-dimensional coding + if(bitIndex < bitIndexMax) { + // check next bit against type of line being sought + return nextLesserThan8Bits(1); + } + } else { + return 1; + } + } + } + + // EOL not found. + throw new EOFException(); + } + + private void getNextChangingElement(int a0, boolean isWhite, int[] ret) throws IIOException { + // Local copies of instance variables + int[] pce = this.prevChangingElems; + int ces = this.changingElemSize; + + // If the previous match was at an odd element, we still + // have to search the preceeding element. + // int start = lastChangingElement & ~0x1; + int start = lastChangingElement > 0 ? lastChangingElement - 1 : 0; + if (isWhite) { + start &= ~0x1; // Search even numbered elements + } else { + start |= 0x1; // Search odd numbered elements + } + + int i = start; + for (; i < ces; i += 2) { + int temp = pce[i]; + if (temp > a0) { + lastChangingElement = i; + ret[0] = temp; + break; + } + } + + if (i + 1 < ces) { + ret[1] = pce[i + 1]; + } + } + + private int nextNBits(int bitsToGet) throws IIOException { + byte b, next, next2next; + int l = data.length - 1; + int bp = this.bytePointer; + + if (fillOrder == 1) { + b = data[bp]; + + if (bp == l) { + next = 0x00; + next2next = 0x00; + } else if ((bp + 1) == l) { + next = data[bp + 1]; + next2next = 0x00; + } else { + next = data[bp + 1]; + next2next = data[bp + 2]; + } + } else if (fillOrder == 2) { + b = flipTable[data[bp] & 0xff]; + + if (bp == l) { + next = 0x00; + next2next = 0x00; + } else if ((bp + 1) == l) { + next = flipTable[data[bp + 1] & 0xff]; + next2next = 0x00; + } else { + next = flipTable[data[bp + 1] & 0xff]; + next2next = flipTable[data[bp + 2] & 0xff]; + } + } else { + throw new IIOException("Invalid FillOrder"); + } + + int bitsLeft = 8 - bitPointer; + int bitsFromNextByte = bitsToGet - bitsLeft; + int bitsFromNext2NextByte = 0; + if (bitsFromNextByte > 8) { + bitsFromNext2NextByte = bitsFromNextByte - 8; + bitsFromNextByte = 8; + } + + bytePointer++; + + int i1 = (b & table1[bitsLeft]) << (bitsToGet - bitsLeft); + int i2 = (next & table2[bitsFromNextByte]) >>> (8 - bitsFromNextByte); + + int i3 = 0; + if (bitsFromNext2NextByte != 0) { + i2 <<= bitsFromNext2NextByte; + i3 = (next2next & table2[bitsFromNext2NextByte]) >>> + (8 - bitsFromNext2NextByte); + i2 |= i3; + bytePointer++; + bitPointer = bitsFromNext2NextByte; + } else { + if (bitsFromNextByte == 8) { + bitPointer = 0; + bytePointer++; + } else { + bitPointer = bitsFromNextByte; + } + } + + int i = i1 | i2; + return i; + } + + private int nextLesserThan8Bits(int bitsToGet) throws IIOException { + byte b, next; + int l = data.length - 1; + int bp = this.bytePointer; + + if (fillOrder == 1) { + b = data[bp]; + if (bp == l) { + next = 0x00; + } else { + next = data[bp + 1]; + } + } else if (fillOrder == 2) { + b = flipTable[data[bp] & 0xff]; + if (bp == l) { + next = 0x00; + } else { + next = flipTable[data[bp + 1] & 0xff]; + } + } else { + throw new IIOException("Invalid FillOrder"); + } + + int bitsLeft = 8 - bitPointer; + int bitsFromNextByte = bitsToGet - bitsLeft; + + int shift = bitsLeft - bitsToGet; + int i1, i2; + if (shift >= 0) { + i1 = (b & table1[bitsLeft]) >>> shift; + bitPointer += bitsToGet; + if (bitPointer == 8) { + bitPointer = 0; + bytePointer++; + } + } else { + i1 = (b & table1[bitsLeft]) << (-shift); + i2 = (next & table2[bitsFromNextByte]) >>> (8 - bitsFromNextByte); + + i1 |= i2; + bytePointer++; + bitPointer = bitsFromNextByte; + } + + return i1; + } + + // Move pointer backwards by given amount of bits + private void updatePointer(int bitsToMoveBack) { + if (bitsToMoveBack > 8) { + bytePointer -= bitsToMoveBack/8; + bitsToMoveBack %= 8; + } + + int i = bitPointer - bitsToMoveBack; + if (i < 0) { + bytePointer--; + bitPointer = 8 + i; + } else { + bitPointer = i; + } + } + + // Forward warning message to reader + private void warning(String msg) { + if(this.reader instanceof TIFFImageReader) { + ((TIFFImageReader)reader).forwardWarningMessage(msg); + } + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFieldNode.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFieldNode.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.util.Arrays; +import java.util.List; +import javax.imageio.metadata.IIOMetadataNode; +import org.w3c.dom.Node; +import javax.imageio.plugins.tiff.TIFFDirectory; +import javax.imageio.plugins.tiff.TIFFField; +import javax.imageio.plugins.tiff.TIFFTag; +import javax.imageio.plugins.tiff.TIFFTagSet; + +/** + * The Node representation of a TIFFField + * wherein the child node is procedural rather than buffered. + */ +public class TIFFFieldNode extends IIOMetadataNode { + private static String getNodeName(TIFFField f) { + return f.getData() instanceof TIFFDirectory ? + "TIFFIFD" : "TIFFField"; + } + + private boolean isIFD; + + private Boolean isInitialized = Boolean.FALSE; + + private TIFFField field; + + public TIFFFieldNode(TIFFField field) { + super(getNodeName(field)); + + isIFD = field.getData() instanceof TIFFDirectory; + + this.field = field; + + TIFFTag tag = field.getTag(); + int tagNumber = tag.getNumber(); + String tagName = tag.getName(); + + if(isIFD) { + if(tagNumber != 0) { + setAttribute("parentTagNumber", Integer.toString(tagNumber)); + } + if(tagName != null) { + setAttribute("parentTagName", tagName); + } + + TIFFDirectory dir = (TIFFDirectory)field.getData(); + TIFFTagSet[] tagSets = dir.getTagSets(); + if(tagSets != null) { + StringBuilder tagSetNames = new StringBuilder(); + for(int i = 0; i < tagSets.length; i++) { + tagSetNames.append(tagSets[i].getClass().getName()); + if(i != tagSets.length - 1) { + tagSetNames.append(","); + } + } + setAttribute("tagSets", tagSetNames.toString()); + } + } else { + setAttribute("number", Integer.toString(tagNumber)); + setAttribute("name", tagName); + } + } + + private synchronized void initialize() { + if(isInitialized) return; + + if(isIFD) { + TIFFDirectory dir = (TIFFDirectory)field.getData(); + TIFFField[] fields = dir.getTIFFFields(); + if(fields != null) { + TIFFTagSet[] tagSets = dir.getTagSets(); + List tagSetList = Arrays.asList(tagSets); + int numFields = fields.length; + for(int i = 0; i < numFields; i++) { + TIFFField f = fields[i]; + int tagNumber = f.getTagNumber(); + TIFFTag tag = TIFFIFD.getTag(tagNumber, tagSetList); + + Node node = f.getAsNativeNode(); + + if (node != null) { + appendChild(node); + } + } + } + } else { + IIOMetadataNode child; + int count = field.getCount(); + if (field.getType() == TIFFTag.TIFF_UNDEFINED) { + child = new IIOMetadataNode("TIFFUndefined"); + + byte[] data = field.getAsBytes(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < count; i++) { + sb.append(Integer.toString(data[i] & 0xff)); + if (i < count - 1) { + sb.append(","); + } + } + child.setAttribute("value", sb.toString()); + } else { + child = new IIOMetadataNode("TIFF" + + TIFFField.getTypeName(field.getType()) + + "s"); + + TIFFTag tag = field.getTag(); + + for (int i = 0; i < count; i++) { + IIOMetadataNode cchild = + new IIOMetadataNode("TIFF" + + TIFFField.getTypeName(field.getType())); + + cchild.setAttribute("value", field.getValueAsString(i)); + if (tag.hasValueNames() && field.isIntegral()) { + int value = field.getAsInt(i); + String name = tag.getValueName(value); + if (name != null) { + cchild.setAttribute("description", name); + } + } + + child.appendChild(cchild); + } + } + appendChild(child); + } + + isInitialized = Boolean.TRUE; + } + + // Need to override this method to avoid a stack overflow exception + // which will occur if super.appendChild is called from initialize(). + public Node appendChild(Node newChild) { + if (newChild == null) { + throw new NullPointerException("newChild == null!"); + } + + return super.insertBefore(newChild, null); + } + + // Override all methods which refer to child nodes. + + public boolean hasChildNodes() { + initialize(); + return super.hasChildNodes(); + } + + public int getLength() { + initialize(); + return super.getLength(); + } + + public Node getFirstChild() { + initialize(); + return super.getFirstChild(); + } + + public Node getLastChild() { + initialize(); + return super.getLastChild(); + } + + public Node getPreviousSibling() { + initialize(); + return super.getPreviousSibling(); + } + + public Node getNextSibling() { + initialize(); + return super.getNextSibling(); + } + + public Node insertBefore(Node newChild, + Node refChild) { + initialize(); + return super.insertBefore(newChild, refChild); + } + + public Node replaceChild(Node newChild, + Node oldChild) { + initialize(); + return super.replaceChild(newChild, oldChild); + } + + public Node removeChild(Node oldChild) { + initialize(); + return super.removeChild(oldChild); + } + + public Node cloneNode(boolean deep) { + initialize(); + return super.cloneNode(deep); + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFIFD.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFIFD.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,837 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.io.EOFException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import javax.imageio.IIOException; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.plugins.tiff.TIFFDirectory; +import javax.imageio.plugins.tiff.TIFFField; +import javax.imageio.plugins.tiff.TIFFTag; +import javax.imageio.plugins.tiff.TIFFTagSet; + +public class TIFFIFD extends TIFFDirectory { + private static final long MAX_SAMPLES_PER_PIXEL = 0xffff; + private static final long MAX_ASCII_SIZE = 0xffff; + + private long stripOrTileByteCountsPosition = -1; + private long stripOrTileOffsetsPosition = -1; + private long lastPosition = -1; + + public static TIFFTag getTag(int tagNumber, List tagSets) { + Iterator iter = tagSets.iterator(); + while (iter.hasNext()) { + TIFFTagSet tagSet = iter.next(); + TIFFTag tag = tagSet.getTag(tagNumber); + if (tag != null) { + return tag; + } + } + + return null; + } + + public static TIFFTag getTag(String tagName, List tagSets) { + Iterator iter = tagSets.iterator(); + while (iter.hasNext()) { + TIFFTagSet tagSet = iter.next(); + TIFFTag tag = tagSet.getTag(tagName); + if (tag != null) { + return tag; + } + } + + return null; + } + + private static void writeTIFFFieldToStream(TIFFField field, + ImageOutputStream stream) + throws IOException { + int count = field.getCount(); + Object data = field.getData(); + + switch (field.getType()) { + case TIFFTag.TIFF_ASCII: + for (int i = 0; i < count; i++) { + String s = ((String[])data)[i]; + int length = s.length(); + for (int j = 0; j < length; j++) { + stream.writeByte(s.charAt(j) & 0xff); + } + stream.writeByte(0); + } + break; + case TIFFTag.TIFF_UNDEFINED: + case TIFFTag.TIFF_BYTE: + case TIFFTag.TIFF_SBYTE: + stream.write((byte[])data); + break; + case TIFFTag.TIFF_SHORT: + stream.writeChars((char[])data, 0, ((char[])data).length); + break; + case TIFFTag.TIFF_SSHORT: + stream.writeShorts((short[])data, 0, ((short[])data).length); + break; + case TIFFTag.TIFF_SLONG: + stream.writeInts((int[])data, 0, ((int[])data).length); + break; + case TIFFTag.TIFF_LONG: + for (int i = 0; i < count; i++) { + stream.writeInt((int)(((long[])data)[i])); + } + break; + case TIFFTag.TIFF_IFD_POINTER: + stream.writeInt(0); // will need to be backpatched + break; + case TIFFTag.TIFF_FLOAT: + stream.writeFloats((float[])data, 0, ((float[])data).length); + break; + case TIFFTag.TIFF_DOUBLE: + stream.writeDoubles((double[])data, 0, ((double[])data).length); + break; + case TIFFTag.TIFF_SRATIONAL: + for (int i = 0; i < count; i++) { + stream.writeInt(((int[][])data)[i][0]); + stream.writeInt(((int[][])data)[i][1]); + } + break; + case TIFFTag.TIFF_RATIONAL: + for (int i = 0; i < count; i++) { + long num = ((long[][])data)[i][0]; + long den = ((long[][])data)[i][1]; + stream.writeInt((int)num); + stream.writeInt((int)den); + } + break; + default: + // error + } + } + + public TIFFIFD(List tagSets, TIFFTag parentTag) { + super(tagSets.toArray(new TIFFTagSet[tagSets.size()]), + parentTag); + } + + public TIFFIFD(List tagSets) { + this(tagSets, null); + } + + public List getTagSetList() { + return Arrays.asList(getTagSets()); + } + + /** + * Returns an Iterator over the TIFF fields. The + * traversal is in the order of increasing tag number. + */ + // Note: the sort is guaranteed for low fields by the use of an + // array wherein the index corresponds to the tag number and for + // the high fields by the use of a TreeMap with tag number keys. + public Iterator iterator() { + return Arrays.asList(getTIFFFields()).iterator(); + } + + /** + * Read the value of a field. The data parameter should be + * an array of length 1 of Object. + * + * @param stream the input stream + * @param type the type as read from the stream + * @param count the count read from the stream + * @param data a container for the data + * @return the updated count + * @throws IOException + */ + private static int readFieldValue(ImageInputStream stream, + int type, int count, Object[] data) throws IOException { + Object obj; + + switch (type) { + case TIFFTag.TIFF_BYTE: + case TIFFTag.TIFF_SBYTE: + case TIFFTag.TIFF_UNDEFINED: + case TIFFTag.TIFF_ASCII: + byte[] bvalues = new byte[count]; + stream.readFully(bvalues, 0, count); + + if (type == TIFFTag.TIFF_ASCII) { + // Can be multiple strings + ArrayList v = new ArrayList<>(); + boolean inString = false; + int prevIndex = 0; + for (int index = 0; index <= count; index++) { + if (index < count && bvalues[index] != 0) { + if (!inString) { + // start of string + prevIndex = index; + inString = true; + } + } else { // null or special case at end of string + if (inString) { + // end of string + String s = new String(bvalues, prevIndex, + index - prevIndex, + StandardCharsets.US_ASCII); + v.add(s); + inString = false; + } + } + } + + count = v.size(); + String[] strings; + if (count != 0) { + strings = new String[count]; + for (int c = 0; c < count; c++) { + strings[c] = v.get(c); + } + } else { + // This case has been observed when the value of + // 'count' recorded in the field is non-zero but + // the value portion contains all nulls. + count = 1; + strings = new String[]{""}; + } + + obj = strings; + } else { + obj = bvalues; + } + break; + + case TIFFTag.TIFF_SHORT: + char[] cvalues = new char[count]; + for (int j = 0; j < count; j++) { + cvalues[j] = (char) (stream.readUnsignedShort()); + } + obj = cvalues; + break; + + case TIFFTag.TIFF_LONG: + case TIFFTag.TIFF_IFD_POINTER: + long[] lvalues = new long[count]; + for (int j = 0; j < count; j++) { + lvalues[j] = stream.readUnsignedInt(); + } + obj = lvalues; + break; + + case TIFFTag.TIFF_RATIONAL: + long[][] llvalues = new long[count][2]; + for (int j = 0; j < count; j++) { + llvalues[j][0] = stream.readUnsignedInt(); + llvalues[j][1] = stream.readUnsignedInt(); + } + obj = llvalues; + break; + + case TIFFTag.TIFF_SSHORT: + short[] svalues = new short[count]; + for (int j = 0; j < count; j++) { + svalues[j] = stream.readShort(); + } + obj = svalues; + break; + + case TIFFTag.TIFF_SLONG: + int[] ivalues = new int[count]; + for (int j = 0; j < count; j++) { + ivalues[j] = stream.readInt(); + } + obj = ivalues; + break; + + case TIFFTag.TIFF_SRATIONAL: + int[][] iivalues = new int[count][2]; + for (int j = 0; j < count; j++) { + iivalues[j][0] = stream.readInt(); + iivalues[j][1] = stream.readInt(); + } + obj = iivalues; + break; + + case TIFFTag.TIFF_FLOAT: + float[] fvalues = new float[count]; + for (int j = 0; j < count; j++) { + fvalues[j] = stream.readFloat(); + } + obj = fvalues; + break; + + case TIFFTag.TIFF_DOUBLE: + double[] dvalues = new double[count]; + for (int j = 0; j < count; j++) { + dvalues[j] = stream.readDouble(); + } + obj = dvalues; + break; + + default: + obj = null; + break; + } + + data[0] = obj; + + return count; + } + + // + // Class to represent an IFD entry where the actual content is at an offset + // in the stream somewhere outside the IFD itself. This occurs when the + // value cannot be contained within four bytes. Seeking is required to read + // such field values. + // + private static class TIFFIFDEntry { + public final TIFFTag tag; + public final int type; + public final int count; + public final long offset; + + TIFFIFDEntry(TIFFTag tag, int type, int count, long offset) { + this.tag = tag; + this.type = type; + this.count = count; + this.offset = offset; + } + } + + // + // Verify that data pointed to outside of the IFD itself are within the + // stream. To be called after all fields have been read and populated. + // + private void checkFieldOffsets(long streamLength) throws IIOException { + if (streamLength < 0) { + return; + } + + // StripOffsets + List offsets = new ArrayList<>(); + TIFFField f = getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS); + int count = 0; + if (f != null) { + count = f.getCount(); + offsets.add(f); + } + + // TileOffsets + f = getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS); + if (f != null) { + int sz = offsets.size(); + int newCount = f.getCount(); + if (sz > 0 && newCount != count) { + throw new IIOException + ("StripOffsets count != TileOffsets count"); + } + + if (sz == 0) { + count = newCount; + } + offsets.add(f); + } + + if (offsets.size() > 0) { + // StripByteCounts + List byteCounts = new ArrayList<>(); + f = getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS); + if (f != null) { + if (f.getCount() != count) { + throw new IIOException + ("StripByteCounts count != number of offsets"); + } + byteCounts.add(f); + } + + // TileByteCounts + f = getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS); + if (f != null) { + if (f.getCount() != count) { + throw new IIOException + ("TileByteCounts count != number of offsets"); + } + byteCounts.add(f); + } + + if (byteCounts.size() > 0) { + for (TIFFField offset : offsets) { + for (TIFFField byteCount : byteCounts) { + for (int i = 0; i < count; i++) { + long dataOffset = offset.getAsLong(i); + long dataByteCount = byteCount.getAsLong(i); + if (dataOffset + dataByteCount > streamLength) { + throw new IIOException + ("Data segment out of stream"); + } + } + } + } + } + } + + // JPEGInterchangeFormat and JPEGInterchangeFormatLength + TIFFField jpegOffset = + getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT); + if (jpegOffset != null) { + TIFFField jpegLength = + getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); + if (jpegLength != null) { + if (jpegOffset.getAsLong(0) + jpegLength.getAsLong(0) + > streamLength) { + throw new IIOException + ("JPEGInterchangeFormat data out of stream"); + } + } + } + + // JPEGQTables - one 64-byte table for each offset. + f = getTIFFField(BaselineTIFFTagSet.TAG_JPEG_Q_TABLES); + if (f != null) { + long[] tableOffsets = f.getAsLongs(); + for (long off : tableOffsets) { + if (off + 64 > streamLength) { + throw new IIOException("JPEGQTables data out of stream"); + } + } + } + + // JPEGDCTables + f = getTIFFField(BaselineTIFFTagSet.TAG_JPEG_DC_TABLES); + if (f != null) { + long[] tableOffsets = f.getAsLongs(); + for (long off : tableOffsets) { + if (off + 16 > streamLength) { + throw new IIOException("JPEGDCTables data out of stream"); + } + } + } + + // JPEGACTables + f = getTIFFField(BaselineTIFFTagSet.TAG_JPEG_AC_TABLES); + if (f != null) { + long[] tableOffsets = f.getAsLongs(); + for (long off : tableOffsets) { + if (off + 16 > streamLength) { + throw new IIOException("JPEGACTables data out of stream"); + } + } + } + } + + // Stream position initially at beginning, left at end + // if ignoreUnknownFields is true, do not load fields for which + // a tag cannot be found in an allowed TagSet. + public void initialize(ImageInputStream stream, boolean isPrimaryIFD, + boolean ignoreUnknownFields) throws IOException { + + removeTIFFFields(); + + long streamLength = stream.length(); + boolean haveStreamLength = streamLength != -1; + + List tagSetList = getTagSetList(); + + List entries = new ArrayList<>(); + Object[] entryData = new Object[1]; // allocate once for later reuse. + + // Read the IFD entries, loading the field values which are no more than + // four bytes long, and storing the 4-byte offsets for the others. + int numEntries = stream.readUnsignedShort(); + for (int i = 0; i < numEntries; i++) { + // Read tag number, value type, and value count. + int tagNumber = stream.readUnsignedShort(); + int type = stream.readUnsignedShort(); + int count = (int)stream.readUnsignedInt(); + + // Get the associated TIFFTag. + TIFFTag tag = getTag(tagNumber, tagSetList); + + // Ignore unknown fields. + if((tag == null && ignoreUnknownFields) + || (tag != null && !tag.isDataTypeOK(type))) { + // Skip the value/offset so as to leave the stream + // position at the start of the next IFD entry. + stream.skipBytes(4); + + // Continue with the next IFD entry. + continue; + } + + if (tag == null) { + tag = new TIFFTag(TIFFTag.UNKNOWN_TAG_NAME, tagNumber, + 1 << type, count); + } else { + int expectedCount = tag.getCount(); + if (expectedCount > 0) { + // If the tag count is positive then the tag defines a + // specific, fixed count that the field must match. + if (count != expectedCount) { + throw new IIOException("Unexpected count " + + count + " for " + tag.getName() + " field"); + } + } else if (type == TIFFTag.TIFF_ASCII) { + // Clamp the size of ASCII fields of unspecified length + // to a maximum value. + int asciiSize = TIFFTag.getSizeOfType(TIFFTag.TIFF_ASCII); + if (count*asciiSize > MAX_ASCII_SIZE) { + count = (int)(MAX_ASCII_SIZE/asciiSize); + } + } + } + + int size = count*TIFFTag.getSizeOfType(type); + if (size > 4 || tag.isIFDPointer()) { + // The IFD entry value is a pointer to the actual field value. + long offset = stream.readUnsignedInt(); + + // Check whether the the field value is within the stream. + if (haveStreamLength && offset + size > streamLength) { + throw new IIOException("Field data is past end-of-stream"); + } + + // Add a TIFFIFDEntry as a placeholder. This avoids a mark, + // seek to the data, and a reset. + entries.add(new TIFFIFDEntry(tag, type, count, offset)); + } else { + // The IFD entry value is the actual field value of no more than + // four bytes. + Object obj = null; + try { + // Read the field value and update the count. + count = readFieldValue(stream, type, count, entryData); + obj = entryData[0]; + } catch (EOFException eofe) { + // The TIFF 6.0 fields have tag numbers less than or equal + // to 532 (ReferenceBlackWhite) or equal to 33432 (Copyright). + // If there is an error reading a baseline tag, then re-throw + // the exception and fail; otherwise continue with the next + // field. + if (BaselineTIFFTagSet.getInstance().getTag(tagNumber) == null) { + throw eofe; + } + } + + // If the field value is smaller than four bytes then skip + // the remaining, unused bytes. + if (size < 4) { + stream.skipBytes(4 - size); + } + + // Add the populated TIFFField to the list of entries. + entries.add(new TIFFField(tag, type, count, obj)); + } + } + + // After reading the IFD entries the stream is positioned at an unsigned + // four byte integer containing either the offset of the next IFD or + // zero if this is the last IFD. + long nextIFDOffset = stream.getStreamPosition(); + + Object[] fieldData = new Object[1]; + for (Object entry : entries) { + if (entry instanceof TIFFField) { + // Add the populated field directly. + addTIFFField((TIFFField)entry); + } else { + TIFFIFDEntry e = (TIFFIFDEntry)entry; + TIFFTag tag = e.tag; + int tagNumber = tag.getNumber(); + int type = e.type; + int count = e.count; + + stream.seek(e.offset); + + if (tag.isIFDPointer()) { + List tagSets = new ArrayList(1); + tagSets.add(tag.getTagSet()); + TIFFIFD subIFD = new TIFFIFD(tagSets); + + subIFD.initialize(stream, false, ignoreUnknownFields); + TIFFField f = new TIFFField(tag, type, e.offset, subIFD); + addTIFFField(f); + } else { + if (tagNumber == BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS + || tagNumber == BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS + || tagNumber == BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH) { + this.stripOrTileByteCountsPosition + = stream.getStreamPosition(); + } else if (tagNumber == BaselineTIFFTagSet.TAG_STRIP_OFFSETS + || tagNumber == BaselineTIFFTagSet.TAG_TILE_OFFSETS + || tagNumber == BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) { + this.stripOrTileOffsetsPosition + = stream.getStreamPosition(); + } + + Object obj = null; + try { + count = readFieldValue(stream, type, count, fieldData); + obj = fieldData[0]; + } catch (EOFException eofe) { + // The TIFF 6.0 fields have tag numbers less than or equal + // to 532 (ReferenceBlackWhite) or equal to 33432 (Copyright). + // If there is an error reading a baseline tag, then re-throw + // the exception and fail; otherwise continue with the next + // field. + if (BaselineTIFFTagSet.getInstance().getTag(tagNumber) == null) { + throw eofe; + } + } + + if (obj == null) { + continue; + } + + TIFFField f = new TIFFField(tag, type, count, obj); + addTIFFField(f); + } + } + } + + if(isPrimaryIFD && haveStreamLength) { + checkFieldOffsets(streamLength); + } + + stream.seek(nextIFDOffset); + this.lastPosition = stream.getStreamPosition(); + } + + public void writeToStream(ImageOutputStream stream) + throws IOException { + + int numFields = getNumTIFFFields(); + stream.writeShort(numFields); + + long nextSpace = stream.getStreamPosition() + 12*numFields + 4; + + Iterator iter = iterator(); + while (iter.hasNext()) { + TIFFField f = iter.next(); + + TIFFTag tag = f.getTag(); + + int type = f.getType(); + int count = f.getCount(); + + // Deal with unknown tags + if (type == 0) { + type = TIFFTag.TIFF_UNDEFINED; + } + int size = count*TIFFTag.getSizeOfType(type); + + if (type == TIFFTag.TIFF_ASCII) { + int chars = 0; + for (int i = 0; i < count; i++) { + chars += f.getAsString(i).length() + 1; + } + count = chars; + size = count; + } + + int tagNumber = f.getTagNumber(); + stream.writeShort(tagNumber); + stream.writeShort(type); + stream.writeInt(count); + + // Write a dummy value to fill space + stream.writeInt(0); + stream.mark(); // Mark beginning of next field + stream.skipBytes(-4); + + long pos; + + if (size > 4 || tag.isIFDPointer()) { + // Ensure IFD or value is written on a word boundary + nextSpace = (nextSpace + 3) & ~0x3; + + stream.writeInt((int)nextSpace); + stream.seek(nextSpace); + pos = nextSpace; + + if (tag.isIFDPointer() && f.hasDirectory()) { + TIFFIFD subIFD = (TIFFIFD)f.getDirectory(); + subIFD.writeToStream(stream); + nextSpace = subIFD.lastPosition; + } else { + writeTIFFFieldToStream(f, stream); + nextSpace = stream.getStreamPosition(); + } + } else { + pos = stream.getStreamPosition(); + writeTIFFFieldToStream(f, stream); + } + + // If we are writing the data for the + // StripByteCounts, TileByteCounts, StripOffsets, + // TileOffsets, JPEGInterchangeFormat, or + // JPEGInterchangeFormatLength fields, record the current stream + // position for backpatching + if (tagNumber == + BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS || + tagNumber == BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS || + tagNumber == BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH) { + this.stripOrTileByteCountsPosition = pos; + } else if (tagNumber == + BaselineTIFFTagSet.TAG_STRIP_OFFSETS || + tagNumber == + BaselineTIFFTagSet.TAG_TILE_OFFSETS || + tagNumber == + BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) { + this.stripOrTileOffsetsPosition = pos; + } + + stream.reset(); // Go to marked position of next field + } + + this.lastPosition = nextSpace; + } + + public long getStripOrTileByteCountsPosition() { + return stripOrTileByteCountsPosition; + } + + public long getStripOrTileOffsetsPosition() { + return stripOrTileOffsetsPosition; + } + + public long getLastPosition() { + return lastPosition; + } + + void setPositions(long stripOrTileOffsetsPosition, + long stripOrTileByteCountsPosition, + long lastPosition) { + this.stripOrTileOffsetsPosition = stripOrTileOffsetsPosition; + this.stripOrTileByteCountsPosition = stripOrTileByteCountsPosition; + this.lastPosition = lastPosition; + } + + /** + * Returns a TIFFIFD wherein all fields from the + * BaselineTIFFTagSet are copied by value and all other + * fields copied by reference. + */ + public TIFFIFD getShallowClone() { + // Get the baseline TagSet. + TIFFTagSet baselineTagSet = BaselineTIFFTagSet.getInstance(); + + // If the baseline TagSet is not included just return. + List tagSetList = getTagSetList(); + if(!tagSetList.contains(baselineTagSet)) { + return this; + } + + // Create a new object. + TIFFIFD shallowClone = new TIFFIFD(tagSetList, getParentTag()); + + // Get the tag numbers in the baseline set. + Set baselineTagNumbers = baselineTagSet.getTagNumbers(); + + // Iterate over the fields in this IFD. + Iterator fields = iterator(); + while(fields.hasNext()) { + // Get the next field. + TIFFField field = fields.next(); + + // Get its tag number. + Integer tagNumber = Integer.valueOf(field.getTagNumber()); + + // Branch based on membership in baseline set. + TIFFField fieldClone; + if(baselineTagNumbers.contains(tagNumber)) { + // Copy by value. + Object fieldData = field.getData(); + + int fieldType = field.getType(); + + try { + switch (fieldType) { + case TIFFTag.TIFF_BYTE: + case TIFFTag.TIFF_SBYTE: + case TIFFTag.TIFF_UNDEFINED: + fieldData = ((byte[])fieldData).clone(); + break; + case TIFFTag.TIFF_ASCII: + fieldData = ((String[])fieldData).clone(); + break; + case TIFFTag.TIFF_SHORT: + fieldData = ((char[])fieldData).clone(); + break; + case TIFFTag.TIFF_LONG: + case TIFFTag.TIFF_IFD_POINTER: + fieldData = ((long[])fieldData).clone(); + break; + case TIFFTag.TIFF_RATIONAL: + fieldData = ((long[][])fieldData).clone(); + break; + case TIFFTag.TIFF_SSHORT: + fieldData = ((short[])fieldData).clone(); + break; + case TIFFTag.TIFF_SLONG: + fieldData = ((int[])fieldData).clone(); + break; + case TIFFTag.TIFF_SRATIONAL: + fieldData = ((int[][])fieldData).clone(); + break; + case TIFFTag.TIFF_FLOAT: + fieldData = ((float[])fieldData).clone(); + break; + case TIFFTag.TIFF_DOUBLE: + fieldData = ((double[])fieldData).clone(); + break; + default: + // Shouldn't happen but do nothing ... + } + } catch(Exception e) { + // Ignore it and copy by reference ... + } + + fieldClone = new TIFFField(field.getTag(), fieldType, + field.getCount(), fieldData); + } else { + // Copy by reference. + fieldClone = field; + } + + // Add the field to the clone. + shallowClone.addTIFFField(fieldClone); + } + + // Set positions. + shallowClone.setPositions(stripOrTileOffsetsPosition, + stripOrTileByteCountsPosition, + lastPosition); + + return shallowClone; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadata.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadata.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,1630 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.stream.ImageInputStream; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.plugins.tiff.ExifParentTIFFTagSet; +import javax.imageio.plugins.tiff.TIFFField; +import javax.imageio.plugins.tiff.TIFFTag; +import javax.imageio.plugins.tiff.TIFFTagSet; + +public class TIFFImageMetadata extends IIOMetadata { + + // package scope + + public static final String NATIVE_METADATA_FORMAT_NAME = + "javax_imageio_tiff_image_1.0"; + + public static final String NATIVE_METADATA_FORMAT_CLASS_NAME = + "javax.imageio.plugins.tiff.TIFFImageMetadataFormat"; + + private List tagSets; + + TIFFIFD rootIFD; + + public TIFFImageMetadata(List tagSets) { + super(true, + NATIVE_METADATA_FORMAT_NAME, + NATIVE_METADATA_FORMAT_CLASS_NAME, + null, null); + + this.tagSets = tagSets; + this.rootIFD = new TIFFIFD(tagSets); + } + + public TIFFImageMetadata(TIFFIFD ifd) { + super(true, + NATIVE_METADATA_FORMAT_NAME, + NATIVE_METADATA_FORMAT_CLASS_NAME, + null, null); + this.tagSets = ifd.getTagSetList(); + this.rootIFD = ifd; + } + + public void initializeFromStream(ImageInputStream stream, + boolean ignoreUnknownFields) + throws IOException { + rootIFD.initialize(stream, true, ignoreUnknownFields); + } + + public void addShortOrLongField(int tagNumber, int value) { + TIFFField field = new TIFFField(rootIFD.getTag(tagNumber), value); + rootIFD.addTIFFField(field); + } + + public boolean isReadOnly() { + return false; + } + + private Node getIFDAsTree(TIFFIFD ifd, + String parentTagName, int parentTagNumber) { + IIOMetadataNode IFDRoot = new IIOMetadataNode("TIFFIFD"); + if (parentTagNumber != 0) { + IFDRoot.setAttribute("parentTagNumber", + Integer.toString(parentTagNumber)); + } + if (parentTagName != null) { + IFDRoot.setAttribute("parentTagName", parentTagName); + } + + List tagSets = ifd.getTagSetList(); + if (tagSets.size() > 0) { + Iterator iter = tagSets.iterator(); + StringBuilder tagSetNames = new StringBuilder(); + while (iter.hasNext()) { + TIFFTagSet tagSet = iter.next(); + tagSetNames.append(tagSet.getClass().getName()); + if (iter.hasNext()) { + tagSetNames.append(","); + } + } + + IFDRoot.setAttribute("tagSets", tagSetNames.toString()); + } + + Iterator iter = ifd.iterator(); + while (iter.hasNext()) { + TIFFField f = iter.next(); + int tagNumber = f.getTagNumber(); + TIFFTag tag = TIFFIFD.getTag(tagNumber, tagSets); + + Node node = null; + if (tag == null) { + node = f.getAsNativeNode(); + } else if (tag.isIFDPointer() && f.hasDirectory()) { + TIFFIFD subIFD = (TIFFIFD)f.getDirectory(); + + // Recurse + node = getIFDAsTree(subIFD, tag.getName(), tag.getNumber()); + } else { + node = f.getAsNativeNode(); + } + + if (node != null) { + IFDRoot.appendChild(node); + } + } + + return IFDRoot; + } + + public Node getAsTree(String formatName) { + if (formatName.equals(nativeMetadataFormatName)) { + return getNativeTree(); + } else if (formatName.equals + (IIOMetadataFormatImpl.standardMetadataFormatName)) { + return getStandardTree(); + } else { + throw new IllegalArgumentException("Not a recognized format!"); + } + } + + private Node getNativeTree() { + IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName); + + Node IFDNode = getIFDAsTree(rootIFD, null, 0); + root.appendChild(IFDNode); + + return root; + } + + private static final String[] colorSpaceNames = { + "GRAY", // WhiteIsZero + "GRAY", // BlackIsZero + "RGB", // RGB + "RGB", // PaletteColor + "GRAY", // TransparencyMask + "CMYK", // CMYK + "YCbCr", // YCbCr + "Lab", // CIELab + "Lab", // ICCLab + }; + + public IIOMetadataNode getStandardChromaNode() { + IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma"); + IIOMetadataNode node = null; // scratch node + + TIFFField f; + + // Set the PhotometricInterpretation and the palette color flag. + int photometricInterpretation = -1; + boolean isPaletteColor = false; + f = getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION); + if (f != null) { + photometricInterpretation = f.getAsInt(0); + + isPaletteColor = + photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR; + } + + // Determine the number of channels. + int numChannels = -1; + if(isPaletteColor) { + numChannels = 3; + } else { + f = getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL); + if (f != null) { + numChannels = f.getAsInt(0); + } else { // f == null + f = getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); + if(f != null) { + numChannels = f.getCount(); + } + } + } + + if(photometricInterpretation != -1) { + if (photometricInterpretation >= 0 && + photometricInterpretation < colorSpaceNames.length) { + node = new IIOMetadataNode("ColorSpaceType"); + String csName; + if(photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK && + numChannels == 3) { + csName = "CMY"; + } else { + csName = colorSpaceNames[photometricInterpretation]; + } + node.setAttribute("name", csName); + chroma_node.appendChild(node); + } + + node = new IIOMetadataNode("BlackIsZero"); + node.setAttribute("value", + (photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) + ? "FALSE" : "TRUE"); + chroma_node.appendChild(node); + } + + if(numChannels != -1) { + node = new IIOMetadataNode("NumChannels"); + node.setAttribute("value", Integer.toString(numChannels)); + chroma_node.appendChild(node); + } + + f = getTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP); + if (f != null) { + // NOTE: The presence of hasAlpha is vestigial: there is + // no way in TIFF to represent an alpha component in a palette + // color image. See bug 5086341. + boolean hasAlpha = false; + + node = new IIOMetadataNode("Palette"); + int len = f.getCount()/(hasAlpha ? 4 : 3); + for (int i = 0; i < len; i++) { + IIOMetadataNode entry = + new IIOMetadataNode("PaletteEntry"); + entry.setAttribute("index", Integer.toString(i)); + + int r = (f.getAsInt(i)*255)/65535; + int g = (f.getAsInt(len + i)*255)/65535; + int b = (f.getAsInt(2*len + i)*255)/65535; + + entry.setAttribute("red", Integer.toString(r)); + entry.setAttribute("green", Integer.toString(g)); + entry.setAttribute("blue", Integer.toString(b)); + if (hasAlpha) { + int alpha = 0; + entry.setAttribute("alpha", Integer.toString(alpha)); + } + node.appendChild(entry); + } + chroma_node.appendChild(node); + } + + return chroma_node; + } + + public IIOMetadataNode getStandardCompressionNode() { + IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); + IIOMetadataNode node = null; // scratch node + + TIFFField f; + + f = getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION); + if (f != null) { + String compressionTypeName = null; + int compression = f.getAsInt(0); + boolean isLossless = true; // obligate initialization. + if(compression == BaselineTIFFTagSet.COMPRESSION_NONE) { + compressionTypeName = "None"; + isLossless = true; + } else { + int[] compressionNumbers = TIFFImageWriter.compressionNumbers; + for(int i = 0; i < compressionNumbers.length; i++) { + if(compression == compressionNumbers[i]) { + compressionTypeName = + TIFFImageWriter.compressionTypes[i]; + isLossless = + TIFFImageWriter.isCompressionLossless[i]; + break; + } + } + } + + if (compressionTypeName != null) { + node = new IIOMetadataNode("CompressionTypeName"); + node.setAttribute("value", compressionTypeName); + compression_node.appendChild(node); + + node = new IIOMetadataNode("Lossless"); + node.setAttribute("value", isLossless ? "TRUE" : "FALSE"); + compression_node.appendChild(node); + } + } + + node = new IIOMetadataNode("NumProgressiveScans"); + node.setAttribute("value", "1"); + compression_node.appendChild(node); + + return compression_node; + } + + private String repeat(String s, int times) { + if (times == 1) { + return s; + } + StringBuffer sb = new StringBuffer((s.length() + 1)*times - 1); + sb.append(s); + for (int i = 1; i < times; i++) { + sb.append(" "); + sb.append(s); + } + return sb.toString(); + } + + public IIOMetadataNode getStandardDataNode() { + IIOMetadataNode data_node = new IIOMetadataNode("Data"); + IIOMetadataNode node = null; // scratch node + + TIFFField f; + + boolean isPaletteColor = false; + f = getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION); + if (f != null) { + isPaletteColor = + f.getAsInt(0) == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR; + } + + f = getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION); + String planarConfiguration = "PixelInterleaved"; + if (f != null && + f.getAsInt(0) == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) { + planarConfiguration = "PlaneInterleaved"; + } + + node = new IIOMetadataNode("PlanarConfiguration"); + node.setAttribute("value", planarConfiguration); + data_node.appendChild(node); + + f = getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION); + if (f != null) { + int photometricInterpretation = f.getAsInt(0); + String sampleFormat = "UnsignedIntegral"; + + if (photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR) { + sampleFormat = "Index"; + } else { + f = getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT); + if (f != null) { + int format = f.getAsInt(0); + if (format == + BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER) { + sampleFormat = "SignedIntegral"; + } else if (format == + BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER) { + sampleFormat = "UnsignedIntegral"; + } else if (format == + BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { + sampleFormat = "Real"; + } else { + sampleFormat = null; // don't know + } + } + } + if (sampleFormat != null) { + node = new IIOMetadataNode("SampleFormat"); + node.setAttribute("value", sampleFormat); + data_node.appendChild(node); + } + } + + f = getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); + int[] bitsPerSample = null; + if(f != null) { + bitsPerSample = f.getAsInts(); + } else { + f = getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION); + int compression = f != null ? + f.getAsInt(0) : BaselineTIFFTagSet.COMPRESSION_NONE; + if(getTIFFField(ExifParentTIFFTagSet.TAG_EXIF_IFD_POINTER) != + null || + compression == BaselineTIFFTagSet.COMPRESSION_JPEG || + compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG || + getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) != + null) { + f = getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION); + if(f != null && + (f.getAsInt(0) == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO || + f.getAsInt(0) == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO)) { + bitsPerSample = new int[] {8}; + } else { + bitsPerSample = new int[] {8, 8, 8}; + } + } else { + bitsPerSample = new int[] {1}; + } + } + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < bitsPerSample.length; i++) { + if (i > 0) { + sb.append(" "); + } + sb.append(Integer.toString(bitsPerSample[i])); + } + node = new IIOMetadataNode("BitsPerSample"); + if(isPaletteColor) { + node.setAttribute("value", repeat(sb.toString(), 3)); + } else { + node.setAttribute("value", sb.toString()); + } + data_node.appendChild(node); + + // SampleMSB + f = getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER); + int fillOrder = f != null ? + f.getAsInt(0) : BaselineTIFFTagSet.FILL_ORDER_LEFT_TO_RIGHT; + sb = new StringBuffer(); + for (int i = 0; i < bitsPerSample.length; i++) { + if (i > 0) { + sb.append(" "); + } + int maxBitIndex = bitsPerSample[i] == 1 ? + 7 : bitsPerSample[i] - 1; + int msb = + fillOrder == BaselineTIFFTagSet.FILL_ORDER_LEFT_TO_RIGHT ? + maxBitIndex : 0; + sb.append(Integer.toString(msb)); + } + node = new IIOMetadataNode("SampleMSB"); + if(isPaletteColor) { + node.setAttribute("value", repeat(sb.toString(), 3)); + } else { + node.setAttribute("value", sb.toString()); + } + data_node.appendChild(node); + + return data_node; + } + + private static final String[] orientationNames = { + null, + "Normal", + "FlipH", + "Rotate180", + "FlipV", + "FlipHRotate90", + "Rotate270", + "FlipVRotate90", + "Rotate90", + }; + + public IIOMetadataNode getStandardDimensionNode() { + IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); + IIOMetadataNode node = null; // scratch node + + TIFFField f; + + long[] xres = null; + long[] yres = null; + + f = getTIFFField(BaselineTIFFTagSet.TAG_X_RESOLUTION); + if (f != null) { + xres = f.getAsRational(0).clone(); + } + + f = getTIFFField(BaselineTIFFTagSet.TAG_Y_RESOLUTION); + if (f != null) { + yres = f.getAsRational(0).clone(); + } + + if (xres != null && yres != null) { + node = new IIOMetadataNode("PixelAspectRatio"); + + // Compute (1/xres)/(1/yres) + // (xres_denom/xres_num)/(yres_denom/yres_num) = + // (xres_denom/xres_num)*(yres_num/yres_denom) = + // (xres_denom*yres_num)/(xres_num*yres_denom) + float ratio = (float)((double)xres[1]*yres[0])/(xres[0]*yres[1]); + node.setAttribute("value", Float.toString(ratio)); + dimension_node.appendChild(node); + } + + if (xres != null || yres != null) { + // Get unit field. + f = getTIFFField(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT); + + // Set resolution unit. + int resolutionUnit = f != null ? + f.getAsInt(0) : BaselineTIFFTagSet.RESOLUTION_UNIT_INCH; + + // Have size if either centimeters or inches. + boolean gotPixelSize = + resolutionUnit != BaselineTIFFTagSet.RESOLUTION_UNIT_NONE; + + // Convert pixels/inch to pixels/centimeter. + if (resolutionUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_INCH) { + // Divide xres by 2.54 + if (xres != null) { + xres[0] *= 100; + xres[1] *= 254; + } + + // Divide yres by 2.54 + if (yres != null) { + yres[0] *= 100; + yres[1] *= 254; + } + } + + if (gotPixelSize) { + if (xres != null) { + float horizontalPixelSize = (float)(10.0*xres[1]/xres[0]); + node = new IIOMetadataNode("HorizontalPixelSize"); + node.setAttribute("value", + Float.toString(horizontalPixelSize)); + dimension_node.appendChild(node); + } + + if (yres != null) { + float verticalPixelSize = (float)(10.0*yres[1]/yres[0]); + node = new IIOMetadataNode("VerticalPixelSize"); + node.setAttribute("value", + Float.toString(verticalPixelSize)); + dimension_node.appendChild(node); + } + } + } + + f = getTIFFField(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT); + int resolutionUnit = f != null ? + f.getAsInt(0) : BaselineTIFFTagSet.RESOLUTION_UNIT_INCH; + if(resolutionUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_INCH || + resolutionUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER) { + f = getTIFFField(BaselineTIFFTagSet.TAG_X_POSITION); + if(f != null) { + long[] xpos = f.getAsRational(0); + float xPosition = (float)xpos[0]/(float)xpos[1]; + // Convert to millimeters. + if(resolutionUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_INCH) { + xPosition *= 254F; + } else { + xPosition *= 10F; + } + node = new IIOMetadataNode("HorizontalPosition"); + node.setAttribute("value", + Float.toString(xPosition)); + dimension_node.appendChild(node); + } + + f = getTIFFField(BaselineTIFFTagSet.TAG_Y_POSITION); + if(f != null) { + long[] ypos = f.getAsRational(0); + float yPosition = (float)ypos[0]/(float)ypos[1]; + // Convert to millimeters. + if(resolutionUnit == BaselineTIFFTagSet.RESOLUTION_UNIT_INCH) { + yPosition *= 254F; + } else { + yPosition *= 10F; + } + node = new IIOMetadataNode("VerticalPosition"); + node.setAttribute("value", + Float.toString(yPosition)); + dimension_node.appendChild(node); + } + } + + f = getTIFFField(BaselineTIFFTagSet.TAG_ORIENTATION); + if (f != null) { + int o = f.getAsInt(0); + if (o >= 0 && o < orientationNames.length) { + node = new IIOMetadataNode("ImageOrientation"); + node.setAttribute("value", orientationNames[o]); + dimension_node.appendChild(node); + } + } + + return dimension_node; + } + + public IIOMetadataNode getStandardDocumentNode() { + IIOMetadataNode document_node = new IIOMetadataNode("Document"); + IIOMetadataNode node = null; // scratch node + + TIFFField f; + + node = new IIOMetadataNode("FormatVersion"); + node.setAttribute("value", "6.0"); + document_node.appendChild(node); + + f = getTIFFField(BaselineTIFFTagSet.TAG_NEW_SUBFILE_TYPE); + if(f != null) { + int newSubFileType = f.getAsInt(0); + String value = null; + if((newSubFileType & + BaselineTIFFTagSet.NEW_SUBFILE_TYPE_TRANSPARENCY) != 0) { + value = "TransparencyMask"; + } else if((newSubFileType & + BaselineTIFFTagSet.NEW_SUBFILE_TYPE_REDUCED_RESOLUTION) != 0) { + value = "ReducedResolution"; + } else if((newSubFileType & + BaselineTIFFTagSet.NEW_SUBFILE_TYPE_SINGLE_PAGE) != 0) { + value = "SinglePage"; + } + if(value != null) { + node = new IIOMetadataNode("SubimageInterpretation"); + node.setAttribute("value", value); + document_node.appendChild(node); + } + } + + f = getTIFFField(BaselineTIFFTagSet.TAG_DATE_TIME); + if (f != null) { + String s = f.getAsString(0); + + // DateTime should be formatted as "YYYY:MM:DD hh:mm:ss". + if(s.length() == 19) { + node = new IIOMetadataNode("ImageCreationTime"); + + // Files with incorrect DateTime format have been + // observed so anticipate an exception from substring() + // and only add the node if the format is presumably + // correct. + boolean appendNode; + try { + node.setAttribute("year", s.substring(0, 4)); + node.setAttribute("month", s.substring(5, 7)); + node.setAttribute("day", s.substring(8, 10)); + node.setAttribute("hour", s.substring(11, 13)); + node.setAttribute("minute", s.substring(14, 16)); + node.setAttribute("second", s.substring(17, 19)); + appendNode = true; + } catch(IndexOutOfBoundsException e) { + appendNode = false; + } + + if(appendNode) { + document_node.appendChild(node); + } + } + } + + return document_node; + } + + public IIOMetadataNode getStandardTextNode() { + IIOMetadataNode text_node = null; + IIOMetadataNode node = null; // scratch node + + TIFFField f; + + int[] textFieldTagNumbers = new int[] { + BaselineTIFFTagSet.TAG_DOCUMENT_NAME, + BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION, + BaselineTIFFTagSet.TAG_MAKE, + BaselineTIFFTagSet.TAG_MODEL, + BaselineTIFFTagSet.TAG_PAGE_NAME, + BaselineTIFFTagSet.TAG_SOFTWARE, + BaselineTIFFTagSet.TAG_ARTIST, + BaselineTIFFTagSet.TAG_HOST_COMPUTER, + BaselineTIFFTagSet.TAG_INK_NAMES, + BaselineTIFFTagSet.TAG_COPYRIGHT + }; + + for(int i = 0; i < textFieldTagNumbers.length; i++) { + f = getTIFFField(textFieldTagNumbers[i]); + if(f != null) { + String value = f.getAsString(0); + if(text_node == null) { + text_node = new IIOMetadataNode("Text"); + } + node = new IIOMetadataNode("TextEntry"); + node.setAttribute("keyword", f.getTag().getName()); + node.setAttribute("value", value); + text_node.appendChild(node); + } + } + + return text_node; + } + + public IIOMetadataNode getStandardTransparencyNode() { + IIOMetadataNode transparency_node = + new IIOMetadataNode("Transparency"); + IIOMetadataNode node = null; // scratch node + + TIFFField f; + + node = new IIOMetadataNode("Alpha"); + String value = "none"; + + f = getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES); + if(f != null) { + int[] extraSamples = f.getAsInts(); + for(int i = 0; i < extraSamples.length; i++) { + if(extraSamples[i] == + BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA) { + value = "premultiplied"; + break; + } else if(extraSamples[i] == + BaselineTIFFTagSet.EXTRA_SAMPLES_UNASSOCIATED_ALPHA) { + value = "nonpremultiplied"; + break; + } + } + } + + node.setAttribute("value", value); + transparency_node.appendChild(node); + + return transparency_node; + } + + // Shorthand for throwing an IIOInvalidTreeException + private static void fatal(Node node, String reason) + throws IIOInvalidTreeException { + throw new IIOInvalidTreeException(reason, node); + } + + private int[] listToIntArray(String list) { + StringTokenizer st = new StringTokenizer(list, " "); + ArrayList intList = new ArrayList(); + while (st.hasMoreTokens()) { + String nextInteger = st.nextToken(); + Integer nextInt = Integer.valueOf(nextInteger); + intList.add(nextInt); + } + + int[] intArray = new int[intList.size()]; + for(int i = 0; i < intArray.length; i++) { + intArray[i] = intList.get(i); + } + + return intArray; + } + + private char[] listToCharArray(String list) { + StringTokenizer st = new StringTokenizer(list, " "); + ArrayList intList = new ArrayList(); + while (st.hasMoreTokens()) { + String nextInteger = st.nextToken(); + Integer nextInt = Integer.valueOf(nextInteger); + intList.add(nextInt); + } + + char[] charArray = new char[intList.size()]; + for(int i = 0; i < charArray.length; i++) { + charArray[i] = (char)(intList.get(i).intValue()); + } + + return charArray; + } + + private void mergeStandardTree(Node root) + throws IIOInvalidTreeException { + TIFFField f; + TIFFTag tag; + + Node node = root; + if (!node.getNodeName() + .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) { + fatal(node, "Root must be " + + IIOMetadataFormatImpl.standardMetadataFormatName); + } + + // Obtain the sample format and set the palette flag if appropriate. + String sampleFormat = null; + Node dataNode = getChildNode(root, "Data"); + boolean isPaletteColor = false; + if(dataNode != null) { + Node sampleFormatNode = getChildNode(dataNode, "SampleFormat"); + if(sampleFormatNode != null) { + sampleFormat = getAttribute(sampleFormatNode, "value"); + isPaletteColor = sampleFormat.equals("Index"); + } + } + + // If palette flag not set check for palette. + if(!isPaletteColor) { + Node chromaNode = getChildNode(root, "Chroma"); + if(chromaNode != null && + getChildNode(chromaNode, "Palette") != null) { + isPaletteColor = true; + } + } + + node = node.getFirstChild(); + while (node != null) { + String name = node.getNodeName(); + + if (name.equals("Chroma")) { + String colorSpaceType = null; + String blackIsZero = null; + boolean gotPalette = false; + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + if (childName.equals("ColorSpaceType")) { + colorSpaceType = getAttribute(child, "name"); + } else if (childName.equals("NumChannels")) { + tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL); + int samplesPerPixel = isPaletteColor ? + 1 : Integer.parseInt(getAttribute(child, "value")); + f = new TIFFField(tag, samplesPerPixel); + rootIFD.addTIFFField(f); + } else if (childName.equals("BlackIsZero")) { + blackIsZero = getAttribute(child, "value"); + } else if (childName.equals("Palette")) { + Node entry = child.getFirstChild(); + HashMap palette = new HashMap<>(); + int maxIndex = -1; + while(entry != null) { + String entryName = entry.getNodeName(); + if(entryName.equals("PaletteEntry")) { + String idx = getAttribute(entry, "index"); + int id = Integer.parseInt(idx); + if(id > maxIndex) { + maxIndex = id; + } + char red = + (char)Integer.parseInt(getAttribute(entry, + "red")); + char green = + (char)Integer.parseInt(getAttribute(entry, + "green")); + char blue = + (char)Integer.parseInt(getAttribute(entry, + "blue")); + palette.put(Integer.valueOf(id), + new char[] {red, green, blue}); + + gotPalette = true; + } + entry = entry.getNextSibling(); + } + + if(gotPalette) { + int mapSize = maxIndex + 1; + int paletteLength = 3*mapSize; + char[] paletteEntries = new char[paletteLength]; + Iterator> paletteIter + = palette.entrySet().iterator(); + while(paletteIter.hasNext()) { + Map.Entry paletteEntry + = paletteIter.next(); + int index = paletteEntry.getKey(); + char[] rgb = paletteEntry.getValue(); + paletteEntries[index] = + (char)((rgb[0]*65535)/255); + paletteEntries[mapSize + index] = + (char)((rgb[1]*65535)/255); + paletteEntries[2*mapSize + index] = + (char)((rgb[2]*65535)/255); + } + + tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_COLOR_MAP); + f = new TIFFField(tag, TIFFTag.TIFF_SHORT, + paletteLength, paletteEntries); + rootIFD.addTIFFField(f); + } + } + + child = child.getNextSibling(); + } + + int photometricInterpretation = -1; + if((colorSpaceType == null || colorSpaceType.equals("GRAY")) && + blackIsZero != null && + blackIsZero.equalsIgnoreCase("FALSE")) { + photometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO; + } else if(colorSpaceType != null) { + if(colorSpaceType.equals("GRAY")) { + boolean isTransparency = false; + if(root instanceof IIOMetadataNode) { + IIOMetadataNode iioRoot = (IIOMetadataNode)root; + NodeList siNodeList = + iioRoot.getElementsByTagName("SubimageInterpretation"); + if(siNodeList.getLength() == 1) { + Node siNode = siNodeList.item(0); + String value = getAttribute(siNode, "value"); + if(value.equals("TransparencyMask")) { + isTransparency = true; + } + } + } + if(isTransparency) { + photometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_TRANSPARENCY_MASK; + } else { + photometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO; + } + } else if(colorSpaceType.equals("RGB")) { + photometricInterpretation = + gotPalette ? + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR : + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB; + } else if(colorSpaceType.equals("YCbCr")) { + photometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR; + } else if(colorSpaceType.equals("CMYK")) { + photometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK; + } else if(colorSpaceType.equals("Lab")) { + photometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB; + } + } + + if(photometricInterpretation != -1) { + tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION); + f = new TIFFField(tag, photometricInterpretation); + rootIFD.addTIFFField(f); + } + } else if (name.equals("Compression")) { + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + if (childName.equals("CompressionTypeName")) { + int compression = -1; + String compressionTypeName = + getAttribute(child, "value"); + if(compressionTypeName.equalsIgnoreCase("None")) { + compression = + BaselineTIFFTagSet.COMPRESSION_NONE; + } else { + String[] compressionNames = + TIFFImageWriter.compressionTypes; + for(int i = 0; i < compressionNames.length; i++) { + if(compressionNames[i].equalsIgnoreCase(compressionTypeName)) { + compression = + TIFFImageWriter.compressionNumbers[i]; + break; + } + } + } + + if(compression != -1) { + tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_COMPRESSION); + f = new TIFFField(tag, compression); + rootIFD.addTIFFField(f); + + // Lossless is irrelevant. + } + } + + child = child.getNextSibling(); + } + } else if (name.equals("Data")) { + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + + if (childName.equals("PlanarConfiguration")) { + String pc = getAttribute(child, "value"); + int planarConfiguration = -1; + if(pc.equals("PixelInterleaved")) { + planarConfiguration = + BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; + } else if(pc.equals("PlaneInterleaved")) { + planarConfiguration = + BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR; + } + if(planarConfiguration != -1) { + tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION); + f = new TIFFField(tag, planarConfiguration); + rootIFD.addTIFFField(f); + } + } else if (childName.equals("BitsPerSample")) { + String bps = getAttribute(child, "value"); + char[] bitsPerSample = listToCharArray(bps); + tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); + if(isPaletteColor) { + f = new TIFFField(tag, TIFFTag.TIFF_SHORT, 1, + new char[] {bitsPerSample[0]}); + } else { + f = new TIFFField(tag, TIFFTag.TIFF_SHORT, + bitsPerSample.length, + bitsPerSample); + } + rootIFD.addTIFFField(f); + } else if (childName.equals("SampleMSB")) { + // Add FillOrder only if lsb-to-msb (right to left) + // for all bands, i.e., SampleMSB is zero for all + // channels. + String sMSB = getAttribute(child, "value"); + int[] sampleMSB = listToIntArray(sMSB); + boolean isRightToLeft = true; + for(int i = 0; i < sampleMSB.length; i++) { + if(sampleMSB[i] != 0) { + isRightToLeft = false; + break; + } + } + int fillOrder = isRightToLeft ? + BaselineTIFFTagSet.FILL_ORDER_RIGHT_TO_LEFT : + BaselineTIFFTagSet.FILL_ORDER_LEFT_TO_RIGHT; + tag = + rootIFD.getTag(BaselineTIFFTagSet.TAG_FILL_ORDER); + f = new TIFFField(tag, fillOrder); + rootIFD.addTIFFField(f); + } + + child = child.getNextSibling(); + } + } else if (name.equals("Dimension")) { + float pixelAspectRatio = -1.0f; + boolean gotPixelAspectRatio = false; + + float horizontalPixelSize = -1.0f; + boolean gotHorizontalPixelSize = false; + + float verticalPixelSize = -1.0f; + boolean gotVerticalPixelSize = false; + + boolean sizeIsAbsolute = false; + + float horizontalPosition = -1.0f; + boolean gotHorizontalPosition = false; + + float verticalPosition = -1.0f; + boolean gotVerticalPosition = false; + + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + if (childName.equals("PixelAspectRatio")) { + String par = getAttribute(child, "value"); + pixelAspectRatio = Float.parseFloat(par); + gotPixelAspectRatio = true; + } else if (childName.equals("ImageOrientation")) { + String orientation = getAttribute(child, "value"); + for (int i = 0; i < orientationNames.length; i++) { + if (orientation.equals(orientationNames[i])) { + char[] oData = new char[1]; + oData[0] = (char)i; + + f = new TIFFField( + rootIFD.getTag(BaselineTIFFTagSet.TAG_ORIENTATION), + TIFFTag.TIFF_SHORT, + 1, + oData); + + rootIFD.addTIFFField(f); + break; + } + } + + } else if (childName.equals("HorizontalPixelSize")) { + String hps = getAttribute(child, "value"); + horizontalPixelSize = Float.parseFloat(hps); + gotHorizontalPixelSize = true; + } else if (childName.equals("VerticalPixelSize")) { + String vps = getAttribute(child, "value"); + verticalPixelSize = Float.parseFloat(vps); + gotVerticalPixelSize = true; + } else if (childName.equals("HorizontalPosition")) { + String hp = getAttribute(child, "value"); + horizontalPosition = Float.parseFloat(hp); + gotHorizontalPosition = true; + } else if (childName.equals("VerticalPosition")) { + String vp = getAttribute(child, "value"); + verticalPosition = Float.parseFloat(vp); + gotVerticalPosition = true; + } + + child = child.getNextSibling(); + } + + sizeIsAbsolute = gotHorizontalPixelSize || + gotVerticalPixelSize; + + // Fill in pixel size data from aspect ratio + if (gotPixelAspectRatio) { + if (gotHorizontalPixelSize && !gotVerticalPixelSize) { + verticalPixelSize = + horizontalPixelSize/pixelAspectRatio; + gotVerticalPixelSize = true; + } else if (gotVerticalPixelSize && + !gotHorizontalPixelSize) { + horizontalPixelSize = + verticalPixelSize*pixelAspectRatio; + gotHorizontalPixelSize = true; + } else if (!gotHorizontalPixelSize && + !gotVerticalPixelSize) { + horizontalPixelSize = pixelAspectRatio; + verticalPixelSize = 1.0f; + gotHorizontalPixelSize = true; + gotVerticalPixelSize = true; + } + } + + // Compute pixels/centimeter + if (gotHorizontalPixelSize) { + float xResolution = + (sizeIsAbsolute ? 10.0f : 1.0f)/horizontalPixelSize; + long[][] hData = new long[1][2]; + hData[0] = new long[2]; + hData[0][0] = (long)(xResolution*10000.0f); + hData[0][1] = (long)10000; + + f = new TIFFField( + rootIFD.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION), + TIFFTag.TIFF_RATIONAL, + 1, + hData); + rootIFD.addTIFFField(f); + } + + if (gotVerticalPixelSize) { + float yResolution = + (sizeIsAbsolute ? 10.0f : 1.0f)/verticalPixelSize; + long[][] vData = new long[1][2]; + vData[0] = new long[2]; + vData[0][0] = (long)(yResolution*10000.0f); + vData[0][1] = (long)10000; + + f = new TIFFField( + rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION), + TIFFTag.TIFF_RATIONAL, + 1, + vData); + rootIFD.addTIFFField(f); + } + + // Emit ResolutionUnit tag + char[] res = new char[1]; + res[0] = (char)(sizeIsAbsolute ? + BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER : + BaselineTIFFTagSet.RESOLUTION_UNIT_NONE); + + f = new TIFFField( + rootIFD.getTag(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT), + TIFFTag.TIFF_SHORT, + 1, + res); + rootIFD.addTIFFField(f); + + // Position + if(sizeIsAbsolute) { + if(gotHorizontalPosition) { + // Convert from millimeters to centimeters via + // numerator multiplier = denominator/10. + long[][] hData = new long[1][2]; + hData[0][0] = (long)(horizontalPosition*10000.0f); + hData[0][1] = (long)100000; + + f = new TIFFField( + rootIFD.getTag(BaselineTIFFTagSet.TAG_X_POSITION), + TIFFTag.TIFF_RATIONAL, + 1, + hData); + rootIFD.addTIFFField(f); + } + + if(gotVerticalPosition) { + // Convert from millimeters to centimeters via + // numerator multiplier = denominator/10. + long[][] vData = new long[1][2]; + vData[0][0] = (long)(verticalPosition*10000.0f); + vData[0][1] = (long)100000; + + f = new TIFFField( + rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_POSITION), + TIFFTag.TIFF_RATIONAL, + 1, + vData); + rootIFD.addTIFFField(f); + } + } + } else if (name.equals("Document")) { + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + + if (childName.equals("SubimageInterpretation")) { + String si = getAttribute(child, "value"); + int newSubFileType = -1; + if(si.equals("TransparencyMask")) { + newSubFileType = + BaselineTIFFTagSet.NEW_SUBFILE_TYPE_TRANSPARENCY; + } else if(si.equals("ReducedResolution")) { + newSubFileType = + BaselineTIFFTagSet.NEW_SUBFILE_TYPE_REDUCED_RESOLUTION; + } else if(si.equals("SinglePage")) { + newSubFileType = + BaselineTIFFTagSet.NEW_SUBFILE_TYPE_SINGLE_PAGE; + } + if(newSubFileType != -1) { + tag = + rootIFD.getTag(BaselineTIFFTagSet.TAG_NEW_SUBFILE_TYPE); + f = new TIFFField(tag, newSubFileType); + rootIFD.addTIFFField(f); + } + } + + if (childName.equals("ImageCreationTime")) { + String year = getAttribute(child, "year"); + String month = getAttribute(child, "month"); + String day = getAttribute(child, "day"); + String hour = getAttribute(child, "hour"); + String minute = getAttribute(child, "minute"); + String second = getAttribute(child, "second"); + + StringBuffer sb = new StringBuffer(); + sb.append(year); + sb.append(":"); + if(month.length() == 1) { + sb.append("0"); + } + sb.append(month); + sb.append(":"); + if(day.length() == 1) { + sb.append("0"); + } + sb.append(day); + sb.append(" "); + if(hour.length() == 1) { + sb.append("0"); + } + sb.append(hour); + sb.append(":"); + if(minute.length() == 1) { + sb.append("0"); + } + sb.append(minute); + sb.append(":"); + if(second.length() == 1) { + sb.append("0"); + } + sb.append(second); + + String[] dt = new String[1]; + dt[0] = sb.toString(); + + f = new TIFFField( + rootIFD.getTag(BaselineTIFFTagSet.TAG_DATE_TIME), + TIFFTag.TIFF_ASCII, + 1, + dt); + rootIFD.addTIFFField(f); + } + + child = child.getNextSibling(); + } + } else if (name.equals("Text")) { + Node child = node.getFirstChild(); + String theAuthor = null; + String theDescription = null; + String theTitle = null; + while (child != null) { + String childName = child.getNodeName(); + if(childName.equals("TextEntry")) { + int tagNumber = -1; + NamedNodeMap childAttrs = child.getAttributes(); + Node keywordNode = childAttrs.getNamedItem("keyword"); + if(keywordNode != null) { + String keyword = keywordNode.getNodeValue(); + String value = getAttribute(child, "value"); + if(!keyword.equals("") && !value.equals("")) { + if(keyword.equalsIgnoreCase("DocumentName")) { + tagNumber = + BaselineTIFFTagSet.TAG_DOCUMENT_NAME; + } else if(keyword.equalsIgnoreCase("ImageDescription")) { + tagNumber = + BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION; + } else if(keyword.equalsIgnoreCase("Make")) { + tagNumber = + BaselineTIFFTagSet.TAG_MAKE; + } else if(keyword.equalsIgnoreCase("Model")) { + tagNumber = + BaselineTIFFTagSet.TAG_MODEL; + } else if(keyword.equalsIgnoreCase("PageName")) { + tagNumber = + BaselineTIFFTagSet.TAG_PAGE_NAME; + } else if(keyword.equalsIgnoreCase("Software")) { + tagNumber = + BaselineTIFFTagSet.TAG_SOFTWARE; + } else if(keyword.equalsIgnoreCase("Artist")) { + tagNumber = + BaselineTIFFTagSet.TAG_ARTIST; + } else if(keyword.equalsIgnoreCase("HostComputer")) { + tagNumber = + BaselineTIFFTagSet.TAG_HOST_COMPUTER; + } else if(keyword.equalsIgnoreCase("InkNames")) { + tagNumber = + BaselineTIFFTagSet.TAG_INK_NAMES; + } else if(keyword.equalsIgnoreCase("Copyright")) { + tagNumber = + BaselineTIFFTagSet.TAG_COPYRIGHT; + } else if(keyword.equalsIgnoreCase("author")) { + theAuthor = value; + } else if(keyword.equalsIgnoreCase("description")) { + theDescription = value; + } else if(keyword.equalsIgnoreCase("title")) { + theTitle = value; + } + if(tagNumber != -1) { + f = new TIFFField(rootIFD.getTag(tagNumber), + TIFFTag.TIFF_ASCII, + 1, + new String[] {value}); + rootIFD.addTIFFField(f); + } + } + } + } + child = child.getNextSibling(); + } // child != null + if(theAuthor != null && + getTIFFField(BaselineTIFFTagSet.TAG_ARTIST) == null) { + f = new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_ARTIST), + TIFFTag.TIFF_ASCII, + 1, + new String[] {theAuthor}); + rootIFD.addTIFFField(f); + } + if(theDescription != null && + getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION) == null) { + f = new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION), + TIFFTag.TIFF_ASCII, + 1, + new String[] {theDescription}); + rootIFD.addTIFFField(f); + } + if(theTitle != null && + getTIFFField(BaselineTIFFTagSet.TAG_DOCUMENT_NAME) == null) { + f = new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_DOCUMENT_NAME), + TIFFTag.TIFF_ASCII, + 1, + new String[] {theTitle}); + rootIFD.addTIFFField(f); + } + } else if (name.equals("Transparency")) { + Node child = node.getFirstChild(); + while (child != null) { + String childName = child.getNodeName(); + + if (childName.equals("Alpha")) { + String alpha = getAttribute(child, "value"); + + f = null; + if (alpha.equals("premultiplied")) { + f = new TIFFField( + rootIFD.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES), + BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA); + } else if (alpha.equals("nonpremultiplied")) { + f = new TIFFField( + rootIFD.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES), + BaselineTIFFTagSet.EXTRA_SAMPLES_UNASSOCIATED_ALPHA); + } + if (f != null) { + rootIFD.addTIFFField(f); + } + } + + child = child.getNextSibling(); + } + } + + node = node.getNextSibling(); + } + + // Set SampleFormat. + if(sampleFormat != null) { + // Derive the value. + int sf = -1; + if(sampleFormat.equals("SignedIntegral")) { + sf = BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER; + } else if(sampleFormat.equals("UnsignedIntegral")) { + sf = BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER; + } else if(sampleFormat.equals("Real")) { + sf = BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT; + } else if(sampleFormat.equals("Index")) { + sf = BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER; + } + + if(sf != -1) { + // Derive the count. + int count = 1; + + // Try SamplesPerPixel first. + f = getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL); + if(f != null) { + count = f.getAsInt(0); + } else { + // Try BitsPerSample. + f = getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); + if(f != null) { + count = f.getCount(); + } + } + + char[] sampleFormatArray = new char[count]; + Arrays.fill(sampleFormatArray, (char)sf); + + // Add SampleFormat. + tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT); + f = new TIFFField(tag, TIFFTag.TIFF_SHORT, + sampleFormatArray.length, sampleFormatArray); + rootIFD.addTIFFField(f); + } + } + } + + private static String getAttribute(Node node, String attrName) { + NamedNodeMap attrs = node.getAttributes(); + Node attr = attrs.getNamedItem(attrName); + return attr != null ? attr.getNodeValue() : null; + } + + private Node getChildNode(Node node, String childName) { + Node childNode = null; + if(node.hasChildNodes()) { + NodeList childNodes = node.getChildNodes(); + int length = childNodes.getLength(); + for(int i = 0; i < length; i++) { + Node item = childNodes.item(i); + if(item.getNodeName().equals(childName)) { + childNode = item; + break; + } + } + } + return childNode; + } + + public static TIFFIFD parseIFD(Node node) throws IIOInvalidTreeException { + if (!node.getNodeName().equals("TIFFIFD")) { + fatal(node, "Expected \"TIFFIFD\" node"); + } + + String tagSetNames = getAttribute(node, "tagSets"); + List tagSets = new ArrayList(5); + + if (tagSetNames != null) { + StringTokenizer st = new StringTokenizer(tagSetNames, ","); + while (st.hasMoreTokens()) { + String className = st.nextToken(); + + Object o = null; + try { + Class setClass = Class.forName(className); + Method getInstanceMethod = + setClass.getMethod("getInstance", (Class[])null); + o = getInstanceMethod.invoke(null, (Object[])null); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + + if (!(o instanceof TIFFTagSet)) { + fatal(node, "Specified tag set class \"" + + className + + "\" is not an instance of TIFFTagSet"); + } else { + tagSets.add((TIFFTagSet)o); + } + } + } + + TIFFIFD ifd = new TIFFIFD(tagSets); + + node = node.getFirstChild(); + while (node != null) { + String name = node.getNodeName(); + + TIFFField f = null; + if (name.equals("TIFFIFD")) { + TIFFIFD subIFD = parseIFD(node); + String parentTagName = getAttribute(node, "parentTagName"); + String parentTagNumber = getAttribute(node, "parentTagNumber"); + TIFFTag tag = null; + if(parentTagName != null) { + tag = TIFFIFD.getTag(parentTagName, tagSets); + } else if(parentTagNumber != null) { + int tagNumber = Integer.parseUnsignedInt(parentTagNumber); + tag = TIFFIFD.getTag(tagNumber, tagSets); + } + + int type; + if (tag == null) { + type = TIFFTag.TIFF_LONG; + tag = new TIFFTag(TIFFTag.UNKNOWN_TAG_NAME, 0, 1 << type); + } else { + if (tag.isDataTypeOK(TIFFTag.TIFF_IFD_POINTER)) { + type = TIFFTag.TIFF_IFD_POINTER; + } else if (tag.isDataTypeOK(TIFFTag.TIFF_LONG)) { + type = TIFFTag.TIFF_LONG; + } else { + for (type = TIFFTag.MAX_DATATYPE; + type >= TIFFTag.MIN_DATATYPE; + type--) { + if (tag.isDataTypeOK(type)) { + break; + } + } + } + } + + f = new TIFFField(tag, type, 1L, subIFD); + } else if (name.equals("TIFFField")) { + int number = Integer.parseInt(getAttribute(node, "number")); + + TIFFTagSet tagSet = null; + Iterator iter = tagSets.iterator(); + while (iter.hasNext()) { + TIFFTagSet t = iter.next(); + if (t.getTag(number) != null) { + tagSet = t; + break; + } + } + + f = TIFFField.createFromMetadataNode(tagSet, node); + } else { + fatal(node, + "Expected either \"TIFFIFD\" or \"TIFFField\" node, got " + + name); + } + + ifd.addTIFFField(f); + node = node.getNextSibling(); + } + + return ifd; + } + + private void mergeNativeTree(Node root) throws IIOInvalidTreeException { + Node node = root; + if (!node.getNodeName().equals(nativeMetadataFormatName)) { + fatal(node, "Root must be " + nativeMetadataFormatName); + } + + node = node.getFirstChild(); + if (node == null || !node.getNodeName().equals("TIFFIFD")) { + fatal(root, "Root must have \"TIFFIFD\" child"); + } + TIFFIFD ifd = parseIFD(node); + + List rootIFDTagSets = rootIFD.getTagSetList(); + Iterator tagSetIter = ifd.getTagSetList().iterator(); + while(tagSetIter.hasNext()) { + Object o = tagSetIter.next(); + if(o instanceof TIFFTagSet && !rootIFDTagSets.contains(o)) { + rootIFD.addTagSet((TIFFTagSet)o); + } + } + + Iterator ifdIter = ifd.iterator(); + while(ifdIter.hasNext()) { + TIFFField field = ifdIter.next(); + rootIFD.addTIFFField(field); + } + } + + public void mergeTree(String formatName, Node root) + throws IIOInvalidTreeException{ + if (formatName.equals(nativeMetadataFormatName)) { + if (root == null) { + throw new NullPointerException("root == null!"); + } + mergeNativeTree(root); + } else if (formatName.equals + (IIOMetadataFormatImpl.standardMetadataFormatName)) { + if (root == null) { + throw new NullPointerException("root == null!"); + } + mergeStandardTree(root); + } else { + throw new IllegalArgumentException("Not a recognized format!"); + } + } + + public void reset() { + rootIFD = new TIFFIFD(tagSets); + } + + public TIFFIFD getRootIFD() { + return rootIFD; + } + + public TIFFField getTIFFField(int tagNumber) { + return rootIFD.getTIFFField(tagNumber); + } + + public void removeTIFFField(int tagNumber) { + rootIFD.removeTIFFField(tagNumber); + } + + /** + * Returns a TIFFImageMetadata wherein all fields in the + * root IFD from the BaselineTIFFTagSet are copied by value + * and all other fields copied by reference. + */ + public TIFFImageMetadata getShallowClone() { + return new TIFFImageMetadata(rootIFD.getShallowClone()); + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormat.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadataFormat; + +public class TIFFImageMetadataFormat extends TIFFMetadataFormat { + + private static TIFFImageMetadataFormat theInstance = null; + + static { + } + + public boolean canNodeAppear(String elementName, + ImageTypeSpecifier imageType) { + return false; + } + + private TIFFImageMetadataFormat() { + this.resourceBaseName = + "javax.imageio.plugins.tiff.TIFFImageMetadataFormatResources"; + this.rootName = TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME; + + TIFFElementInfo einfo; + TIFFAttrInfo ainfo; + String[] empty = new String[0]; + String[] childNames; + String[] attrNames; + + childNames = new String[] { "TIFFIFD" }; + einfo = new TIFFElementInfo(childNames, empty, CHILD_POLICY_SEQUENCE); + + elementInfoMap.put(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME, + einfo); + + childNames = new String[] { "TIFFField", "TIFFIFD" }; + attrNames = + new String[] { "tagSets", "parentTagNumber", "parentTagName" }; + einfo = new TIFFElementInfo(childNames, attrNames, CHILD_POLICY_SEQUENCE); + elementInfoMap.put("TIFFIFD", einfo); + + ainfo = new TIFFAttrInfo(); + ainfo.dataType = DATATYPE_STRING; + ainfo.isRequired = true; + attrInfoMap.put("TIFFIFD/tagSets", ainfo); + + ainfo = new TIFFAttrInfo(); + ainfo.dataType = DATATYPE_INTEGER; + ainfo.isRequired = false; + attrInfoMap.put("TIFFIFD/parentTagNumber", ainfo); + + ainfo = new TIFFAttrInfo(); + ainfo.dataType = DATATYPE_STRING; + ainfo.isRequired = false; + attrInfoMap.put("TIFFIFD/parentTagName", ainfo); + + String[] types = { + "TIFFByte", + "TIFFAscii", + "TIFFShort", + "TIFFSShort", + "TIFFLong", + "TIFFSLong", + "TIFFRational", + "TIFFSRational", + "TIFFFloat", + "TIFFDouble", + "TIFFUndefined" + }; + + attrNames = new String[] { "value", "description" }; + String[] attrNamesValueOnly = new String[] { "value" }; + TIFFAttrInfo ainfoValue = new TIFFAttrInfo(); + TIFFAttrInfo ainfoDescription = new TIFFAttrInfo(); + + for (int i = 0; i < types.length; i++) { + if (!types[i].equals("TIFFUndefined")) { + childNames = new String[1]; + childNames[0] = types[i]; + einfo = + new TIFFElementInfo(childNames, empty, CHILD_POLICY_SEQUENCE); + elementInfoMap.put(types[i] + "s", einfo); + } + + boolean hasDescription = + !types[i].equals("TIFFUndefined") && + !types[i].equals("TIFFAscii") && + !types[i].equals("TIFFRational") && + !types[i].equals("TIFFSRational") && + !types[i].equals("TIFFFloat") && + !types[i].equals("TIFFDouble"); + + String[] anames = hasDescription ? attrNames : attrNamesValueOnly; + einfo = new TIFFElementInfo(empty, anames, CHILD_POLICY_EMPTY); + elementInfoMap.put(types[i], einfo); + + attrInfoMap.put(types[i] + "/value", ainfoValue); + if (hasDescription) { + attrInfoMap.put(types[i] + "/description", ainfoDescription); + } + } + + childNames = new String[2*types.length - 1]; + for (int i = 0; i < types.length; i++) { + childNames[2*i] = types[i]; + if (!types[i].equals("TIFFUndefined")) { + childNames[2*i + 1] = types[i] + "s"; + } + } + attrNames = new String[] { "number", "name" }; + einfo = new TIFFElementInfo(childNames, attrNames, CHILD_POLICY_CHOICE); + elementInfoMap.put("TIFFField", einfo); + + ainfo = new TIFFAttrInfo(); + ainfo.isRequired = true; + attrInfoMap.put("TIFFField/number", ainfo); + + ainfo = new TIFFAttrInfo(); + attrInfoMap.put("TIFFField/name", ainfo); + } + + public static synchronized IIOMetadataFormat getInstance() { + if (theInstance == null) { + theInstance = new TIFFImageMetadataFormat(); + } + return theInstance; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormatResources.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormatResources.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.util.ListResourceBundle; + +public class TIFFImageMetadataFormatResources extends ListResourceBundle { + + private static final Object[][] contents = { + { "TIFFIFD", "An IFD (directory) containing fields" }, + { "TIFFIFD/parentTagNumber", + "The tag number of the field pointing to this IFD" }, + { "TIFFIFD/parentTagName", + "A mnemonic name for the field pointing to this IFD, if known" }, + { "TIFFField", "A field containing data" }, + { "TIFFField/number", "The tag number asociated with the field" }, + { "TIFFField/name", + "A mnemonic name associated with the field, if known" }, + + { "TIFFUndefined", "Uninterpreted byte data" }, + { "TIFFUndefined/value", "A list of comma-separated byte values" }, + + { "TIFFBytes", "A sequence of TIFFByte nodes" }, + { "TIFFByte", "An integral value between 0 and 255" }, + { "TIFFByte/value", "The value" }, + { "TIFFByte/description", "A description, if available" }, + + { "TIFFAsciis", "A sequence of TIFFAscii nodes" }, + { "TIFFAscii", "A String value" }, + { "TIFFAscii/value", "The value" }, + + { "TIFFShorts", "A sequence of TIFFShort nodes" }, + { "TIFFShort", "An integral value between 0 and 65535" }, + { "TIFFShort/value", "The value" }, + { "TIFFShort/description", "A description, if available" }, + + { "TIFFSShorts", "A sequence of TIFFSShort nodes" }, + { "TIFFSShort", "An integral value between -32768 and 32767" }, + { "TIFFSShort/value", "The value" }, + { "TIFFSShort/description", "A description, if available" }, + + { "TIFFLongs", "A sequence of TIFFLong nodes" }, + { "TIFFLong", "An integral value between 0 and 4294967295" }, + { "TIFFLong/value", "The value" }, + { "TIFFLong/description", "A description, if available" }, + + { "TIFFSLongs", "A sequence of TIFFSLong nodes" }, + { "TIFFSLong", "An integral value between -2147483648 and 2147483647" }, + { "TIFFSLong/value", "The value" }, + { "TIFFSLong/description", "A description, if available" }, + + { "TIFFRationals", "A sequence of TIFFRational nodes" }, + { "TIFFRational", + "A rational value consisting of an unsigned numerator and denominator" }, + { "TIFFRational/value", + "The numerator and denominator, separated by a slash" }, + + { "TIFFSRationals", "A sequence of TIFFSRational nodes" }, + { "TIFFSRational", + "A rational value consisting of a signed numerator and denominator" }, + { "TIFFSRational/value", + "The numerator and denominator, separated by a slash" }, + + { "TIFFFloats", "A sequence of TIFFFloat nodes" }, + { "TIFFFloat", "A single-precision floating-point value" }, + { "TIFFFloat/value", "The value" }, + + { "TIFFDoubles", "A sequence of TIFFDouble nodes" }, + { "TIFFDouble", "A double-precision floating-point value" }, + { "TIFFDouble/value", "The value" }, + + }; + + public TIFFImageMetadataFormatResources() { + } + + public Object[][] getContents() { + return contents.clone(); + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReader.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReader.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,1329 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.color.ColorSpace; +import java.awt.color.ICC_ColorSpace; +import java.awt.color.ICC_Profile; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import javax.imageio.IIOException; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import org.w3c.dom.Node; +import com.sun.imageio.plugins.common.ImageUtil; +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.plugins.tiff.TIFFField; +import javax.imageio.plugins.tiff.TIFFImageReadParam; +import javax.imageio.plugins.tiff.TIFFTagSet; + +public class TIFFImageReader extends ImageReader { + + // A somewhat arbitrary upper bound on SamplesPerPixel. Hyperspectral + // images as of this writing appear to be under 300 bands so this should + // account for those cases should they arise. + private static final int SAMPLES_PER_PIXEL_MAX = 1024; + + // In baseline TIFF the largest data types are 64-bit long and double. + private static final int BITS_PER_SAMPLE_MAX = 64; + + // The current ImageInputStream source. + private ImageInputStream stream = null; + + // True if the file header has been read. + private boolean gotHeader = false; + + private ImageReadParam imageReadParam = getDefaultReadParam(); + + // Stream metadata, or null. + private TIFFStreamMetadata streamMetadata = null; + + // The current image index. + private int currIndex = -1; + + // Metadata for image at 'currIndex', or null. + private TIFFImageMetadata imageMetadata = null; + + // A List of Longs indicating the stream + // positions of the start of the IFD for each image. Entries + // are added as needed. + private List imageStartPosition = new ArrayList(); + + // The number of images in the stream, if known, otherwise -1. + private int numImages = -1; + + // The ImageTypeSpecifiers of the images in the stream. + // Contains a map of Integers to Lists. + private HashMap> imageTypeMap + = new HashMap>(); + + private BufferedImage theImage = null; + + private int width = -1; + private int height = -1; + private int numBands = -1; + private int tileOrStripWidth = -1, tileOrStripHeight = -1; + + private int planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; + + private int compression; + private int photometricInterpretation; + private int samplesPerPixel; + private int[] sampleFormat; + private int[] bitsPerSample; + private int[] extraSamples; + private char[] colorMap; + + private int sourceXOffset; + private int sourceYOffset; + private int srcXSubsampling; + private int srcYSubsampling; + + private int dstWidth; + private int dstHeight; + private int dstMinX; + private int dstMinY; + private int dstXOffset; + private int dstYOffset; + + private int tilesAcross; + private int tilesDown; + + private int pixelsRead; + private int pixelsToRead; + + public TIFFImageReader(ImageReaderSpi originatingProvider) { + super(originatingProvider); + } + + public void setInput(Object input, + boolean seekForwardOnly, + boolean ignoreMetadata) { + super.setInput(input, seekForwardOnly, ignoreMetadata); + + // Clear all local values based on the previous stream contents. + resetLocal(); + + if (input != null) { + if (!(input instanceof ImageInputStream)) { + throw new IllegalArgumentException("input not an ImageInputStream!"); + } + this.stream = (ImageInputStream) input; + } else { + this.stream = null; + } + } + + // Do not seek to the beginning of the stream so as to allow users to + // point us at an IFD within some other file format + private void readHeader() throws IIOException { + if (gotHeader) { + return; + } + if (stream == null) { + throw new IllegalStateException("Input not set!"); + } + + // Create an object to store the stream metadata + this.streamMetadata = new TIFFStreamMetadata(); + + try { + int byteOrder = stream.readUnsignedShort(); + if (byteOrder == 0x4d4d) { + streamMetadata.byteOrder = ByteOrder.BIG_ENDIAN; + stream.setByteOrder(ByteOrder.BIG_ENDIAN); + } else if (byteOrder == 0x4949) { + streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN; + stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + } else { + processWarningOccurred( + "Bad byte order in header, assuming little-endian"); + streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN; + stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + } + + int magic = stream.readUnsignedShort(); + if (magic != 42) { + processWarningOccurred( + "Bad magic number in header, continuing"); + } + + // Seek to start of first IFD + long offset = stream.readUnsignedInt(); + imageStartPosition.add(Long.valueOf(offset)); + stream.seek(offset); + } catch (IOException e) { + throw new IIOException("I/O error reading header!", e); + } + + gotHeader = true; + } + + private int locateImage(int imageIndex) throws IIOException { + readHeader(); + + try { + // Find closest known index + int index = Math.min(imageIndex, imageStartPosition.size() - 1); + + // Seek to that position + Long l = imageStartPosition.get(index); + stream.seek(l.longValue()); + + // Skip IFDs until at desired index or last image found + while (index < imageIndex) { + int count = stream.readUnsignedShort(); + stream.skipBytes(12 * count); + + long offset = stream.readUnsignedInt(); + if (offset == 0) { + return index; + } + + imageStartPosition.add(Long.valueOf(offset)); + stream.seek(offset); + ++index; + } + } catch (IOException e) { + throw new IIOException("Couldn't seek!", e); + } + + if (currIndex != imageIndex) { + imageMetadata = null; + } + currIndex = imageIndex; + return imageIndex; + } + + public int getNumImages(boolean allowSearch) throws IOException { + if (stream == null) { + throw new IllegalStateException("Input not set!"); + } + if (seekForwardOnly && allowSearch) { + throw new IllegalStateException("seekForwardOnly and allowSearch can't both be true!"); + } + + if (numImages > 0) { + return numImages; + } + if (allowSearch) { + this.numImages = locateImage(Integer.MAX_VALUE) + 1; + } + return numImages; + } + + public IIOMetadata getStreamMetadata() throws IIOException { + readHeader(); + return streamMetadata; + } + + // Throw an IndexOutOfBoundsException if index < minIndex, + // and bump minIndex if required. + private void checkIndex(int imageIndex) { + if (imageIndex < minIndex) { + throw new IndexOutOfBoundsException("imageIndex < minIndex!"); + } + if (seekForwardOnly) { + minIndex = imageIndex; + } + } + + // Verify that imageIndex is in bounds, find the image IFD, read the + // image metadata, initialize instance variables from the metadata. + private void seekToImage(int imageIndex) throws IIOException { + checkIndex(imageIndex); + + int index = locateImage(imageIndex); + if (index != imageIndex) { + throw new IndexOutOfBoundsException("imageIndex out of bounds!"); + } + + readMetadata(); + + initializeFromMetadata(); + } + + // Stream must be positioned at start of IFD for 'currIndex' + private void readMetadata() throws IIOException { + if (stream == null) { + throw new IllegalStateException("Input not set!"); + } + + if (imageMetadata != null) { + return; + } + try { + // Create an object to store the image metadata + List tagSets; + if (imageReadParam instanceof TIFFImageReadParam) { + tagSets + = ((TIFFImageReadParam) imageReadParam).getAllowedTagSets(); + } else { + tagSets = new ArrayList(1); + tagSets.add(BaselineTIFFTagSet.getInstance()); + } + + this.imageMetadata = new TIFFImageMetadata(tagSets); + imageMetadata.initializeFromStream(stream, ignoreMetadata); + } catch (IIOException iioe) { + throw iioe; + } catch (IOException ioe) { + throw new IIOException("I/O error reading image metadata!", ioe); + } + } + + private int getWidth() { + return this.width; + } + + private int getHeight() { + return this.height; + } + + // Returns tile width if image is tiled, else image width + private int getTileOrStripWidth() { + TIFFField f + = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH); + return (f == null) ? getWidth() : f.getAsInt(0); + } + + // Returns tile height if image is tiled, else strip height + private int getTileOrStripHeight() { + TIFFField f + = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH); + if (f != null) { + return f.getAsInt(0); + } + + f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP); + // Default for ROWS_PER_STRIP is 2^32 - 1, i.e., infinity + int h = (f == null) ? -1 : f.getAsInt(0); + return (h == -1) ? getHeight() : h; + } + + private int getPlanarConfiguration() { + TIFFField f + = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION); + if (f != null) { + int planarConfigurationValue = f.getAsInt(0); + if (planarConfigurationValue + == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) { + // Some writers (e.g. Kofax standard Multi-Page TIFF + // Storage Filter v2.01.000; cf. bug 4929147) do not + // correctly set the value of this field. Attempt to + // ascertain whether the value is correctly Planar. + if (getCompression() + == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG + && imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT) + != null) { + // JPEG interchange format cannot have + // PlanarConfiguration value Chunky so reset. + processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with JPEGInterchangeFormat; resetting to \"Chunky\"."); + planarConfigurationValue + = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; + } else { + TIFFField offsetField + = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS); + if (offsetField == null) { + // Tiles + offsetField + = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS); + int tw = getTileOrStripWidth(); + int th = getTileOrStripHeight(); + int tAcross = (getWidth() + tw - 1) / tw; + int tDown = (getHeight() + th - 1) / th; + int tilesPerImage = tAcross * tDown; + long[] offsetArray = offsetField.getAsLongs(); + if (offsetArray != null + && offsetArray.length == tilesPerImage) { + // Length of offsets array is + // TilesPerImage for Chunky and + // SamplesPerPixel*TilesPerImage for Planar. + processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with TileOffsets field value count; resetting to \"Chunky\"."); + planarConfigurationValue + = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; + } + } else { + // Strips + int rowsPerStrip = getTileOrStripHeight(); + int stripsPerImage + = (getHeight() + rowsPerStrip - 1) / rowsPerStrip; + long[] offsetArray = offsetField.getAsLongs(); + if (offsetArray != null + && offsetArray.length == stripsPerImage) { + // Length of offsets array is + // StripsPerImage for Chunky and + // SamplesPerPixel*StripsPerImage for Planar. + processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with StripOffsets field value count; resetting to \"Chunky\"."); + planarConfigurationValue + = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; + } + } + } + } + return planarConfigurationValue; + } + + return BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; + } + + private long getTileOrStripOffset(int tileIndex) throws IIOException { + TIFFField f + = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS); + if (f == null) { + f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS); + } + if (f == null) { + f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT); + } + + if (f == null) { + throw new IIOException("Missing required strip or tile offsets field."); + } + + return f.getAsLong(tileIndex); + } + + private long getTileOrStripByteCount(int tileIndex) throws IOException { + TIFFField f + = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS); + if (f == null) { + f + = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS); + } + if (f == null) { + f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); + } + + long tileOrStripByteCount; + if (f != null) { + tileOrStripByteCount = f.getAsLong(tileIndex); + } else { + processWarningOccurred("TIFF directory contains neither StripByteCounts nor TileByteCounts field: attempting to calculate from strip or tile width and height."); + + // Initialize to number of bytes per strip or tile assuming + // no compression. + int bitsPerPixel = bitsPerSample[0]; + for (int i = 1; i < samplesPerPixel; i++) { + bitsPerPixel += bitsPerSample[i]; + } + int bytesPerRow = (getTileOrStripWidth() * bitsPerPixel + 7) / 8; + tileOrStripByteCount = bytesPerRow * getTileOrStripHeight(); + + // Clamp to end of stream if possible. + long streamLength = stream.length(); + if (streamLength != -1) { + tileOrStripByteCount + = Math.min(tileOrStripByteCount, + streamLength - getTileOrStripOffset(tileIndex)); + } else { + processWarningOccurred("Stream length is unknown: cannot clamp estimated strip or tile byte count to EOF."); + } + } + + return tileOrStripByteCount; + } + + private int getCompression() { + TIFFField f + = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION); + if (f == null) { + return BaselineTIFFTagSet.COMPRESSION_NONE; + } else { + return f.getAsInt(0); + } + } + + public int getWidth(int imageIndex) throws IOException { + seekToImage(imageIndex); + return getWidth(); + } + + public int getHeight(int imageIndex) throws IOException { + seekToImage(imageIndex); + return getHeight(); + } + + /** + * Initializes these instance variables from the image metadata: + *
+     * compression
+     * width
+     * height
+     * samplesPerPixel
+     * numBands
+     * colorMap
+     * photometricInterpretation
+     * sampleFormat
+     * bitsPerSample
+     * extraSamples
+     * tileOrStripWidth
+     * tileOrStripHeight
+     * 
+ */ + private void initializeFromMetadata() throws IIOException { + TIFFField f; + + // Compression + f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION); + if (f == null) { + processWarningOccurred("Compression field is missing; assuming no compression"); + compression = BaselineTIFFTagSet.COMPRESSION_NONE; + } else { + compression = f.getAsInt(0); + } + + // Whether key dimensional information is absent. + boolean isMissingDimension = false; + + // ImageWidth -> width + f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH); + if (f != null) { + this.width = f.getAsInt(0); + } else { + processWarningOccurred("ImageWidth field is missing."); + isMissingDimension = true; + } + + // ImageLength -> height + f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH); + if (f != null) { + this.height = f.getAsInt(0); + } else { + processWarningOccurred("ImageLength field is missing."); + isMissingDimension = true; + } + + // SamplesPerPixel + f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL); + if (f != null) { + samplesPerPixel = f.getAsInt(0); + } else { + samplesPerPixel = 1; + isMissingDimension = true; + } + + // If any dimension is missing and there is a JPEG stream available + // get the information from it. + int defaultBitDepth = 1; + if (isMissingDimension + && (f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT)) != null) { + Iterator iter = ImageIO.getImageReadersByFormatName("JPEG"); + if (iter != null && iter.hasNext()) { + ImageReader jreader = iter.next(); + try { + stream.mark(); + stream.seek(f.getAsLong(0)); + jreader.setInput(stream); + if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH) == null) { + this.width = jreader.getWidth(0); + } + if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH) == null) { + this.height = jreader.getHeight(0); + } + ImageTypeSpecifier imageType = jreader.getRawImageType(0); + if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL) == null) { + this.samplesPerPixel = + imageType != null ? + imageType.getSampleModel().getNumBands() : 3; + } + stream.reset(); + defaultBitDepth = + imageType != null ? + imageType.getColorModel().getComponentSize(0) : 8; + } catch (IOException e) { + // Ignore it and proceed: an error will occur later. + } + jreader.dispose(); + } + } + + if (samplesPerPixel < 1) { + throw new IIOException("Samples per pixel < 1!"); + } else if (samplesPerPixel > SAMPLES_PER_PIXEL_MAX) { + throw new IIOException + ("Samples per pixel (" + samplesPerPixel + + ") greater than allowed maximum (" + + SAMPLES_PER_PIXEL_MAX + ")"); + } + + // SamplesPerPixel -> numBands + numBands = samplesPerPixel; + + // ColorMap + this.colorMap = null; + f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP); + if (f != null) { + // Grab color map + colorMap = f.getAsChars(); + } + + // PhotometricInterpretation + f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION); + if (f == null) { + if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE + || compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4 + || compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) { + processWarningOccurred("PhotometricInterpretation field is missing; " + + "assuming WhiteIsZero"); + photometricInterpretation + = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO; + } else if (this.colorMap != null) { + photometricInterpretation + = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR; + } else if (samplesPerPixel == 3 || samplesPerPixel == 4) { + photometricInterpretation + = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB; + } else { + processWarningOccurred("PhotometricInterpretation field is missing; " + + "assuming BlackIsZero"); + photometricInterpretation + = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO; + } + } else { + photometricInterpretation = f.getAsInt(0); + } + + // SampleFormat + boolean replicateFirst = false; + int first = -1; + + f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT); + sampleFormat = new int[samplesPerPixel]; + replicateFirst = false; + if (f == null) { + replicateFirst = true; + first = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED; + } else if (f.getCount() != samplesPerPixel) { + replicateFirst = true; + first = f.getAsInt(0); + } + + for (int i = 0; i < samplesPerPixel; i++) { + sampleFormat[i] = replicateFirst ? first : f.getAsInt(i); + if (sampleFormat[i] + != BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER + && sampleFormat[i] + != BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER + && sampleFormat[i] + != BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT + && sampleFormat[i] + != BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED) { + processWarningOccurred( + "Illegal value for SAMPLE_FORMAT, assuming SAMPLE_FORMAT_UNDEFINED"); + sampleFormat[i] = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED; + } + } + + // BitsPerSample + f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); + this.bitsPerSample = new int[samplesPerPixel]; + replicateFirst = false; + if (f == null) { + replicateFirst = true; + first = defaultBitDepth; + } else if (f.getCount() != samplesPerPixel) { + replicateFirst = true; + first = f.getAsInt(0); + } + + for (int i = 0; i < samplesPerPixel; i++) { + // Replicate initial value if not enough values provided + bitsPerSample[i] = replicateFirst ? first : f.getAsInt(i); + if (bitsPerSample[i] > BITS_PER_SAMPLE_MAX) { + throw new IIOException + ("Bits per sample (" + bitsPerSample[i] + + ") greater than allowed maximum (" + + BITS_PER_SAMPLE_MAX + ")"); + } + } + + // ExtraSamples + this.extraSamples = null; + f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES); + if (f != null) { + extraSamples = f.getAsInts(); + } + } + + public Iterator getImageTypes(int imageIndex) throws IIOException { + List l; // List of ImageTypeSpecifiers + + Integer imageIndexInteger = Integer.valueOf(imageIndex); + if (imageTypeMap.containsKey(imageIndexInteger)) { + // Return the cached ITS List. + l = imageTypeMap.get(imageIndexInteger); + } else { + // Create a new ITS List. + l = new ArrayList(1); + + // Create the ITS and cache if for later use so that this method + // always returns an Iterator containing the same ITS objects. + seekToImage(imageIndex); + ImageTypeSpecifier itsRaw + = TIFFDecompressor.getRawImageTypeSpecifier(photometricInterpretation, + compression, + samplesPerPixel, + bitsPerSample, + sampleFormat, + extraSamples, + colorMap); + + // Check for an ICCProfile field. + TIFFField iccProfileField + = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE); + + // If an ICCProfile field is present change the ImageTypeSpecifier + // to use it if the data layout is component type. + if (iccProfileField != null + && itsRaw.getColorModel() instanceof ComponentColorModel) { + // Create a ColorSpace from the profile. + byte[] iccProfileValue = iccProfileField.getAsBytes(); + ICC_Profile iccProfile + = ICC_Profile.getInstance(iccProfileValue); + ICC_ColorSpace iccColorSpace + = new ICC_ColorSpace(iccProfile); + + // Get the raw sample and color information. + ColorModel cmRaw = itsRaw.getColorModel(); + ColorSpace csRaw = cmRaw.getColorSpace(); + SampleModel smRaw = itsRaw.getSampleModel(); + + // Get the number of samples per pixel and the number + // of color components. + int numBands = smRaw.getNumBands(); + int numComponents = iccColorSpace.getNumComponents(); + + // Replace the ColorModel with the ICC ColorModel if the + // numbers of samples and color components are amenable. + if (numBands == numComponents + || numBands == numComponents + 1) { + // Set alpha flags. + boolean hasAlpha = numComponents != numBands; + boolean isAlphaPre + = hasAlpha && cmRaw.isAlphaPremultiplied(); + + // Create a ColorModel of the same class and with + // the same transfer type. + ColorModel iccColorModel + = new ComponentColorModel(iccColorSpace, + cmRaw.getComponentSize(), + hasAlpha, + isAlphaPre, + cmRaw.getTransparency(), + cmRaw.getTransferType()); + + // Prepend the ICC profile-based ITS to the List. The + // ColorModel and SampleModel are guaranteed to be + // compatible as the old and new ColorModels are both + // ComponentColorModels with the same transfer type + // and the same number of components. + l.add(new ImageTypeSpecifier(iccColorModel, smRaw)); + + // Append the raw ITS to the List if and only if its + // ColorSpace has the same type and number of components + // as the ICC ColorSpace. + if (csRaw.getType() == iccColorSpace.getType() + && csRaw.getNumComponents() + == iccColorSpace.getNumComponents()) { + l.add(itsRaw); + } + } else { // ICCProfile not compatible with SampleModel. + // Append the raw ITS to the List. + l.add(itsRaw); + } + } else { // No ICCProfile field or raw ColorModel not component. + // Append the raw ITS to the List. + l.add(itsRaw); + } + + // Cache the ITS List. + imageTypeMap.put(imageIndexInteger, l); + } + + return l.iterator(); + } + + public IIOMetadata getImageMetadata(int imageIndex) throws IIOException { + seekToImage(imageIndex); + TIFFImageMetadata im + = new TIFFImageMetadata(imageMetadata.getRootIFD().getTagSetList()); + Node root + = imageMetadata.getAsTree(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME); + im.setFromTree(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME, root); + return im; + } + + public IIOMetadata getStreamMetadata(int imageIndex) throws IIOException { + readHeader(); + TIFFStreamMetadata sm = new TIFFStreamMetadata(); + Node root = sm.getAsTree(TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME); + sm.setFromTree(TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME, root); + return sm; + } + + public boolean isRandomAccessEasy(int imageIndex) throws IOException { + if (currIndex != -1) { + seekToImage(currIndex); + return getCompression() == BaselineTIFFTagSet.COMPRESSION_NONE; + } else { + return false; + } + } + + // Thumbnails + public boolean readSupportsThumbnails() { + return false; + } + + public boolean hasThumbnails(int imageIndex) { + return false; + } + + public int getNumThumbnails(int imageIndex) throws IOException { + return 0; + } + + public ImageReadParam getDefaultReadParam() { + return new TIFFImageReadParam(); + } + + public boolean isImageTiled(int imageIndex) throws IOException { + seekToImage(imageIndex); + + TIFFField f + = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH); + return f != null; + } + + public int getTileWidth(int imageIndex) throws IOException { + seekToImage(imageIndex); + return getTileOrStripWidth(); + } + + public int getTileHeight(int imageIndex) throws IOException { + seekToImage(imageIndex); + return getTileOrStripHeight(); + } + + public BufferedImage readTile(int imageIndex, int tileX, int tileY) + throws IOException { + + int w = getWidth(imageIndex); + int h = getHeight(imageIndex); + int tw = getTileWidth(imageIndex); + int th = getTileHeight(imageIndex); + + int x = tw * tileX; + int y = th * tileY; + + if (tileX < 0 || tileY < 0 || x >= w || y >= h) { + throw new IllegalArgumentException("Tile indices are out of bounds!"); + } + + if (x + tw > w) { + tw = w - x; + } + + if (y + th > h) { + th = h - y; + } + + ImageReadParam param = getDefaultReadParam(); + Rectangle tileRect = new Rectangle(x, y, tw, th); + param.setSourceRegion(tileRect); + + return read(imageIndex, param); + } + + public boolean canReadRaster() { + return false; + } + + public Raster readRaster(int imageIndex, ImageReadParam param) + throws IOException { + throw new UnsupportedOperationException(); + } + + private int[] sourceBands; + private int[] destinationBands; + + private TIFFDecompressor decompressor; + + // floor(num/den) + private static int ifloor(int num, int den) { + if (num < 0) { + num -= den - 1; + } + return num / den; + } + + // ceil(num/den) + private static int iceil(int num, int den) { + if (num > 0) { + num += den - 1; + } + return num / den; + } + + private void prepareRead(int imageIndex, ImageReadParam param) + throws IOException { + if (stream == null) { + throw new IllegalStateException("Input not set!"); + } + + // A null ImageReadParam means we use the default + if (param == null) { + param = getDefaultReadParam(); + } + + this.imageReadParam = param; + + seekToImage(imageIndex); + + this.tileOrStripWidth = getTileOrStripWidth(); + this.tileOrStripHeight = getTileOrStripHeight(); + this.planarConfiguration = getPlanarConfiguration(); + + this.sourceBands = param.getSourceBands(); + if (sourceBands == null) { + sourceBands = new int[numBands]; + for (int i = 0; i < numBands; i++) { + sourceBands[i] = i; + } + } + + // Initialize the destination image + Iterator imageTypes = getImageTypes(imageIndex); + ImageTypeSpecifier theImageType + = ImageUtil.getDestinationType(param, imageTypes); + + int destNumBands = theImageType.getSampleModel().getNumBands(); + + this.destinationBands = param.getDestinationBands(); + if (destinationBands == null) { + destinationBands = new int[destNumBands]; + for (int i = 0; i < destNumBands; i++) { + destinationBands[i] = i; + } + } + + if (sourceBands.length != destinationBands.length) { + throw new IllegalArgumentException( + "sourceBands.length != destinationBands.length"); + } + + for (int i = 0; i < sourceBands.length; i++) { + int sb = sourceBands[i]; + if (sb < 0 || sb >= numBands) { + throw new IllegalArgumentException( + "Source band out of range!"); + } + int db = destinationBands[i]; + if (db < 0 || db >= destNumBands) { + throw new IllegalArgumentException( + "Destination band out of range!"); + } + } + } + + public RenderedImage readAsRenderedImage(int imageIndex, + ImageReadParam param) + throws IOException { + prepareRead(imageIndex, param); + return new TIFFRenderedImage(this, imageIndex, imageReadParam, + width, height); + } + + private void decodeTile(int ti, int tj, int band) throws IOException { + // Compute the region covered by the strip or tile + Rectangle tileRect = new Rectangle(ti * tileOrStripWidth, + tj * tileOrStripHeight, + tileOrStripWidth, + tileOrStripHeight); + + // Clip against the image bounds if the image is not tiled. If it + // is tiled, the tile may legally extend beyond the image bounds. + if (!isImageTiled(currIndex)) { + tileRect + = tileRect.intersection(new Rectangle(0, 0, width, height)); + } + + // Return if the intersection is empty. + if (tileRect.width <= 0 || tileRect.height <= 0) { + return; + } + + int srcMinX = tileRect.x; + int srcMinY = tileRect.y; + int srcWidth = tileRect.width; + int srcHeight = tileRect.height; + + // Determine dest region that can be derived from the + // source region + dstMinX = iceil(srcMinX - sourceXOffset, srcXSubsampling); + int dstMaxX = ifloor(srcMinX + srcWidth - 1 - sourceXOffset, + srcXSubsampling); + + dstMinY = iceil(srcMinY - sourceYOffset, srcYSubsampling); + int dstMaxY = ifloor(srcMinY + srcHeight - 1 - sourceYOffset, + srcYSubsampling); + + dstWidth = dstMaxX - dstMinX + 1; + dstHeight = dstMaxY - dstMinY + 1; + + dstMinX += dstXOffset; + dstMinY += dstYOffset; + + // Clip against image bounds + Rectangle dstRect = new Rectangle(dstMinX, dstMinY, + dstWidth, dstHeight); + dstRect + = dstRect.intersection(theImage.getRaster().getBounds()); + + dstMinX = dstRect.x; + dstMinY = dstRect.y; + dstWidth = dstRect.width; + dstHeight = dstRect.height; + + if (dstWidth <= 0 || dstHeight <= 0) { + return; + } + + // Backwards map dest region to source to determine + // active source region + int activeSrcMinX = (dstMinX - dstXOffset) * srcXSubsampling + + sourceXOffset; + int sxmax + = (dstMinX + dstWidth - 1 - dstXOffset) * srcXSubsampling + + sourceXOffset; + int activeSrcWidth = sxmax - activeSrcMinX + 1; + + int activeSrcMinY = (dstMinY - dstYOffset) * srcYSubsampling + + sourceYOffset; + int symax + = (dstMinY + dstHeight - 1 - dstYOffset) * srcYSubsampling + + sourceYOffset; + int activeSrcHeight = symax - activeSrcMinY + 1; + + decompressor.setSrcMinX(srcMinX); + decompressor.setSrcMinY(srcMinY); + decompressor.setSrcWidth(srcWidth); + decompressor.setSrcHeight(srcHeight); + + decompressor.setDstMinX(dstMinX); + decompressor.setDstMinY(dstMinY); + decompressor.setDstWidth(dstWidth); + decompressor.setDstHeight(dstHeight); + + decompressor.setActiveSrcMinX(activeSrcMinX); + decompressor.setActiveSrcMinY(activeSrcMinY); + decompressor.setActiveSrcWidth(activeSrcWidth); + decompressor.setActiveSrcHeight(activeSrcHeight); + + int tileIndex = tj * tilesAcross + ti; + + if (planarConfiguration + == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) { + tileIndex += band * tilesAcross * tilesDown; + } + + long offset = getTileOrStripOffset(tileIndex); + long byteCount = getTileOrStripByteCount(tileIndex); + + decompressor.setStream(stream); + decompressor.setOffset(offset); + decompressor.setByteCount((int) byteCount); + + decompressor.beginDecoding(); + + stream.mark(); + decompressor.decode(); + stream.reset(); + } + + private void reportProgress() { + // Report image progress/update to listeners after each tile + pixelsRead += dstWidth * dstHeight; + processImageProgress(100.0f * pixelsRead / pixelsToRead); + processImageUpdate(theImage, + dstMinX, dstMinY, dstWidth, dstHeight, + 1, 1, + destinationBands); + } + + public BufferedImage read(int imageIndex, ImageReadParam param) + throws IOException { + prepareRead(imageIndex, param); + this.theImage = getDestination(param, + getImageTypes(imageIndex), + width, height); + + srcXSubsampling = imageReadParam.getSourceXSubsampling(); + srcYSubsampling = imageReadParam.getSourceYSubsampling(); + + Point p = imageReadParam.getDestinationOffset(); + dstXOffset = p.x; + dstYOffset = p.y; + + // This could probably be made more efficient... + Rectangle srcRegion = new Rectangle(0, 0, 0, 0); + Rectangle destRegion = new Rectangle(0, 0, 0, 0); + + computeRegions(imageReadParam, width, height, theImage, + srcRegion, destRegion); + + // Initial source pixel, taking source region and source + // subsamplimg offsets into account + sourceXOffset = srcRegion.x; + sourceYOffset = srcRegion.y; + + pixelsToRead = destRegion.width * destRegion.height; + pixelsRead = 0; + + processImageStarted(imageIndex); + processImageProgress(0.0f); + + tilesAcross = (width + tileOrStripWidth - 1) / tileOrStripWidth; + tilesDown = (height + tileOrStripHeight - 1) / tileOrStripHeight; + + int compression = getCompression(); + + // Set the decompressor + if (compression == BaselineTIFFTagSet.COMPRESSION_NONE) { + // Get the fillOrder field. + TIFFField fillOrderField + = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER); + + // Set the decompressor based on the fill order. + if (fillOrderField != null && fillOrderField.getAsInt(0) == 2) { + this.decompressor = new TIFFLSBDecompressor(); + } else { + this.decompressor = new TIFFNullDecompressor(); + } + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) { + this.decompressor = new TIFFFaxDecompressor(); + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) { + this.decompressor = new TIFFFaxDecompressor(); + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) { + this.decompressor = new TIFFFaxDecompressor(); + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_PACKBITS) { + this.decompressor = new TIFFPackBitsDecompressor(); + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_LZW) { + TIFFField predictorField + = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR); + int predictor = ((predictorField == null) + ? BaselineTIFFTagSet.PREDICTOR_NONE + : predictorField.getAsInt(0)); + this.decompressor = new TIFFLZWDecompressor(predictor); + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_JPEG) { + this.decompressor = new TIFFJPEGDecompressor(); + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_ZLIB + || compression + == BaselineTIFFTagSet.COMPRESSION_DEFLATE) { + TIFFField predictorField + = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR); + int predictor = ((predictorField == null) + ? BaselineTIFFTagSet.PREDICTOR_NONE + : predictorField.getAsInt(0)); + this.decompressor = new TIFFDeflateDecompressor(predictor); + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { + TIFFField JPEGProcField + = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_PROC); + if (JPEGProcField == null) { + processWarningOccurred("JPEGProc field missing; assuming baseline sequential JPEG process."); + } else if (JPEGProcField.getAsInt(0) + != BaselineTIFFTagSet.JPEG_PROC_BASELINE) { + throw new IIOException("Old-style JPEG supported for baseline sequential JPEG process only!"); + } + this.decompressor = new TIFFOldJPEGDecompressor(); + //throw new IIOException("Old-style JPEG not supported!"); + } else { + throw new IIOException("Unsupported compression type (tag value = " + + compression + ")!"); + } + + if (photometricInterpretation + == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR + && compression != BaselineTIFFTagSet.COMPRESSION_JPEG + && compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { + boolean convertYCbCrToRGB + = theImage.getColorModel().getColorSpace().getType() + == ColorSpace.TYPE_RGB; + TIFFDecompressor wrappedDecompressor + = this.decompressor instanceof TIFFNullDecompressor + ? null : this.decompressor; + this.decompressor + = new TIFFYCbCrDecompressor(wrappedDecompressor, + convertYCbCrToRGB); + } + + TIFFColorConverter colorConverter = null; + if (photometricInterpretation + == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB + && theImage.getColorModel().getColorSpace().getType() + == ColorSpace.TYPE_RGB) { + colorConverter = new TIFFCIELabColorConverter(); + } else if (photometricInterpretation + == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR + && !(this.decompressor instanceof TIFFYCbCrDecompressor) + && compression != BaselineTIFFTagSet.COMPRESSION_JPEG + && compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { + colorConverter = new TIFFYCbCrColorConverter(imageMetadata); + } + + decompressor.setReader(this); + decompressor.setMetadata(imageMetadata); + decompressor.setImage(theImage); + + decompressor.setPhotometricInterpretation(photometricInterpretation); + decompressor.setCompression(compression); + decompressor.setSamplesPerPixel(samplesPerPixel); + decompressor.setBitsPerSample(bitsPerSample); + decompressor.setSampleFormat(sampleFormat); + decompressor.setExtraSamples(extraSamples); + decompressor.setColorMap(colorMap); + + decompressor.setColorConverter(colorConverter); + + decompressor.setSourceXOffset(sourceXOffset); + decompressor.setSourceYOffset(sourceYOffset); + decompressor.setSubsampleX(srcXSubsampling); + decompressor.setSubsampleY(srcYSubsampling); + + decompressor.setDstXOffset(dstXOffset); + decompressor.setDstYOffset(dstYOffset); + + decompressor.setSourceBands(sourceBands); + decompressor.setDestinationBands(destinationBands); + + // Compute bounds on the tile indices for this source region. + int minTileX + = TIFFImageWriter.XToTileX(srcRegion.x, 0, tileOrStripWidth); + int minTileY + = TIFFImageWriter.YToTileY(srcRegion.y, 0, tileOrStripHeight); + int maxTileX + = TIFFImageWriter.XToTileX(srcRegion.x + srcRegion.width - 1, + 0, tileOrStripWidth); + int maxTileY + = TIFFImageWriter.YToTileY(srcRegion.y + srcRegion.height - 1, + 0, tileOrStripHeight); + + if (planarConfiguration + == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) { + + decompressor.setPlanar(true); + + int[] sb = new int[1]; + int[] db = new int[1]; + for (int tj = minTileY; tj <= maxTileY; tj++) { + for (int ti = minTileX; ti <= maxTileX; ti++) { + for (int band = 0; band < numBands; band++) { + sb[0] = sourceBands[band]; + decompressor.setSourceBands(sb); + db[0] = destinationBands[band]; + decompressor.setDestinationBands(db); + + decodeTile(ti, tj, band); + } + + reportProgress(); + } + } + } else { + for (int tj = minTileY; tj <= maxTileY; tj++) { + for (int ti = minTileX; ti <= maxTileX; ti++) { + decodeTile(ti, tj, -1); + + reportProgress(); + } + } + } + + if (abortRequested()) { + processReadAborted(); + } else { + processImageComplete(); + } + + return theImage; + } + + public void reset() { + super.reset(); + resetLocal(); + } + + protected void resetLocal() { + stream = null; + gotHeader = false; + imageReadParam = getDefaultReadParam(); + streamMetadata = null; + currIndex = -1; + imageMetadata = null; + imageStartPosition = new ArrayList(); + numImages = -1; + imageTypeMap = new HashMap>(); + width = -1; + height = -1; + numBands = -1; + tileOrStripWidth = -1; + tileOrStripHeight = -1; + planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY; + } + + /** + * Package scope method to allow decompressors, for example, to emit warning + * messages. + */ + void forwardWarningMessage(String warning) { + processWarningOccurred(warning); + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReaderSpi.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReaderSpi.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.io.IOException; +import java.util.Locale; +import javax.imageio.ImageReader; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.spi.ServiceRegistry; +import javax.imageio.stream.ImageInputStream; + +public class TIFFImageReaderSpi extends ImageReaderSpi { + + private boolean registered = false; + + public TIFFImageReaderSpi() { + super("Oracle Corporation", + "1.0", + new String[] {"tif", "TIF", "tiff", "TIFF"}, + new String[] {"tif", "tiff"}, + new String[] {"image/tiff"}, + "com.sun.imageio.plugins.tiff.TIFFImageReader", + new Class[] { ImageInputStream.class }, + new String[] {"com.sun.imageio.plugins.tiff.TIFFImageWriterSpi"}, + false, + TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME, + "com.sun.imageio.plugins.tiff.TIFFStreamMetadataFormat", + null, null, + true, + TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME, + "com.sun.imageio.plugins.tiff.TIFFImageMetadataFormat", + null, null + ); + } + + public String getDescription(Locale locale) { + return "Standard TIFF image reader"; + } + + public boolean canDecodeInput(Object input) throws IOException { + if (!(input instanceof ImageInputStream)) { + return false; + } + + ImageInputStream stream = (ImageInputStream)input; + byte[] b = new byte[4]; + stream.mark(); + stream.readFully(b); + stream.reset(); + + return ((b[0] == (byte)0x49 && b[1] == (byte)0x49 && + b[2] == (byte)0x2a && b[3] == (byte)0x00) || + (b[0] == (byte)0x4d && b[1] == (byte)0x4d && + b[2] == (byte)0x00 && b[3] == (byte)0x2a)); + } + + public ImageReader createReaderInstance(Object extension) { + return new TIFFImageReader(this); + } + + public void onRegistration(ServiceRegistry registry, + Class category) { + if (registered) { + return; + } + + registered = true; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriteParam.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriteParam.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.util.Locale; +import javax.imageio.ImageWriteParam; + +/** + * A subclass of {@link ImageWriteParam ImageWriteParam} + * allowing control over the TIFF writing process. The set of innately + * supported compression types is listed in the following table: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Supported Compression Types
Compression Type Description Reference
CCITT RLEModified Huffman compressionTIFF 6.0 Specification, Section 10
CCITT T.4CCITT T.4 bilevel encoding/Group 3 facsimile compressionTIFF 6.0 Specification, Section 11
CCITT T.6CCITT T.6 bilevel encoding/Group 4 facsimile compressionTIFF 6.0 Specification, Section 11
LZWLZW compressionTIFF 6.0 Specification, Section 13
JPEG"New" JPEG-in-TIFF compressionTIFF + * Technical Note #2
ZLib"Deflate/Inflate" compression (see note following this table) + * Adobe Photoshop® TIFF Technical Notes (PDF)
PackBitsByte-oriented, run length compressionTIFF 6.0 Specification, Section 9
Deflate"Zip-in-TIFF" compression (see note following this table) + * ZLIB Compressed Data Format Specification, + * + * DEFLATE Compressed Data Format Specification
Exif JPEGExif-specific JPEG compression (see note following this table)Exif 2.2 Specification + * (PDF), section 4.5.5, "Basic Structure of Thumbnail Data"
+ * + *

+ * Old-style JPEG compression as described in section 22 of the TIFF 6.0 + * Specification is not supported. + *

+ * + *

The CCITT compression types are applicable to bilevel (1-bit) + * images only. The JPEG compression type is applicable to byte + * grayscale (1-band) and RGB (3-band) images only.

+ * + *

+ * ZLib and Deflate compression are identical except for the value of the + * TIFF Compression field: for ZLib the Compression field has value 8 + * whereas for Deflate it has value 32946 (0x80b2). In both cases each + * image segment (strip or tile) is written as a single complete zlib data + * stream. + *

+ * + *

+ * "Exif JPEG" is a compression type used when writing the contents of an + * APP1 Exif marker segment for inclusion in a JPEG native image metadata + * tree. The contents appended to the output when this compression type is + * used are a function of whether an empty or non-empty image is written. + * If the image is empty, then a TIFF IFD adhering to the specification of + * a compressed Exif primary IFD is appended. If the image is non-empty, + * then a complete IFD and image adhering to the specification of a + * compressed Exif thumbnail IFD and image are appended. Note that the + * data of the empty image may not later be appended using the pixel + * replacement capability of the TIFF writer. + *

+ * + *

If ZLib/Deflate or JPEG compression is used, the compression quality + * may be set. For ZLib/Deflate the supplied floating point quality value is + * rescaled to the range [1, 9] and truncated to an integer + * to derive the Deflate compression level. For JPEG the floating point + * quality value is passed directly to the JPEG writer plug-in which + * interprets it in the usual way.

+ * + *

The canWriteTiles and + * canWriteCompressed methods will return + * true; the canOffsetTiles and + * canWriteProgressive methods will return + * false.

+ * + *

If tiles are being written, then each of their dimensions will be + * rounded to the nearest multiple of 16 per the TIFF specification. If + * JPEG-in-TIFF compression is being used, and tiles are being written + * each tile dimension will be rounded to the nearest multiple of 8 times + * the JPEG minimum coded unit (MCU) in that dimension. If JPEG-in-TIFF + * compression is being used and strips are being written, the number of + * rows per strip is rounded to a multiple of 8 times the maximum MCU over + * both dimensions.

+ */ +public class TIFFImageWriteParam extends ImageWriteParam { + + /** + * Constructs a TIFFImageWriteParam instance + * for a given Locale. + * + * @param locale the Locale for which messages + * should be localized. + */ + public TIFFImageWriteParam(Locale locale) { + super(locale); + this.canWriteCompressed = true; + this.canWriteTiles = true; + this.compressionTypes = TIFFImageWriter.TIFFCompressionTypes; + }; +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriter.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,3610 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.color.ColorSpace; +import java.awt.color.ICC_ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.IndexColorModel; +import java.awt.image.RenderedImage; +import java.awt.image.Raster; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.imageio.IIOException; +import javax.imageio.IIOImage; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; +import org.w3c.dom.Node; +import com.sun.imageio.plugins.common.ImageUtil; +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.plugins.tiff.ExifParentTIFFTagSet; +import javax.imageio.plugins.tiff.ExifTIFFTagSet; +import javax.imageio.plugins.tiff.TIFFField; +import javax.imageio.plugins.tiff.TIFFTag; +import javax.imageio.plugins.tiff.TIFFTagSet; +import com.sun.imageio.plugins.common.SimpleRenderedImage; +import com.sun.imageio.plugins.common.SingleTileRenderedImage; +import java.nio.charset.StandardCharsets; + +public class TIFFImageWriter extends ImageWriter { + + static final String EXIF_JPEG_COMPRESSION_TYPE = "Exif JPEG"; + + private static final int DEFAULT_BYTES_PER_STRIP = 8192; + + /** + * Supported TIFF compression types. + */ + static final String[] TIFFCompressionTypes = { + "CCITT RLE", + "CCITT T.4", + "CCITT T.6", + "LZW", + // "Old JPEG", + "JPEG", + "ZLib", + "PackBits", + "Deflate", + EXIF_JPEG_COMPRESSION_TYPE + }; + + // + // The lengths of the arrays 'compressionTypes', + // 'isCompressionLossless', and 'compressionNumbers' + // must be equal. + // + + /** + * Known TIFF compression types. + */ + static final String[] compressionTypes = { + "CCITT RLE", + "CCITT T.4", + "CCITT T.6", + "LZW", + "Old JPEG", + "JPEG", + "ZLib", + "PackBits", + "Deflate", + EXIF_JPEG_COMPRESSION_TYPE + }; + + /** + * Lossless flag for known compression types. + */ + static final boolean[] isCompressionLossless = { + true, // RLE + true, // T.4 + true, // T.6 + true, // LZW + false, // Old JPEG + false, // JPEG + true, // ZLib + true, // PackBits + true, // DEFLATE + false // Exif JPEG + }; + + /** + * Compression tag values for known compression types. + */ + static final int[] compressionNumbers = { + BaselineTIFFTagSet.COMPRESSION_CCITT_RLE, + BaselineTIFFTagSet.COMPRESSION_CCITT_T_4, + BaselineTIFFTagSet.COMPRESSION_CCITT_T_6, + BaselineTIFFTagSet.COMPRESSION_LZW, + BaselineTIFFTagSet.COMPRESSION_OLD_JPEG, + BaselineTIFFTagSet.COMPRESSION_JPEG, + BaselineTIFFTagSet.COMPRESSION_ZLIB, + BaselineTIFFTagSet.COMPRESSION_PACKBITS, + BaselineTIFFTagSet.COMPRESSION_DEFLATE, + BaselineTIFFTagSet.COMPRESSION_OLD_JPEG, // Exif JPEG + }; + + private ImageOutputStream stream; + private long headerPosition; + private RenderedImage image; + private ImageTypeSpecifier imageType; + private ByteOrder byteOrder; + private ImageWriteParam param; + private TIFFCompressor compressor; + private TIFFColorConverter colorConverter; + + private TIFFStreamMetadata streamMetadata; + private TIFFImageMetadata imageMetadata; + + private int sourceXOffset; + private int sourceYOffset; + private int sourceWidth; + private int sourceHeight; + private int[] sourceBands; + private int periodX; + private int periodY; + + private int bitDepth; // bits per channel + private int numBands; + private int tileWidth; + private int tileLength; + private int tilesAcross; + private int tilesDown; + + private int[] sampleSize = null; // Input sample size per band, in bits + private int scalingBitDepth = -1; // Output bit depth of the scaling tables + private boolean isRescaling = false; // Whether rescaling is needed. + + private boolean isBilevel; // Whether image is bilevel + private boolean isImageSimple; // Whether image can be copied into directly + private boolean isInverted; // Whether photometric inversion is required + + private boolean isTiled; // Whether the image is tiled (true) or stipped (false). + + private int nativePhotometricInterpretation; + private int photometricInterpretation; + + private char[] bitsPerSample; // Output sample size per band + private int sampleFormat = + BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED; // Output sample format + + // Tables for 1, 2, 4, or 8 bit output + private byte[][] scale = null; // 8 bit table + private byte[] scale0 = null; // equivalent to scale[0] + + // Tables for 16 bit output + private byte[][] scaleh = null; // High bytes of output + private byte[][] scalel = null; // Low bytes of output + + private int compression; + private int predictor; + + private int totalPixels; + private int pixelsDone; + + private long nextIFDPointerPos; + + // Next available space. + private long nextSpace = 0L; + + // Whether a sequence is being written. + private boolean isWritingSequence = false; + private boolean isInsertingEmpty = false; + private boolean isWritingEmpty = false; + + private int currentImage = 0; + + /** + * Converts a pixel's X coordinate into a horizontal tile index + * relative to a given tile grid layout specified by its X offset + * and tile width. + * + *

If tileWidth < 0, the results of this method + * are undefined. If tileWidth == 0, an + * ArithmeticException will be thrown. + * + * @throws ArithmeticException If tileWidth == 0. + */ + public static int XToTileX(int x, int tileGridXOffset, int tileWidth) { + x -= tileGridXOffset; + if (x < 0) { + x += 1 - tileWidth; // force round to -infinity (ceiling) + } + return x/tileWidth; + } + + /** + * Converts a pixel's Y coordinate into a vertical tile index + * relative to a given tile grid layout specified by its Y offset + * and tile height. + * + *

If tileHeight < 0, the results of this method + * are undefined. If tileHeight == 0, an + * ArithmeticException will be thrown. + * + * @throws ArithmeticException If tileHeight == 0. + */ + public static int YToTileY(int y, int tileGridYOffset, int tileHeight) { + y -= tileGridYOffset; + if (y < 0) { + y += 1 - tileHeight; // force round to -infinity (ceiling) + } + return y/tileHeight; + } + + public TIFFImageWriter(ImageWriterSpi originatingProvider) { + super(originatingProvider); + } + + public ImageWriteParam getDefaultWriteParam() { + return new TIFFImageWriteParam(getLocale()); + } + + public void setOutput(Object output) { + super.setOutput(output); + + if (output != null) { + if (!(output instanceof ImageOutputStream)) { + throw new IllegalArgumentException + ("output not an ImageOutputStream!"); + } + this.stream = (ImageOutputStream)output; + + // + // The output is expected to be positioned at a TIFF header + // or at some arbitrary location which may or may not be + // the EOF. In the former case the writer should be able + // either to overwrite the existing sequence or append to it. + // + + // Set the position of the header and the next available space. + try { + headerPosition = this.stream.getStreamPosition(); + try { + // Read byte order and magic number. + byte[] b = new byte[4]; + stream.readFully(b); + + // Check bytes for TIFF header. + if((b[0] == (byte)0x49 && b[1] == (byte)0x49 && + b[2] == (byte)0x2a && b[3] == (byte)0x00) || + (b[0] == (byte)0x4d && b[1] == (byte)0x4d && + b[2] == (byte)0x00 && b[3] == (byte)0x2a)) { + // TIFF header. + this.nextSpace = stream.length(); + } else { + // Neither TIFF header nor EOF: overwrite. + this.nextSpace = headerPosition; + } + } catch(IOException io) { // thrown by readFully() + // At EOF or not at a TIFF header. + this.nextSpace = headerPosition; + } + stream.seek(headerPosition); + } catch(IOException ioe) { // thrown by getStreamPosition() + // Assume it's at zero. + this.nextSpace = headerPosition = 0L; + } + } else { + this.stream = null; + } + } + + public IIOMetadata + getDefaultStreamMetadata(ImageWriteParam param) { + return new TIFFStreamMetadata(); + } + + public IIOMetadata + getDefaultImageMetadata(ImageTypeSpecifier imageType, + ImageWriteParam param) { + + List tagSets = new ArrayList(1); + tagSets.add(BaselineTIFFTagSet.getInstance()); + TIFFImageMetadata imageMetadata = new TIFFImageMetadata(tagSets); + + if(imageType != null) { + TIFFImageMetadata im = + (TIFFImageMetadata)convertImageMetadata(imageMetadata, + imageType, + param); + if(im != null) { + imageMetadata = im; + } + } + + return imageMetadata; + } + + public IIOMetadata convertStreamMetadata(IIOMetadata inData, + ImageWriteParam param) { + // Check arguments. + if(inData == null) { + throw new NullPointerException("inData == null!"); + } + + // Note: param is irrelevant as it does not contain byte order. + + TIFFStreamMetadata outData = null; + if(inData instanceof TIFFStreamMetadata) { + outData = new TIFFStreamMetadata(); + outData.byteOrder = ((TIFFStreamMetadata)inData).byteOrder; + return outData; + } else if(Arrays.asList(inData.getMetadataFormatNames()).contains( + TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME)) { + outData = new TIFFStreamMetadata(); + String format = TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME; + try { + outData.mergeTree(format, inData.getAsTree(format)); + } catch(IIOInvalidTreeException e) { + return null; + } + } + + return outData; + } + + public IIOMetadata + convertImageMetadata(IIOMetadata inData, + ImageTypeSpecifier imageType, + ImageWriteParam param) { + // Check arguments. + if(inData == null) { + throw new NullPointerException("inData == null!"); + } + if(imageType == null) { + throw new NullPointerException("imageType == null!"); + } + + TIFFImageMetadata outData = null; + + // Obtain a TIFFImageMetadata object. + if(inData instanceof TIFFImageMetadata) { + // Create a new metadata object from a clone of the input IFD. + TIFFIFD inIFD = ((TIFFImageMetadata)inData).getRootIFD(); + outData = new TIFFImageMetadata(inIFD.getShallowClone()); + } else if(Arrays.asList(inData.getMetadataFormatNames()).contains( + TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME)) { + // Initialize from the native metadata form of the input tree. + try { + outData = convertNativeImageMetadata(inData); + } catch(IIOInvalidTreeException e) { + return null; + } + } else if(inData.isStandardMetadataFormatSupported()) { + // Initialize from the standard metadata form of the input tree. + try { + outData = convertStandardImageMetadata(inData); + } catch(IIOInvalidTreeException e) { + return null; + } + } + + // Update the metadata per the image type and param. + if(outData != null) { + TIFFImageWriter bogusWriter = + new TIFFImageWriter(this.originatingProvider); + bogusWriter.imageMetadata = outData; + bogusWriter.param = param; + SampleModel sm = imageType.getSampleModel(); + try { + bogusWriter.setupMetadata(imageType.getColorModel(), sm, + sm.getWidth(), sm.getHeight()); + return bogusWriter.imageMetadata; + } catch(IIOException e) { + return null; + } + } + + return outData; + } + + /** + * Converts a standard javax_imageio_1.0 tree to a + * TIFFImageMetadata object. + * + * @param inData The metadata object. + * @return a TIFFImageMetadata or null if + * the standard tree derived from the input object is null. + * @throws IllegalArgumentException if inData is + * null. + * @throws IllegalArgumentException if inData does not support + * the standard metadata format. + * @throws IIOInvalidTreeException if inData generates an + * invalid standard metadata tree. + */ + private TIFFImageMetadata convertStandardImageMetadata(IIOMetadata inData) + throws IIOInvalidTreeException { + + if(inData == null) { + throw new NullPointerException("inData == null!"); + } else if(!inData.isStandardMetadataFormatSupported()) { + throw new IllegalArgumentException + ("inData does not support standard metadata format!"); + } + + TIFFImageMetadata outData = null; + + String formatName = IIOMetadataFormatImpl.standardMetadataFormatName; + Node tree = inData.getAsTree(formatName); + if (tree != null) { + List tagSets = new ArrayList(1); + tagSets.add(BaselineTIFFTagSet.getInstance()); + outData = new TIFFImageMetadata(tagSets); + outData.setFromTree(formatName, tree); + } + + return outData; + } + + /** + * Converts a native + * javax_imageio_tiff_image_1.0 tree to a + * TIFFImageMetadata object. + * + * @param inData The metadata object. + * @return a TIFFImageMetadata or null if + * the native tree derived from the input object is null. + * @throws IllegalArgumentException if inData is + * null or does not support the native metadata format. + * @throws IIOInvalidTreeException if inData generates an + * invalid native metadata tree. + */ + private TIFFImageMetadata convertNativeImageMetadata(IIOMetadata inData) + throws IIOInvalidTreeException { + + if(inData == null) { + throw new NullPointerException("inData == null!"); + } else if(!Arrays.asList(inData.getMetadataFormatNames()).contains( + TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME)) { + throw new IllegalArgumentException + ("inData does not support native metadata format!"); + } + + TIFFImageMetadata outData = null; + + String formatName = TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME; + Node tree = inData.getAsTree(formatName); + if (tree != null) { + List tagSets = new ArrayList(1); + tagSets.add(BaselineTIFFTagSet.getInstance()); + outData = new TIFFImageMetadata(tagSets); + outData.setFromTree(formatName, tree); + } + + return outData; + } + + /** + * Sets up the output metadata adding, removing, and overriding fields + * as needed. The destination image dimensions are provided as parameters + * because these might differ from those of the source due to subsampling. + * + * @param cm The ColorModel of the image being written. + * @param sm The SampleModel of the image being written. + * @param destWidth The width of the written image after subsampling. + * @param destHeight The height of the written image after subsampling. + */ + void setupMetadata(ColorModel cm, SampleModel sm, + int destWidth, int destHeight) + throws IIOException { + // Get initial IFD from metadata + + // Always emit these fields: + // + // Override values from metadata: + // + // planarConfiguration -> chunky (planar not supported on output) + // + // Override values from metadata with image-derived values: + // + // bitsPerSample (if not bilivel) + // colorMap (if palette color) + // photometricInterpretation (derive from image) + // imageLength + // imageWidth + // + // rowsPerStrip \ / tileLength + // stripOffsets | OR | tileOffsets + // stripByteCounts / | tileByteCounts + // \ tileWidth + // + // + // Override values from metadata with write param values: + // + // compression + + // Use values from metadata if present for these fields, + // otherwise use defaults: + // + // resolutionUnit + // XResolution (take from metadata if present) + // YResolution + // rowsPerStrip + // sampleFormat + + TIFFIFD rootIFD = imageMetadata.getRootIFD(); + + BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance(); + + // If PlanarConfiguration field present, set value to chunky. + + TIFFField f = + rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION); + if(f != null && + f.getAsInt(0) != BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY) { + TIFFField planarConfigurationField = + new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION), + BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY); + rootIFD.addTIFFField(planarConfigurationField); + } + + char[] extraSamples = null; + + this.photometricInterpretation = -1; + boolean forcePhotometricInterpretation = false; + + f = + rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION); + if (f != null) { + photometricInterpretation = f.getAsInt(0); + if(photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR && + !(cm instanceof IndexColorModel)) { + photometricInterpretation = -1; + } else { + forcePhotometricInterpretation = true; + } + } + + int[] sampleSize = sm.getSampleSize(); + + int numBands = sm.getNumBands(); + int numExtraSamples = 0; + + // Check that numBands > 1 here because TIFF requires that + // SamplesPerPixel = numBands + numExtraSamples and numBands + // cannot be zero. + if (numBands > 1 && cm != null && cm.hasAlpha()) { + --numBands; + numExtraSamples = 1; + extraSamples = new char[1]; + if (cm.isAlphaPremultiplied()) { + extraSamples[0] = + BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA; + } else { + extraSamples[0] = + BaselineTIFFTagSet.EXTRA_SAMPLES_UNASSOCIATED_ALPHA; + } + } + + if (numBands == 3) { + this.nativePhotometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB; + if (photometricInterpretation == -1) { + photometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB; + } + } else if (sm.getNumBands() == 1 && cm instanceof IndexColorModel) { + IndexColorModel icm = (IndexColorModel)cm; + int r0 = icm.getRed(0); + int r1 = icm.getRed(1); + if (icm.getMapSize() == 2 && + (r0 == icm.getGreen(0)) && (r0 == icm.getBlue(0)) && + (r1 == icm.getGreen(1)) && (r1 == icm.getBlue(1)) && + (r0 == 0 || r0 == 255) && + (r1 == 0 || r1 == 255) && + (r0 != r1)) { + // Black/white image + + if (r0 == 0) { + nativePhotometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO; + } else { + nativePhotometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO; + } + + + // If photometricInterpretation is already set to + // WhiteIsZero or BlackIsZero, leave it alone + if (photometricInterpretation != + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO && + photometricInterpretation != + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) { + photometricInterpretation = + r0 == 0 ? + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO : + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO; + } + } else { + nativePhotometricInterpretation = + photometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR; + } + } else { + if(cm != null) { + switch(cm.getColorSpace().getType()) { + case ColorSpace.TYPE_Lab: + nativePhotometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB; + break; + case ColorSpace.TYPE_YCbCr: + nativePhotometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR; + break; + case ColorSpace.TYPE_CMYK: + nativePhotometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK; + break; + default: + nativePhotometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO; + } + } else { + nativePhotometricInterpretation = + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO; + } + if (photometricInterpretation == -1) { + photometricInterpretation = nativePhotometricInterpretation; + } + } + + // Emit compression tag + + int compressionMode = param.getCompressionMode(); + switch(compressionMode) { + case ImageWriteParam.MODE_EXPLICIT: + { + String compressionType = param.getCompressionType(); + if (compressionType == null) { + this.compression = BaselineTIFFTagSet.COMPRESSION_NONE; + } else { + // Determine corresponding compression tag value. + int len = compressionTypes.length; + for (int i = 0; i < len; i++) { + if (compressionType.equals(compressionTypes[i])) { + this.compression = compressionNumbers[i]; + } + } + } + } + break; + case ImageWriteParam.MODE_COPY_FROM_METADATA: + { + TIFFField compField = + rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION); + if(compField != null) { + this.compression = compField.getAsInt(0); + } else { + this.compression = BaselineTIFFTagSet.COMPRESSION_NONE; + } + } + break; + default: + this.compression = BaselineTIFFTagSet.COMPRESSION_NONE; + } + + TIFFField predictorField = + rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR); + if (predictorField != null) { + this.predictor = predictorField.getAsInt(0); + + // We only support Horizontal Predictor for a bitDepth of 8 + if (sampleSize[0] != 8 || + // Check the value of the tag for validity + (predictor != BaselineTIFFTagSet.PREDICTOR_NONE && + predictor != + BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING)) { + // Set to default + predictor = BaselineTIFFTagSet.PREDICTOR_NONE; + + // Emit this changed predictor value to metadata + TIFFField newPredictorField = + new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_PREDICTOR), + predictor); + rootIFD.addTIFFField(newPredictorField); + } + } + + TIFFField compressionField = + new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_COMPRESSION), + compression); + rootIFD.addTIFFField(compressionField); + + // Set Exif flag. Note that there is no way to determine definitively + // when an uncompressed thumbnail is being written as the Exif IFD + // pointer field is optional for thumbnails. + boolean isExif = false; + if(numBands == 3 && + sampleSize[0] == 8 && sampleSize[1] == 8 && sampleSize[2] == 8) { + // Three bands with 8 bits per sample. + if(rootIFD.getTIFFField(ExifParentTIFFTagSet.TAG_EXIF_IFD_POINTER) + != null) { + // Exif IFD pointer present. + if(compression == BaselineTIFFTagSet.COMPRESSION_NONE && + (photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB || + photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR)) { + // Uncompressed RGB or YCbCr. + isExif = true; + } else if(compression == + BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { + // Compressed. + isExif = true; + } + } else if(compressionMode == ImageWriteParam.MODE_EXPLICIT && + EXIF_JPEG_COMPRESSION_TYPE.equals + (param.getCompressionType())) { + // Exif IFD pointer absent but Exif JPEG compression set. + isExif = true; + } + } + + // Initialize JPEG interchange format flag which is used to + // indicate that the image is stored as a single JPEG stream. + // This flag is separated from the 'isExif' flag in case JPEG + // interchange format is eventually supported for non-Exif images. + boolean isJPEGInterchange = + isExif && compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG; + + this.compressor = null; + if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) { + compressor = new TIFFRLECompressor(); + + if (!forcePhotometricInterpretation) { + photometricInterpretation + = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO; + } + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) { + compressor = new TIFFT4Compressor(); + + if (!forcePhotometricInterpretation) { + photometricInterpretation + = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO; + } + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) { + compressor = new TIFFT6Compressor(); + + if (!forcePhotometricInterpretation) { + photometricInterpretation + = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO; + } + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_LZW) { + compressor = new TIFFLZWCompressor(predictor); + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { + if (isExif) { + compressor = new TIFFExifJPEGCompressor(param); + } else { + throw new IIOException("Old JPEG compression not supported!"); + } + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_JPEG) { + if (numBands == 3 && sampleSize[0] == 8 + && sampleSize[1] == 8 && sampleSize[2] == 8) { + photometricInterpretation + = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR; + } else if (numBands == 1 && sampleSize[0] == 8) { + photometricInterpretation + = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO; + } else { + throw new IIOException("JPEG compression supported for 1- and 3-band byte images only!"); + } + compressor = new TIFFJPEGCompressor(param); + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_ZLIB) { + compressor = new TIFFZLibCompressor(param, predictor); + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_PACKBITS) { + compressor = new TIFFPackBitsCompressor(); + } else if (compression + == BaselineTIFFTagSet.COMPRESSION_DEFLATE) { + compressor = new TIFFDeflateCompressor(param, predictor); + } else { + // Determine inverse fill setting. + f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER); + boolean inverseFill = (f != null && f.getAsInt(0) == 2); + + if (inverseFill) { + compressor = new TIFFLSBCompressor(); + } else { + compressor = new TIFFNullCompressor(); + } + } + + + this.colorConverter = null; + if (cm != null + && cm.getColorSpace().getType() == ColorSpace.TYPE_RGB) { + // + // Perform color conversion only if image has RGB color space. + // + if (photometricInterpretation + == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR + && compression + != BaselineTIFFTagSet.COMPRESSION_JPEG) { + // + // Convert RGB to YCbCr only if compression type is not + // JPEG in which case this is handled implicitly by the + // compressor. + // + colorConverter = new TIFFYCbCrColorConverter(imageMetadata); + } else if (photometricInterpretation + == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB) { + colorConverter = new TIFFCIELabColorConverter(); + } + } + + // + // Cannot at this time do YCbCr subsampling so set the + // YCbCrSubsampling field value to [1, 1] and the YCbCrPositioning + // field value to "cosited". + // + if(photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR && + compression != + BaselineTIFFTagSet.COMPRESSION_JPEG) { + // Remove old subsampling and positioning fields. + rootIFD.removeTIFFField + (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING); + rootIFD.removeTIFFField + (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING); + + // Add unity chrominance subsampling factors. + rootIFD.addTIFFField + (new TIFFField + (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING), + TIFFTag.TIFF_SHORT, + 2, + new char[] {(char)1, (char)1})); + + // Add cosited positioning. + rootIFD.addTIFFField + (new TIFFField + (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING), + TIFFTag.TIFF_SHORT, + 1, + new char[] { + (char)BaselineTIFFTagSet.Y_CB_CR_POSITIONING_COSITED + })); + } + + TIFFField photometricInterpretationField = + new TIFFField( + base.getTag(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION), + photometricInterpretation); + rootIFD.addTIFFField(photometricInterpretationField); + + this.bitsPerSample = new char[numBands + numExtraSamples]; + this.bitDepth = 0; + for (int i = 0; i < numBands; i++) { + this.bitDepth = Math.max(bitDepth, sampleSize[i]); + } + if (bitDepth == 3) { + bitDepth = 4; + } else if (bitDepth > 4 && bitDepth < 8) { + bitDepth = 8; + } else if (bitDepth > 8 && bitDepth < 16) { + bitDepth = 16; + } else if (bitDepth > 16 && bitDepth < 32) { + bitDepth = 32; + } else if (bitDepth > 32) { + bitDepth = 64; + } + + for (int i = 0; i < bitsPerSample.length; i++) { + bitsPerSample[i] = (char)bitDepth; + } + + // Emit BitsPerSample. If the image is bilevel, emit if and only + // if already in the metadata and correct (count and value == 1). + if (bitsPerSample.length != 1 || bitsPerSample[0] != 1) { + TIFFField bitsPerSampleField = + new TIFFField( + base.getTag(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE), + TIFFTag.TIFF_SHORT, + bitsPerSample.length, + bitsPerSample); + rootIFD.addTIFFField(bitsPerSampleField); + } else { // bitsPerSample.length == 1 && bitsPerSample[0] == 1 + TIFFField bitsPerSampleField = + rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); + if(bitsPerSampleField != null) { + int[] bps = bitsPerSampleField.getAsInts(); + if(bps == null || bps.length != 1 || bps[0] != 1) { + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); + } + } + } + + // Prepare SampleFormat field. + f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT); + if(f == null && (bitDepth == 16 || bitDepth == 32 || bitDepth == 64)) { + // Set up default content for 16-, 32-, and 64-bit cases. + char sampleFormatValue; + int dataType = sm.getDataType(); + if(bitDepth == 16 && dataType == DataBuffer.TYPE_USHORT) { + sampleFormatValue = + (char)BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER; + } else if((bitDepth == 32 && dataType == DataBuffer.TYPE_FLOAT) || + (bitDepth == 64 && dataType == DataBuffer.TYPE_DOUBLE)) { + sampleFormatValue = + (char)BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT; + } else { + sampleFormatValue = + BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER; + } + this.sampleFormat = (int)sampleFormatValue; + char[] sampleFormatArray = new char[bitsPerSample.length]; + Arrays.fill(sampleFormatArray, sampleFormatValue); + + // Update the metadata. + TIFFTag sampleFormatTag = + base.getTag(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT); + + TIFFField sampleFormatField = + new TIFFField(sampleFormatTag, TIFFTag.TIFF_SHORT, + sampleFormatArray.length, sampleFormatArray); + + rootIFD.addTIFFField(sampleFormatField); + } else if(f != null) { + // Get whatever was provided. + sampleFormat = f.getAsInt(0); + } else { + // Set default value for internal use only. + sampleFormat = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED; + } + + if (extraSamples != null) { + TIFFField extraSamplesField = + new TIFFField( + base.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES), + TIFFTag.TIFF_SHORT, + extraSamples.length, + extraSamples); + rootIFD.addTIFFField(extraSamplesField); + } else { + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES); + } + + TIFFField samplesPerPixelField = + new TIFFField( + base.getTag(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL), + bitsPerSample.length); + rootIFD.addTIFFField(samplesPerPixelField); + + // Emit ColorMap if image is of palette color type + if (photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR && + cm instanceof IndexColorModel) { + char[] colorMap = new char[3*(1 << bitsPerSample[0])]; + + IndexColorModel icm = (IndexColorModel)cm; + + // mapSize is determined by BitsPerSample, not by incoming ICM. + int mapSize = 1 << bitsPerSample[0]; + int indexBound = Math.min(mapSize, icm.getMapSize()); + for (int i = 0; i < indexBound; i++) { + colorMap[i] = (char)((icm.getRed(i)*65535)/255); + colorMap[mapSize + i] = (char)((icm.getGreen(i)*65535)/255); + colorMap[2*mapSize + i] = (char)((icm.getBlue(i)*65535)/255); + } + + TIFFField colorMapField = + new TIFFField( + base.getTag(BaselineTIFFTagSet.TAG_COLOR_MAP), + TIFFTag.TIFF_SHORT, + colorMap.length, + colorMap); + rootIFD.addTIFFField(colorMapField); + } else { + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP); + } + + // Emit ICCProfile if there is no ICCProfile field already in the + // metadata and the ColorSpace is non-standard ICC. + if(cm != null && + rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE) == null && + ImageUtil.isNonStandardICCColorSpace(cm.getColorSpace())) { + ICC_ColorSpace iccColorSpace = (ICC_ColorSpace)cm.getColorSpace(); + byte[] iccProfileData = iccColorSpace.getProfile().getData(); + TIFFField iccProfileField = + new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_ICC_PROFILE), + TIFFTag.TIFF_UNDEFINED, + iccProfileData.length, + iccProfileData); + rootIFD.addTIFFField(iccProfileField); + } + + // Always emit XResolution and YResolution. + + TIFFField XResolutionField = + rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_X_RESOLUTION); + TIFFField YResolutionField = + rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_Y_RESOLUTION); + + if(XResolutionField == null && YResolutionField == null) { + long[][] resRational = new long[1][2]; + resRational[0] = new long[2]; + + TIFFField ResolutionUnitField = + rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT); + + // Don't force dimensionless if one of the other dimensional + // quantities is present. + if(ResolutionUnitField == null && + rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_X_POSITION) == null && + rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_Y_POSITION) == null) { + // Set resolution to unit and units to dimensionless. + resRational[0][0] = 1; + resRational[0][1] = 1; + + ResolutionUnitField = + new TIFFField(rootIFD.getTag + (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT), + BaselineTIFFTagSet.RESOLUTION_UNIT_NONE); + rootIFD.addTIFFField(ResolutionUnitField); + } else { + // Set resolution to a value which would make the maximum + // image dimension equal to 4 inches as arbitrarily stated + // in the description of ResolutionUnit in the TIFF 6.0 + // specification. If the ResolutionUnit field specifies + // "none" then set the resolution to unity (1/1). + int resolutionUnit = ResolutionUnitField != null ? + ResolutionUnitField.getAsInt(0) : + BaselineTIFFTagSet.RESOLUTION_UNIT_INCH; + int maxDimension = Math.max(destWidth, destHeight); + switch(resolutionUnit) { + case BaselineTIFFTagSet.RESOLUTION_UNIT_INCH: + resRational[0][0] = maxDimension; + resRational[0][1] = 4; + break; + case BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER: + resRational[0][0] = 100L*maxDimension; // divide out 100 + resRational[0][1] = 4*254; // 2.54 cm/inch * 100 + break; + default: + resRational[0][0] = 1; + resRational[0][1] = 1; + } + } + + XResolutionField = + new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION), + TIFFTag.TIFF_RATIONAL, + 1, + resRational); + rootIFD.addTIFFField(XResolutionField); + + YResolutionField = + new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION), + TIFFTag.TIFF_RATIONAL, + 1, + resRational); + rootIFD.addTIFFField(YResolutionField); + } else if(XResolutionField == null && YResolutionField != null) { + // Set XResolution to YResolution. + long[] yResolution = + YResolutionField.getAsRational(0).clone(); + XResolutionField = + new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION), + TIFFTag.TIFF_RATIONAL, + 1, + yResolution); + rootIFD.addTIFFField(XResolutionField); + } else if(XResolutionField != null && YResolutionField == null) { + // Set YResolution to XResolution. + long[] xResolution = + XResolutionField.getAsRational(0).clone(); + YResolutionField = + new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION), + TIFFTag.TIFF_RATIONAL, + 1, + xResolution); + rootIFD.addTIFFField(YResolutionField); + } + + // Set mandatory fields, overriding metadata passed in + + int width = destWidth; + TIFFField imageWidthField = + new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_WIDTH), + width); + rootIFD.addTIFFField(imageWidthField); + + int height = destHeight; + TIFFField imageLengthField = + new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_LENGTH), + height); + rootIFD.addTIFFField(imageLengthField); + + // Determine rowsPerStrip + + int rowsPerStrip; + + TIFFField rowsPerStripField = + rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP); + if (rowsPerStripField != null) { + rowsPerStrip = rowsPerStripField.getAsInt(0); + if(rowsPerStrip < 0) { + rowsPerStrip = height; + } + } else { + int bitsPerPixel = bitDepth*(numBands + numExtraSamples); + int bytesPerRow = (bitsPerPixel*width + 7)/8; + rowsPerStrip = + Math.max(Math.max(DEFAULT_BYTES_PER_STRIP/bytesPerRow, 1), 8); + } + rowsPerStrip = Math.min(rowsPerStrip, height); + + // Tiling flag. + boolean useTiling = false; + + // Analyze tiling parameters + int tilingMode = param.getTilingMode(); + if (tilingMode == ImageWriteParam.MODE_DISABLED || + tilingMode == ImageWriteParam.MODE_DEFAULT) { + this.tileWidth = width; + this.tileLength = rowsPerStrip; + useTiling = false; + } else if (tilingMode == ImageWriteParam.MODE_EXPLICIT) { + tileWidth = param.getTileWidth(); + tileLength = param.getTileHeight(); + useTiling = true; + } else if (tilingMode == ImageWriteParam.MODE_COPY_FROM_METADATA) { + f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH); + if (f == null) { + tileWidth = width; + useTiling = false; + } else { + tileWidth = f.getAsInt(0); + useTiling = true; + } + + f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH); + if (f == null) { + tileLength = rowsPerStrip; + } else { + tileLength = f.getAsInt(0); + useTiling = true; + } + } else { + throw new IIOException("Illegal value of tilingMode!"); + } + + if(compression == BaselineTIFFTagSet.COMPRESSION_JPEG) { + // Reset tile size per TTN2 spec for JPEG compression. + int subX; + int subY; + if(numBands == 1) { + subX = subY = 1; + } else { + subX = subY = TIFFJPEGCompressor.CHROMA_SUBSAMPLING; + } + if(useTiling) { + int MCUMultipleX = 8*subX; + int MCUMultipleY = 8*subY; + tileWidth = + Math.max(MCUMultipleX*((tileWidth + + MCUMultipleX/2)/MCUMultipleX), + MCUMultipleX); + tileLength = + Math.max(MCUMultipleY*((tileLength + + MCUMultipleY/2)/MCUMultipleY), + MCUMultipleY); + } else if(rowsPerStrip < height) { + int MCUMultiple = 8*Math.max(subX, subY); + rowsPerStrip = tileLength = + Math.max(MCUMultiple*((tileLength + + MCUMultiple/2)/MCUMultiple), + MCUMultiple); + } + + // The written image may be unreadable if these fields are present. + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT); + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); + + // Also remove fields related to the old JPEG encoding scheme + // which may be misleading when the compression type is JPEG. + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_PROC); + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_RESTART_INTERVAL); + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_LOSSLESS_PREDICTORS); + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_POINT_TRANSFORMS); + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_Q_TABLES); + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_DC_TABLES); + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_AC_TABLES); + } else if(isJPEGInterchange) { + // Force tile size to equal image size. + tileWidth = width; + tileLength = height; + } else if(useTiling) { + // Round tile size to multiple of 16 per TIFF 6.0 specification + // (see pages 67-68 of version 6.0.1 from Adobe). + int tileWidthRemainder = tileWidth % 16; + if(tileWidthRemainder != 0) { + // Round to nearest multiple of 16 not less than 16. + tileWidth = Math.max(16*((tileWidth + 8)/16), 16); + processWarningOccurred(currentImage, + "Tile width rounded to multiple of 16."); + } + + int tileLengthRemainder = tileLength % 16; + if(tileLengthRemainder != 0) { + // Round to nearest multiple of 16 not less than 16. + tileLength = Math.max(16*((tileLength + 8)/16), 16); + processWarningOccurred(currentImage, + "Tile height rounded to multiple of 16."); + } + } + + this.tilesAcross = (width + tileWidth - 1)/tileWidth; + this.tilesDown = (height + tileLength - 1)/tileLength; + + if (!useTiling) { + this.isTiled = false; + + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH); + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH); + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS); + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS); + + rowsPerStripField = + new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP), + rowsPerStrip); + rootIFD.addTIFFField(rowsPerStripField); + + TIFFField stripOffsetsField = + new TIFFField( + base.getTag(BaselineTIFFTagSet.TAG_STRIP_OFFSETS), + TIFFTag.TIFF_LONG, + tilesDown); + rootIFD.addTIFFField(stripOffsetsField); + + TIFFField stripByteCountsField = + new TIFFField( + base.getTag(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS), + TIFFTag.TIFF_LONG, + tilesDown); + rootIFD.addTIFFField(stripByteCountsField); + } else { + this.isTiled = true; + + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP); + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS); + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS); + + TIFFField tileWidthField = + new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_TILE_WIDTH), + tileWidth); + rootIFD.addTIFFField(tileWidthField); + + TIFFField tileLengthField = + new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_TILE_LENGTH), + tileLength); + rootIFD.addTIFFField(tileLengthField); + + TIFFField tileOffsetsField = + new TIFFField( + base.getTag(BaselineTIFFTagSet.TAG_TILE_OFFSETS), + TIFFTag.TIFF_LONG, + tilesDown*tilesAcross); + rootIFD.addTIFFField(tileOffsetsField); + + TIFFField tileByteCountsField = + new TIFFField( + base.getTag(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS), + TIFFTag.TIFF_LONG, + tilesDown*tilesAcross); + rootIFD.addTIFFField(tileByteCountsField); + } + + if(isExif) { + // + // Ensure presence of mandatory fields and absence of prohibited + // fields and those that duplicate information in JPEG marker + // segments per tables 14-18 of the Exif 2.2 specification. + // + + // If an empty image is being written or inserted then infer + // that the primary IFD is being set up. + boolean isPrimaryIFD = isEncodingEmpty(); + + // Handle TIFF fields in order of increasing tag number. + if(compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { + // ImageWidth + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH); + + // ImageLength + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH); + + // BitsPerSample + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); + // Compression + if(isPrimaryIFD) { + rootIFD.removeTIFFField + (BaselineTIFFTagSet.TAG_COMPRESSION); + } + + // PhotometricInterpretation + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION); + + // StripOffsets + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS); + + // SamplesPerPixel + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL); + + // RowsPerStrip + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP); + + // StripByteCounts + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS); + // XResolution and YResolution are handled above for all TIFFs. + + // PlanarConfiguration + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION); + + // ResolutionUnit + if(rootIFD.getTIFFField + (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT) == null) { + f = new TIFFField(base.getTag + (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT), + BaselineTIFFTagSet.RESOLUTION_UNIT_INCH); + rootIFD.addTIFFField(f); + } + + if(isPrimaryIFD) { + // JPEGInterchangeFormat + rootIFD.removeTIFFField + (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT); + + // JPEGInterchangeFormatLength + rootIFD.removeTIFFField + (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); + + // YCbCrSubsampling + rootIFD.removeTIFFField + (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING); + + // YCbCrPositioning + if(rootIFD.getTIFFField + (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING) == null) { + f = new TIFFField + (base.getTag + (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING), + TIFFTag.TIFF_SHORT, + 1, + new char[] { + (char)BaselineTIFFTagSet.Y_CB_CR_POSITIONING_CENTERED + }); + rootIFD.addTIFFField(f); + } + } else { // Thumbnail IFD + // JPEGInterchangeFormat + f = new TIFFField + (base.getTag + (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT), + TIFFTag.TIFF_LONG, + 1); + rootIFD.addTIFFField(f); + + // JPEGInterchangeFormatLength + f = new TIFFField + (base.getTag + (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH), + TIFFTag.TIFF_LONG, + 1); + rootIFD.addTIFFField(f); + + // YCbCrSubsampling + rootIFD.removeTIFFField + (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING); + } + } else { // Uncompressed + // ImageWidth through PlanarConfiguration are set above. + // XResolution and YResolution are handled above for all TIFFs. + + // ResolutionUnit + if(rootIFD.getTIFFField + (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT) == null) { + f = new TIFFField(base.getTag + (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT), + BaselineTIFFTagSet.RESOLUTION_UNIT_INCH); + rootIFD.addTIFFField(f); + } + + + // JPEGInterchangeFormat + rootIFD.removeTIFFField + (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT); + + // JPEGInterchangeFormatLength + rootIFD.removeTIFFField + (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); + + if(photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB) { + // YCbCrCoefficients + rootIFD.removeTIFFField + (BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS); + + // YCbCrSubsampling + rootIFD.removeTIFFField + (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING); + + // YCbCrPositioning + rootIFD.removeTIFFField + (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING); + } + } + + // Get Exif tags. + TIFFTagSet exifTags = ExifTIFFTagSet.getInstance(); + + // Retrieve or create the Exif IFD. + TIFFIFD exifIFD = null; + f = rootIFD.getTIFFField + (ExifParentTIFFTagSet.TAG_EXIF_IFD_POINTER); + if(f != null && f.hasDirectory()) { + // Retrieve the Exif IFD. + exifIFD = (TIFFIFD)f.getDirectory(); + } else if(isPrimaryIFD) { + // Create the Exif IFD. + List exifTagSets = new ArrayList(1); + exifTagSets.add(exifTags); + exifIFD = new TIFFIFD(exifTagSets); + + // Add it to the root IFD. + TIFFTagSet tagSet = ExifParentTIFFTagSet.getInstance(); + TIFFTag exifIFDTag = + tagSet.getTag(ExifParentTIFFTagSet.TAG_EXIF_IFD_POINTER); + rootIFD.addTIFFField(new TIFFField(exifIFDTag, + TIFFTag.TIFF_LONG, + 1L, + exifIFD)); + } + + if(exifIFD != null) { + // Handle Exif private fields in order of increasing + // tag number. + + // ExifVersion + if(exifIFD.getTIFFField + (ExifTIFFTagSet.TAG_EXIF_VERSION) == null) { + f = new TIFFField + (exifTags.getTag(ExifTIFFTagSet.TAG_EXIF_VERSION), + TIFFTag.TIFF_UNDEFINED, + 4, + ExifTIFFTagSet.EXIF_VERSION_2_2.getBytes(StandardCharsets.US_ASCII)); + exifIFD.addTIFFField(f); + } + + if(compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { + // ComponentsConfiguration + if(exifIFD.getTIFFField + (ExifTIFFTagSet.TAG_COMPONENTS_CONFIGURATION) == null) { + f = new TIFFField + (exifTags.getTag + (ExifTIFFTagSet.TAG_COMPONENTS_CONFIGURATION), + TIFFTag.TIFF_UNDEFINED, + 4, + new byte[] { + (byte)ExifTIFFTagSet.COMPONENTS_CONFIGURATION_Y, + (byte)ExifTIFFTagSet.COMPONENTS_CONFIGURATION_CB, + (byte)ExifTIFFTagSet.COMPONENTS_CONFIGURATION_CR, + (byte)0 + }); + exifIFD.addTIFFField(f); + } + } else { + // ComponentsConfiguration + exifIFD.removeTIFFField + (ExifTIFFTagSet.TAG_COMPONENTS_CONFIGURATION); + + // CompressedBitsPerPixel + exifIFD.removeTIFFField + (ExifTIFFTagSet.TAG_COMPRESSED_BITS_PER_PIXEL); + } + + // FlashpixVersion + if(exifIFD.getTIFFField + (ExifTIFFTagSet.TAG_FLASHPIX_VERSION) == null) { + f = new TIFFField + (exifTags.getTag(ExifTIFFTagSet.TAG_FLASHPIX_VERSION), + TIFFTag.TIFF_UNDEFINED, + 4, + new byte[] {(byte)'0', (byte)'1', (byte)'0', (byte)'0'}); + exifIFD.addTIFFField(f); + } + + // ColorSpace + if(exifIFD.getTIFFField + (ExifTIFFTagSet.TAG_COLOR_SPACE) == null) { + f = new TIFFField + (exifTags.getTag(ExifTIFFTagSet.TAG_COLOR_SPACE), + TIFFTag.TIFF_SHORT, + 1, + new char[] { + (char)ExifTIFFTagSet.COLOR_SPACE_SRGB + }); + exifIFD.addTIFFField(f); + } + + if(compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { + // PixelXDimension + if(exifIFD.getTIFFField + (ExifTIFFTagSet.TAG_PIXEL_X_DIMENSION) == null) { + f = new TIFFField + (exifTags.getTag(ExifTIFFTagSet.TAG_PIXEL_X_DIMENSION), + width); + exifIFD.addTIFFField(f); + } + + // PixelYDimension + if(exifIFD.getTIFFField + (ExifTIFFTagSet.TAG_PIXEL_Y_DIMENSION) == null) { + f = new TIFFField + (exifTags.getTag(ExifTIFFTagSet.TAG_PIXEL_Y_DIMENSION), + height); + exifIFD.addTIFFField(f); + } + } else { + exifIFD.removeTIFFField + (ExifTIFFTagSet.TAG_INTEROPERABILITY_IFD_POINTER); + } + } + + } // if(isExif) + } + + ImageTypeSpecifier getImageType() { + return imageType; + } + + /** + @param tileRect The area to be written which might be outside the image. + */ + private int writeTile(Rectangle tileRect, TIFFCompressor compressor) + throws IOException { + // Determine the rectangle which will actually be written + // and set the padding flag. Padding will occur only when the + // image is written as a tiled TIFF and the tile bounds are not + // contained within the image bounds. + Rectangle activeRect; + boolean isPadded; + Rectangle imageBounds = + new Rectangle(image.getMinX(), image.getMinY(), + image.getWidth(), image.getHeight()); + if(!isTiled) { + // Stripped + activeRect = tileRect.intersection(imageBounds); + tileRect = activeRect; + isPadded = false; + } else if(imageBounds.contains(tileRect)) { + // Tiled, tile within image bounds + activeRect = tileRect; + isPadded = false; + } else { + // Tiled, tile not within image bounds + activeRect = imageBounds.intersection(tileRect); + isPadded = true; + } + + // Return early if empty intersection. + if(activeRect.isEmpty()) { + return 0; + } + + int minX = tileRect.x; + int minY = tileRect.y; + int width = tileRect.width; + int height = tileRect.height; + + if(isImageSimple) { + + SampleModel sm = image.getSampleModel(); + + // Read only data from the active rectangle. + Raster raster = image.getData(activeRect); + + // If padding is required, create a larger Raster and fill + // it from the active rectangle. + if(isPadded) { + WritableRaster wr = + raster.createCompatibleWritableRaster(minX, minY, + width, height); + wr.setRect(raster); + raster = wr; + } + + if(isBilevel) { + byte[] buf = ImageUtil.getPackedBinaryData(raster, + tileRect); + + if(isInverted) { + DataBuffer dbb = raster.getDataBuffer(); + if(dbb instanceof DataBufferByte && + buf == ((DataBufferByte)dbb).getData()) { + byte[] bbuf = new byte[buf.length]; + int len = buf.length; + for(int i = 0; i < len; i++) { + bbuf[i] = (byte)(buf[i] ^ 0xff); + } + buf = bbuf; + } else { + int len = buf.length; + for(int i = 0; i < len; i++) { + buf[i] ^= 0xff; + } + } + } + + return compressor.encode(buf, 0, + width, height, sampleSize, + (tileRect.width + 7)/8); + } else if(bitDepth == 8 && + sm.getDataType() == DataBuffer.TYPE_BYTE) { + ComponentSampleModel csm = + (ComponentSampleModel)raster.getSampleModel(); + + byte[] buf = + ((DataBufferByte)raster.getDataBuffer()).getData(); + + int off = + csm.getOffset(minX - + raster.getSampleModelTranslateX(), + minY - + raster.getSampleModelTranslateY()); + + return compressor.encode(buf, off, + width, height, sampleSize, + csm.getScanlineStride()); + } + } + + // Set offsets and skips based on source subsampling factors + int xOffset = minX; + int xSkip = periodX; + int yOffset = minY; + int ySkip = periodY; + + // Early exit if no data for this pass + int hpixels = (width + xSkip - 1)/xSkip; + int vpixels = (height + ySkip - 1)/ySkip; + if (hpixels == 0 || vpixels == 0) { + return 0; + } + + // Convert X offset and skip from pixels to samples + xOffset *= numBands; + xSkip *= numBands; + + // Initialize sizes + int samplesPerByte = 8/bitDepth; + int numSamples = width*numBands; + int bytesPerRow = hpixels*numBands; + + // Update number of bytes per row. + if (bitDepth < 8) { + bytesPerRow = (bytesPerRow + samplesPerByte - 1)/samplesPerByte; + } else if (bitDepth == 16) { + bytesPerRow *= 2; + } else if (bitDepth == 32) { + bytesPerRow *= 4; + } else if (bitDepth == 64) { + bytesPerRow *= 8; + } + + // Create row buffers + int[] samples = null; + float[] fsamples = null; + double[] dsamples = null; + if(sampleFormat == BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { + if (bitDepth == 32) { + fsamples = new float[numSamples]; + } else { + dsamples = new double[numSamples]; + } + } else { + samples = new int[numSamples]; + } + + // Create tile buffer + byte[] currTile = new byte[bytesPerRow*vpixels]; + + // Sub-optimal case: shy of "isImageSimple" only by virtue of + // not being contiguous. + if(!isInverted && // no inversion + !isRescaling && // no value rescaling + sourceBands == null && // no subbanding + periodX == 1 && periodY == 1 && // no subsampling + colorConverter == null) { + + SampleModel sm = image.getSampleModel(); + + if(sm instanceof ComponentSampleModel && // component + bitDepth == 8 && // 8 bits/sample + sm.getDataType() == DataBuffer.TYPE_BYTE) { // byte type + + // Read only data from the active rectangle. + Raster raster = image.getData(activeRect); + + // If padding is required, create a larger Raster and fill + // it from the active rectangle. + if(isPadded) { + WritableRaster wr = + raster.createCompatibleWritableRaster(minX, minY, + width, height); + wr.setRect(raster); + raster = wr; + } + + // Get SampleModel info. + ComponentSampleModel csm = + (ComponentSampleModel)raster.getSampleModel(); + int[] bankIndices = csm.getBankIndices(); + byte[][] bankData = + ((DataBufferByte)raster.getDataBuffer()).getBankData(); + int lineStride = csm.getScanlineStride(); + int pixelStride = csm.getPixelStride(); + + // Copy the data into a contiguous pixel interleaved buffer. + for(int k = 0; k < numBands; k++) { + byte[] bandData = bankData[bankIndices[k]]; + int lineOffset = + csm.getOffset(raster.getMinX() - + raster.getSampleModelTranslateX(), + raster.getMinY() - + raster.getSampleModelTranslateY(), k); + int idx = k; + for(int j = 0; j < vpixels; j++) { + int offset = lineOffset; + for(int i = 0; i < hpixels; i++) { + currTile[idx] = bandData[offset]; + idx += numBands; + offset += pixelStride; + } + lineOffset += lineStride; + } + } + + // Compressor and return. + return compressor.encode(currTile, 0, + width, height, sampleSize, + width*numBands); + } + } + + int tcount = 0; + + // Save active rectangle variables. + int activeMinX = activeRect.x; + int activeMinY = activeRect.y; + int activeMaxY = activeMinY + activeRect.height - 1; + int activeWidth = activeRect.width; + + // Set a SampleModel for use in padding. + SampleModel rowSampleModel = null; + if(isPadded) { + rowSampleModel = + image.getSampleModel().createCompatibleSampleModel(width, 1); + } + + for (int row = yOffset; row < yOffset + height; row += ySkip) { + Raster ras = null; + if(isPadded) { + // Create a raster for the entire row. + WritableRaster wr = + Raster.createWritableRaster(rowSampleModel, + new Point(minX, row)); + + // Populate the raster from the active sub-row, if any. + if(row >= activeMinY && row <= activeMaxY) { + Rectangle rect = + new Rectangle(activeMinX, row, activeWidth, 1); + ras = image.getData(rect); + wr.setRect(ras); + } + + // Update the raster variable. + ras = wr; + } else { + Rectangle rect = new Rectangle(minX, row, width, 1); + ras = image.getData(rect); + } + if (sourceBands != null) { + ras = ras.createChild(minX, row, width, 1, minX, row, + sourceBands); + } + + if(sampleFormat == + BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { + if (fsamples != null) { + ras.getPixels(minX, row, width, 1, fsamples); + } else { + ras.getPixels(minX, row, width, 1, dsamples); + } + } else { + ras.getPixels(minX, row, width, 1, samples); + + if ((nativePhotometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO && + photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) || + (nativePhotometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO && + photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO)) { + int bitMask = (1 << bitDepth) - 1; + for (int s = 0; s < numSamples; s++) { + samples[s] ^= bitMask; + } + } + } + + if (colorConverter != null) { + int idx = 0; + float[] result = new float[3]; + + if(sampleFormat == + BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { + if (bitDepth == 32) { + for (int i = 0; i < width; i++) { + float r = fsamples[idx]; + float g = fsamples[idx + 1]; + float b = fsamples[idx + 2]; + + colorConverter.fromRGB(r, g, b, result); + + fsamples[idx] = result[0]; + fsamples[idx + 1] = result[1]; + fsamples[idx + 2] = result[2]; + + idx += 3; + } + } else { + for (int i = 0; i < width; i++) { + // Note: Possible loss of precision. + float r = (float)dsamples[idx]; + float g = (float)dsamples[idx + 1]; + float b = (float)dsamples[idx + 2]; + + colorConverter.fromRGB(r, g, b, result); + + dsamples[idx] = result[0]; + dsamples[idx + 1] = result[1]; + dsamples[idx + 2] = result[2]; + + idx += 3; + } + } + } else { + for (int i = 0; i < width; i++) { + float r = (float)samples[idx]; + float g = (float)samples[idx + 1]; + float b = (float)samples[idx + 2]; + + colorConverter.fromRGB(r, g, b, result); + + samples[idx] = (int)(result[0]); + samples[idx + 1] = (int)(result[1]); + samples[idx + 2] = (int)(result[2]); + + idx += 3; + } + } + } + + int tmp = 0; + int pos = 0; + + switch (bitDepth) { + case 1: case 2: case 4: + // Image can only have a single band + + if(isRescaling) { + for (int s = 0; s < numSamples; s += xSkip) { + byte val = scale0[samples[s]]; + tmp = (tmp << bitDepth) | val; + + if (++pos == samplesPerByte) { + currTile[tcount++] = (byte)tmp; + tmp = 0; + pos = 0; + } + } + } else { + for (int s = 0; s < numSamples; s += xSkip) { + byte val = (byte)samples[s]; + tmp = (tmp << bitDepth) | val; + + if (++pos == samplesPerByte) { + currTile[tcount++] = (byte)tmp; + tmp = 0; + pos = 0; + } + } + } + + // Left shift the last byte + if (pos != 0) { + tmp <<= ((8/bitDepth) - pos)*bitDepth; + currTile[tcount++] = (byte)tmp; + } + break; + + case 8: + if (numBands == 1) { + if(isRescaling) { + for (int s = 0; s < numSamples; s += xSkip) { + currTile[tcount++] = scale0[samples[s]]; + } + } else { + for (int s = 0; s < numSamples; s += xSkip) { + currTile[tcount++] = (byte)samples[s]; + } + } + } else { + if(isRescaling) { + for (int s = 0; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + currTile[tcount++] = scale[b][samples[s + b]]; + } + } + } else { + for (int s = 0; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + currTile[tcount++] = (byte)samples[s + b]; + } + } + } + } + break; + + case 16: + if(isRescaling) { + if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { + for (int s = 0; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + int sample = samples[s + b]; + currTile[tcount++] = scaleh[b][sample]; + currTile[tcount++] = scalel[b][sample]; + } + } + } else { // ByteOrder.LITLE_ENDIAN + for (int s = 0; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + int sample = samples[s + b]; + currTile[tcount++] = scalel[b][sample]; + currTile[tcount++] = scaleh[b][sample]; + } + } + } + } else { + if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { + for (int s = 0; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + int sample = samples[s + b]; + currTile[tcount++] = + (byte)((sample >>> 8) & 0xff); + currTile[tcount++] = + (byte)(sample & 0xff); + } + } + } else { // ByteOrder.LITLE_ENDIAN + for (int s = 0; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + int sample = samples[s + b]; + currTile[tcount++] = + (byte)(sample & 0xff); + currTile[tcount++] = + (byte)((sample >>> 8) & 0xff); + } + } + } + } + break; + + case 32: + if(sampleFormat == + BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { + if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { + for (int s = 0; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + float fsample = fsamples[s + b]; + int isample = Float.floatToIntBits(fsample); + currTile[tcount++] = + (byte)((isample & 0xff000000) >> 24); + currTile[tcount++] = + (byte)((isample & 0x00ff0000) >> 16); + currTile[tcount++] = + (byte)((isample & 0x0000ff00) >> 8); + currTile[tcount++] = + (byte)(isample & 0x000000ff); + } + } + } else { // ByteOrder.LITLE_ENDIAN + for (int s = 0; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + float fsample = fsamples[s + b]; + int isample = Float.floatToIntBits(fsample); + currTile[tcount++] = + (byte)(isample & 0x000000ff); + currTile[tcount++] = + (byte)((isample & 0x0000ff00) >> 8); + currTile[tcount++] = + (byte)((isample & 0x00ff0000) >> 16); + currTile[tcount++] = + (byte)((isample & 0xff000000) >> 24); + } + } + } + } else { + if(isRescaling) { + long[] maxIn = new long[numBands]; + long[] halfIn = new long[numBands]; + long maxOut = (1L << (long)bitDepth) - 1L; + + for (int b = 0; b < numBands; b++) { + maxIn[b] = ((1L << (long)sampleSize[b]) - 1L); + halfIn[b] = maxIn[b]/2; + } + + if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { + for (int s = 0; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + long sampleOut = + (samples[s + b]*maxOut + halfIn[b])/ + maxIn[b]; + currTile[tcount++] = + (byte)((sampleOut & 0xff000000) >> 24); + currTile[tcount++] = + (byte)((sampleOut & 0x00ff0000) >> 16); + currTile[tcount++] = + (byte)((sampleOut & 0x0000ff00) >> 8); + currTile[tcount++] = + (byte)(sampleOut & 0x000000ff); + } + } + } else { // ByteOrder.LITLE_ENDIAN + for (int s = 0; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + long sampleOut = + (samples[s + b]*maxOut + halfIn[b])/ + maxIn[b]; + currTile[tcount++] = + (byte)(sampleOut & 0x000000ff); + currTile[tcount++] = + (byte)((sampleOut & 0x0000ff00) >> 8); + currTile[tcount++] = + (byte)((sampleOut & 0x00ff0000) >> 16); + currTile[tcount++] = + (byte)((sampleOut & 0xff000000) >> 24); + } + } + } + } else { + if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { + for (int s = 0; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + int isample = samples[s + b]; + currTile[tcount++] = + (byte)((isample & 0xff000000) >> 24); + currTile[tcount++] = + (byte)((isample & 0x00ff0000) >> 16); + currTile[tcount++] = + (byte)((isample & 0x0000ff00) >> 8); + currTile[tcount++] = + (byte)(isample & 0x000000ff); + } + } + } else { // ByteOrder.LITLE_ENDIAN + for (int s = 0; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + int isample = samples[s + b]; + currTile[tcount++] = + (byte)(isample & 0x000000ff); + currTile[tcount++] = + (byte)((isample & 0x0000ff00) >> 8); + currTile[tcount++] = + (byte)((isample & 0x00ff0000) >> 16); + currTile[tcount++] = + (byte)((isample & 0xff000000) >> 24); + } + } + } + } + } + break; + + case 64: + if(sampleFormat == + BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { + if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { + for (int s = 0; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + double dsample = dsamples[s + b]; + long lsample = Double.doubleToLongBits(dsample); + currTile[tcount++] = + (byte)((lsample & 0xff00000000000000L) >> 56); + currTile[tcount++] = + (byte)((lsample & 0x00ff000000000000L) >> 48); + currTile[tcount++] = + (byte)((lsample & 0x0000ff0000000000L) >> 40); + currTile[tcount++] = + (byte)((lsample & 0x000000ff00000000L) >> 32); + currTile[tcount++] = + (byte)((lsample & 0x00000000ff000000L) >> 24); + currTile[tcount++] = + (byte)((lsample & 0x0000000000ff0000L) >> 16); + currTile[tcount++] = + (byte)((lsample & 0x000000000000ff00L) >> 8); + currTile[tcount++] = + (byte)(lsample & 0x00000000000000ffL); + } + } + } else { // ByteOrder.LITLE_ENDIAN + for (int s = 0; s < numSamples; s += xSkip) { + for (int b = 0; b < numBands; b++) { + double dsample = dsamples[s + b]; + long lsample = Double.doubleToLongBits(dsample); + currTile[tcount++] = + (byte)(lsample & 0x00000000000000ffL); + currTile[tcount++] = + (byte)((lsample & 0x000000000000ff00L) >> 8); + currTile[tcount++] = + (byte)((lsample & 0x0000000000ff0000L) >> 16); + currTile[tcount++] = + (byte)((lsample & 0x00000000ff000000L) >> 24); + currTile[tcount++] = + (byte)((lsample & 0x000000ff00000000L) >> 32); + currTile[tcount++] = + (byte)((lsample & 0x0000ff0000000000L) >> 40); + currTile[tcount++] = + (byte)((lsample & 0x00ff000000000000L) >> 48); + currTile[tcount++] = + (byte)((lsample & 0xff00000000000000L) >> 56); + } + } + } + } + break; + } + } + + int[] bitsPerSample = new int[numBands]; + for (int i = 0; i < bitsPerSample.length; i++) { + bitsPerSample[i] = bitDepth; + } + + int byteCount = compressor.encode(currTile, 0, + hpixels, vpixels, + bitsPerSample, + bytesPerRow); + return byteCount; + } + + // Check two int arrays for value equality, always returns false + // if either array is null + private boolean equals(int[] s0, int[] s1) { + if (s0 == null || s1 == null) { + return false; + } + if (s0.length != s1.length) { + return false; + } + for (int i = 0; i < s0.length; i++) { + if (s0[i] != s1[i]) { + return false; + } + } + return true; + } + + // Initialize the scale/scale0 or scaleh/scalel arrays to + // hold the results of scaling an input value to the desired + // output bit depth + private void initializeScaleTables(int[] sampleSize) { + // Save the sample size in the instance variable. + + // If the existing tables are still valid, just return. + if (bitDepth == scalingBitDepth && + equals(sampleSize, this.sampleSize)) { + return; + } + + // Reset scaling variables. + isRescaling = false; + scalingBitDepth = -1; + scale = scalel = scaleh = null; + scale0 = null; + + // Set global sample size to parameter. + this.sampleSize = sampleSize; + + // Check whether rescaling is called for. + if(bitDepth <= 16) { + for(int b = 0; b < numBands; b++) { + if(sampleSize[b] != bitDepth) { + isRescaling = true; + break; + } + } + } + + // If not rescaling then return after saving the sample size. + if(!isRescaling) { + return; + } + + // Compute new tables + this.scalingBitDepth = bitDepth; + int maxOutSample = (1 << bitDepth) - 1; + if (bitDepth <= 8) { + scale = new byte[numBands][]; + for (int b = 0; b < numBands; b++) { + int maxInSample = (1 << sampleSize[b]) - 1; + int halfMaxInSample = maxInSample/2; + scale[b] = new byte[maxInSample + 1]; + for (int s = 0; s <= maxInSample; s++) { + scale[b][s] = + (byte)((s*maxOutSample + halfMaxInSample)/maxInSample); + } + } + scale0 = scale[0]; + scaleh = scalel = null; + } else if(bitDepth <= 16) { + // Divide scaling table into high and low bytes + scaleh = new byte[numBands][]; + scalel = new byte[numBands][]; + + for (int b = 0; b < numBands; b++) { + int maxInSample = (1 << sampleSize[b]) - 1; + int halfMaxInSample = maxInSample/2; + scaleh[b] = new byte[maxInSample + 1]; + scalel[b] = new byte[maxInSample + 1]; + for (int s = 0; s <= maxInSample; s++) { + int val = (s*maxOutSample + halfMaxInSample)/maxInSample; + scaleh[b][s] = (byte)(val >> 8); + scalel[b][s] = (byte)(val & 0xff); + } + } + scale = null; + scale0 = null; + } + } + + public void write(IIOMetadata sm, + IIOImage iioimage, + ImageWriteParam p) throws IOException { + write(sm, iioimage, p, true, true); + } + + private void writeHeader() throws IOException { + if (streamMetadata != null) { + this.byteOrder = streamMetadata.byteOrder; + } else { + this.byteOrder = ByteOrder.BIG_ENDIAN; + } + + stream.setByteOrder(byteOrder); + if (byteOrder == ByteOrder.BIG_ENDIAN) { + stream.writeShort(0x4d4d); + } else { + stream.writeShort(0x4949); + } + + stream.writeShort(42); // Magic number + stream.writeInt(0); // Offset of first IFD (0 == none) + + nextSpace = stream.getStreamPosition(); + headerPosition = nextSpace - 8; + } + + private void write(IIOMetadata sm, + IIOImage iioimage, + ImageWriteParam p, + boolean writeHeader, + boolean writeData) throws IOException { + if (stream == null) { + throw new IllegalStateException("output == null!"); + } + if (iioimage == null) { + throw new NullPointerException("image == null!"); + } + if(iioimage.hasRaster() && !canWriteRasters()) { + throw new UnsupportedOperationException + ("TIFF ImageWriter cannot write Rasters!"); + } + + this.image = iioimage.getRenderedImage(); + SampleModel sampleModel = image.getSampleModel(); + + this.sourceXOffset = image.getMinX(); + this.sourceYOffset = image.getMinY(); + this.sourceWidth = image.getWidth(); + this.sourceHeight = image.getHeight(); + + Rectangle imageBounds = new Rectangle(sourceXOffset, + sourceYOffset, + sourceWidth, + sourceHeight); + + ColorModel colorModel = null; + if (p == null) { + this.param = getDefaultWriteParam(); + this.sourceBands = null; + this.periodX = 1; + this.periodY = 1; + this.numBands = sampleModel.getNumBands(); + colorModel = image.getColorModel(); + } else { + this.param = p; + + // Get source region and subsampling factors + Rectangle sourceRegion = param.getSourceRegion(); + if (sourceRegion != null) { + // Clip to actual image bounds + sourceRegion = sourceRegion.intersection(imageBounds); + + sourceXOffset = sourceRegion.x; + sourceYOffset = sourceRegion.y; + sourceWidth = sourceRegion.width; + sourceHeight = sourceRegion.height; + } + + // Adjust for subsampling offsets + int gridX = param.getSubsamplingXOffset(); + int gridY = param.getSubsamplingYOffset(); + this.sourceXOffset += gridX; + this.sourceYOffset += gridY; + this.sourceWidth -= gridX; + this.sourceHeight -= gridY; + + // Get subsampling factors + this.periodX = param.getSourceXSubsampling(); + this.periodY = param.getSourceYSubsampling(); + + int[] sBands = param.getSourceBands(); + if (sBands != null) { + sourceBands = sBands; + this.numBands = sourceBands.length; + } else { + this.numBands = sampleModel.getNumBands(); + } + + ImageTypeSpecifier destType = p.getDestinationType(); + if(destType != null) { + ColorModel cm = destType.getColorModel(); + if(cm.getNumComponents() == numBands) { + colorModel = cm; + } + } + + if(colorModel == null) { + colorModel = image.getColorModel(); + } + } + + this.imageType = new ImageTypeSpecifier(colorModel, sampleModel); + + ImageUtil.canEncodeImage(this, this.imageType); + + // Compute output dimensions + int destWidth = (sourceWidth + periodX - 1)/periodX; + int destHeight = (sourceHeight + periodY - 1)/periodY; + if (destWidth <= 0 || destHeight <= 0) { + throw new IllegalArgumentException("Empty source region!"); + } + + clearAbortRequest(); + processImageStarted(0); + + // Optionally write the header. + if (writeHeader) { + // Clear previous stream metadata. + this.streamMetadata = null; + + // Try to convert non-null input stream metadata. + if (sm != null) { + this.streamMetadata = + (TIFFStreamMetadata)convertStreamMetadata(sm, param); + } + + // Set to default if not converted. + if(this.streamMetadata == null) { + this.streamMetadata = + (TIFFStreamMetadata)getDefaultStreamMetadata(param); + } + + // Write the header. + writeHeader(); + + // Seek to the position of the IFD pointer in the header. + stream.seek(headerPosition + 4); + + // Ensure IFD is written on a word boundary + nextSpace = (nextSpace + 3) & ~0x3; + + // Write the pointer to the first IFD after the header. + stream.writeInt((int)nextSpace); + } + + // Write out the IFD and any sub IFDs, followed by a zero + + // Clear previous image metadata. + this.imageMetadata = null; + + // Initialize the metadata object. + IIOMetadata im = iioimage.getMetadata(); + if(im != null) { + if (im instanceof TIFFImageMetadata) { + // Clone the one passed in. + this.imageMetadata = ((TIFFImageMetadata)im).getShallowClone(); + } else if(Arrays.asList(im.getMetadataFormatNames()).contains( + TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME)) { + this.imageMetadata = convertNativeImageMetadata(im); + } else if(im.isStandardMetadataFormatSupported()) { + // Convert standard metadata. + this.imageMetadata = convertStandardImageMetadata(im); + } + if (this.imageMetadata == null) { + processWarningOccurred(currentImage, + "Could not initialize image metadata"); + } + } + + // Use default metadata if still null. + if(this.imageMetadata == null) { + this.imageMetadata = + (TIFFImageMetadata)getDefaultImageMetadata(this.imageType, + this.param); + } + + // Set or overwrite mandatory fields in the root IFD + setupMetadata(colorModel, sampleModel, destWidth, destHeight); + + // Set compressor fields. + compressor.setWriter(this); + // Metadata needs to be set on the compressor before the IFD is + // written as the compressor could modify the metadata. + compressor.setMetadata(imageMetadata); + compressor.setStream(stream); + + // Initialize scaling tables for this image + sampleSize = sampleModel.getSampleSize(); + initializeScaleTables(sampleModel.getSampleSize()); + + // Determine whether bilevel. + this.isBilevel = ImageUtil.isBinary(this.image.getSampleModel()); + + // Check for photometric inversion. + this.isInverted = + (nativePhotometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO && + photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) || + (nativePhotometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO && + photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO); + + // Analyze image data suitability for direct copy. + this.isImageSimple = + (isBilevel || + (!isInverted && ImageUtil.imageIsContiguous(this.image))) && + !isRescaling && // no value rescaling + sourceBands == null && // no subbanding + periodX == 1 && periodY == 1 && // no subsampling + colorConverter == null; + + TIFFIFD rootIFD = imageMetadata.getRootIFD(); + + rootIFD.writeToStream(stream); + + this.nextIFDPointerPos = stream.getStreamPosition(); + stream.writeInt(0); + + // Seek to end of IFD data + long lastIFDPosition = rootIFD.getLastPosition(); + stream.seek(lastIFDPosition); + if(lastIFDPosition > this.nextSpace) { + this.nextSpace = lastIFDPosition; + } + + // If not writing the image data, i.e., if writing or inserting an + // empty image, return. + if(!writeData) { + return; + } + + // Get positions of fields within the IFD to update as we write + // each strip or tile + long stripOrTileByteCountsPosition = + rootIFD.getStripOrTileByteCountsPosition(); + long stripOrTileOffsetsPosition = + rootIFD.getStripOrTileOffsetsPosition(); + + // Compute total number of pixels for progress notification + this.totalPixels = tileWidth*tileLength*tilesDown*tilesAcross; + this.pixelsDone = 0; + + // Write the image, a strip or tile at a time + for (int tj = 0; tj < tilesDown; tj++) { + for (int ti = 0; ti < tilesAcross; ti++) { + long pos = stream.getStreamPosition(); + + // Write the (possibly compressed) tile data + + Rectangle tileRect = + new Rectangle(sourceXOffset + ti*tileWidth*periodX, + sourceYOffset + tj*tileLength*periodY, + tileWidth*periodX, + tileLength*periodY); + + try { + int byteCount = writeTile(tileRect, compressor); + + if(pos + byteCount > nextSpace) { + nextSpace = pos + byteCount; + } + + pixelsDone += tileRect.width*tileRect.height; + processImageProgress(100.0F*pixelsDone/totalPixels); + + // Fill in the offset and byte count for the file + stream.mark(); + stream.seek(stripOrTileOffsetsPosition); + stream.writeInt((int)pos); + stripOrTileOffsetsPosition += 4; + + stream.seek(stripOrTileByteCountsPosition); + stream.writeInt(byteCount); + stripOrTileByteCountsPosition += 4; + stream.reset(); + } catch (IOException e) { + throw new IIOException("I/O error writing TIFF file!", e); + } + + if (abortRequested()) { + processWriteAborted(); + return; + } + } + } + + processImageComplete(); + currentImage++; + } + + public boolean canWriteSequence() { + return true; + } + + public void prepareWriteSequence(IIOMetadata streamMetadata) + throws IOException { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + + // Set up stream metadata. + if (streamMetadata != null) { + streamMetadata = convertStreamMetadata(streamMetadata, null); + } + if(streamMetadata == null) { + streamMetadata = getDefaultStreamMetadata(null); + } + this.streamMetadata = (TIFFStreamMetadata)streamMetadata; + + // Write the header. + writeHeader(); + + // Set the sequence flag. + this.isWritingSequence = true; + } + + public void writeToSequence(IIOImage image, ImageWriteParam param) + throws IOException { + // Check sequence flag. + if(!this.isWritingSequence) { + throw new IllegalStateException + ("prepareWriteSequence() has not been called!"); + } + + // Append image. + writeInsert(-1, image, param); + } + + public void endWriteSequence() throws IOException { + // Check output. + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + + // Check sequence flag. + if(!isWritingSequence) { + throw new IllegalStateException + ("prepareWriteSequence() has not been called!"); + } + + // Unset sequence flag. + this.isWritingSequence = false; + + // Position the stream at the end, not at the next IFD pointer position. + long streamLength = this.stream.length(); + if (streamLength != -1) { + stream.seek(streamLength); + } + } + + public boolean canInsertImage(int imageIndex) throws IOException { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + + // Mark position as locateIFD() will seek to IFD at imageIndex. + stream.mark(); + + // locateIFD() will throw an IndexOutOfBoundsException if + // imageIndex is < -1 or is too big thereby satisfying the spec. + long[] ifdpos = new long[1]; + long[] ifd = new long[1]; + locateIFD(imageIndex, ifdpos, ifd); + + // Reset to position before locateIFD(). + stream.reset(); + + return true; + } + + // Locate start of IFD for image. + // Throws IIOException if not at a TIFF header and + // IndexOutOfBoundsException if imageIndex is < -1 or is too big. + private void locateIFD(int imageIndex, long[] ifdpos, long[] ifd) + throws IOException { + + if(imageIndex < -1) { + throw new IndexOutOfBoundsException("imageIndex < -1!"); + } + + long startPos = stream.getStreamPosition(); + + stream.seek(headerPosition); + int byteOrder = stream.readUnsignedShort(); + if (byteOrder == 0x4d4d) { + stream.setByteOrder(ByteOrder.BIG_ENDIAN); + } else if (byteOrder == 0x4949) { + stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + } else { + stream.seek(startPos); + throw new IIOException("Illegal byte order"); + } + if (stream.readUnsignedShort() != 42) { + stream.seek(startPos); + throw new IIOException("Illegal magic number"); + } + + ifdpos[0] = stream.getStreamPosition(); + ifd[0] = stream.readUnsignedInt(); + if (ifd[0] == 0) { + // imageIndex has to be >= -1 due to check above. + if(imageIndex > 0) { + stream.seek(startPos); + throw new IndexOutOfBoundsException + ("imageIndex is greater than the largest available index!"); + } + return; + } + stream.seek(ifd[0]); + + for (int i = 0; imageIndex == -1 || i < imageIndex; i++) { + int numFields; + try { + numFields = stream.readShort(); + } catch (EOFException eof) { + stream.seek(startPos); + ifd[0] = 0; + return; + } + + stream.skipBytes(12*numFields); + + ifdpos[0] = stream.getStreamPosition(); + ifd[0] = stream.readUnsignedInt(); + if (ifd[0] == 0) { + if (imageIndex != -1 && i < imageIndex - 1) { + stream.seek(startPos); + throw new IndexOutOfBoundsException( + "imageIndex is greater than the largest available index!"); + } + break; + } + stream.seek(ifd[0]); + } + } + + public void writeInsert(int imageIndex, + IIOImage image, + ImageWriteParam param) throws IOException { + int currentImageCached = currentImage; + try { + insert(imageIndex, image, param, true); + } catch (Exception e) { + throw e; + } finally { + currentImage = currentImageCached; + } + } + + private void insert(int imageIndex, + IIOImage image, + ImageWriteParam param, + boolean writeData) throws IOException { + if (stream == null) { + throw new IllegalStateException("Output not set!"); + } + if (image == null) { + throw new NullPointerException("image == null!"); + } + + // Locate the position of the old IFD (ifd) and the location + // of the pointer to that position (ifdpos). + long[] ifdpos = new long[1]; + long[] ifd = new long[1]; + + // locateIFD() will throw an IndexOutOfBoundsException if + // imageIndex is < -1 or is too big thereby satisfying the spec. + locateIFD(imageIndex, ifdpos, ifd); + + // Seek to the position containing the pointer to the old IFD. + stream.seek(ifdpos[0]); + + // Update next space pointer in anticipation of next write. + if(ifdpos[0] + 4 > nextSpace) { + nextSpace = ifdpos[0] + 4; + } + + // Ensure IFD is written on a word boundary + nextSpace = (nextSpace + 3) & ~0x3; + + // Update the value to point to the next available space. + stream.writeInt((int)nextSpace); + + // Seek to the next available space. + stream.seek(nextSpace); + + // Write the image (IFD and data). + write(null, image, param, false, writeData); + + // Seek to the position containing the pointer in the new IFD. + stream.seek(nextIFDPointerPos); + + // Update the new IFD to point to the old IFD. + stream.writeInt((int)ifd[0]); + // Don't need to update nextSpace here as already done in write(). + } + + // ----- BEGIN insert/writeEmpty methods ----- + + private boolean isEncodingEmpty() { + return isInsertingEmpty || isWritingEmpty; + } + + public boolean canInsertEmpty(int imageIndex) throws IOException { + return canInsertImage(imageIndex); + } + + public boolean canWriteEmpty() throws IOException { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + return true; + } + + // Check state and parameters for writing or inserting empty images. + private void checkParamsEmpty(ImageTypeSpecifier imageType, + int width, + int height, + List thumbnails) { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + + if(imageType == null) { + throw new NullPointerException("imageType == null!"); + } + + if(width < 1 || height < 1) { + throw new IllegalArgumentException("width < 1 || height < 1!"); + } + + if(thumbnails != null) { + int numThumbs = thumbnails.size(); + for(int i = 0; i < numThumbs; i++) { + Object thumb = thumbnails.get(i); + if(thumb == null || !(thumb instanceof BufferedImage)) { + throw new IllegalArgumentException + ("thumbnails contains null references or objects other than BufferedImages!"); + } + } + } + + if(this.isInsertingEmpty) { + throw new IllegalStateException + ("Previous call to prepareInsertEmpty() without corresponding call to endInsertEmpty()!"); + } + + if(this.isWritingEmpty) { + throw new IllegalStateException + ("Previous call to prepareWriteEmpty() without corresponding call to endWriteEmpty()!"); + } + } + + public void prepareInsertEmpty(int imageIndex, + ImageTypeSpecifier imageType, + int width, + int height, + IIOMetadata imageMetadata, + List thumbnails, + ImageWriteParam param) throws IOException { + checkParamsEmpty(imageType, width, height, thumbnails); + + this.isInsertingEmpty = true; + + SampleModel emptySM = imageType.getSampleModel(); + RenderedImage emptyImage = + new EmptyImage(0, 0, width, height, + 0, 0, emptySM.getWidth(), emptySM.getHeight(), + emptySM, imageType.getColorModel()); + + insert(imageIndex, new IIOImage(emptyImage, null, imageMetadata), + param, false); + } + + public void prepareWriteEmpty(IIOMetadata streamMetadata, + ImageTypeSpecifier imageType, + int width, + int height, + IIOMetadata imageMetadata, + List thumbnails, + ImageWriteParam param) throws IOException { + checkParamsEmpty(imageType, width, height, thumbnails); + + this.isWritingEmpty = true; + + SampleModel emptySM = imageType.getSampleModel(); + RenderedImage emptyImage = + new EmptyImage(0, 0, width, height, + 0, 0, emptySM.getWidth(), emptySM.getHeight(), + emptySM, imageType.getColorModel()); + + write(streamMetadata, new IIOImage(emptyImage, null, imageMetadata), + param, true, false); + } + + public void endInsertEmpty() throws IOException { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + + if(!this.isInsertingEmpty) { + throw new IllegalStateException + ("No previous call to prepareInsertEmpty()!"); + } + + if(this.isWritingEmpty) { + throw new IllegalStateException + ("Previous call to prepareWriteEmpty() without corresponding call to endWriteEmpty()!"); + } + + if (inReplacePixelsNest) { + throw new IllegalStateException + ("In nested call to prepareReplacePixels!"); + } + + this.isInsertingEmpty = false; + } + + public void endWriteEmpty() throws IOException { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + + if(!this.isWritingEmpty) { + throw new IllegalStateException + ("No previous call to prepareWriteEmpty()!"); + } + + if(this.isInsertingEmpty) { + throw new IllegalStateException + ("Previous call to prepareInsertEmpty() without corresponding call to endInsertEmpty()!"); + } + + if (inReplacePixelsNest) { + throw new IllegalStateException + ("In nested call to prepareReplacePixels!"); + } + + this.isWritingEmpty = false; + } + + // ----- END insert/writeEmpty methods ----- + + // ----- BEGIN replacePixels methods ----- + + private TIFFIFD readIFD(int imageIndex) throws IOException { + if (stream == null) { + throw new IllegalStateException("Output not set!"); + } + if (imageIndex < 0) { + throw new IndexOutOfBoundsException("imageIndex < 0!"); + } + + stream.mark(); + long[] ifdpos = new long[1]; + long[] ifd = new long[1]; + locateIFD(imageIndex, ifdpos, ifd); + if (ifd[0] == 0) { + stream.reset(); + throw new IndexOutOfBoundsException + ("imageIndex out of bounds!"); + } + + List tagSets = new ArrayList(1); + tagSets.add(BaselineTIFFTagSet.getInstance()); + TIFFIFD rootIFD = new TIFFIFD(tagSets); + rootIFD.initialize(stream, true, true); + stream.reset(); + + return rootIFD; + } + + public boolean canReplacePixels(int imageIndex) throws IOException { + if (getOutput() == null) { + throw new IllegalStateException("getOutput() == null!"); + } + + TIFFIFD rootIFD = readIFD(imageIndex); + TIFFField f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION); + int compression = f.getAsInt(0); + + return compression == BaselineTIFFTagSet.COMPRESSION_NONE; + } + + private Object replacePixelsLock = new Object(); + + private int replacePixelsIndex = -1; + private TIFFImageMetadata replacePixelsMetadata = null; + private long[] replacePixelsTileOffsets = null; + private long[] replacePixelsByteCounts = null; + private long replacePixelsOffsetsPosition = 0L; + private long replacePixelsByteCountsPosition = 0L; + private Rectangle replacePixelsRegion = null; + private boolean inReplacePixelsNest = false; + + private TIFFImageReader reader = null; + + public void prepareReplacePixels(int imageIndex, + Rectangle region) throws IOException { + synchronized(replacePixelsLock) { + // Check state and parameters vis-a-vis ImageWriter specification. + if (stream == null) { + throw new IllegalStateException("Output not set!"); + } + if (region == null) { + throw new NullPointerException("region == null!"); + } + if (region.getWidth() < 1) { + throw new IllegalArgumentException("region.getWidth() < 1!"); + } + if (region.getHeight() < 1) { + throw new IllegalArgumentException("region.getHeight() < 1!"); + } + if (inReplacePixelsNest) { + throw new IllegalStateException + ("In nested call to prepareReplacePixels!"); + } + + // Read the IFD for the pixel replacement index. + TIFFIFD replacePixelsIFD = readIFD(imageIndex); + + // Ensure that compression is "none". + TIFFField f = + replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION); + int compression = f.getAsInt(0); + if (compression != BaselineTIFFTagSet.COMPRESSION_NONE) { + throw new UnsupportedOperationException + ("canReplacePixels(imageIndex) == false!"); + } + + // Get the image dimensions. + f = + replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH); + if(f == null) { + throw new IIOException("Cannot read ImageWidth field."); + } + int w = f.getAsInt(0); + + f = + replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH); + if(f == null) { + throw new IIOException("Cannot read ImageHeight field."); + } + int h = f.getAsInt(0); + + // Create image bounds. + Rectangle bounds = new Rectangle(0, 0, w, h); + + // Intersect region with bounds. + region = region.intersection(bounds); + + // Check for empty intersection. + if(region.isEmpty()) { + throw new IIOException("Region does not intersect image bounds"); + } + + // Save the region. + replacePixelsRegion = region; + + // Get the tile offsets. + f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS); + if(f == null) { + f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS); + } + replacePixelsTileOffsets = f.getAsLongs(); + + // Get the byte counts. + f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS); + if(f == null) { + f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS); + } + replacePixelsByteCounts = f.getAsLongs(); + + replacePixelsOffsetsPosition = + replacePixelsIFD.getStripOrTileOffsetsPosition(); + replacePixelsByteCountsPosition = + replacePixelsIFD.getStripOrTileByteCountsPosition(); + + // Get the image metadata. + replacePixelsMetadata = new TIFFImageMetadata(replacePixelsIFD); + + // Save the image index. + replacePixelsIndex = imageIndex; + + // Set the pixel replacement flag. + inReplacePixelsNest = true; + } + } + + private Raster subsample(Raster raster, int[] sourceBands, + int subOriginX, int subOriginY, + int subPeriodX, int subPeriodY, + int dstOffsetX, int dstOffsetY, + Rectangle target) { + + int x = raster.getMinX(); + int y = raster.getMinY(); + int w = raster.getWidth(); + int h = raster.getHeight(); + int b = raster.getSampleModel().getNumBands(); + int t = raster.getSampleModel().getDataType(); + + int outMinX = XToTileX(x, subOriginX, subPeriodX) + dstOffsetX; + int outMinY = YToTileY(y, subOriginY, subPeriodY) + dstOffsetY; + int outMaxX = XToTileX(x + w - 1, subOriginX, subPeriodX) + dstOffsetX; + int outMaxY = YToTileY(y + h - 1, subOriginY, subPeriodY) + dstOffsetY; + int outWidth = outMaxX - outMinX + 1; + int outHeight = outMaxY - outMinY + 1; + + if(outWidth <= 0 || outHeight <= 0) return null; + + int inMinX = (outMinX - dstOffsetX)*subPeriodX + subOriginX; + int inMaxX = (outMaxX - dstOffsetX)*subPeriodX + subOriginX; + int inWidth = inMaxX - inMinX + 1; + int inMinY = (outMinY - dstOffsetY)*subPeriodY + subOriginY; + int inMaxY = (outMaxY - dstOffsetY)*subPeriodY + subOriginY; + int inHeight = inMaxY - inMinY + 1; + + WritableRaster wr = + raster.createCompatibleWritableRaster(outMinX, outMinY, + outWidth, outHeight); + + int jMax = inMinY + inHeight; + + if(t == DataBuffer.TYPE_FLOAT) { + float[] fsamples = new float[inWidth]; + float[] fsubsamples = new float[outWidth]; + + for(int k = 0; k < b; k++) { + int outY = outMinY; + for(int j = inMinY; j < jMax; j += subPeriodY) { + raster.getSamples(inMinX, j, inWidth, 1, k, fsamples); + int s = 0; + for(int i = 0; i < inWidth; i += subPeriodX) { + fsubsamples[s++] = fsamples[i]; + } + wr.setSamples(outMinX, outY++, outWidth, 1, k, + fsubsamples); + } + } + } else if (t == DataBuffer.TYPE_DOUBLE) { + double[] dsamples = new double[inWidth]; + double[] dsubsamples = new double[outWidth]; + + for(int k = 0; k < b; k++) { + int outY = outMinY; + for(int j = inMinY; j < jMax; j += subPeriodY) { + raster.getSamples(inMinX, j, inWidth, 1, k, dsamples); + int s = 0; + for(int i = 0; i < inWidth; i += subPeriodX) { + dsubsamples[s++] = dsamples[i]; + } + wr.setSamples(outMinX, outY++, outWidth, 1, k, + dsubsamples); + } + } + } else { + int[] samples = new int[inWidth]; + int[] subsamples = new int[outWidth]; + + for(int k = 0; k < b; k++) { + int outY = outMinY; + for(int j = inMinY; j < jMax; j += subPeriodY) { + raster.getSamples(inMinX, j, inWidth, 1, k, samples); + int s = 0; + for(int i = 0; i < inWidth; i += subPeriodX) { + subsamples[s++] = samples[i]; + } + wr.setSamples(outMinX, outY++, outWidth, 1, k, + subsamples); + } + } + } + + return wr.createChild(outMinX, outMinY, + target.width, target.height, + target.x, target.y, + sourceBands); + } + + public void replacePixels(RenderedImage image, ImageWriteParam param) + throws IOException { + + synchronized(replacePixelsLock) { + // Check state and parameters vis-a-vis ImageWriter specification. + if (stream == null) { + throw new IllegalStateException("stream == null!"); + } + + if (image == null) { + throw new NullPointerException("image == null!"); + } + + if (!inReplacePixelsNest) { + throw new IllegalStateException + ("No previous call to prepareReplacePixels!"); + } + + // Subsampling values. + int stepX = 1, stepY = 1, gridX = 0, gridY = 0; + + // Initialize the ImageWriteParam. + if (param == null) { + // Use the default. + param = getDefaultWriteParam(); + } else { + // Make a copy of the ImageWriteParam. + ImageWriteParam paramCopy = getDefaultWriteParam(); + + // Force uncompressed. + paramCopy.setCompressionMode(ImageWriteParam.MODE_DISABLED); + + // Force tiling to remain as in the already written image. + paramCopy.setTilingMode(ImageWriteParam.MODE_COPY_FROM_METADATA); + + // Retain source and destination region and band settings. + paramCopy.setDestinationOffset(param.getDestinationOffset()); + paramCopy.setSourceBands(param.getSourceBands()); + paramCopy.setSourceRegion(param.getSourceRegion()); + + // Save original subsampling values for subsampling the + // replacement data - not the data re-read from the image. + stepX = param.getSourceXSubsampling(); + stepY = param.getSourceYSubsampling(); + gridX = param.getSubsamplingXOffset(); + gridY = param.getSubsamplingYOffset(); + + // Replace the param. + param = paramCopy; + } + + // Check band count and bit depth compatibility. + TIFFField f = + replacePixelsMetadata.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); + if(f == null) { + throw new IIOException + ("Cannot read destination BitsPerSample"); + } + int[] dstBitsPerSample = f.getAsInts(); + int[] srcBitsPerSample = image.getSampleModel().getSampleSize(); + int[] sourceBands = param.getSourceBands(); + if(sourceBands != null) { + if(sourceBands.length != dstBitsPerSample.length) { + throw new IIOException + ("Source and destination have different SamplesPerPixel"); + } + for(int i = 0; i < sourceBands.length; i++) { + if(dstBitsPerSample[i] != + srcBitsPerSample[sourceBands[i]]) { + throw new IIOException + ("Source and destination have different BitsPerSample"); + } + } + } else { + int srcNumBands = image.getSampleModel().getNumBands(); + if(srcNumBands != dstBitsPerSample.length) { + throw new IIOException + ("Source and destination have different SamplesPerPixel"); + } + for(int i = 0; i < srcNumBands; i++) { + if(dstBitsPerSample[i] != srcBitsPerSample[i]) { + throw new IIOException + ("Source and destination have different BitsPerSample"); + } + } + } + + // Get the source image bounds. + Rectangle srcImageBounds = + new Rectangle(image.getMinX(), image.getMinY(), + image.getWidth(), image.getHeight()); + + // Initialize the source rect. + Rectangle srcRect = param.getSourceRegion(); + if(srcRect == null) { + srcRect = srcImageBounds; + } + + // Set subsampling grid parameters. + int subPeriodX = stepX; + int subPeriodY = stepY; + int subOriginX = gridX + srcRect.x; + int subOriginY = gridY + srcRect.y; + + // Intersect with the source bounds. + if(!srcRect.equals(srcImageBounds)) { + srcRect = srcRect.intersection(srcImageBounds); + if(srcRect.isEmpty()) { + throw new IllegalArgumentException + ("Source region does not intersect source image!"); + } + } + + // Get the destination offset. + Point dstOffset = param.getDestinationOffset(); + + // Forward map source rectangle to determine destination width. + int dMinX = XToTileX(srcRect.x, subOriginX, subPeriodX) + + dstOffset.x; + int dMinY = YToTileY(srcRect.y, subOriginY, subPeriodY) + + dstOffset.y; + int dMaxX = XToTileX(srcRect.x + srcRect.width, + subOriginX, subPeriodX) + dstOffset.x; + int dMaxY = YToTileY(srcRect.y + srcRect.height, + subOriginY, subPeriodY) + dstOffset.y; + + // Initialize the destination rectangle. + Rectangle dstRect = + new Rectangle(dstOffset.x, dstOffset.y, + dMaxX - dMinX, dMaxY - dMinY); + + // Intersect with the replacement region. + dstRect = dstRect.intersection(replacePixelsRegion); + if(dstRect.isEmpty()) { + throw new IllegalArgumentException + ("Forward mapped source region does not intersect destination region!"); + } + + // Backward map to the active source region. + int activeSrcMinX = (dstRect.x - dstOffset.x)*subPeriodX + + subOriginX; + int sxmax = + (dstRect.x + dstRect.width - 1 - dstOffset.x)*subPeriodX + + subOriginX; + int activeSrcWidth = sxmax - activeSrcMinX + 1; + + int activeSrcMinY = (dstRect.y - dstOffset.y)*subPeriodY + + subOriginY; + int symax = + (dstRect.y + dstRect.height - 1 - dstOffset.y)*subPeriodY + + subOriginY; + int activeSrcHeight = symax - activeSrcMinY + 1; + Rectangle activeSrcRect = + new Rectangle(activeSrcMinX, activeSrcMinY, + activeSrcWidth, activeSrcHeight); + if(activeSrcRect.intersection(srcImageBounds).isEmpty()) { + throw new IllegalArgumentException + ("Backward mapped destination region does not intersect source image!"); + } + + if(reader == null) { + reader = new TIFFImageReader(new TIFFImageReaderSpi()); + } else { + reader.reset(); + } + + stream.mark(); + + try { + stream.seek(headerPosition); + reader.setInput(stream); + + this.imageMetadata = replacePixelsMetadata; + this.param = param; + SampleModel sm = image.getSampleModel(); + ColorModel cm = image.getColorModel(); + this.numBands = sm.getNumBands(); + this.imageType = new ImageTypeSpecifier(image); + this.periodX = param.getSourceXSubsampling(); + this.periodY = param.getSourceYSubsampling(); + this.sourceBands = null; + int[] sBands = param.getSourceBands(); + if (sBands != null) { + this.sourceBands = sBands; + this.numBands = sourceBands.length; + } + setupMetadata(cm, sm, + reader.getWidth(replacePixelsIndex), + reader.getHeight(replacePixelsIndex)); + int[] scaleSampleSize = sm.getSampleSize(); + initializeScaleTables(scaleSampleSize); + + // Determine whether bilevel. + this.isBilevel = ImageUtil.isBinary(image.getSampleModel()); + + // Check for photometric inversion. + this.isInverted = + (nativePhotometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO && + photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) || + (nativePhotometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO && + photometricInterpretation == + BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO); + + // Analyze image data suitability for direct copy. + this.isImageSimple = + (isBilevel || + (!isInverted && ImageUtil.imageIsContiguous(image))) && + !isRescaling && // no value rescaling + sourceBands == null && // no subbanding + periodX == 1 && periodY == 1 && // no subsampling + colorConverter == null; + + int minTileX = XToTileX(dstRect.x, 0, tileWidth); + int minTileY = YToTileY(dstRect.y, 0, tileLength); + int maxTileX = XToTileX(dstRect.x + dstRect.width - 1, + 0, tileWidth); + int maxTileY = YToTileY(dstRect.y + dstRect.height - 1, + 0, tileLength); + + TIFFCompressor encoder = new TIFFNullCompressor(); + encoder.setWriter(this); + encoder.setStream(stream); + encoder.setMetadata(this.imageMetadata); + + Rectangle tileRect = new Rectangle(); + for(int ty = minTileY; ty <= maxTileY; ty++) { + for(int tx = minTileX; tx <= maxTileX; tx++) { + int tileIndex = ty*tilesAcross + tx; + boolean isEmpty = + replacePixelsByteCounts[tileIndex] == 0L; + WritableRaster raster; + if(isEmpty) { + SampleModel tileSM = + sm.createCompatibleSampleModel(tileWidth, + tileLength); + raster = Raster.createWritableRaster(tileSM, null); + } else { + BufferedImage tileImage = + reader.readTile(replacePixelsIndex, tx, ty); + raster = tileImage.getRaster(); + } + + tileRect.setLocation(tx*tileWidth, + ty*tileLength); + tileRect.setSize(raster.getWidth(), + raster.getHeight()); + raster = + raster.createWritableTranslatedChild(tileRect.x, + tileRect.y); + + Rectangle replacementRect = + tileRect.intersection(dstRect); + + int srcMinX = + (replacementRect.x - dstOffset.x)*subPeriodX + + subOriginX; + int srcXmax = + (replacementRect.x + replacementRect.width - 1 - + dstOffset.x)*subPeriodX + subOriginX; + int srcWidth = srcXmax - srcMinX + 1; + + int srcMinY = + (replacementRect.y - dstOffset.y)*subPeriodY + + subOriginY; + int srcYMax = + (replacementRect.y + replacementRect.height - 1 - + dstOffset.y)*subPeriodY + subOriginY; + int srcHeight = srcYMax - srcMinY + 1; + Rectangle srcTileRect = + new Rectangle(srcMinX, srcMinY, + srcWidth, srcHeight); + + Raster replacementData = image.getData(srcTileRect); + if(subPeriodX == 1 && subPeriodY == 1 && + subOriginX == 0 && subOriginY == 0) { + replacementData = + replacementData.createChild(srcTileRect.x, + srcTileRect.y, + srcTileRect.width, + srcTileRect.height, + replacementRect.x, + replacementRect.y, + sourceBands); + } else { + replacementData = subsample(replacementData, + sourceBands, + subOriginX, + subOriginY, + subPeriodX, + subPeriodY, + dstOffset.x, + dstOffset.y, + replacementRect); + if(replacementData == null) { + continue; + } + } + + raster.setRect(replacementData); + + if(isEmpty) { + stream.seek(nextSpace); + } else { + stream.seek(replacePixelsTileOffsets[tileIndex]); + } + + this.image = new SingleTileRenderedImage(raster, cm); + + int numBytes = writeTile(tileRect, encoder); + + if(isEmpty) { + // Update Strip/TileOffsets and + // Strip/TileByteCounts fields. + stream.mark(); + stream.seek(replacePixelsOffsetsPosition + + 4*tileIndex); + stream.writeInt((int)nextSpace); + stream.seek(replacePixelsByteCountsPosition + + 4*tileIndex); + stream.writeInt(numBytes); + stream.reset(); + + // Increment location of next available space. + nextSpace += numBytes; + } + } + } + + } catch(IOException e) { + throw e; + } finally { + stream.reset(); + } + } + } + + public void replacePixels(Raster raster, ImageWriteParam param) + throws IOException { + if (raster == null) { + throw new NullPointerException("raster == null!"); + } + + replacePixels(new SingleTileRenderedImage(raster, + image.getColorModel()), + param); + } + + public void endReplacePixels() throws IOException { + synchronized(replacePixelsLock) { + if(!this.inReplacePixelsNest) { + throw new IllegalStateException + ("No previous call to prepareReplacePixels()!"); + } + replacePixelsIndex = -1; + replacePixelsMetadata = null; + replacePixelsTileOffsets = null; + replacePixelsByteCounts = null; + replacePixelsOffsetsPosition = 0L; + replacePixelsByteCountsPosition = 0L; + replacePixelsRegion = null; + inReplacePixelsNest = false; + } + } + + // ----- END replacePixels methods ----- + + public void reset() { + super.reset(); + + stream = null; + image = null; + imageType = null; + byteOrder = null; + param = null; + compressor = null; + colorConverter = null; + streamMetadata = null; + imageMetadata = null; + + isWritingSequence = false; + isWritingEmpty = false; + isInsertingEmpty = false; + + replacePixelsIndex = -1; + replacePixelsMetadata = null; + replacePixelsTileOffsets = null; + replacePixelsByteCounts = null; + replacePixelsOffsetsPosition = 0L; + replacePixelsByteCountsPosition = 0L; + replacePixelsRegion = null; + inReplacePixelsNest = false; + } +} + +class EmptyImage extends SimpleRenderedImage { + EmptyImage(int minX, int minY, int width, int height, + int tileGridXOffset, int tileGridYOffset, + int tileWidth, int tileHeight, + SampleModel sampleModel, ColorModel colorModel) { + this.minX = minX; + this.minY = minY; + this.width = width; + this.height = height; + this.tileGridXOffset = tileGridXOffset; + this.tileGridYOffset = tileGridYOffset; + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + this.sampleModel = sampleModel; + this.colorModel = colorModel; + } + + public Raster getTile(int tileX, int tileY) { + return null; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriterSpi.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriterSpi.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.util.Locale; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriter; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.spi.ServiceRegistry; +import javax.imageio.stream.ImageOutputStream; + +public class TIFFImageWriterSpi extends ImageWriterSpi { + + private boolean registered = false; + + public TIFFImageWriterSpi() { + super("Oracle Corporation", + "1.0", + new String[] {"tif", "TIF", "tiff", "TIFF"}, + new String[] {"tif", "tiff"}, + new String[] {"image/tiff"}, + "com.sun.imageio.plugins.tiff.TIFFImageWriter", + new Class[] {ImageOutputStream.class}, + new String[] {"com.sun.imageio.plugins.tiff.TIFFImageReaderSpi"}, + false, + TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME, + "com.sun.imageio.plugins.tiff.TIFFStreamMetadataFormat", + null, null, + false, + TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME, + "com.sun.imageio.plugins.tiff.TIFFImageMetadataFormat", + null, null + ); + } + + public boolean canEncodeImage(ImageTypeSpecifier type) { + return true; + } + + public String getDescription(Locale locale) { + return "Standard TIFF image writer"; + } + + public ImageWriter createWriterInstance(Object extension) { + return new TIFFImageWriter(this); + } + + public void onRegistration(ServiceRegistry registry, + Class category) { + if (registered) { + return; + } + + registered = true; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGCompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGCompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.plugins.tiff.TIFFField; +import javax.imageio.plugins.tiff.TIFFTag; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.Iterator; +import javax.imageio.ImageReader; +import javax.imageio.ImageWriteParam; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.IIORegistry; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.spi.ServiceRegistry; +import javax.imageio.stream.MemoryCacheImageInputStream; +import javax.imageio.stream.MemoryCacheImageOutputStream; + +/** + * Compressor for encoding compression type 7, TTN2/Adobe JPEG-in-TIFF. + */ +public class TIFFJPEGCompressor extends TIFFBaseJPEGCompressor { + + // Subsampling factor for chroma bands (Cb Cr). + static final int CHROMA_SUBSAMPLING = 2; + + /** + * A filter which identifies the ImageReaderSpi of a JPEG reader + * which supports JPEG native stream metadata. + */ + private static class JPEGSPIFilter implements ServiceRegistry.Filter { + JPEGSPIFilter() {} + + public boolean filter(Object provider) { + ImageReaderSpi readerSPI = (ImageReaderSpi)provider; + + if(readerSPI != null) { + String streamMetadataName = + readerSPI.getNativeStreamMetadataFormatName(); + if(streamMetadataName != null) { + return streamMetadataName.equals(STREAM_METADATA_NAME); + } else { + return false; + } + } + + return false; + } + } + + /** + * Retrieves a JPEG reader which supports native JPEG stream metadata. + */ + private static ImageReader getJPEGTablesReader() { + ImageReader jpegReader = null; + + try { + IIORegistry registry = IIORegistry.getDefaultInstance(); + Class imageReaderClass = + Class.forName("javax.imageio.spi.ImageReaderSpi"); + Iterator readerSPIs = + registry.getServiceProviders(imageReaderClass, + new JPEGSPIFilter(), + true); + if(readerSPIs.hasNext()) { + ImageReaderSpi jpegReaderSPI = + (ImageReaderSpi)readerSPIs.next(); + jpegReader = jpegReaderSPI.createReaderInstance(); + } + } catch(Exception e) { + // Ignore it ... + } + + return jpegReader; + } + + public TIFFJPEGCompressor(ImageWriteParam param) { + super("JPEG", BaselineTIFFTagSet.COMPRESSION_JPEG, false, param); + } + + /** + * Sets the value of the metadata field. + * + *

The implementation in this class also adds the TIFF fields + * JPEGTables, YCbCrSubSampling, YCbCrPositioning, and + * ReferenceBlackWhite superseding any prior settings of those + * fields.

+ * + * @param metadata the IIOMetadata object for the + * image being written. + * + * @see #getMetadata() + */ + public void setMetadata(IIOMetadata metadata) { + super.setMetadata(metadata); + + if (metadata instanceof TIFFImageMetadata) { + TIFFImageMetadata tim = (TIFFImageMetadata)metadata; + TIFFIFD rootIFD = tim.getRootIFD(); + BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance(); + + TIFFField f = + tim.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL); + int numBands = f.getAsInt(0); + + if(numBands == 1) { + // Remove YCbCr fields not relevant for grayscale. + + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING); + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING); + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE); + } else { // numBands == 3 + // Replace YCbCr fields. + + // YCbCrSubSampling + TIFFField YCbCrSubSamplingField = new TIFFField + (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING), + TIFFTag.TIFF_SHORT, 2, + new char[] {CHROMA_SUBSAMPLING, CHROMA_SUBSAMPLING}); + rootIFD.addTIFFField(YCbCrSubSamplingField); + + // YCbCrPositioning + TIFFField YCbCrPositioningField = new TIFFField + (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING), + TIFFTag.TIFF_SHORT, 1, + new char[] + {BaselineTIFFTagSet.Y_CB_CR_POSITIONING_CENTERED}); + rootIFD.addTIFFField(YCbCrPositioningField); + + // ReferenceBlackWhite + TIFFField referenceBlackWhiteField = new TIFFField + (base.getTag(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE), + TIFFTag.TIFF_RATIONAL, 6, + new long[][] { // no headroon/footroom + {0, 1}, {255, 1}, + {128, 1}, {255, 1}, + {128, 1}, {255, 1} + }); + rootIFD.addTIFFField(referenceBlackWhiteField); + } + + // JPEGTables field is written if and only if one is + // already present in the metadata. If one is present + // and has either zero length or does not represent a + // valid tables-only stream, then a JPEGTables field + // will be written initialized to the standard tables- + // only stream written by the JPEG writer. + + // Retrieve the JPEGTables field. + TIFFField JPEGTablesField = + tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_TABLES); + + // Initialize JPEG writer to one supporting abbreviated streams. + if(JPEGTablesField != null) { + // Intialize the JPEG writer to one that supports stream + // metadata, i.e., abbreviated streams, and may or may not + // support image metadata. + initJPEGWriter(true, false); + } + + // Write JPEGTables field if a writer supporting abbreviated + // streams was available. + if(JPEGTablesField != null && JPEGWriter != null) { + // Set the abbreviated stream flag. + this.writeAbbreviatedStream = true; + + //Branch based on field value count. + if(JPEGTablesField.getCount() > 0) { + // Derive the stream metadata from the field. + + // Get the field values. + byte[] tables = JPEGTablesField.getAsBytes(); + + // Create an input stream for the tables. + ByteArrayInputStream bais = + new ByteArrayInputStream(tables); + MemoryCacheImageInputStream iis = + new MemoryCacheImageInputStream(bais); + + // Read the tables stream using the JPEG reader. + ImageReader jpegReader = getJPEGTablesReader(); + jpegReader.setInput(iis); + + // Initialize the stream metadata object. + try { + JPEGStreamMetadata = jpegReader.getStreamMetadata(); + } catch(Exception e) { + // Fall back to default tables. + JPEGStreamMetadata = null; + } finally { + jpegReader.reset(); + } + } + + if(JPEGStreamMetadata == null) { + // Derive the field from default stream metadata. + + // Get default stream metadata. + JPEGStreamMetadata = + JPEGWriter.getDefaultStreamMetadata(JPEGParam); + + // Create an output stream for the tables. + ByteArrayOutputStream tableByteStream = + new ByteArrayOutputStream(); + MemoryCacheImageOutputStream tableStream = + new MemoryCacheImageOutputStream(tableByteStream); + + // Write a tables-only stream. + JPEGWriter.setOutput(tableStream); + try { + JPEGWriter.prepareWriteSequence(JPEGStreamMetadata); + tableStream.flush(); + JPEGWriter.endWriteSequence(); + + // Get the tables-only stream content. + byte[] tables = tableByteStream.toByteArray(); + + // Add the JPEGTables field. + JPEGTablesField = new TIFFField + (base.getTag(BaselineTIFFTagSet.TAG_JPEG_TABLES), + TIFFTag.TIFF_UNDEFINED, + tables.length, + tables); + rootIFD.addTIFFField(JPEGTablesField); + } catch(Exception e) { + // Do not write JPEGTables field. + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_TABLES); + this.writeAbbreviatedStream = false; + } + } + } else { // Do not write JPEGTables field. + // Remove any field present. + rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_TABLES); + + // Initialize the writer preferring codecLib. + initJPEGWriter(false, false); + } + } + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGDecompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGDecompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.io.IOException; +import java.io.ByteArrayInputStream; +import java.util.Iterator; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.ImageReadParam; +import javax.imageio.stream.MemoryCacheImageInputStream; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.plugins.tiff.TIFFField; + +public class TIFFJPEGDecompressor extends TIFFDecompressor { + // Start of Image + protected static final int SOI = 0xD8; + + // End of Image + protected static final int EOI = 0xD9; + + protected ImageReader JPEGReader = null; + protected ImageReadParam JPEGParam; + + protected boolean hasJPEGTables = false; + protected byte[] tables = null; + + private byte[] data = new byte[0]; + + public TIFFJPEGDecompressor() {} + + public void beginDecoding() { + // Initialize the JPEG reader if needed. + if(this.JPEGReader == null) { + // Get all JPEG readers. + Iterator iter = ImageIO.getImageReadersByFormatName("jpeg"); + + if(!iter.hasNext()) { + throw new IllegalStateException("No JPEG readers found!"); + } + + // Initialize reader to the first one. + this.JPEGReader = iter.next(); + + this.JPEGParam = JPEGReader.getDefaultReadParam(); + } + + // Get the JPEGTables field. + TIFFImageMetadata tmetadata = (TIFFImageMetadata)metadata; + TIFFField f = + tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_TABLES); + + if (f != null) { + this.hasJPEGTables = true; + this.tables = f.getAsBytes(); + } else { + this.hasJPEGTables = false; + } + } + + public void decodeRaw(byte[] b, + int dstOffset, + int bitsPerPixel, + int scanlineStride) throws IOException { + // Seek to the data position for this segment. + stream.seek(offset); + + // Set the stream variable depending on presence of JPEGTables. + ImageInputStream is; + if(this.hasJPEGTables) { + // The current strip or tile is an abbreviated JPEG stream. + + // Reallocate memory if there is not enough already. + int dataLength = tables.length + byteCount; + if(data.length < dataLength) { + data = new byte[dataLength]; + } + + // Copy the tables ignoring any EOI and subsequent bytes. + int dataOffset = tables.length; + for(int i = tables.length - 2; i > 0; i--) { + if((tables[i] & 0xff) == 0xff && + (tables[i+1] & 0xff) == EOI) { + dataOffset = i; + break; + } + } + System.arraycopy(tables, 0, data, 0, dataOffset); + + // Check for SOI and skip it if present. + byte byte1 = (byte)stream.read(); + byte byte2 = (byte)stream.read(); + if(!((byte1 & 0xff) == 0xff && (byte2 & 0xff) == SOI)) { + data[dataOffset++] = byte1; + data[dataOffset++] = byte2; + } + + // Read remaining data. + stream.readFully(data, dataOffset, byteCount - 2); + + // Create ImageInputStream. + ByteArrayInputStream bais = new ByteArrayInputStream(data); + is = new MemoryCacheImageInputStream(bais); + } else { + // The current strip or tile is a complete JPEG stream. + is = stream; + } + + // Set the stream on the reader. + JPEGReader.setInput(is, false, true); + + // Set the destination to the raw image ignoring the parameters. + JPEGParam.setDestination(rawImage); + + // Read the strip or tile. + JPEGReader.read(0, JPEGParam); + } + + protected void finalize() throws Throwable { + super.finalize(); + JPEGReader.dispose(); + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBCompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBCompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import java.io.IOException; + +/** + * Uncompressed data with LSB-to-MSB fill order. + */ +public class TIFFLSBCompressor extends TIFFCompressor { + + public TIFFLSBCompressor() { + super("", BaselineTIFFTagSet.COMPRESSION_NONE, true); + } + + public int encode(byte[] b, int off, + int width, int height, + int[] bitsPerSample, + int scanlineStride) throws IOException { + int bitsPerPixel = 0; + for (int i = 0; i < bitsPerSample.length; i++) { + bitsPerPixel += bitsPerSample[i]; + } + int bytesPerRow = (bitsPerPixel*width + 7)/8; + byte[] compData = new byte[bytesPerRow]; + byte[] flipTable = TIFFFaxDecompressor.flipTable; + for (int row = 0; row < height; row++) { + System.arraycopy(b, off, compData, 0, bytesPerRow); + for(int j = 0; j < bytesPerRow; j++) { + compData[j] = flipTable[compData[j]&0xff]; + } + stream.write(compData, 0, bytesPerRow); + off += scanlineStride; + } + return height*bytesPerRow; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBDecompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBDecompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.io.IOException; + +public class TIFFLSBDecompressor extends TIFFDecompressor { + + /** + * Table for flipping bytes from LSB-to-MSB to MSB-to-LSB. + */ + private static final byte[] flipTable = TIFFFaxDecompressor.flipTable; + + public TIFFLSBDecompressor() {} + + public void decodeRaw(byte[] b, + int dstOffset, + int bitsPerPixel, + int scanlineStride) throws IOException { + stream.seek(offset); + + int bytesPerRow = (srcWidth*bitsPerPixel + 7)/8; + if(bytesPerRow == scanlineStride) { + int numBytes = bytesPerRow*srcHeight; + stream.readFully(b, dstOffset, numBytes); + int xMax = dstOffset + numBytes; + for (int x = dstOffset; x < xMax; x++) { + b[x] = flipTable[b[x]&0xff]; + } + } else { + for (int y = 0; y < srcHeight; y++) { + stream.readFully(b, dstOffset, bytesPerRow); + int xMax = dstOffset + bytesPerRow; + for (int x = dstOffset; x < xMax; x++) { + b[x] = flipTable[b[x]&0xff]; + } + dstOffset += scanlineStride; + } + } + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWCompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWCompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import com.sun.imageio.plugins.common.LZWCompressor; +import java.io.IOException; +import javax.imageio.stream.ImageOutputStream; +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; + +/** + * LZW Compressor. + */ +public class TIFFLZWCompressor extends TIFFCompressor { + + private final int predictor; + + public TIFFLZWCompressor(int predictorValue) { + super("LZW", BaselineTIFFTagSet.COMPRESSION_LZW, true); + this.predictor = predictorValue; + } + + public void setStream(ImageOutputStream stream) { + super.setStream(stream); + } + + public int encode(byte[] b, int off, + int width, int height, + int[] bitsPerSample, + int scanlineStride) throws IOException { + + LZWCompressor lzwCompressor = new LZWCompressor(stream, 8, true); + + int samplesPerPixel = bitsPerSample.length; + int bitsPerPixel = 0; + for (int i = 0; i < samplesPerPixel; i++) { + bitsPerPixel += bitsPerSample[i]; + } + int bytesPerRow = (bitsPerPixel*width + 7)/8; + + long initialStreamPosition = stream.getStreamPosition(); + + boolean usePredictor = + predictor == BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING; + + if(bytesPerRow == scanlineStride && !usePredictor) { + lzwCompressor.compress(b, off, bytesPerRow*height); + } else { + byte[] rowBuf = usePredictor ? new byte[bytesPerRow] : null; + for(int i = 0; i < height; i++) { + if(usePredictor) { + // Cannot modify b[] in place as it might be a data + // array from the image being written so make a copy. + System.arraycopy(b, off, rowBuf, 0, bytesPerRow); + for(int j = bytesPerRow - 1; j >= samplesPerPixel; j--) { + rowBuf[j] -= rowBuf[j - samplesPerPixel]; + } + lzwCompressor.compress(rowBuf, 0, bytesPerRow); + } else { + lzwCompressor.compress(b, off, bytesPerRow); + } + off += scanlineStride; + } + } + + lzwCompressor.flush(); + + int bytesWritten = + (int)(stream.getStreamPosition() - initialStreamPosition); + + return bytesWritten; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWDecompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWDecompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.io.IOException; +import javax.imageio.IIOException; +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; + +class TIFFLZWDecompressor extends TIFFDecompressor { + + private static final int andTable[] = { + 511, + 1023, + 2047, + 4095 + }; + + private int predictor; + + private byte[] srcData; + private byte[] dstData; + + private int srcIndex; + private int dstIndex; + + private byte stringTable[][]; + private int tableIndex, bitsToGet = 9; + + private int nextData = 0; + private int nextBits = 0; + + public TIFFLZWDecompressor(int predictor) throws IIOException { + super(); + + if (predictor != BaselineTIFFTagSet.PREDICTOR_NONE && + predictor != + BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) { + throw new IIOException("Illegal value for Predictor in " + + "TIFF file"); + } + + this.predictor = predictor; + } + + public void decodeRaw(byte[] b, + int dstOffset, + int bitsPerPixel, + int scanlineStride) throws IOException { + + // Check bitsPerSample. + if (predictor == + BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) { + int len = bitsPerSample.length; + for(int i = 0; i < len; i++) { + if(bitsPerSample[i] != 8) { + throw new IIOException + (bitsPerSample[i] + "-bit samples "+ + "are not supported for Horizontal "+ + "differencing Predictor"); + } + } + } + + stream.seek(offset); + + byte[] sdata = new byte[byteCount]; + stream.readFully(sdata); + + int bytesPerRow = (srcWidth*bitsPerPixel + 7)/8; + byte[] buf; + int bufOffset; + if(bytesPerRow == scanlineStride) { + buf = b; + bufOffset = dstOffset; + } else { + buf = new byte[bytesPerRow*srcHeight]; + bufOffset = 0; + } + + int numBytesDecoded = decode(sdata, 0, buf, bufOffset); + + if(bytesPerRow != scanlineStride) { + int off = 0; + for (int y = 0; y < srcHeight; y++) { + System.arraycopy(buf, off, b, dstOffset, bytesPerRow); + off += bytesPerRow; + dstOffset += scanlineStride; + } + } + } + + public int decode(byte[] sdata, int srcOffset, + byte[] ddata, int dstOffset) + throws IOException { + if (sdata[0] == (byte)0x00 && sdata[1] == (byte)0x01) { + throw new IIOException + ("TIFF 5.0-style LZW compression is not supported!"); + } + + this.srcData = sdata; + this.dstData = ddata; + + this.srcIndex = srcOffset; + this.dstIndex = dstOffset; + + this.nextData = 0; + this.nextBits = 0; + + initializeStringTable(); + + int code, oldCode = 0; + byte[] string; + + while ((code = getNextCode()) != 257) { + if (code == 256) { + initializeStringTable(); + code = getNextCode(); + if (code == 257) { + break; + } + + writeString(stringTable[code]); + oldCode = code; + } else { + if (code < tableIndex) { + string = stringTable[code]; + + writeString(string); + addStringToTable(stringTable[oldCode], string[0]); + oldCode = code; + } else { + string = stringTable[oldCode]; + string = composeString(string, string[0]); + writeString(string); + addStringToTable(string); + oldCode = code; + } + } + } + + if (predictor == + BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) { + + for (int j = 0; j < srcHeight; j++) { + + int count = dstOffset + samplesPerPixel * (j * srcWidth + 1); + + for (int i = samplesPerPixel; i < srcWidth * samplesPerPixel; i++) { + + dstData[count] += dstData[count - samplesPerPixel]; + count++; + } + } + } + + return dstIndex - dstOffset; + } + + /** + * Initialize the string table. + */ + public void initializeStringTable() { + stringTable = new byte[4096][]; + + for (int i = 0; i < 256; i++) { + stringTable[i] = new byte[1]; + stringTable[i][0] = (byte)i; + } + + tableIndex = 258; + bitsToGet = 9; + } + + /** + * Write out the string just uncompressed. + */ + public void writeString(byte string[]) { + if(dstIndex < dstData.length) { + int maxIndex = Math.min(string.length, + dstData.length - dstIndex); + + for (int i=0; i < maxIndex; i++) { + dstData[dstIndex++] = string[i]; + } + } + } + + /** + * Add a new string to the string table. + */ + public void addStringToTable(byte oldString[], byte newString) { + int length = oldString.length; + byte string[] = new byte[length + 1]; + System.arraycopy(oldString, 0, string, 0, length); + string[length] = newString; + + // Add this new String to the table + stringTable[tableIndex++] = string; + + if (tableIndex == 511) { + bitsToGet = 10; + } else if (tableIndex == 1023) { + bitsToGet = 11; + } else if (tableIndex == 2047) { + bitsToGet = 12; + } + } + + /** + * Add a new string to the string table. + */ + public void addStringToTable(byte string[]) { + // Add this new String to the table + stringTable[tableIndex++] = string; + + if (tableIndex == 511) { + bitsToGet = 10; + } else if (tableIndex == 1023) { + bitsToGet = 11; + } else if (tableIndex == 2047) { + bitsToGet = 12; + } + } + + /** + * Append newString to the end of oldString. + */ + public byte[] composeString(byte oldString[], byte newString) { + int length = oldString.length; + byte string[] = new byte[length + 1]; + System.arraycopy(oldString, 0, string, 0, length); + string[length] = newString; + + return string; + } + + // Returns the next 9, 10, 11 or 12 bits + public int getNextCode() { + // Attempt to get the next code. The exception is caught to make + // this robust to cases wherein the EndOfInformation code has been + // omitted from a strip. Examples of such cases have been observed + // in practice. + + try { + nextData = (nextData << 8) | (srcData[srcIndex++] & 0xff); + nextBits += 8; + + if (nextBits < bitsToGet) { + nextData = (nextData << 8) | (srcData[srcIndex++] & 0xff); + nextBits += 8; + } + + int code = + (nextData >> (nextBits - bitsToGet)) & andTable[bitsToGet - 9]; + nextBits -= bitsToGet; + + return code; + } catch (ArrayIndexOutOfBoundsException e) { + // Strip not terminated as expected: return EndOfInformation code. + return 257; + } + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWUtil.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWUtil.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.io.IOException; +import javax.imageio.IIOException; + +class TIFFLZWUtil { + public TIFFLZWUtil() { + } + + byte[] srcData; + int srcIndex; + + byte[] dstData; + int dstIndex = 0; + + byte stringTable[][]; + int tableIndex, bitsToGet = 9; + + int nextData = 0; + int nextBits = 0; + + private static final int andTable[] = { + 511, + 1023, + 2047, + 4095 + }; + + public byte[] decode(byte[] data, int predictor, int samplesPerPixel, + int width, int height) throws IOException { + if (data[0] == (byte)0x00 && data[1] == (byte)0x01) { + throw new IIOException("TIFF 5.0-style LZW compression is not supported!"); + } + + this.srcData = data; + this.srcIndex = 0; + this.nextData = 0; + this.nextBits = 0; + + this.dstData = new byte[8192]; + this.dstIndex = 0; + + initializeStringTable(); + + int code, oldCode = 0; + byte[] string; + + while ((code = getNextCode()) != 257) { + if (code == 256) { + initializeStringTable(); + code = getNextCode(); + if (code == 257) { + break; + } + + writeString(stringTable[code]); + oldCode = code; + } else { + if (code < tableIndex) { + string = stringTable[code]; + + writeString(string); + addStringToTable(stringTable[oldCode], string[0]); + oldCode = code; + } else { + string = stringTable[oldCode]; + string = composeString(string, string[0]); + writeString(string); + addStringToTable(string); + oldCode = code; + } + } + } + + if (predictor == 2) { + + int count; + for (int j = 0; j < height; j++) { + + count = samplesPerPixel * (j * width + 1); + + for (int i = samplesPerPixel; i < width * samplesPerPixel; i++) { + + dstData[count] += dstData[count - samplesPerPixel]; + count++; + } + } + } + + byte[] newDstData = new byte[dstIndex]; + System.arraycopy(dstData, 0, newDstData, 0, dstIndex); + return newDstData; + } + + /** + * Initialize the string table. + */ + public void initializeStringTable() { + stringTable = new byte[4096][]; + + for (int i = 0; i < 256; i++) { + stringTable[i] = new byte[1]; + stringTable[i][0] = (byte)i; + } + + tableIndex = 258; + bitsToGet = 9; + } + + private void ensureCapacity(int bytesToAdd) { + if (dstIndex + bytesToAdd > dstData.length) { + byte[] newDstData = new byte[Math.max((int)(dstData.length*1.2f), + dstIndex + bytesToAdd)]; + System.arraycopy(dstData, 0, newDstData, 0, dstData.length); + dstData = newDstData; + } + } + + /** + * Write out the string just uncompressed. + */ + public void writeString(byte string[]) { + ensureCapacity(string.length); + for (int i = 0; i < string.length; i++) { + dstData[dstIndex++] = string[i]; + } + } + + /** + * Add a new string to the string table. + */ + public void addStringToTable(byte oldString[], byte newString) { + int length = oldString.length; + byte string[] = new byte[length + 1]; + System.arraycopy(oldString, 0, string, 0, length); + string[length] = newString; + + // Add this new String to the table + stringTable[tableIndex++] = string; + + if (tableIndex == 511) { + bitsToGet = 10; + } else if (tableIndex == 1023) { + bitsToGet = 11; + } else if (tableIndex == 2047) { + bitsToGet = 12; + } + } + + /** + * Add a new string to the string table. + */ + public void addStringToTable(byte string[]) { + // Add this new String to the table + stringTable[tableIndex++] = string; + + if (tableIndex == 511) { + bitsToGet = 10; + } else if (tableIndex == 1023) { + bitsToGet = 11; + } else if (tableIndex == 2047) { + bitsToGet = 12; + } + } + + /** + * Append newString to the end of oldString. + */ + public byte[] composeString(byte oldString[], byte newString) { + int length = oldString.length; + byte string[] = new byte[length + 1]; + System.arraycopy(oldString, 0, string, 0, length); + string[length] = newString; + + return string; + } + + // Returns the next 9, 10, 11 or 12 bits + public int getNextCode() { + // Attempt to get the next code. The exception is caught to make + // this robust to cases wherein the EndOfInformation code has been + // omitted from a strip. Examples of such cases have been observed + // in practice. + + try { + nextData = (nextData << 8) | (srcData[srcIndex++] & 0xff); + nextBits += 8; + + if (nextBits < bitsToGet) { + nextData = (nextData << 8) | (srcData[srcIndex++] & 0xff); + nextBits += 8; + } + + int code = + (nextData >> (nextBits - bitsToGet)) & andTable[bitsToGet - 9]; + nextBits -= bitsToGet; + + return code; + } catch (ArrayIndexOutOfBoundsException e) { + // Strip not terminated as expected: return EndOfInformation code. + return 257; + } + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFMetadataFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFMetadataFormat.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import javax.imageio.metadata.IIOMetadataFormat; + +public abstract class TIFFMetadataFormat implements IIOMetadataFormat { + + protected Map elementInfoMap = new HashMap(); + protected Map attrInfoMap = new HashMap(); + + protected String resourceBaseName; + protected String rootName; + + public String getRootName() { + return rootName; + } + + private String getResource(String key, Locale locale) { + if (locale == null) { + locale = Locale.getDefault(); + } + try { + ResourceBundle bundle = + ResourceBundle.getBundle(resourceBaseName, locale); + return bundle.getString(key); + } catch (MissingResourceException e) { + return null; + } + } + + private TIFFElementInfo getElementInfo(String elementName) { + if (elementName == null) { + throw new NullPointerException("elementName == null!"); + } + TIFFElementInfo info = + elementInfoMap.get(elementName); + if (info == null) { + throw new IllegalArgumentException("No such element: " + + elementName); + } + return info; + } + + private TIFFAttrInfo getAttrInfo(String elementName, String attrName) { + if (elementName == null) { + throw new NullPointerException("elementName == null!"); + } + if (attrName == null) { + throw new NullPointerException("attrName == null!"); + } + String key = elementName + "/" + attrName; + TIFFAttrInfo info = attrInfoMap.get(key); + if (info == null) { + throw new IllegalArgumentException("No such attribute: " + key); + } + return info; + } + + public int getElementMinChildren(String elementName) { + TIFFElementInfo info = getElementInfo(elementName); + return info.minChildren; + } + + public int getElementMaxChildren(String elementName) { + TIFFElementInfo info = getElementInfo(elementName); + return info.maxChildren; + } + + public String getElementDescription(String elementName, Locale locale) { + if (!elementInfoMap.containsKey(elementName)) { + throw new IllegalArgumentException("No such element: " + + elementName); + } + return getResource(elementName, locale); + } + + public int getChildPolicy(String elementName) { + TIFFElementInfo info = getElementInfo(elementName); + return info.childPolicy; + } + + public String[] getChildNames(String elementName) { + TIFFElementInfo info = getElementInfo(elementName); + return info.childNames; + } + + public String[] getAttributeNames(String elementName) { + TIFFElementInfo info = getElementInfo(elementName); + return info.attributeNames; + } + + public int getAttributeValueType(String elementName, String attrName) { + TIFFAttrInfo info = getAttrInfo(elementName, attrName); + return info.valueType; + } + + public int getAttributeDataType(String elementName, String attrName) { + TIFFAttrInfo info = getAttrInfo(elementName, attrName); + return info.dataType; + } + + public boolean isAttributeRequired(String elementName, String attrName) { + TIFFAttrInfo info = getAttrInfo(elementName, attrName); + return info.isRequired; + } + + public String getAttributeDefaultValue(String elementName, + String attrName) { + return null; + } + + public String[] getAttributeEnumerations(String elementName, + String attrName) { + throw new IllegalArgumentException("The attribute is not an enumeration."); + } + + public String getAttributeMinValue(String elementName, String attrName) { + throw new IllegalArgumentException("The attribute is not a range."); + } + + public String getAttributeMaxValue(String elementName, String attrName) { + throw new IllegalArgumentException("The attribute is not a range."); + } + + public int getAttributeListMinLength(String elementName, String attrName) { + TIFFAttrInfo info = getAttrInfo(elementName, attrName); + return info.listMinLength; + } + + public int getAttributeListMaxLength(String elementName, String attrName) { + TIFFAttrInfo info = getAttrInfo(elementName, attrName); + return info.listMaxLength; + } + + public String getAttributeDescription(String elementName, String attrName, + Locale locale) { + String key = elementName + "/" + attrName; + if (!attrInfoMap.containsKey(key)) { + throw new IllegalArgumentException("No such attribute: " + key); + } + return getResource(key, locale); + } + + public int getObjectValueType(String elementName) { + TIFFElementInfo info = getElementInfo(elementName); + return info.objectValueType; + } + + public Class getObjectClass(String elementName) { + TIFFElementInfo info = getElementInfo(elementName); + if (info.objectValueType == VALUE_NONE) { + throw new IllegalArgumentException( + "Element cannot contain an object value: " + elementName); + } + return info.objectClass; + } + + public Object getObjectDefaultValue(String elementName) { + TIFFElementInfo info = getElementInfo(elementName); + if (info.objectValueType == VALUE_NONE) { + throw new IllegalArgumentException( + "Element cannot contain an object value: " + elementName); + } + return info.objectDefaultValue; + } + + public Object[] getObjectEnumerations(String elementName) { + TIFFElementInfo info = getElementInfo(elementName); + if (info.objectValueType == VALUE_NONE) { + throw new IllegalArgumentException( + "Element cannot contain an object value: " + elementName); + } + return info.objectEnumerations; + } + + public Comparable getObjectMinValue(String elementName) { + TIFFElementInfo info = getElementInfo(elementName); + if (info.objectValueType == VALUE_NONE) { + throw new IllegalArgumentException( + "Element cannot contain an object value: " + elementName); + } + return info.objectMinValue; + } + + public Comparable getObjectMaxValue(String elementName) { + TIFFElementInfo info = getElementInfo(elementName); + if (info.objectValueType == VALUE_NONE) { + throw new IllegalArgumentException( + "Element cannot contain an object value: " + elementName); + } + return info.objectMaxValue; + } + + public int getObjectArrayMinLength(String elementName) { + TIFFElementInfo info = getElementInfo(elementName); + if (info.objectValueType == VALUE_NONE) { + throw new IllegalArgumentException( + "Element cannot contain an object value: " + elementName); + } + return info.objectArrayMinLength; + } + + public int getObjectArrayMaxLength(String elementName) { + TIFFElementInfo info = getElementInfo(elementName); + if (info.objectValueType == VALUE_NONE) { + throw new IllegalArgumentException( + "Element cannot contain an object value: " + elementName); + } + return info.objectArrayMaxLength; + } + + public TIFFMetadataFormat() {} +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullCompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullCompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import java.io.IOException; + +public class TIFFNullCompressor extends TIFFCompressor { + + public TIFFNullCompressor() { + super("", BaselineTIFFTagSet.COMPRESSION_NONE, true); + } + + public int encode(byte[] b, int off, + int width, int height, + int[] bitsPerSample, + int scanlineStride) throws IOException { + int bitsPerPixel = 0; + for (int i = 0; i < bitsPerSample.length; i++) { + bitsPerPixel += bitsPerSample[i]; + } + + int bytesPerRow = (bitsPerPixel*width + 7)/8; + int numBytes = height*bytesPerRow; + + if(bytesPerRow == scanlineStride) { + stream.write(b, off, numBytes); + } else { + for (int row = 0; row < height; row++) { + stream.write(b, off, bytesPerRow); + off += scanlineStride; + } + } + + return numBytes; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullDecompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullDecompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.io.EOFException; +import java.io.IOException; + +public class TIFFNullDecompressor extends TIFFDecompressor { + + /** + * Whether to read the active source region only. + */ + private boolean isReadActiveOnly = false; + + /** The original value of srcMinX. */ + private int originalSrcMinX; + + /** The original value of srcMinY. */ + private int originalSrcMinY; + + /** The original value of srcWidth. */ + private int originalSrcWidth; + + /** The original value of srcHeight. */ + private int originalSrcHeight; + + public TIFFNullDecompressor() {} + + // + // This approach to reading the active region is a not the best + // as the original values of the entire source region are stored, + // overwritten, and then restored. It would probably be better to + // revise TIFFDecompressor such that this were not necessary, i.e., + // change beginDecoding() and decode() to use the active region values + // when random access is easy and the entire region values otherwise. + // + public void beginDecoding() { + // Determine number of bits per pixel. + int bitsPerPixel = 0; + for(int i = 0; i < bitsPerSample.length; i++) { + bitsPerPixel += bitsPerSample[i]; + } + + // Can read active region only if row starts on a byte boundary. + if((activeSrcMinX != srcMinX || activeSrcMinY != srcMinY || + activeSrcWidth != srcWidth || activeSrcHeight != srcHeight) && + ((activeSrcMinX - srcMinX)*bitsPerPixel) % 8 == 0) { + // Set flag. + isReadActiveOnly = true; + + // Cache original region. + originalSrcMinX = srcMinX; + originalSrcMinY = srcMinY; + originalSrcWidth = srcWidth; + originalSrcHeight = srcHeight; + + // Replace region with active region. + srcMinX = activeSrcMinX; + srcMinY = activeSrcMinY; + srcWidth = activeSrcWidth; + srcHeight = activeSrcHeight; + } else { + // Clear flag. + isReadActiveOnly = false; + } + + super.beginDecoding(); + } + + public void decode() throws IOException { + super.decode(); + + // Reset state. + if(isReadActiveOnly) { + // Restore original source region values. + srcMinX = originalSrcMinX; + srcMinY = originalSrcMinY; + srcWidth = originalSrcWidth; + srcHeight = originalSrcHeight; + + // Unset flag. + isReadActiveOnly = false; + } + } + + public void decodeRaw(byte[] b, + int dstOffset, + int bitsPerPixel, + int scanlineStride) throws IOException { + if(isReadActiveOnly) { + // Read the active source region only. + + int activeBytesPerRow = (activeSrcWidth*bitsPerPixel + 7)/8; + int totalBytesPerRow = (originalSrcWidth*bitsPerPixel + 7)/8; + int bytesToSkipPerRow = totalBytesPerRow - activeBytesPerRow; + + // + // Seek to the start of the active region: + // + // active offset = original offset + + // number of bytes to start of first active row + + // number of bytes to first active pixel within row + // + // Since the condition for reading from the active region only is + // + // ((activeSrcMinX - srcMinX)*bitsPerPixel) % 8 == 0 + // + // the bit offset to the first active pixel within the first + // active row is a multiple of 8. + // + stream.seek(offset + + (activeSrcMinY - originalSrcMinY)*totalBytesPerRow + + ((activeSrcMinX - originalSrcMinX)*bitsPerPixel)/8); + + int lastRow = activeSrcHeight - 1; + for (int y = 0; y < activeSrcHeight; y++) { + int bytesRead = stream.read(b, dstOffset, activeBytesPerRow); + if (bytesRead < 0) { + throw new EOFException(); + } else if (bytesRead != activeBytesPerRow) { + break; + } + dstOffset += scanlineStride; + + // Skip unneeded bytes (row suffix + row prefix). + if(y != lastRow) { + stream.skipBytes(bytesToSkipPerRow); + } + } + } else { + // Read the entire source region. + stream.seek(offset); + int bytesPerRow = (srcWidth*bitsPerPixel + 7)/8; + if(bytesPerRow == scanlineStride) { + if (stream.read(b, dstOffset, bytesPerRow*srcHeight) < 0) { + throw new EOFException(); + } + } else { + for (int y = 0; y < srcHeight; y++) { + int bytesRead = stream.read(b, dstOffset, bytesPerRow); + if (bytesRead < 0) { + throw new EOFException(); + } else if (bytesRead != bytesPerRow) { + break; + } + dstOffset += scanlineStride; + } + } + } + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFOldJPEGDecompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFOldJPEGDecompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,617 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.io.IOException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import javax.imageio.IIOException; +import javax.imageio.stream.MemoryCacheImageInputStream; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.plugins.tiff.TIFFField; + +/** + * TIFFDecompressor for "Old JPEG" compression. + */ +public class TIFFOldJPEGDecompressor extends TIFFJPEGDecompressor { + + // Define Huffman Tables + private static final int DHT = 0xC4; + + // Define Quantisation Tables + private static final int DQT = 0xDB; + + // Define Restart Interval + private static final int DRI = 0xDD; + + // Baseline DCT + private static final int SOF0 = 0xC0; + + // Start of Scan + private static final int SOS = 0xDA; + + // End of Image + // private static final int EOI = 0xD9; // now defined in superclass + + // Whether the decompressor has been initialized. + private boolean isInitialized = false; + + // + // Instance variables set by the initialize() method. + // + // Offset to a complete, contiguous JPEG stream. + private Long JPEGStreamOffset = null; + // Offset to the SOF marker. + private int SOFPosition = -1; + // Value of the SOS marker. + private byte[] SOSMarker = null; + + // Horizontal chroma subsampling factor. + private int subsamplingX = 2; + + // Vertical chroma subsampling factor. + private int subsamplingY = 2; + + public TIFFOldJPEGDecompressor() {} + + // + // Intialize instance variables according to an analysis of the + // TIFF field content. See bug 4929147 for test image information. + // + // Case 1: Image contains a single strip or tile and the offset to + // that strip or tile points to an SOI marker. + // + // Example: + // "Visionshape Inc. Compression Software, version 2.5" + // ColorTiffWithBarcode.tif + // Color2.tif (pages 2-5 (indexes 1-4) + // color3.tif (pages 2-5 (indexes 1-4) + // + // "Kofax standard Multi-Page TIFF Storage Filter v2.01.000" + // 01.tif (pages 1 and 3(indexes 0 and 2)) + // + // Instance variables set: JPEGStreamOffset + // + // Case 2: Image contains a single strip or tile and a + // JPEGInterchangeFormat field is present but the + // JPEGInterchangeFormatLength is erroneously missing. + // + // Example: + // "Kofax standard Multi-Page TIFF Storage Filter v2.01.000" + // 01.tif (pages 1 and 3(indexes 0 and 2)) + // (but this example also satisfies case 1) + // + // Instance variables set: JPEGStreamOffset + // + // Case 3: Image contains a single strip or tile, the + // JPEGInterchangeFormat and JPEGInterchangeFormatLength + // fields are both present, the value of JPEGInterchangeFormat + // is less than the offset to the strip or tile, and the sum + // of the values of JPEGInterchangeFormat and + // JPEGInterchangeFormatLength is greater than the offset to + // the strip or tile. + // + // Instance variables set: JPEGStreamOffset + // + // Example: + // "HP IL v1.1" + // smallliz.tif from libtiff test data. + // + // Instance variables set: JPEGStreamOffset + // + // Cases 4-5 apply if none of cases 1-3 applies or the image has multiple + // strips or tiles. + // + // Case 4: JPEGInterchangeFormat and JPEGInterchangeFormatLength are + // present, the value of JPEGInterchangeFormatLength is at least 2, + // and the sum of the values of these two fields is at most the + // value of the offset to the first strip or tile. + // + // Instance variables set: tables, SOFPosition, SOSMarker + // + // Example: + // "Oi/GFS, writer v00.06.00P, (c) Wang Labs, Inc. 1990, 1991" + // 03.tif (pages 1 and 3(indexes 0 and 2)) + // + // "Oi/GFS, writer v00.06.02" + // Color2.tif (page 1 (index 0)) + // color3.tif (page 1 (index 0)) + // + // Case 5: If none of the foregoing cases apply. For this case the + // JPEGQTables, JPEGACTables, and JPEGDCTables must be valid. + // + // Instance variables set: tables, SOFPosition, SOSMarker + // + // Example: + // "NeXT" + // zackthecat.tif from libtiff test data. + // + private synchronized void initialize() throws IOException { + if(isInitialized) { + return; + } + + // Get the TIFF metadata object. + TIFFImageMetadata tim = (TIFFImageMetadata)metadata; + + // Get the JPEGInterchangeFormat field. + TIFFField JPEGInterchangeFormatField = + tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT); + + // Get the tile or strip offsets. + TIFFField segmentOffsetField = + tim.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS); + if(segmentOffsetField == null) { + segmentOffsetField = + tim.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS); + if(segmentOffsetField == null) { + segmentOffsetField = JPEGInterchangeFormatField; + } + } + long[] segmentOffsets = segmentOffsetField.getAsLongs(); + + // Determine whether the image has more than one strip or tile. + boolean isTiled = segmentOffsets.length > 1; + + if(!isTiled) { + // + // If the image has only a single strip or tile and it looks + // as if a complete JPEG stream is present then set the value + // of JPEGStreamOffset to the offset of the JPEG stream; + // otherwise leave JPEGStreamOffset set to null. + // + + stream.seek(offset); + stream.mark(); + if(stream.read() == 0xff && stream.read() == SOI) { + // Tile or strip offset points to SOI. + JPEGStreamOffset = Long.valueOf(offset); + + // Set initialization flag and return. + ((TIFFImageReader)reader).forwardWarningMessage("SOI marker detected at start of strip or tile."); + isInitialized = true; + return; + } + stream.reset(); + + if(JPEGInterchangeFormatField != null) { + // Get the value of JPEGInterchangeFormat. + long jpegInterchangeOffset = + JPEGInterchangeFormatField.getAsLong(0); + + // Get the JPEGInterchangeFormatLength field. + TIFFField JPEGInterchangeFormatLengthField = + tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); + + if(JPEGInterchangeFormatLengthField == null) { + // JPEGInterchangeFormat stream is of indeterminate + // length so use it as a complete JPEG stream. + JPEGStreamOffset = Long.valueOf(jpegInterchangeOffset); + + // Set initialization flag and return. + ((TIFFImageReader)reader).forwardWarningMessage("JPEGInterchangeFormatLength field is missing"); + isInitialized = true; + return; + } else { + // Get the JPEGInterchangeFormatLength field's value. + long jpegInterchangeLength = + JPEGInterchangeFormatLengthField.getAsLong(0); + + if(jpegInterchangeOffset < segmentOffsets[0] && + (jpegInterchangeOffset + jpegInterchangeLength) > + segmentOffsets[0]) { + // JPEGInterchangeFormat points to a position + // below the segment start position and ends at + // a position after the segment start position. + JPEGStreamOffset = Long.valueOf(jpegInterchangeOffset); + + // Set initialization flag and return. + isInitialized = true; + return; + } + } + } + } + + // Get the subsampling factors. + TIFFField YCbCrSubsamplingField = + tim.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING); + if(YCbCrSubsamplingField != null) { + subsamplingX = YCbCrSubsamplingField.getAsChars()[0]; + subsamplingY = YCbCrSubsamplingField.getAsChars()[1]; + } + + // + // Initialize the 'tables' instance variable either for later + // use in prepending to individual abbreviated strips or tiles. + // + if(JPEGInterchangeFormatField != null) { + // Get the value of JPEGInterchangeFormat. + long jpegInterchangeOffset = + JPEGInterchangeFormatField.getAsLong(0); + + // Get the JPEGInterchangeFormatLength field. + TIFFField JPEGInterchangeFormatLengthField = + tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); + + if(JPEGInterchangeFormatLengthField != null) { + // Get the JPEGInterchangeFormatLength field's value. + long jpegInterchangeLength = + JPEGInterchangeFormatLengthField.getAsLong(0); + + if(jpegInterchangeLength >= 2 && + jpegInterchangeOffset + jpegInterchangeLength <= + segmentOffsets[0]) { + // Determine the length excluding any terminal EOI marker + // and allocate table memory. + stream.mark(); + stream.seek(jpegInterchangeOffset+jpegInterchangeLength-2); + if(stream.read() == 0xff && stream.read() == EOI) { + this.tables = new byte[(int)(jpegInterchangeLength-2)]; + } else { + this.tables = new byte[(int)jpegInterchangeLength]; + } + stream.reset(); + + // Read the tables. + stream.mark(); + stream.seek(jpegInterchangeOffset); + stream.readFully(tables); + stream.reset(); + + ((TIFFImageReader)reader).forwardWarningMessage("Incorrect JPEG interchange format: using JPEGInterchangeFormat offset to derive tables."); + } else { + ((TIFFImageReader)reader).forwardWarningMessage("JPEGInterchangeFormat+JPEGInterchangeFormatLength > offset to first strip or tile."); + } + } + } + + if(this.tables == null) { + // + // Create tables-only stream in tables[] consisting of + // SOI+DQTs+DHTs + // + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + // Save stream length; + long streamLength = stream.length(); + + // SOI + baos.write(0xff); + baos.write(SOI); + + // Quantization Tables + TIFFField f = + tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_Q_TABLES); + if(f == null) { + throw new IIOException("JPEGQTables field missing!"); + } + long[] off = f.getAsLongs(); + + for(int i = 0; i < off.length; i++) { + baos.write(0xff); // Marker ID + baos.write(DQT); + + char markerLength = (char)67; + baos.write((markerLength >>> 8) & 0xff); // Length + baos.write(markerLength & 0xff); + + baos.write(i); // Table ID and precision + + byte[] qtable = new byte[64]; + if(streamLength != -1 && off[i] > streamLength) { + throw new IIOException("JPEGQTables offset for index "+ + i+" is not in the stream!"); + } + stream.seek(off[i]); + stream.readFully(qtable); + + baos.write(qtable); // Table data + } + + // Huffman Tables (k == 0 ? DC : AC). + for(int k = 0; k < 2; k++) { + int tableTagNumber = k == 0 ? + BaselineTIFFTagSet.TAG_JPEG_DC_TABLES : + BaselineTIFFTagSet.TAG_JPEG_AC_TABLES; + f = tim.getTIFFField(tableTagNumber); + String fieldName = + tableTagNumber == + BaselineTIFFTagSet.TAG_JPEG_DC_TABLES ? + "JPEGDCTables" : "JPEGACTables"; + + if(f == null) { + throw new IIOException(fieldName+" field missing!"); + } + off = f.getAsLongs(); + + for(int i = 0; i < off.length; i++) { + baos.write(0xff); // Marker ID + baos.write(DHT); + + byte[] blengths = new byte[16]; + if(streamLength != -1 && off[i] > streamLength) { + throw new IIOException(fieldName+" offset for index "+ + i+" is not in the stream!"); + } + stream.seek(off[i]); + stream.readFully(blengths); + int numCodes = 0; + for(int j = 0; j < 16; j++) { + numCodes += blengths[j]&0xff; + } + + char markerLength = (char)(19 + numCodes); + + baos.write((markerLength >>> 8) & 0xff); // Length + baos.write(markerLength & 0xff); + + baos.write(i | (k << 4)); // Table ID and type + + baos.write(blengths); // Number of codes + + byte[] bcodes = new byte[numCodes]; + stream.readFully(bcodes); + baos.write(bcodes); // Codes + } + } + + // SOF0 + baos.write((byte)0xff); // Marker identifier + baos.write((byte)SOF0); + short sval = (short)(8 + 3*samplesPerPixel); // Length + baos.write((byte)((sval >>> 8) & 0xff)); + baos.write((byte)(sval & 0xff)); + baos.write((byte)8); // Data precision + sval = (short)srcHeight; // Tile/strip height + baos.write((byte)((sval >>> 8) & 0xff)); + baos.write((byte)(sval & 0xff)); + sval = (short)srcWidth; // Tile/strip width + baos.write((byte)((sval >>> 8) & 0xff)); + baos.write((byte)(sval & 0xff)); + baos.write((byte)samplesPerPixel); // Number of components + if(samplesPerPixel == 1) { + baos.write((byte)1); // Component ID + baos.write((byte)0x11); // Subsampling factor + baos.write((byte)0); // Quantization table ID + } else { // 3 + for(int i = 0; i < 3; i++) { + baos.write((byte)(i + 1)); // Component ID + baos.write((i != 0) ? + (byte)0x11 : + (byte)(((subsamplingX & 0x0f) << 4) | + (subsamplingY & 0x0f))); + + baos.write((byte)i); // Quantization table ID + } + }; + + + // DRI (optional). + f = tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_RESTART_INTERVAL); + if(f != null) { + char restartInterval = f.getAsChars()[0]; + + if(restartInterval != 0) { + baos.write((byte)0xff); // Marker identifier + baos.write((byte)DRI); + + sval = 4; + baos.write((byte)((sval >>> 8) & 0xff)); // Length + baos.write((byte)(sval & 0xff)); + + // RestartInterval + baos.write((byte)((restartInterval >>> 8) & 0xff)); + baos.write((byte)(restartInterval & 0xff)); + } + } + + tables = baos.toByteArray(); + } + + // + // Check for presence of SOF marker and save its position. + // + int idx = 0; + int idxMax = tables.length - 1; + while(idx < idxMax) { + if((tables[idx]&0xff) == 0xff && + (tables[idx+1]&0xff) == SOF0) { + SOFPosition = idx; + break; + } + idx++; + } + + // + // If no SOF marker, add one. + // + if(SOFPosition == -1) { + byte[] tmpTables = + new byte[tables.length + 10 + 3*samplesPerPixel]; + System.arraycopy(tables, 0, tmpTables, 0, tables.length); + int tmpOffset = tables.length; + SOFPosition = tables.length; + tables = tmpTables; + + tables[tmpOffset++] = (byte)0xff; // Marker identifier + tables[tmpOffset++] = (byte)SOF0; + short sval = (short)(8 + 3*samplesPerPixel); // Length + tables[tmpOffset++] = (byte)((sval >>> 8) & 0xff); + tables[tmpOffset++] = (byte)(sval & 0xff); + tables[tmpOffset++] = (byte)8; // Data precision + sval = (short)srcHeight; // Tile/strip height + tables[tmpOffset++] = (byte)((sval >>> 8) & 0xff); + tables[tmpOffset++] = (byte)(sval & 0xff); + sval = (short)srcWidth; // Tile/strip width + tables[tmpOffset++] = (byte)((sval >>> 8) & 0xff); + tables[tmpOffset++] = (byte)(sval & 0xff); + tables[tmpOffset++] = (byte)samplesPerPixel; // Number of components + if(samplesPerPixel == 1) { + tables[tmpOffset++] = (byte)1; // Component ID + tables[tmpOffset++] = (byte)0x11; // Subsampling factor + tables[tmpOffset++] = (byte)0; // Quantization table ID + } else { // 3 + for(int i = 0; i < 3; i++) { + tables[tmpOffset++] = (byte)(i + 1); // Component ID + tables[tmpOffset++] = (i != 0) ? + (byte)0x11 : + (byte)(((subsamplingX & 0x0f) << 4) | + (subsamplingY & 0x0f)); + + tables[tmpOffset++] = (byte)i; // Quantization table ID + } + }; + } + + // + // Initialize SOSMarker. + // + stream.mark(); + stream.seek(segmentOffsets[0]); + if(stream.read() == 0xff && stream.read() == SOS) { + // + // If the first segment starts with an SOS marker save it. + // + int SOSLength = (stream.read()<<8)|stream.read(); + SOSMarker = new byte[SOSLength+2]; + SOSMarker[0] = (byte)0xff; + SOSMarker[1] = (byte)SOS; + SOSMarker[2] = (byte)((SOSLength & 0xff00) >> 8); + SOSMarker[3] = (byte)(SOSLength & 0xff); + stream.readFully(SOSMarker, 4, SOSLength - 2); + } else { + // + // Manufacture an SOS marker. + // + SOSMarker = new byte[2 + 6 + 2*samplesPerPixel]; + int SOSMarkerIndex = 0; + SOSMarker[SOSMarkerIndex++] = (byte)0xff; // Marker identifier + SOSMarker[SOSMarkerIndex++] = (byte)SOS; + short sval = (short)(6 + 2*samplesPerPixel); // Length + SOSMarker[SOSMarkerIndex++] = (byte)((sval >>> 8) & 0xff); + SOSMarker[SOSMarkerIndex++] = (byte)(sval & 0xff); + // Number of components in scan + SOSMarker[SOSMarkerIndex++] = (byte)samplesPerPixel; + if(samplesPerPixel == 1) { + SOSMarker[SOSMarkerIndex++] = (byte)1; // Component ID + SOSMarker[SOSMarkerIndex++] = (byte)0; // Huffman table ID + } else { // 3 + for(int i = 0; i < 3; i++) { + SOSMarker[SOSMarkerIndex++] = + (byte)(i + 1); // Component ID + SOSMarker[SOSMarkerIndex++] = + (byte)((i << 4) | i); // Huffman table IDs + } + }; + SOSMarker[SOSMarkerIndex++] = (byte)0; + SOSMarker[SOSMarkerIndex++] = (byte)0x3f; + SOSMarker[SOSMarkerIndex++] = (byte)0; + } + stream.reset(); + + // Set initialization flag. + isInitialized = true; + } + + // + // The strategy for reading cases 1-3 is to treat the data as a complete + // JPEG interchange stream located at JPEGStreamOffset. + // + // The strategy for cases 4-5 is to concatenate a tables stream created + // in initialize() with the entropy coded data in each strip or tile. + // + public void decodeRaw(byte[] b, + int dstOffset, + int bitsPerPixel, + int scanlineStride) throws IOException { + + initialize(); + + TIFFImageMetadata tim = (TIFFImageMetadata)metadata; + + if(JPEGStreamOffset != null) { + stream.seek(JPEGStreamOffset.longValue()); + JPEGReader.setInput(stream, false, true); + } else { + // Determine buffer length and allocate. + int tableLength = tables.length; + int bufLength = + tableLength + SOSMarker.length + byteCount + 2; // 2 for EOI. + byte[] buf = new byte[bufLength]; + System.arraycopy(tables, 0, buf, 0, tableLength); + int bufOffset = tableLength; + + // Update the SOF dimensions. + short sval = (short)srcHeight; // Tile/strip height + buf[SOFPosition + 5] = (byte)((sval >>> 8) & 0xff); + buf[SOFPosition + 6] = (byte)(sval & 0xff); + sval = (short)srcWidth; // Tile/strip width + buf[SOFPosition + 7] = (byte)((sval >>> 8) & 0xff); + buf[SOFPosition + 8] = (byte)(sval & 0xff); + + // Seek to data. + stream.seek(offset); + + // Add SOS marker if data segment does not start with one. + byte[] twoBytes = new byte[2]; + stream.readFully(twoBytes); + if(!((twoBytes[0]&0xff) == 0xff && (twoBytes[1]&0xff) == SOS)) { + // Segment does not start with SOS marker; + // use the pre-calculated SOS marker. + System.arraycopy(SOSMarker, 0, buf, bufOffset, + SOSMarker.length); + bufOffset += SOSMarker.length; + } + + // Copy the segment data into the buffer. + buf[bufOffset++] = twoBytes[0]; + buf[bufOffset++] = twoBytes[1]; + stream.readFully(buf, bufOffset, byteCount - 2); + bufOffset += byteCount - 2; + + // EOI. + buf[bufOffset++] = (byte)0xff; // Marker identifier + buf[bufOffset++] = (byte)EOI; + + ByteArrayInputStream bais = + new ByteArrayInputStream(buf, 0, bufOffset); + ImageInputStream is = new MemoryCacheImageInputStream(bais); + + JPEGReader.setInput(is, true, true); + } + + // Read real image + JPEGParam.setDestination(rawImage); + JPEGReader.read(0, JPEGParam); + } + + protected void finalize() throws Throwable { + super.finalize(); + JPEGReader.dispose(); + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsCompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsCompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import java.io.IOException; + +public class TIFFPackBitsCompressor extends TIFFCompressor { + + public TIFFPackBitsCompressor() { + super("PackBits", BaselineTIFFTagSet.COMPRESSION_PACKBITS, true); + } + + /** + * Performs PackBits compression for a single buffer of data. + * This should be called for each row of each tile. The returned + * value is the offset into the output buffer after compression. + */ + private static int packBits(byte[] input, int inOffset, int inCount, + byte[] output, int outOffset) { + int inMax = inOffset + inCount - 1; + int inMaxMinus1 = inMax - 1; + + while(inOffset <= inMax) { + int run = 1; + byte replicate = input[inOffset]; + while(run < 127 && inOffset < inMax && + input[inOffset] == input[inOffset+1]) { + run++; + inOffset++; + } + if(run > 1) { + inOffset++; + output[outOffset++] = (byte)(-(run - 1)); + output[outOffset++] = replicate; + } + + run = 0; + int saveOffset = outOffset; + while(run < 128 && + ((inOffset < inMax && + input[inOffset] != input[inOffset+1]) || + (inOffset < inMaxMinus1 && + input[inOffset] != input[inOffset+2]))) { + run++; + output[++outOffset] = input[inOffset++]; + } + if(run > 0) { + output[saveOffset] = (byte)(run - 1); + outOffset++; + } + + if(inOffset == inMax) { + if(run > 0 && run < 128) { + output[saveOffset]++; + output[outOffset++] = input[inOffset++]; + } else { + output[outOffset++] = (byte)0; + output[outOffset++] = input[inOffset++]; + } + } + } + + return outOffset; + } + + public int encode(byte[] b, int off, + int width, int height, + int[] bitsPerSample, + int scanlineStride) throws IOException { + int bitsPerPixel = 0; + for (int i = 0; i < bitsPerSample.length; i++) { + bitsPerPixel += bitsPerSample[i]; + } + int bytesPerRow = (bitsPerPixel*width + 7)/8; + int bufSize = (bytesPerRow + (bytesPerRow + 127)/128); + byte[] compData = new byte[bufSize]; + + int bytesWritten = 0; + + for(int i = 0; i < height; i++) { + int bytes = packBits(b, off, scanlineStride, compData, 0); + off += scanlineStride; + bytesWritten += bytes; + stream.write(compData, 0, bytes); + } + + return bytesWritten; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsDecompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsDecompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.io.IOException; + +public class TIFFPackBitsDecompressor extends TIFFDecompressor { + + public TIFFPackBitsDecompressor() { + } + + public int decode(byte[] srcData, int srcOffset, + byte[] dstData, int dstOffset) + throws IOException { + + int srcIndex = srcOffset; + int dstIndex = dstOffset; + + int dstArraySize = dstData.length; + int srcArraySize = srcData.length; + try { + while (dstIndex < dstArraySize && srcIndex < srcArraySize) { + byte b = srcData[srcIndex++]; + + if (b >= 0 && b <= 127) { + // Literal run packet + + for (int i = 0; i < b + 1; i++) { + dstData[dstIndex++] = srcData[srcIndex++]; + } + } else if (b <= -1 && b >= -127) { + // 2-byte encoded run packet + byte repeat = srcData[srcIndex++]; + for (int i = 0; i < (-b + 1); i++) { + dstData[dstIndex++] = repeat; + } + } else { + // No-op packet, do nothing + ++srcIndex; + } + } + } catch(ArrayIndexOutOfBoundsException e) { + if(reader instanceof TIFFImageReader) { + ((TIFFImageReader)reader).forwardWarningMessage + ("ArrayIndexOutOfBoundsException ignored in TIFFPackBitsDecompressor.decode()"); + } + } + + return dstIndex - dstOffset; + } + + public void decodeRaw(byte[] b, + int dstOffset, + int bitsPerPixel, + int scanlineStride) throws IOException { + stream.seek(offset); + + byte[] srcData = new byte[byteCount]; + stream.readFully(srcData); + + int bytesPerRow = (srcWidth*bitsPerPixel + 7)/8; + byte[] buf; + int bufOffset; + if(bytesPerRow == scanlineStride) { + buf = b; + bufOffset = dstOffset; + } else { + buf = new byte[bytesPerRow*srcHeight]; + bufOffset = 0; + } + + decode(srcData, 0, buf, bufOffset); + + if(bytesPerRow != scanlineStride) { + int off = 0; + for (int y = 0; y < srcHeight; y++) { + System.arraycopy(buf, off, b, dstOffset, bytesPerRow); + off += bytesPerRow; + dstOffset += scanlineStride; + } + } + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsUtil.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsUtil.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.io.IOException; + +public class TIFFPackBitsUtil { + + byte[] dstData = new byte[8192]; + int dstIndex = 0; + + public TIFFPackBitsUtil() { + } + + private void ensureCapacity(int bytesToAdd) { + if (dstIndex + bytesToAdd > dstData.length) { + byte[] newDstData = new byte[Math.max((int)(dstData.length*1.2f), + dstIndex + bytesToAdd)]; + System.arraycopy(dstData, 0, newDstData, 0, dstData.length); + dstData = newDstData; + } + } + + public byte[] decode(byte[] srcData) throws IOException { + int inIndex = 0; + while (inIndex < srcData.length) { + byte b = srcData[inIndex++]; + + if (b >= 0 && b <= 127) { + // Literal run packet + + ensureCapacity(b + 1); + for (int i = 0; i < b + 1; i++) { + dstData[dstIndex++] = srcData[inIndex++]; + } + } else if (b <= -1 && b >= -127) { + // 2-byte encoded run packet + byte repeat = srcData[inIndex++]; + ensureCapacity(-b + 1); + for (int i = 0; i < (-b + 1); i++) { + dstData[dstIndex++] = repeat; + } + } else { + // No-op packet, do nothing + ++inIndex; + } + } + + byte[] newDstData = new byte[dstIndex]; + System.arraycopy(dstData, 0, newDstData, 0, dstIndex); + return newDstData; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRLECompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRLECompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import java.io.IOException; +import javax.imageio.IIOException; + +/** + * + */ +public class TIFFRLECompressor extends TIFFFaxCompressor { + + public TIFFRLECompressor() { + super("CCITT RLE", BaselineTIFFTagSet.COMPRESSION_CCITT_RLE, true); + } + + /** + * Encode a row of data using Modified Huffman Compression also known as + * CCITT RLE (Run Lenth Encoding). + * + * @param data The row of data to compress. + * @param rowOffset Starting index in data. + * @param colOffset Bit offset within first data[rowOffset]. + * @param rowLength Number of bits in the row. + * @param compData The compressed data. + * + * @return The number of bytes saved in the compressed data array. + */ + public int encodeRLE(byte[] data, + int rowOffset, + int colOffset, + int rowLength, + byte[] compData) { + // + // Initialize bit buffer machinery. + // + initBitBuf(); + + // + // Run-length encode line. + // + int outIndex = + encode1D(data, rowOffset, colOffset, rowLength, compData, 0); + + // + // Flush pending bits + // + while (ndex > 0) { + compData[outIndex++] = (byte)(bits >>> 24); + bits <<= 8; + ndex -= 8; + } + + // + // Flip the bytes if inverse fill was requested. + // + if (inverseFill) { + byte[] flipTable = TIFFFaxDecompressor.flipTable; + for(int i = 0; i < outIndex; i++) { + compData[i] = flipTable[compData[i] & 0xff]; + } + } + + return outIndex; + } + + public int encode(byte[] b, int off, + int width, int height, + int[] bitsPerSample, + int scanlineStride) throws IOException { + if (bitsPerSample.length != 1 || bitsPerSample[0] != 1) { + throw new IIOException( + "Bits per sample must be 1 for RLE compression!"); + } + + // In the worst case, 2 bits of input will result in 9 bits of output, + // plus 2 extra bits if the row starts with black. + int maxBits = 9*((width + 1)/2) + 2; + byte[] compData = new byte[(maxBits + 7)/8]; + + int bytes = 0; + int rowOffset = off; + + for (int i = 0; i < height; i++) { + int rowBytes = encodeRLE(b, rowOffset, 0, width, compData); + stream.write(compData, 0, rowBytes); + + rowOffset += scanlineStride; + bytes += rowBytes; + } + + return bytes; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRenderedImage.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRenderedImage.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.io.IOException; +import java.util.Iterator; +import java.util.List; +import java.util.Vector; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.plugins.tiff.TIFFImageReadParam; +import javax.imageio.plugins.tiff.TIFFTagSet; + +public class TIFFRenderedImage implements RenderedImage { + + private TIFFImageReader reader; + private int imageIndex; + private ImageReadParam tileParam; + + private int subsampleX; + private int subsampleY; + + private boolean isSubsampling; + + private int width; + private int height; + private int tileWidth; + private int tileHeight; + + private ImageTypeSpecifier its; + + public TIFFRenderedImage(TIFFImageReader reader, + int imageIndex, + ImageReadParam readParam, + int width, int height) throws IOException { + this.reader = reader; + this.imageIndex = imageIndex; + this.tileParam = cloneImageReadParam(readParam, false); + + this.subsampleX = tileParam.getSourceXSubsampling(); + this.subsampleY = tileParam.getSourceYSubsampling(); + + this.isSubsampling = this.subsampleX != 1 || this.subsampleY != 1; + + this.width = width/subsampleX; + this.height = height/subsampleY; + + // If subsampling is being used, we may not match the + // true tile grid exactly, but everything should still work + this.tileWidth = reader.getTileWidth(imageIndex)/subsampleX; + this.tileHeight = reader.getTileHeight(imageIndex)/subsampleY; + + Iterator iter = reader.getImageTypes(imageIndex); + this.its = iter.next(); + tileParam.setDestinationType(its); + } + + /** + * Creates a copy of param. The source subsampling and + * and bands settings and the destination bands and offset settings + * are copied. If param is a TIFFImageReadParam + * then the TIFFDecompressor and + * TIFFColorConverter settings are also copied; otherwise + * they are explicitly set to null. + * + * @param param the parameters to be copied. + * @param copyTagSets whether the TIFFTagSet settings + * should be copied if set. + * @return copied parameters. + */ + private ImageReadParam cloneImageReadParam(ImageReadParam param, + boolean copyTagSets) { + // Create a new TIFFImageReadParam. + TIFFImageReadParam newParam = new TIFFImageReadParam(); + + // Copy the basic settings. + newParam.setSourceSubsampling(param.getSourceXSubsampling(), + param.getSourceYSubsampling(), + param.getSubsamplingXOffset(), + param.getSubsamplingYOffset()); + newParam.setSourceBands(param.getSourceBands()); + newParam.setDestinationBands(param.getDestinationBands()); + newParam.setDestinationOffset(param.getDestinationOffset()); + + if (param instanceof TIFFImageReadParam && copyTagSets) { + // Copy the settings from the input parameter. + TIFFImageReadParam tparam = (TIFFImageReadParam) param; + + List tagSets = tparam.getAllowedTagSets(); + if (tagSets != null) { + Iterator tagSetIter = tagSets.iterator(); + if (tagSetIter != null) { + while (tagSetIter.hasNext()) { + TIFFTagSet tagSet = tagSetIter.next(); + newParam.addAllowedTagSet(tagSet); + } + } + } + } + + return newParam; + } + + public Vector getSources() { + return null; + } + + public Object getProperty(String name) { + return java.awt.Image.UndefinedProperty; + } + + public String[] getPropertyNames() { + return null; + } + + public ColorModel getColorModel() { + return its.getColorModel(); + } + + public SampleModel getSampleModel() { + return its.getSampleModel(); + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public int getMinX() { + return 0; + } + + public int getMinY() { + return 0; + } + + public int getNumXTiles() { + return (width + tileWidth - 1)/tileWidth; + } + + public int getNumYTiles() { + return (height + tileHeight - 1)/tileHeight; + } + + public int getMinTileX() { + return 0; + } + + public int getMinTileY() { + return 0; + } + + public int getTileWidth() { + return tileWidth; + } + + public int getTileHeight() { + return tileHeight; + } + + public int getTileGridXOffset() { + return 0; + } + + public int getTileGridYOffset() { + return 0; + } + + public Raster getTile(int tileX, int tileY) { + Rectangle tileRect = new Rectangle(tileX*tileWidth, + tileY*tileHeight, + tileWidth, + tileHeight); + return getData(tileRect); + } + + public Raster getData() { + return read(new Rectangle(0, 0, getWidth(), getHeight())); + } + + public Raster getData(Rectangle rect) { + return read(rect); + } + + // This method needs to be synchronized as it updates the instance + // variable 'tileParam'. + public synchronized WritableRaster read(Rectangle rect) { + tileParam.setSourceRegion(isSubsampling ? + new Rectangle(subsampleX*rect.x, + subsampleY*rect.y, + subsampleX*rect.width, + subsampleY*rect.height) : + rect); + + try { + BufferedImage bi = reader.read(imageIndex, tileParam); + WritableRaster ras = bi.getRaster(); + return ras.createWritableChild(0, 0, + ras.getWidth(), ras.getHeight(), + rect.x, rect.y, + null); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public WritableRaster copyData(WritableRaster raster) { + if (raster == null) { + return read(new Rectangle(0, 0, getWidth(), getHeight())); + } else { + Raster src = read(raster.getBounds()); + raster.setRect(src); + return raster; + } + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadata.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadata.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.nio.ByteOrder; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.metadata.IIOInvalidTreeException; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +public class TIFFStreamMetadata extends IIOMetadata { + + // package scope + static final String NATIVE_METADATA_FORMAT_NAME = + "javax_imageio_tiff_stream_1.0"; + + static final String NATIVE_METADATA_FORMAT_CLASS_NAME = + "javax.imageio.plugins.tiff.TIFFStreamMetadataFormat"; + + private static final String bigEndianString = + ByteOrder.BIG_ENDIAN.toString(); + private static final String littleEndianString = + ByteOrder.LITTLE_ENDIAN.toString(); + + public ByteOrder byteOrder = ByteOrder.BIG_ENDIAN; + + public TIFFStreamMetadata() { + super(false, + NATIVE_METADATA_FORMAT_NAME, + NATIVE_METADATA_FORMAT_CLASS_NAME, + null, null); + } + + public boolean isReadOnly() { + return false; + } + + // Shorthand for throwing an IIOInvalidTreeException + private static void fatal(Node node, String reason) + throws IIOInvalidTreeException { + throw new IIOInvalidTreeException(reason, node); + } + + public Node getAsTree(String formatName) { + IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName); + + IIOMetadataNode byteOrderNode = new IIOMetadataNode("ByteOrder"); + byteOrderNode.setAttribute("value", byteOrder.toString()); + + root.appendChild(byteOrderNode); + return root; + } + +// public void setFromTree(String formatName, Node root) { +// } + + private void mergeNativeTree(Node root) throws IIOInvalidTreeException { + Node node = root; + if (!node.getNodeName().equals(nativeMetadataFormatName)) { + fatal(node, "Root must be " + nativeMetadataFormatName); + } + + node = node.getFirstChild(); + if (node == null || !node.getNodeName().equals("ByteOrder")) { + fatal(node, "Root must have \"ByteOrder\" child"); + } + + NamedNodeMap attrs = node.getAttributes(); + String order = attrs.getNamedItem("value").getNodeValue(); + + if (order == null) { + fatal(node, "ByteOrder node must have a \"value\" attribute"); + } + if (order.equals(bigEndianString)) { + this.byteOrder = ByteOrder.BIG_ENDIAN; + } else if (order.equals(littleEndianString)) { + this.byteOrder = ByteOrder.LITTLE_ENDIAN; + } else { + fatal(node, "Incorrect value for ByteOrder \"value\" attribute"); + } + } + + public void mergeTree(String formatName, Node root) + throws IIOInvalidTreeException { + if (formatName.equals(nativeMetadataFormatName)) { + if (root == null) { + throw new NullPointerException("root == null!"); + } + mergeNativeTree(root); + } else { + throw new IllegalArgumentException("Not a recognized format!"); + } + } + + public void reset() { + this.byteOrder = ByteOrder.BIG_ENDIAN; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormat.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormat.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadataFormat; + +public class TIFFStreamMetadataFormat extends TIFFMetadataFormat { + + private static TIFFStreamMetadataFormat theInstance = null; + + public boolean canNodeAppear(String elementName, + ImageTypeSpecifier imageType) { + return false; + } + + private TIFFStreamMetadataFormat() { + this.resourceBaseName = + "javax.imageio.plugins.tiff.TIFFStreamMetadataFormatResources"; + this.rootName = TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME; + + TIFFElementInfo einfo; + TIFFAttrInfo ainfo; + String[] empty = new String[0]; + String[] childNames; + String[] attrNames; + + childNames = new String[] { "ByteOrder" }; + einfo = new TIFFElementInfo(childNames, empty, CHILD_POLICY_ALL); + + elementInfoMap.put(TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME, + einfo); + + childNames = empty; + attrNames = new String[] { "value" }; + einfo = new TIFFElementInfo(childNames, attrNames, CHILD_POLICY_EMPTY); + elementInfoMap.put("ByteOrder", einfo); + + ainfo = new TIFFAttrInfo(); + ainfo.dataType = DATATYPE_STRING; + ainfo.isRequired = true; + attrInfoMap.put("ByteOrder/value", ainfo); + } + + public static synchronized IIOMetadataFormat getInstance() { + if (theInstance == null) { + theInstance = new TIFFStreamMetadataFormat(); + } + return theInstance; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormatResources.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormatResources.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.util.ListResourceBundle; + +public class TIFFStreamMetadataFormatResources extends ListResourceBundle { + + private static final Object[][] contents = { + { "ByteOrder", "The stream byte order" }, + { "ByteOrder/value", "One of \"BIG_ENDIAN\" or \"LITTLE_ENDIAN\"" } + }; + + public TIFFStreamMetadataFormatResources() { + } + + public Object[][] getContents() { + return contents.clone(); + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT4Compressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT4Compressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.plugins.tiff.TIFFField; +import javax.imageio.plugins.tiff.TIFFTag; +import java.io.IOException; +import javax.imageio.IIOException; +import javax.imageio.metadata.IIOMetadata; + +public class TIFFT4Compressor extends TIFFFaxCompressor { + + private boolean is1DMode = false; + private boolean isEOLAligned = false; + + public TIFFT4Compressor() { + super("CCITT T.4", BaselineTIFFTagSet.COMPRESSION_CCITT_T_4, true); + } + + /** + * Sets the value of the metadata field. + * + *

The implementation in this class also sets local options + * from the T4_OPTIONS field if it exists, and if it doesn't, adds + * it with default values.

+ * + * @param metadata the IIOMetadata object for the + * image being written. + * + * @see #getMetadata() + */ + public void setMetadata(IIOMetadata metadata) { + super.setMetadata(metadata); + + if (metadata instanceof TIFFImageMetadata) { + TIFFImageMetadata tim = (TIFFImageMetadata)metadata; + TIFFField f = tim.getTIFFField(BaselineTIFFTagSet.TAG_T4_OPTIONS); + if (f != null) { + int options = f.getAsInt(0); + is1DMode = (options & 0x1) == 0; + isEOLAligned = (options & 0x4) == 0x4; + } else { + long[] oarray = new long[1]; + oarray[0] = (isEOLAligned ? 0x4 : 0x0) | + (is1DMode ? 0x0 : 0x1); + + BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance(); + TIFFField T4Options = + new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_T4_OPTIONS), + TIFFTag.TIFF_LONG, + 1, + oarray); + tim.rootIFD.addTIFFField(T4Options); + } + } + } + + /** + * Encode a buffer of data using CCITT T.4 Compression also known as + * Group 3 facsimile compression. + * + * @param is1DMode Whether to perform one-dimensional encoding. + * @param isEOLAligned Whether EOL bit sequences should be padded. + * @param data The row of data to compress. + * @param lineStride Byte step between the same sample in different rows. + * @param colOffset Bit offset within first data[rowOffset]. + * @param width Number of bits in the row. + * @param height Number of rows in the buffer. + * @param compData The compressed data. + * + * @return The number of bytes saved in the compressed data array. + */ + public int encodeT4(boolean is1DMode, + boolean isEOLAligned, + byte[] data, + int lineStride, + int colOffset, + int width, + int height, + byte[] compData) + { + // + // ao, a1, a2 are bit indices in the current line + // b1 and b2 are bit indices in the reference line (line above) + // color is the current color (WHITE or BLACK) + // + byte[] refData = data; + int lineAddr = 0; + int outIndex = 0; + + initBitBuf(); + + int KParameter = 2; + for(int numRows = 0; numRows < height; numRows++) { + if(is1DMode || (numRows % KParameter) == 0) { // 1D encoding + // Write EOL+1 + outIndex += addEOL(is1DMode, isEOLAligned, true, + compData, outIndex); + + // Encode row + outIndex += encode1D(data, lineAddr, colOffset, width, + compData, outIndex); + } else { // 2D encoding. + // Write EOL+0 + outIndex += addEOL(is1DMode, isEOLAligned, false, + compData, outIndex); + + // Set reference to previous line + int refAddr = lineAddr - lineStride; + + // Encode row + int a0 = colOffset; + int last = a0 + width; + + int testbit = + ((data[lineAddr + (a0>>>3)]&0xff) >>> + (7-(a0 & 0x7))) & 0x1; + int a1 = testbit != 0 ? + a0 : nextState(data, lineAddr, a0, last); + + testbit = ((refData[refAddr + (a0>>>3)]&0xff) >>> + (7-(a0 & 0x7))) & 0x1; + int b1 = testbit != 0 ? + a0 : nextState(refData, refAddr, a0, last); + + // The current color is set to WHITE at line start + int color = WHITE; + + while(true) { + int b2 = nextState(refData, refAddr, b1, last); + if(b2 < a1) { // pass mode + outIndex += add2DBits(compData, outIndex, pass, 0); + a0 = b2; + } else { + int tmp = b1 - a1 + 3; + if((tmp <= 6) && (tmp >= 0)) { // vertical mode + outIndex += + add2DBits(compData, outIndex, vert, tmp); + a0 = a1; + } else { // horizontal mode + int a2 = nextState(data, lineAddr, a1, last); + outIndex += + add2DBits(compData, outIndex, horz, 0); + outIndex += + add1DBits(compData, outIndex, a1-a0, color); + outIndex += + add1DBits(compData, outIndex, a2-a1, color^1); + a0 = a2; + } + } + if(a0 >= last) { + break; + } + color = ((data[lineAddr + (a0>>>3)]&0xff) >>> + (7-(a0 & 0x7))) & 0x1; + a1 = nextState(data, lineAddr, a0, last); + b1 = nextState(refData, refAddr, a0, last); + testbit = ((refData[refAddr + (b1>>>3)]&0xff) >>> + (7-(b1 & 0x7))) & 0x1; + if(testbit == color) { + b1 = nextState(refData, refAddr, b1, last); + } + } + } + + // Skip to next line. + lineAddr += lineStride; + } + + for(int i = 0; i < 6; i++) { + outIndex += addEOL(is1DMode, isEOLAligned, true, + compData, outIndex); + } + + // + // flush all pending bits + // + while(ndex > 0) { + compData[outIndex++] = (byte)(bits >>> 24); + bits <<= 8; + ndex -= 8; + } + + // Flip the bytes if inverse fill was requested. + if(inverseFill) { + for(int i = 0; i < outIndex; i++) { + compData[i] = TIFFFaxDecompressor.flipTable[compData[i]&0xff]; + } + } + + return outIndex; + } + + public int encode(byte[] b, int off, + int width, int height, + int[] bitsPerSample, + int scanlineStride) throws IOException { + if (bitsPerSample.length != 1 || bitsPerSample[0] != 1) { + throw new IIOException( + "Bits per sample must be 1 for T4 compression!"); + } + + // This initial buffer size is based on an alternating 1-0 + // pattern generating the most bits when converted to code + // words: 9 bits out for each pair of bits in. So the number + // of bit pairs is determined, multiplied by 9, converted to + // bytes, and a ceil() is taken to account for fill bits at the + // end of each line. The "2" addend accounts for the case + // of the pattern beginning with black. The buffer is intended + // to hold only a single row. + + int maxBits = 9*((width + 1)/2) + 2; + int bufSize = (maxBits + 7)/8; + + // Calculate the maximum row as the G3-1D size plus the EOL, + // multiply this by the number of rows in the tile, and add + // 6 EOLs for the RTC (return to control). + bufSize = height*(bufSize + 2) + 12; + + byte[] compData = new byte[bufSize]; + + int bytes = encodeT4(is1DMode, + isEOLAligned, + b, scanlineStride, 8*off, + width, height, + compData); + + stream.write(compData, 0, bytes); + return bytes; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT6Compressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT6Compressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.plugins.tiff.TIFFField; +import javax.imageio.plugins.tiff.TIFFTag; +import java.io.IOException; +import javax.imageio.IIOException; + +public class TIFFT6Compressor extends TIFFFaxCompressor { + + public TIFFT6Compressor() { + super("CCITT T.6", BaselineTIFFTagSet.COMPRESSION_CCITT_T_6, true); + } + + /** + * Encode a buffer of data using CCITT T.6 Compression also known as + * Group 4 facsimile compression. + * + * @param data The row of data to compress. + * @param lineStride Byte step between the same sample in different rows. + * @param colOffset Bit offset within first data[rowOffset]. + * @param width Number of bits in the row. + * @param height Number of rows in the buffer. + * @param compData The compressed data. + * + * @return The number of bytes saved in the compressed data array. + */ + public synchronized int encodeT6(byte[] data, + int lineStride, + int colOffset, + int width, + int height, + byte[] compData) + { + // + // ao, a1, a2 are bit indices in the current line + // b1 and b2 are bit indices in the reference line (line above) + // color is the current color (WHITE or BLACK) + // + byte[] refData = null; + int refAddr = 0; + int lineAddr = 0; + int outIndex = 0; + + initBitBuf(); + + // + // Iterate over all lines + // + while(height-- != 0) { + int a0 = colOffset; + int last = a0 + width; + + int testbit = + ((data[lineAddr + (a0>>>3)]&0xff) >>> + (7-(a0 & 0x7))) & 0x1; + int a1 = testbit != 0 ? + a0 : nextState(data, lineAddr, a0, last); + + testbit = refData == null ? + 0: ((refData[refAddr + (a0>>>3)]&0xff) >>> + (7-(a0 & 0x7))) & 0x1; + int b1 = testbit != 0 ? + a0 : nextState(refData, refAddr, a0, last); + + // + // The current color is set to WHITE at line start + // + int color = WHITE; + + while(true) { + int b2 = nextState(refData, refAddr, b1, last); + if(b2 < a1) { // pass mode + outIndex += add2DBits(compData, outIndex, pass, 0); + a0 = b2; + } else { + int tmp = b1 - a1 + 3; + if((tmp <= 6) && (tmp >= 0)) { // vertical mode + outIndex += add2DBits(compData, outIndex, vert, tmp); + a0 = a1; + } else { // horizontal mode + int a2 = nextState(data, lineAddr, a1, last); + outIndex += add2DBits(compData, outIndex, horz, 0); + outIndex += add1DBits(compData, outIndex, a1-a0, color); + outIndex += add1DBits(compData, outIndex, a2-a1, color^1); + a0 = a2; + } + } + if(a0 >= last) { + break; + } + color = ((data[lineAddr + (a0>>>3)]&0xff) >>> + (7-(a0 & 0x7))) & 0x1; + a1 = nextState(data, lineAddr, a0, last); + b1 = nextState(refData, refAddr, a0, last); + testbit = refData == null ? + 0: ((refData[refAddr + (b1>>>3)]&0xff) >>> + (7-(b1 & 0x7))) & 0x1; + if(testbit == color) { + b1 = nextState(refData, refAddr, b1, last); + } + } + + refData = data; + refAddr = lineAddr; + lineAddr += lineStride; + + } // End while(height--) + + // + // append eofb + // + outIndex += addEOFB(compData, outIndex); + + // Flip the bytes if inverse fill was requested. + if(inverseFill) { + for(int i = 0; i < outIndex; i++) { + compData[i] = TIFFFaxDecompressor.flipTable[compData[i]&0xff]; + } + } + + return outIndex; + } + + public int encode(byte[] b, int off, + int width, int height, + int[] bitsPerSample, + int scanlineStride) throws IOException { + if (bitsPerSample.length != 1 || bitsPerSample[0] != 1) { + throw new IIOException( + "Bits per sample must be 1 for T6 compression!"); + } + + + if (metadata instanceof TIFFImageMetadata) { + TIFFImageMetadata tim = (TIFFImageMetadata)metadata; + + long[] options = new long[1]; + options[0] = 0; + + BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance(); + TIFFField T6Options = + new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_T6_OPTIONS), + TIFFTag.TIFF_LONG, + 1, + options); + tim.rootIFD.addTIFFField(T6Options); + } + + // See comment in TIFFT4Compressor + int maxBits = 9*((width + 1)/2) + 2; + int bufSize = (maxBits + 7)/8; + bufSize = height*(bufSize + 2) + 12; + + byte[] compData = new byte[bufSize]; + int bytes = encodeT6(b, scanlineStride, 8*off, width, height, + compData); + stream.write(compData, 0, bytes); + return bytes; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrColorConverter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrColorConverter.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.plugins.tiff.TIFFField; + +public class TIFFYCbCrColorConverter extends TIFFColorConverter { + + private static final float CODING_RANGE_Y = 255.0f; + private static final float CODING_RANGE_CB_CR = 127.0f; + + private float lumaRed = 0.299f; + private float lumaGreen = 0.587f; + private float lumaBlue = 0.114f; + + private float referenceBlackY = 0.0f; + private float referenceWhiteY = 255.0f; + + private float referenceBlackCb = 128.0f; + private float referenceWhiteCb = 255.0f; + + private float referenceBlackCr = 128.0f; + private float referenceWhiteCr = 255.0f; + + public TIFFYCbCrColorConverter(TIFFImageMetadata metadata) { + TIFFImageMetadata tmetadata = metadata; + + TIFFField f = + tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS); + if (f != null && f.getCount() == 3) { + this.lumaRed = f.getAsFloat(0); + this.lumaGreen = f.getAsFloat(1); + this.lumaBlue = f.getAsFloat(2); + } + + f = + tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE); + if (f != null && f.getCount() == 6) { + this.referenceBlackY = f.getAsFloat(0); + this.referenceWhiteY = f.getAsFloat(1); + this.referenceBlackCb = f.getAsFloat(2); + this.referenceWhiteCb = f.getAsFloat(3); + this.referenceBlackCr = f.getAsFloat(4); + this.referenceWhiteCr = f.getAsFloat(5); + } + } + + /* + The full range component value is converted from the code by: + + FullRangeValue = (code - ReferenceBlack) * CodingRange + / (ReferenceWhite - ReferenceBlack); + + The code is converted from the full-range component value by: + + code = (FullRangeValue * (ReferenceWhite - ReferenceBlack) + / CodingRange) + ReferenceBlack; + + */ + public void fromRGB(float r, float g, float b, float[] result) { + // Convert RGB to full-range YCbCr. + float Y = (lumaRed*r + lumaGreen*g + lumaBlue*b); + float Cb = (b - Y)/(2 - 2*lumaBlue); + float Cr = (r - Y)/(2 - 2*lumaRed); + + // Convert full-range YCbCr to code. + result[0] = Y*(referenceWhiteY - referenceBlackY)/CODING_RANGE_Y + + referenceBlackY; + result[1] = Cb*(referenceWhiteCb - referenceBlackCb)/CODING_RANGE_CB_CR + + referenceBlackCb; + result[2] = Cr*(referenceWhiteCr - referenceBlackCr)/CODING_RANGE_CB_CR + + referenceBlackCr; + } + + public void toRGB(float x0, float x1, float x2, float[] rgb) { + // Convert YCbCr code to full-range YCbCr. + float Y = (x0 - referenceBlackY)*CODING_RANGE_Y/ + (referenceWhiteY - referenceBlackY); + float Cb = (x1 - referenceBlackCb)*CODING_RANGE_CB_CR/ + (referenceWhiteCb - referenceBlackCb); + float Cr = (x2 - referenceBlackCr)*CODING_RANGE_CB_CR/ + (referenceWhiteCr - referenceBlackCr); + + // Convert YCbCr to RGB. + rgb[0] = Cr*(2 - 2*lumaRed) + Y; + rgb[2] = Cb*(2 - 2*lumaBlue) + Y; + rgb[1] = (Y - lumaBlue*rgb[2] - lumaRed*rgb[0])/lumaGreen; + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrDecompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrDecompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,538 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.IOException; +import javax.imageio.ImageReader; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.stream.MemoryCacheImageInputStream; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.plugins.tiff.TIFFField; + +public class TIFFYCbCrDecompressor extends TIFFDecompressor { + // Store constants in S15.16 format + private static final int FRAC_BITS = 16; + private static final float FRAC_SCALE = (float)(1 << FRAC_BITS); + + private float lumaRed = 0.299f; + private float lumaGreen = 0.587f; + private float lumaBlue = 0.114f; + + private float referenceBlackY = 0.0f; + private float referenceWhiteY = 255.0f; + + private float referenceBlackCb = 128.0f; + private float referenceWhiteCb = 255.0f; + + private float referenceBlackCr = 128.0f; + private float referenceWhiteCr = 255.0f; + + private float codingRangeY = 255.0f; + + private int[] iYTab = new int[256]; + private int[] iCbTab = new int[256]; + private int[] iCrTab = new int[256]; + + private int[] iGYTab = new int[256]; + private int[] iGCbTab = new int[256]; + private int[] iGCrTab = new int[256]; + + private int chromaSubsampleH = 2; + private int chromaSubsampleV = 2; + + private boolean colorConvert; + + private TIFFDecompressor decompressor; + + private BufferedImage tmpImage; + + // + // If 'decompressor' is not null then it reads the data from the + // actual stream first and passes the result on to YCrCr decompression + // and possibly color conversion. + // + + public TIFFYCbCrDecompressor(TIFFDecompressor decompressor, + boolean colorConvert) { + this.decompressor = decompressor; + this.colorConvert = colorConvert; + } + + private void warning(String message) { + if(this.reader instanceof TIFFImageReader) { + ((TIFFImageReader)reader).forwardWarningMessage(message); + } + } + + // + // "Chained" decompressor methods. + // + + public void setReader(ImageReader reader) { + if(decompressor != null) { + decompressor.setReader(reader); + } + super.setReader(reader); + } + + public void setMetadata(IIOMetadata metadata) { + if(decompressor != null) { + decompressor.setMetadata(metadata); + } + super.setMetadata(metadata); + } + + public void setPhotometricInterpretation(int photometricInterpretation) { + if(decompressor != null) { + decompressor.setPhotometricInterpretation(photometricInterpretation); + } + super.setPhotometricInterpretation(photometricInterpretation); + } + + public void setCompression(int compression) { + if(decompressor != null) { + decompressor.setCompression(compression); + } + super.setCompression(compression); + } + + public void setPlanar(boolean planar) { + if(decompressor != null) { + decompressor.setPlanar(planar); + } + super.setPlanar(planar); + } + + public void setSamplesPerPixel(int samplesPerPixel) { + if(decompressor != null) { + decompressor.setSamplesPerPixel(samplesPerPixel); + } + super.setSamplesPerPixel(samplesPerPixel); + } + + public void setBitsPerSample(int[] bitsPerSample) { + if(decompressor != null) { + decompressor.setBitsPerSample(bitsPerSample); + } + super.setBitsPerSample(bitsPerSample); + } + + public void setSampleFormat(int[] sampleFormat) { + if(decompressor != null) { + decompressor.setSampleFormat(sampleFormat); + } + super.setSampleFormat(sampleFormat); + } + + public void setExtraSamples(int[] extraSamples) { + if(decompressor != null) { + decompressor.setExtraSamples(extraSamples); + } + super.setExtraSamples(extraSamples); + } + + public void setColorMap(char[] colorMap) { + if(decompressor != null) { + decompressor.setColorMap(colorMap); + } + super.setColorMap(colorMap); + } + + public void setStream(ImageInputStream stream) { + if(decompressor != null) { + decompressor.setStream(stream); + } else { + super.setStream(stream); + } + } + + public void setOffset(long offset) { + if(decompressor != null) { + decompressor.setOffset(offset); + } + super.setOffset(offset); + } + + public void setByteCount(int byteCount) { + if(decompressor != null) { + decompressor.setByteCount(byteCount); + } + super.setByteCount(byteCount); + } + + public void setSrcMinX(int srcMinX) { + if(decompressor != null) { + decompressor.setSrcMinX(srcMinX); + } + super.setSrcMinX(srcMinX); + } + + public void setSrcMinY(int srcMinY) { + if(decompressor != null) { + decompressor.setSrcMinY(srcMinY); + } + super.setSrcMinY(srcMinY); + } + + public void setSrcWidth(int srcWidth) { + if(decompressor != null) { + decompressor.setSrcWidth(srcWidth); + } + super.setSrcWidth(srcWidth); + } + + public void setSrcHeight(int srcHeight) { + if(decompressor != null) { + decompressor.setSrcHeight(srcHeight); + } + super.setSrcHeight(srcHeight); + } + + public void setSourceXOffset(int sourceXOffset) { + if(decompressor != null) { + decompressor.setSourceXOffset(sourceXOffset); + } + super.setSourceXOffset(sourceXOffset); + } + + public void setDstXOffset(int dstXOffset) { + if(decompressor != null) { + decompressor.setDstXOffset(dstXOffset); + } + super.setDstXOffset(dstXOffset); + } + + public void setSourceYOffset(int sourceYOffset) { + if(decompressor != null) { + decompressor.setSourceYOffset(sourceYOffset); + } + super.setSourceYOffset(sourceYOffset); + } + + public void setDstYOffset(int dstYOffset) { + if(decompressor != null) { + decompressor.setDstYOffset(dstYOffset); + } + super.setDstYOffset(dstYOffset); + } + + /* Should not need to override these mutators as subsampling + should not be done by the wrapped decompressor. + public void setSubsampleX(int subsampleX) { + if(decompressor != null) { + decompressor.setSubsampleX(subsampleX); + } + super.setSubsampleX(subsampleX); + } + + public void setSubsampleY(int subsampleY) { + if(decompressor != null) { + decompressor.setSubsampleY(subsampleY); + } + super.setSubsampleY(subsampleY); + } + */ + + public void setSourceBands(int[] sourceBands) { + if(decompressor != null) { + decompressor.setSourceBands(sourceBands); + } + super.setSourceBands(sourceBands); + } + + public void setDestinationBands(int[] destinationBands) { + if(decompressor != null) { + decompressor.setDestinationBands(destinationBands); + } + super.setDestinationBands(destinationBands); + } + + public void setImage(BufferedImage image) { + if(decompressor != null) { + ColorModel cm = image.getColorModel(); + tmpImage = + new BufferedImage(cm, + image.getRaster().createCompatibleWritableRaster(1, 1), + cm.isAlphaPremultiplied(), + null); + decompressor.setImage(tmpImage); + } + super.setImage(image); + } + + public void setDstMinX(int dstMinX) { + if(decompressor != null) { + decompressor.setDstMinX(dstMinX); + } + super.setDstMinX(dstMinX); + } + + public void setDstMinY(int dstMinY) { + if(decompressor != null) { + decompressor.setDstMinY(dstMinY); + } + super.setDstMinY(dstMinY); + } + + public void setDstWidth(int dstWidth) { + if(decompressor != null) { + decompressor.setDstWidth(dstWidth); + } + super.setDstWidth(dstWidth); + } + + public void setDstHeight(int dstHeight) { + if(decompressor != null) { + decompressor.setDstHeight(dstHeight); + } + super.setDstHeight(dstHeight); + } + + public void setActiveSrcMinX(int activeSrcMinX) { + if(decompressor != null) { + decompressor.setActiveSrcMinX(activeSrcMinX); + } + super.setActiveSrcMinX(activeSrcMinX); + } + + public void setActiveSrcMinY(int activeSrcMinY) { + if(decompressor != null) { + decompressor.setActiveSrcMinY(activeSrcMinY); + } + super.setActiveSrcMinY(activeSrcMinY); + } + + public void setActiveSrcWidth(int activeSrcWidth) { + if(decompressor != null) { + decompressor.setActiveSrcWidth(activeSrcWidth); + } + super.setActiveSrcWidth(activeSrcWidth); + } + + public void setActiveSrcHeight(int activeSrcHeight) { + if(decompressor != null) { + decompressor.setActiveSrcHeight(activeSrcHeight); + } + super.setActiveSrcHeight(activeSrcHeight); + } + + private byte clamp(int f) { + if (f < 0) { + return (byte)0; + } else if (f > 255*65536) { + return (byte)255; + } else { + return (byte)(f >> 16); + } + } + + public void beginDecoding() { + if(decompressor != null) { + decompressor.beginDecoding(); + } + + TIFFImageMetadata tmetadata = (TIFFImageMetadata)metadata; + TIFFField f; + + f = tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING); + if (f != null) { + if (f.getCount() == 2) { + this.chromaSubsampleH = f.getAsInt(0); + this.chromaSubsampleV = f.getAsInt(1); + + if (chromaSubsampleH != 1 && chromaSubsampleH != 2 && + chromaSubsampleH != 4) { + warning("Y_CB_CR_SUBSAMPLING[0] has illegal value " + + chromaSubsampleH + + " (should be 1, 2, or 4), setting to 1"); + chromaSubsampleH = 1; + } + + if (chromaSubsampleV != 1 && chromaSubsampleV != 2 && + chromaSubsampleV != 4) { + warning("Y_CB_CR_SUBSAMPLING[1] has illegal value " + + chromaSubsampleV + + " (should be 1, 2, or 4), setting to 1"); + chromaSubsampleV = 1; + } + } else { + warning("Y_CB_CR_SUBSAMPLING count != 2, " + + "assuming no subsampling"); + } + } + + f = + tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS); + if (f != null) { + if (f.getCount() == 3) { + this.lumaRed = f.getAsFloat(0); + this.lumaGreen = f.getAsFloat(1); + this.lumaBlue = f.getAsFloat(2); + } else { + warning("Y_CB_CR_COEFFICIENTS count != 3, " + + "assuming default values for CCIR 601-1"); + } + } + + f = + tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE); + if (f != null) { + if (f.getCount() == 6) { + this.referenceBlackY = f.getAsFloat(0); + this.referenceWhiteY = f.getAsFloat(1); + this.referenceBlackCb = f.getAsFloat(2); + this.referenceWhiteCb = f.getAsFloat(3); + this.referenceBlackCr = f.getAsFloat(4); + this.referenceWhiteCr = f.getAsFloat(5); + } else { + warning("REFERENCE_BLACK_WHITE count != 6, ignoring it"); + } + } else { + warning("REFERENCE_BLACK_WHITE not found, assuming 0-255/128-255/128-255"); + } + + this.colorConvert = true; + + float BCb = (2.0f - 2.0f*lumaBlue); + float RCr = (2.0f - 2.0f*lumaRed); + + float GY = (1.0f - lumaBlue - lumaRed)/lumaGreen; + float GCb = 2.0f*lumaBlue*(lumaBlue - 1.0f)/lumaGreen; + float GCr = 2.0f*lumaRed*(lumaRed - 1.0f)/lumaGreen; + + for (int i = 0; i < 256; i++) { + float fY = (i - referenceBlackY)*codingRangeY/ + (referenceWhiteY - referenceBlackY); + float fCb = (i - referenceBlackCb)*127.0f/ + (referenceWhiteCb - referenceBlackCb); + float fCr = (i - referenceBlackCr)*127.0f/ + (referenceWhiteCr - referenceBlackCr); + + iYTab[i] = (int)(fY*FRAC_SCALE); + iCbTab[i] = (int)(fCb*BCb*FRAC_SCALE); + iCrTab[i] = (int)(fCr*RCr*FRAC_SCALE); + + iGYTab[i] = (int)(fY*GY*FRAC_SCALE); + iGCbTab[i] = (int)(fCb*GCb*FRAC_SCALE); + iGCrTab[i] = (int)(fCr*GCr*FRAC_SCALE); + } + } + + public void decodeRaw(byte[] buf, + int dstOffset, + int bitsPerPixel, + int scanlineStride) throws IOException { + int elementsPerPacket = chromaSubsampleH*chromaSubsampleV + 2; + byte[] packet = new byte[elementsPerPacket]; + + if(decompressor != null) { + int bytesPerRow = 3*srcWidth; + byte[] tmpBuf = new byte[bytesPerRow*srcHeight]; + decompressor.decodeRaw(tmpBuf, dstOffset, bitsPerPixel, + bytesPerRow); + ByteArrayInputStream byteStream = + new ByteArrayInputStream(tmpBuf); + stream = new MemoryCacheImageInputStream(byteStream); + } else { + stream.seek(offset); + } + + for (int y = srcMinY; y < srcMinY + srcHeight; y += chromaSubsampleV) { + // Decode chromaSubsampleV rows + for (int x = srcMinX; x < srcMinX + srcWidth; + x += chromaSubsampleH) { + try { + stream.readFully(packet); + } catch (EOFException e) { + return; + } + + byte Cb = packet[elementsPerPacket - 2]; + byte Cr = packet[elementsPerPacket - 1]; + + int iCb = 0, iCr = 0, iGCb = 0, iGCr = 0; + + if (colorConvert) { + int Cbp = Cb & 0xff; + int Crp = Cr & 0xff; + + iCb = iCbTab[Cbp]; + iCr = iCrTab[Crp]; + + iGCb = iGCbTab[Cbp]; + iGCr = iGCrTab[Crp]; + } + + int yIndex = 0; + for (int v = 0; v < chromaSubsampleV; v++) { + int idx = dstOffset + 3*(x - srcMinX) + + scanlineStride*(y - srcMinY + v); + + // Check if we reached the last scanline + if (y + v >= srcMinY + srcHeight) { + break; + } + + for (int h = 0; h < chromaSubsampleH; h++) { + if (x + h >= srcMinX + srcWidth) { + break; + } + + byte Y = packet[yIndex++]; + + if (colorConvert) { + int Yp = Y & 0xff; + int iY = iYTab[Yp]; + int iGY = iGYTab[Yp]; + + int iR = iY + iCr; + int iG = iGY + iGCb + iGCr; + int iB = iY + iCb; + + byte r = clamp(iR); + byte g = clamp(iG); + byte b = clamp(iB); + + buf[idx] = r; + buf[idx + 1] = g; + buf[idx + 2] = b; + } else { + buf[idx] = Y; + buf[idx + 1] = Cb; + buf[idx + 2] = Cr; + } + + idx += 3; + } + } + } + } + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFZLibCompressor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFZLibCompressor.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2005, 2015, 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.tiff; + +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.ImageWriteParam; + +/** + * Compressor for ZLib compression. + */ +public class TIFFZLibCompressor extends TIFFDeflater { + public TIFFZLibCompressor(ImageWriteParam param, int predictor) { + super("ZLib", BaselineTIFFTagSet.COMPRESSION_ZLIB, param, predictor); + } +} diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/javax/imageio/metadata/doc-files/tiff_metadata.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/javax/imageio/metadata/doc-files/tiff_metadata.html Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,1200 @@ + + + + + +TIFF Metadata Format Specification and Usage Notes + + + + +

+TIFF Metadata Format Specification and Usage Notes +

+ +

+Reading Images
+ +

+ +Writing Images
+ + + +Native Stream Metadata Format
+Native Image Metadata Format +

+ +

Reading Images

+ +TIFF images are read by an ImageReader +which may be controlled by its public interface as well as via a supplied +TIFFImageReadParam. + + + + + +

Color Conversion

+ +

If the source image data +have photometric type CIE L*a*b* or YCbCr, and the destination color space +type is RGB, then the source image data will be automatically converted to +RGB using an internal color converter.

+ +

Color Spaces

+ +The raw color space assigned by default, i.e., in the absence of a +user-supplied ImageTypeSpecifier, +will be the first among the following which applies: + + + +

The normalized color coordinate transformations +used for the default CMYK color space are defined as follows: + +

+

+ +

The generic color space used when no other color space +can be inferred is provided merely to enable the data to be loaded. It is not +intended to provide accurate conversions of any kind.

+ +

If the data are known to be in a color space not correctly handled by the +foregoing, then an ImageTypeSpecifier should be +supplied to the reader and should be derived from a color space which is correct +for the data in question.

+ +

ICC Profiles

+ +If an ICC profile is contained in the image metadata +( +BaselineTIFFTagSet.TAG_ICC_PROFILE, tag number 34675), +an attempt will be made to use it to create the color space +of the loaded image. It will be used if the data layout is of component type +and the number of samples per pixel equals or is one greater than the number +of components described by the ICC profile. If the ICC profile is not used +then the color space will be inferred in one of the subsequent steps described +above. + +

If for some reason the embedded ICC profile is not used automatically, then +it may be used manually by following this procedure: + +

    +
  1. Obtain the image metadata from +ImageReader.getImageMetadata
  2. +
  3. Extract the ICC profile field and its value.
  4. +
  5. Create an +ICC_ColorSpace from an + +ICC_Profile created from the ICC profile field data +using ICC_Profile.getInstance(byte[]).
  6. +
  7. Create an ImageTypeSpecifier from the new color +space using one of its factory methods which accepts an +ICC_ColorSpace. +
  8. Create a compatible ImageReadParam +and set the ImageTypeSpecifier using +ImageReadParam.setDestinationType.
  9. +
  10. Pass the parameter object to the appropriate read method.
  11. +
+

+ +

If the inferred color space not based on the ICC Profile field is compatible +with the ICC profile-based color space, then a second +ImageTypeSpecifier derived from this inferred color +space will be included in the +Iterator returned by +ImageReader.getImageTypes. If the iterator contains +more than one type, the first one will be based on the ICC profile and the +second on the inferred color space.

+ +

Metadata Issues

+ +By default all fields in the TIFF image file directory (IFD) are loaded into +the native image metadata object. In cases where the IFD includes fields which +contain large amounts of data this could be very inefficient. Which fields +are loaded may be controlled by setting which TIFF tags the reader is allowed +to recognize and whether it is ignoring metadata. The reader is informed to +disregard metadata as usual via the ignoreMetadata parameter of +ImageReader.setInput(Object,boolean,boolean). It is +informed of which TIFFTags to +recognize or not to recognize via +TIFFImageReadParam.addAllowedTagSet(TIFFTagSet) +and +TIFFImageReadParam.removeAllowedTagSet(TIFFTagSet). +If ignoreMetadata is true, then the reader will +load into the native image metadata object only those fields which have a +TIFFTag contained in the one of the allowed +TIFFTagSets. + +

Use of a TIFFDirectory +object may simplify gaining access to metadata values. An instance of +TIFFDirectory may be created from the IIOMetadata +object returned by the TIFF reader using the +TIFFDirectory.createFromMetadata method.

+ +
+Mapping of TIFF Native Image Metadata to the Standard Metadata Format
+ +The derivation of standard metadata format +javax_imageio_1.0 +elements from TIFF native image metadata is given +in the following table. + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Standard Metadata ElementDerivation from TIFF Fields
/Chroma/ColorSpaceType@namePhotometricInterpretation: WhiteIsZero, BlackIsZero, TransparencyMask = +"GRAY"; RGB, PaletteColor => "RGB"; CMYK => "CMYK"; +YCbCr => "YCbCr"; +CIELab, ICCLab => "Lab".
/Chroma/NumChannels@valueSamplesPerPixel
/Chroma/BlackIsZero@value"TRUE" <=> PhotometricInterpretation => WhiteIsZero
/Chroma/PaletteColorMap
/Compression/CompressionTypeName@valueCompression: Uncompressed => "none"; CCITT 1D => "CCITT +RLE"; +Group 3 Fax => "CCITT T.4"; Group 4 Fax => "CCITT T.6"; +LZW => "LZW"; +JPEG => "Old JPEG"; New JPEG => "JPEG"; Zlib =>> "ZLib"; PackBits => +"PackBits"; +Deflate => "Deflate"; Exif JPEG => "JPEG".
/Compression/Lossless@valueCompression: JPEG or New JPEG => "FALSE"; otherwise "TRUE".
/Data/PlanarConfiguration@valueChunky => "PixelInterleaved"; Planar => "PlaneInterleaved".
/Data/SampleFormat@valuePhotometricInterpretation PaletteColor => "Index"; +SampleFormat unsigned integer data => "UnsignedIntegral"; +SampleFormat two's complement signed integer data => "SignedIntegral"; +SampleFormat IEEE floating point data => "Real"; +otherwise element not emitted. +
/Data/BitsPerSample@valueBitsPerSample as a space-separated list.
/Data/SampleMSB@valueFillOrder: left-to-right => space-separated list of BitsPerSample-1; +right-to-left => space-separated list of 0s.
/Dimension/PixelAspectRatio@value(1/XResolution)/(1/YResolution)
/Dimension/ImageOrientation@valueOrientation
/Dimension/HorizontalPixelSize@value1/XResolution in millimeters if ResolutionUnit is not None.
/Dimension/VerticalPixelSize@value1/YResolution in millimeters if ResolutionUnit is not None.
/Dimension/HorizontalPosition@valueXPosition in millimeters if ResolutionUnit is not None.
/Dimension/VerticalPosition@valueYPosition in millimeters if ResolutionUnit is not None.
/Document/FormatVersion@value6.0
/Document/SubimageInterpretation@valueNewSubFileType: transparency => "TransparencyMask"; +reduced-resolution => "ReducedResolution"; +single page => "SinglePage".
/Document/ImageCreationTime@valueDateTime
/Text/TextEntryDocumentName, ImageDescription, Make, Model, PageName, Software, +Artist, HostComputer, InkNames, Copyright: +/Text/TextEntry@keyword = field name, +/Text/TextEntry@value = field value.
+Example: TIFF Software field => /Text/TextEntry@keyword = "Software", +/Text/TextEntry@value = Name and version number of the software package(s) +used to create the image.
/Transparency/Alpha@valueExtraSamples: associated alpha => "premultiplied"; +unassociated alpha => "nonpremultiplied".
+

+ +

Reading Exif Images

+ +The TIFF reader may be used to read an uncompressed Exif image or the +contents of the APP1 marker segment of a compressed Exif image. + +
Reading Uncompressed Exif Images
+ +An uncompressed Exif image is a one- or two-page uncompressed TIFF image +with a specific ordering of its IFD and image data content. Each pixel +has three 8-bit samples with photometric interpretation RGB or YCbCr. +The image stream must contain a single primary image and may contain a +single thumbnail which if present must also be uncompressed. The usual +ImageReader methods may be used to read the image +data and metadata: + +

+    ImageInputStream input;
+    ImageReader tiffReader;
+    ImageReadParam tiffReadParam;
+
+    tiffReader.setInput(input);
+
+    // Read primary image and IFD.
+    BufferedImage image = tiffReader.read(0, tiffReadParam);
+    IIOMetadata primaryIFD = tiffReader.getImageMetadata(0);
+
+    // Read thumbnail if present.
+    BufferedImage thumbnail = null;
+    if (tiffReader.getNumImages(true) > 1) {
+        thumbnail = tiffReader.read(1, tiffReadParam);
+    }
+
+ +Note that the Exif thumbnail is treated as a separate page in the TIFF +stream and not as a thumbnail, i.e., +tiffReader.hasThumbnails(0) will return false. + +
Reading Compressed Exif Images
+ +A compressed Exif image is a 3-band ISO/IEC 10918-1 baseline DCT JPEG stream +with an inserted APP1 marker segment. The parameters of the marker +segment after the length are the 6-byte sequence +{'E', 'x', 'i', 'f', 0x00, 0x00} +followed by a complete TIFF stream. The embedded TIFF stream contains a primary +IFD describing the JPEG image optionally followed by a thumbnail IFD and +compressed or uncompressed thumbnail image data. Note that the embedded TIFF +stream does not contain any image data associated with the primary IFD +nor any descriptive fields which duplicate information found in the JPEG +stream itself. + +

The parameter content of the APP1 marker segment may be obtained +from the user object of the associated Node in a +javax_imageio_jpeg_image_1.0 native image metadata tree extracted +from the image metadata object returned by the JPEG reader. This APP1 Exif +node will be a child of the node named "markerSequence" and will +have name unknown and an attribute named MarkerTag with +integral value 0xE1 (String value +"225"). The user object of this node will be a byte array +which starts with the six bytes {'E', 'x', 'i', 'f', '0', '0'}. +The primary IFD and the thumbnail IFD and image may be +read from the user object by the usual ImageReader +methods: + +


+    ImageReader jpegReader;
+    ImageReader tiffReader;
+
+    // Obtain the APP1 Exif marker data from the JPEG image metadata.
+    IIOMetadata jpegImageMetadata = jpegReader.getImageMetadata(0);
+    String nativeFormat = jpegImageMetadata.getNativeMetadataFormatName();
+    Node jpegImageMetadataTree = jpegImageMetadata.getAsTree(nativeFormat);
+
+    // getExifMarkerData() returns the byte array which is the user object
+    // of the APP1 Exif marker node.
+    byte[] app1Params = getExifMarkerData(jpegImageMetadataTree);
+    if (app1Params == null) {
+        throw new IIOException("APP1 Exif marker not found.");
+    }
+
+    // Set up input, skipping Exif ID 6-byte sequence.
+    MemoryCacheImageInputStream app1ExifInput
+        = new MemoryCacheImageInputStream
+            (new ByteArrayInputStream(app1Params, 6, app1Params.length - 6));
+    tiffReader.setInput(app1ExifInput);
+
+    // Read primary IFD.
+    IIOMetadata primaryIFD = tiffReader.getImageMetadata(0);
+
+    // Read thumbnail if present.
+    BufferedImage thumbnail = null;
+    if (tiffReader.getNumImages(true) > 1) {
+        thumbnail = tiffReader.read(1, tiffReadParam);
+    }
+
+    // Read the primary image.
+    BufferedImage image = jpegReader.read(0);
+
+ +Note that tiffReader.getNumImages(true) returns the number of +IFDs in the embedded TIFF stream including those corresponding to empty +images. Calling tiffReader.read(0, readParam) will throw +an exception as the primary image in the embedded TIFF stream is always +empty; the primary image should be obtained using the JPEG reader itself. +

+ +

Writing Images

+ +TIFF images are written by a ImageWriter which may be +controlled by its public interface as well as via a supplied +ImageWriteParam. For an ImageWriteParam returned +by the getDefaultWriteParam() method of the TIFF ImageWriter, +the canWriteTiles() and canWriteCompressed() methods +will return true; the canOffsetTiles() and +canWriteProgressive() methods will return false.

+ +The TIFF writer supports many optional capabilities including writing tiled +images, inserting images, writing or inserting empty images, and replacing image +data. Pixels may be replaced in either empty or non-empty images but if and +only if the data are not compressed. + +

If tiles are being written, then each of their dimensions will be +rounded to the nearest multiple of 16 per the TIFF specification. If +JPEG-in-TIFF compression is being used, and tiles are being written +each tile dimension will be rounded to the nearest multiple of 8 times +the JPEG minimum coded unit (MCU) in that dimension. If JPEG-in-TIFF +compression is being used and strips are being written, the number of +rows per strip is rounded to a multiple of 8 times the maximum MCU over +both dimensions.

+ + + + + +

Compression

+ +The compression type may be set via the setCompressionType() method of +the ImageWriteParam after setting the compression mode to +MODE_EXPLICIT. The set of innately +supported compression types is listed in the following table: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Supported Compression Types
Compression Type Description Reference
CCITT RLEModified Huffman compressionTIFF 6.0 Specification, Section 10
CCITT T.4CCITT T.4 bilevel encoding/Group 3 facsimile compressionTIFF 6.0 Specification, Section 11
CCITT T.6CCITT T.6 bilevel encoding/Group 4 facsimile compressionTIFF 6.0 Specification, Section 11
LZWLZW compressionTIFF 6.0 Specification, Section 13
JPEG"New" JPEG-in-TIFF compressionTIFF +Technical Note #2
ZLib"Deflate/Inflate" compression (see note following this table) +Adobe Photoshop® TIFF Technical Notes (PDF)
PackBitsByte-oriented, run length compressionTIFF 6.0 Specification, Section 9
Deflate"Zip-in-TIFF" compression (see note following this table) +ZLIB Compressed Data Format Specification, + +DEFLATE Compressed Data Format Specification
Exif JPEGExif-specific JPEG compression (see note following this table)Exif 2.2 Specification +(PDF), section 4.5.5, "Basic Structure of Thumbnail Data"
+ +

+Old-style JPEG compression as described in section 22 of the TIFF 6.0 +Specification is not supported. +

+ +

The CCITT compression types are applicable to bilevel (1-bit) +images only. The JPEG compression type is applicable to byte +grayscale (1-band) and RGB (3-band) images only.

+ +

+ZLib and Deflate compression are identical except for the value of the +TIFF Compression field: for ZLib the Compression field has value 8 +whereas for Deflate it has value 32946 (0x80b2). In both cases each +image segment (strip or tile) is written as a single complete zlib data +stream. +

+ +

+"Exif JPEG" is a compression type used when writing the contents of an +APP1 Exif marker segment for inclusion in a JPEG native image metadata +tree. The contents appended to the output when this compression type is +used are a function of whether an empty or non-empty image is written. +If the image is empty, then a TIFF IFD adhering to the specification of +a compressed Exif primary IFD is appended. If the image is non-empty, +then a complete IFD and image adhering to the specification of a +compressed Exif thumbnail IFD and image are appended. Note that the +data of the empty image may not later be appended using the pixel +replacement capability of the TIFF writer. +

+ +

If ZLib/Deflate or JPEG compression is used, the compression quality +may be set. For ZLib/Deflate the supplied floating point quality value is +rescaled to the range [1, 9] and truncated to an integer +to derive the Deflate compression level. For JPEG the floating point +quality value is passed directly to the JPEG writer plug-in which +interprets it in the usual way.

+ +

Color Conversion

+ +

If the source image data +color space type is RGB, and the destination photometric type is CIE L*a*b* or +YCbCr, then the source image data will be automatically converted from +RGB using an internal color converter.

+ +

ICC Profiles

+ +An ICC Profile field will be written if either: +
    +
  • one is present in the native image metadata +IIOMetadata instance supplied to the writer, +or
  • +
  • the ColorSpace +of the destination ImageTypeSpecifier is an instance of +ICC_ColorSpace which is not one of the standard +color spaces defined by the CS_* constants in the +ColorSpace class. The destination type is set via +ImageWriteParam.setDestinationType(ImageTypeSpecifier) and defaults +to the ImageTypeSpecifier of the image being written. +
  • +
+ +

Metadata Issues

+ +Some behavior of the writer is affected by or may affect the contents of +the image metadata which may be supplied by the user. + +

For bilevel images, the FillOrder, and T4Options +fields affect the output data. The data will be filled right-to-left if +FillOrder is present with a value of 2 +(BaselineTIFFTagSet.FILL_ORDER_RIGHT_TO_LEFT) +and will be filled left-to-right otherwise. The value of T4Options +specifies whether the data should be 1D- or 2D-encoded and whether EOL +padding should be used.

+ +

For all images the value of the RowsPerStrip field is used +to the set the number of rows per strip if the image is not tiled. The +default number of rows per strip is either 8 or the number of rows which +would fill no more than 8 kilobytes, whichever is larger.

+ +

For all images the tile dimensions may be set using the TileWidth +and TileLength field values if the tiling mode is +ImageWriteParam.MODE_COPY_FROM_METADATA. If this mode +is set but the fields are not, their respective default values are the image +width and height.

+ +

When using JPEG-in-TIFF compression, a JPEGTables field will be +written to the IFD and abbreviated JPEG streams to each strip or tile if and +only if a JPEGTables field is contained in the metadata object +provided to the writer. If the contents of the JPEGTables field is +a valid tables-only JPEG stream, then it will be used; otherwise the contents +of the field will be replaced with default visually lossless tables. If no +such JPEGTables field is present in the metadata, then no +JPEGTables field will be written to the output and each strip or +tile will be written as a separate, self-contained JPEG stream.

+ +

When using Deflate/ZLib or LZW compression, if the image has 8 bits per +sample, a horizontal differencing predictor will be used if the +Predictor field is present with a value of 2 +(BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING). +If prediction is so requested but the image does not have +8 bits per sample the field will be reset to have the value 1 +(BaselineTIFFTagSet.PREDICTOR_NONE). +

+ +

Some fields may be added or modified: + +

    +
  • PhotometricInterpretation if not present.
  • +
  • PlanarConfiguration if this field is present with value +Planar is is reset to Chunky.
  • +
  • Compression always.
  • +
  • BitsPerSample if the image is not bilevel.
  • +
  • SamplesPerPixel always.
  • +
  • ExtraSamples if an alpha channel is present.
  • +
  • SampleFormat if not present and the data are 16- or 32-bit +integers or floating point.
  • +
  • ColorMap if the PhotometricInterpretation is +RGBPalette.
  • +
  • ImageWidth and ImageLength always.
  • +
  • TileWidth, TileLength, TileOffsets, and +TileByteCounts if a tiled image is being written.
  • +
  • RowsPerStrip, StripOffsets, and StripByteCounts +if a tiled image is not being written.
  • +
  • XResolution, YResolution, and ResolutionUnit +if none of these is present.
  • +
  • YCbCrSubsampling and YCbCrPositioning if the +photometric interpretation is YCbCr and the compression type is not JPEG +(only [1, 1] subsampling and cosited positioning are supported for +non-JPEG YCbCr output).
  • +
  • YCbCrSubsampling, YCbCrPositioning, and +ReferenceBlackWhite: if the compression type is JPEG and the color +space is RGB these will be reset to [2, 2] centered subsampling with no +headroom/footroom (0:255,128:255,128:255).
  • +
+ +

Some fields may be removed: + +

    +
  • BitsPerSample if the image is bilevel.
  • +
  • ExtraSamples if the image does not have an alpha channel.
  • +
  • ColorMap if the photometric interpretation is not +RGBPalette.
  • +
  • TileWidth, TileLength, TileOffsets, and +TileByteCounts if tiling is not being used.
  • +
  • RowsPerStrip, StripOffsets, and StripByteCounts +if tiling is being used.
  • +
  • YCbCrSubsampling, YCbCrPositioning, and +ReferenceBlackWhite if the compression type is JPEG and the +color space is grayscale.
  • +
  • JPEGProc, JPEGInterchangeFormat, +JPEGInterchangeFormatLength, JPEGRestartInterval, +JPEGLosslessPredictors, JPEGPointTransforms, +JPEGQTables, JPEGDCTables, and +JPEGACTables if the compression type is JPEG.
  • +
+

+ +

Other fields present in the supplied metadata are uninterpreted and will +be written as supplied.

+ +

If an Exif image is being written, the set of fields present and their +values will be modified such that the result is in accord with the Exif 2.2 +specification.

+ +

Setting up the image metadata to write to a TIFF stream may be simplified +by using the TIFFDirectory class +which represents a TIFF IFD. A field in a TIFF IFD is represented by an +instance of TIFFField. For each +field to be written a TIFFField may be added to the +TIFFDirectory and the latter converted to an +IIOMetadata object by invoking +TIFFDirectory.getAsMetadata. The +IIOMetadata object so obtained may then be passed to the TIFF +writer.

+ +
+Mapping of the Standard Metadata Format to TIFF Native Image Metadata
+ +The derivation of TIFF native image metadata +elements from the standard metadata format +javax_imageio_1.0 is +given in the following table. + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TIFF FieldDerivation from Standard Metadata Elements
+PhotometricInterpretation +/Chroma/ColorSpaceType@name: "GRAY" and /Chroma/BlackIsZero@value = "FALSE" +=> WhiteIsZero; "GRAY" and /Document/SubimageInterpretation@value = +"TransparencyMask" => TransparencyMask; "RGB" and /Chroma/Palette present => +PaletteColor; "GRAY" => BlackIsZero; "RGB" => RGB; "YCbCr" => YCbCr; +"CMYK" => CMYK; "Lab" => CIELab.
SamplesPerPixel/Chroma/NumChannels@value
ColorMap/Chroma/Palette
Compression/Compression/CompressionTypeName@value: "none" => Uncompressed; +"CCITT RLE" => CCITT 1D; "CCITT T.4" => Group 3 Fax; "CCITT T.6" => Group 4 +Fax; "LZW" => LZW; "Old JPEG" => JPEG; "JPEG" => New JPEG; "ZLib" => ZLib; +"PackBits" => PackBits; "Deflate" => Deflate.
PlanarConfiguration/Data/PlanarConfiguration@value: "PixelInterleaved" => Chunky; +"PlaneInterleaved" => Planar.
SampleFormat/Data/SampleFormat@value: "SignedIntegral" => two's complement signed +integer data; "UnsignedIntegral" => unsigned integer data; "Real" => +IEEE floating point data; "Index" => unsigned integer data. +
BitsPerSample/Data/BitsPerSample@value: space-separated list parsed to char array.
FillOrder/Data/SampleMSB@value: if all values in space-separated list are 0s => +right-to-left; otherwise => left-to-right. +
XResolution(10 / /Dimension/HorizontalPixelSize@value) or +(10 / (/Dimension/VerticalPixelSize@value * +/Dimension/PixelAspectRatio@value))
YResolution(10 / /Dimension/VerticalPixelSize@value) or +(10 / (/Dimension/HorizontalPixelSize@value / +/Dimension/PixelAspectRatio@value))
ResolutionUnitCentimeter if XResolution or YResolution set; otherwise None.
Orientation/Dimension/ImageOrientation@value
XPosition/Dimension/HorizontalPosition@value / 10
YPosition/Dimension/VerticalPosition@value / 10
NewSubFileType/Document/SubimageInterpretation@value: "TransparencyMask" => +transparency mask; "ReducedResolution" => reduced-resolution; +"SinglePage" => single page.
DateTime/Document/ImageCreationTime@value
DocumentName, ImageDescription, Make, Model, PageName, Software, +Artist, HostComputer, InkNames, Copyright/Text/TextEntry: if /Text/TextEntry@keyword is the name of any of the +TIFF Fields, e.g., "Software", then the field is added with content +/Text/TextEntry@value and count 1.
ExtraSamples/Transparency/Alpha@value: "premultiplied" => associated alpha, count 1; +"nonpremultiplied" => unassociated alpha, count 1.
+

+ +

Writing Exif Images

+ +The TIFF writer may be used to write an uncompressed Exif image or the +contents of the APP1 marker segment of a compressed Exif image. + +
Writing Uncompressed Exif Images
+ +When writing a sequence of images each image is normally recorded as +{IFD, IFD Value, Image Data}. The Exif specification requires +that an uncompressed Exif image be structured as follows: + +
    + +
  1. Image File Header
  2. +
  3. Primary IFD
  4. +
  5. Primary IFD Value
  6. +
  7. Thumbnail IFD
  8. +
  9. Thumbnail IFD Value
  10. +
  11. Thumbnail Image Data
  12. +
  13. Primary Image Data
  14. +
+ +To meet the requirement of the primary image data being recorded last, the +primary image must be written initially as an empty image and have its data +added via pixel replacement after the thumbnail IFD and image data have been +written: + +

+    ImageWriter tiffWriter;
+    ImageWriteParam tiffWriteParam;
+    IIOMetadata tiffStreamMetadata;
+    IIOMetadata primaryIFD;
+    BufferedImage image;
+    BufferedImage thumbnail;
+
+    // Specify uncompressed output.
+    tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);
+
+    if (thumbnail != null) {
+        // Write the TIFF header.
+        tiffWriter.prepareWriteSequence(tiffStreamMetadata);
+
+        // Append the primary IFD.
+        tiffWriter.prepareInsertEmpty(-1, // append
+                new ImageTypeSpecifier(image),
+                image.getWidth(),
+                image.getHeight(),
+                primaryIFD,
+                null, // thumbnails
+                tiffWriteParam);
+        tiffWriter.endInsertEmpty();
+
+        // Append the thumbnail image data.
+        tiffWriter.writeToSequence(new IIOImage(thumbnail, null, null),
+                tiffWriteParam);
+
+        // Insert the primary image data.
+        tiffWriter.prepareReplacePixels(0, new Rectangle(image.getWidth(),
+                image.getHeight()));
+        tiffWriter.replacePixels(image, tiffWriteParam);
+        tiffWriter.endReplacePixels();
+
+        // End writing.
+        tiffWriter.endWriteSequence();
+    } else {
+        // Write only the primary IFD and image data.
+        tiffWriter.write(tiffStreamMetadata,
+                new IIOImage(image, null, primaryIFD),
+                tiffWriteParam);
+    }
+
+ +
Writing Compressed Exif Images
+ +The structure of the embedded TIFF stream in the APP1 segment of a +compressed Exif image is identical to the +uncompressed Exif image structure except that there are no primary +image data, i.e., the primary IFD does not refer to any image data. + +

+    ImageWriter tiffWriter;
+    ImageWriteParam tiffWriteParam;
+    IIOMetadata tiffStreamMetadata;
+    BufferedImage image;
+    BufferedImage thumbnail;
+    IIOMetadata primaryIFD;
+    ImageOutputStream output;
+
+    // Set up an output to contain the APP1 Exif TIFF stream.
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    MemoryCacheImageOutputStream app1ExifOutput =
+        new MemoryCacheImageOutputStream(baos);
+    tiffWriter.setOutput(app1ExifOutput);
+
+    // Set compression for the thumbnail.
+    tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+    tiffWriteParam.setCompressionType("Exif JPEG");
+
+    // Write the APP1 Exif TIFF stream.
+    if (thumbnail != null) {
+        // Write the TIFF header.
+        tiffWriter.prepareWriteSequence(tiffStreamMetadata);
+
+        // Append the primary IFD.
+        tiffWriter.prepareInsertEmpty(-1, // append
+                new ImageTypeSpecifier(image),
+                image.getWidth(),
+                image.getHeight(),
+                primaryIFD,
+                null, // thumbnails
+                tiffWriteParam);
+        tiffWriter.endInsertEmpty();
+
+        // Append the thumbnail IFD and image data.
+        tiffWriter.writeToSequence(new IIOImage(thumbnail, null,
+                null), tiffWriteParam);
+
+        // End writing.
+        tiffWriter.endWriteSequence();
+    } else {
+        // Write only the primary IFD.
+        tiffWriter.prepareWriteEmpty(tiffStreamMetadata,
+                new ImageTypeSpecifier(image),
+                image.getWidth(),
+                image.getHeight(),
+                primaryIFD,
+                null, // thumbnails
+                tiffWriteParam);
+        tiffWriter.endWriteEmpty();
+    }
+
+    // Flush data into byte stream.
+    app1ExifOutput.flush();
+
+    // Create APP1 parameter array.
+    byte[] app1Parameters = new byte[6 + baos.size()];
+
+    // Add APP1 Exif ID bytes.
+    app1Parameters[0] = (byte) 'E';
+    app1Parameters[1] = (byte) 'x';
+    app1Parameters[2] = (byte) 'i';
+    app1Parameters[3] = (byte) 'f';
+    app1Parameters[4] = app1Parameters[5] = (byte) 0;
+
+    // Append TIFF stream to APP1 parameters.
+    System.arraycopy(baos.toByteArray(), 0, app1Parameters, 6, baos.size());
+
+    // Create the APP1 Exif node to be added to native JPEG image metadata.
+    IIOMetadataNode app1Node = new IIOMetadataNode("unknown");
+    app1Node.setAttribute("MarkerTag", String.valueOf(0xE1));
+    app1Node.setUserObject(app1Parameters);
+
+    // Append the APP1 Exif marker to the "markerSequence" node.
+    IIOMetadata jpegImageMetadata =
+        jpegWriter.getDefaultImageMetadata(new ImageTypeSpecifier(image),
+            jpegWriteParam);
+    String nativeFormat = jpegImageMetadata.getNativeMetadataFormatName();
+    Node tree = jpegImageMetadata.getAsTree(nativeFormat);
+    NodeList children = tree.getChildNodes();
+    int numChildren = children.getLength();
+    for (int i = 0; i < numChildren; i++) {
+        Node child = children.item(i);
+        if (child.getNodeName().equals("markerSequence")) {
+            child.appendChild(app1Node);
+            break;
+        }
+    }
+    jpegImageMetadata.setFromTree(nativeFormat, tree);
+
+    // Write the JPEG image data including the APP1 Exif marker.
+    jpegWriter.setOutput(output);
+    jpegWriter.write(new IIOImage(image, null, jpegImageMetadata));
+
+ +The "unknown" node created above would be appended to the +"markerSequence" node of the native JPEG image metadata +and written to the JPEG stream when the primary image is written using +the JPEG writer. + +

Stream Metadata

+ +The DTD for the TIFF native stream metadata format is as follows: + +
+<!DOCTYPE "javax_imageio_tiff_stream_1.0" [
+
+  <!ELEMENT "javax_imageio_tiff_stream_1.0" (ByteOrder)>
+
+    <!ELEMENT "ByteOrder" EMPTY>
+      <!-- The stream byte order --> 
+      <!ATTLIST "ByteOrder" "value" #CDATA #REQUIRED>
+        <!-- One of "BIG_ENDIAN" or "LITTLE_ENDIAN" --> 
+        <!-- Data type: String -->
+]>
+
+ +

Image Metadata

+ +The DTD for the TIFF native image metadata format is as follows: + +
+<!DOCTYPE "javax_imageio_tiff_image_1.0" [
+
+  <!ELEMENT "javax_imageio_tiff_image_1.0" (TIFFIFD)*>
+
+    <!ELEMENT "TIFFIFD" (TIFFField | TIFFIFD)*>
+      <!-- An IFD (directory) containing fields --> 
+      <!ATTLIST "TIFFIFD" "tagSets" #CDATA #REQUIRED>
+        <!-- Data type: String -->
+      <!ATTLIST "TIFFIFD" "parentTagNumber" #CDATA #IMPLIED>
+        <!-- The tag number of the field pointing to this IFD --> 
+        <!-- Data type: Integer -->
+      <!ATTLIST "TIFFIFD" "parentTagName" #CDATA #IMPLIED>
+        <!-- A mnemonic name for the field pointing to this IFD, if known 
+             --> 
+        <!-- Data type: String -->
+
+      <!ELEMENT "TIFFField" (TIFFBytes | TIFFAsciis |
+        TIFFShorts | TIFFSShorts | TIFFLongs | TIFFSLongs |
+        TIFFRationals | TIFFSRationals |
+        TIFFFloats | TIFFDoubles | TIFFUndefined)>
+        <!-- A field containing data --> 
+        <!ATTLIST "TIFFField" "number" #CDATA #REQUIRED>
+          <!-- The tag number asociated with the field --> 
+          <!-- Data type: String -->
+        <!ATTLIST "TIFFField" "name" #CDATA #IMPLIED>
+          <!-- A mnemonic name associated with the field, if known --> 
+          <!-- Data type: String -->
+
+        <!ELEMENT "TIFFBytes" (TIFFByte)*>
+          <!-- A sequence of TIFFByte nodes --> 
+
+          <!ELEMENT "TIFFByte" EMPTY>
+            <!-- An integral value between 0 and 255 --> 
+            <!ATTLIST "TIFFByte" "value" #CDATA #IMPLIED>
+              <!-- The value --> 
+              <!-- Data type: String -->
+            <!ATTLIST "TIFFByte" "description" #CDATA #IMPLIED>
+              <!-- A description, if available --> 
+              <!-- Data type: String -->
+
+        <!ELEMENT "TIFFAsciis" (TIFFAscii)*>
+          <!-- A sequence of TIFFAscii nodes --> 
+
+          <!ELEMENT "TIFFAscii" EMPTY>
+            <!-- A String value --> 
+            <!ATTLIST "TIFFAscii" "value" #CDATA #IMPLIED>
+              <!-- The value --> 
+              <!-- Data type: String -->
+
+        <!ELEMENT "TIFFShorts" (TIFFShort)*>
+          <!-- A sequence of TIFFShort nodes --> 
+
+          <!ELEMENT "TIFFShort" EMPTY>
+            <!-- An integral value between 0 and 65535 --> 
+            <!ATTLIST "TIFFShort" "value" #CDATA #IMPLIED>
+              <!-- The value --> 
+              <!-- Data type: String -->
+            <!ATTLIST "TIFFShort" "description" #CDATA #IMPLIED>
+              <!-- A description, if available --> 
+              <!-- Data type: String -->
+
+        <!ELEMENT "TIFFSShorts" (TIFFSShort)*>
+          <!-- A sequence of TIFFSShort nodes --> 
+
+          <!ELEMENT "TIFFSShort" EMPTY>
+            <!-- An integral value between -32768 and 32767 --> 
+            <!ATTLIST "TIFFSShort" "value" #CDATA #IMPLIED>
+              <!-- The value --> 
+              <!-- Data type: String -->
+            <!ATTLIST "TIFFSShort" "description" #CDATA #IMPLIED>
+              <!-- A description, if available --> 
+              <!-- Data type: String -->
+
+        <!ELEMENT "TIFFLongs" (TIFFLong)*>
+          <!-- A sequence of TIFFLong nodes --> 
+
+          <!ELEMENT "TIFFLong" EMPTY>
+            <!-- An integral value between 0 and 4294967295 --> 
+            <!ATTLIST "TIFFLong" "value" #CDATA #IMPLIED>
+              <!-- The value --> 
+              <!-- Data type: String -->
+            <!ATTLIST "TIFFLong" "description" #CDATA #IMPLIED>
+              <!-- A description, if available --> 
+              <!-- Data type: String -->
+
+        <!ELEMENT "TIFFSLongs" (TIFFSLong)*>
+          <!-- A sequence of TIFFSLong nodes --> 
+
+          <!ELEMENT "TIFFSLong" EMPTY>
+            <!-- An integral value between -2147483648 and 2147482647 --> 
+            <!ATTLIST "TIFFSLong" "value" #CDATA #IMPLIED>
+              <!-- The value --> 
+              <!-- Data type: String -->
+            <!ATTLIST "TIFFSLong" "description" #CDATA #IMPLIED>
+              <!-- A description, if available --> 
+              <!-- Data type: String -->
+
+        <!ELEMENT "TIFFRationals" (TIFFRational)*>
+          <!-- A sequence of TIFFRational nodes --> 
+
+          <!ELEMENT "TIFFRational" EMPTY>
+            <!-- A rational value consisting of an unsigned numerator and 
+                 denominator --> 
+            <!ATTLIST "TIFFRational" "value" #CDATA #IMPLIED>
+              <!-- The numerator and denominator, separated by a slash --> 
+              <!-- Data type: String -->
+
+        <!ELEMENT "TIFFSRationals" (TIFFSRational)*>
+          <!-- A sequence of TIFFSRational nodes --> 
+
+          <!ELEMENT "TIFFSRational" EMPTY>
+            <!-- A rational value consisting of a signed numerator and 
+                 denominator --> 
+            <!ATTLIST "TIFFSRational" "value" #CDATA #IMPLIED>
+              <!-- The numerator and denominator, separated by a slash --> 
+              <!-- Data type: String -->
+
+        <!ELEMENT "TIFFFloats" (TIFFFloat)*>
+          <!-- A sequence of TIFFFloat nodes --> 
+
+          <!ELEMENT "TIFFFloat" EMPTY>
+            <!-- A single-precision floating-point value --> 
+            <!ATTLIST "TIFFFloat" "value" #CDATA #IMPLIED>
+              <!-- The value --> 
+              <!-- Data type: String -->
+
+        <!ELEMENT "TIFFDoubles" (TIFFDouble)*>
+          <!-- A sequence of TIFFDouble nodes --> 
+
+          <!ELEMENT "TIFFDouble" EMPTY>
+            <!-- A double-precision floating-point value --> 
+            <!ATTLIST "TIFFDouble" "value" #CDATA #IMPLIED>
+              <!-- The value --> 
+              <!-- Data type: String -->
+
+        <!ELEMENT "TIFFUndefined" EMPTY>
+          <!-- Uninterpreted byte data --> 
+          <!ATTLIST "TIFFUndefined" "value" #CDATA #IMPLIED>
+            <!-- A list of comma-separated byte values --> 
+            <!-- Data type: String -->
+]>
+
+ +@since 1.9 + + + diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/javax/imageio/metadata/package.html --- a/jdk/src/java.desktop/share/classes/javax/imageio/metadata/package.html Mon Nov 23 10:00:50 2015 -0800 +++ b/jdk/src/java.desktop/share/classes/javax/imageio/metadata/package.html Mon Nov 23 12:26:12 2015 -0800 @@ -2,7 +2,7 @@ + + + + + +Public classes used by the built-in TIFF plug-ins. + +

+This package contains classes supporting the built-in TIFF reader and writer +plug-ins. Classes are provided for simplifying interaction with metadata, +including Exif metadata common in digital photography, and an extension of +{@link javax.imageio.ImageReadParam} which permits specifying which metadata +tags are allowed to be read. For more information about the operation of the +built-in TIFF plug-ins, see the +TIFF metadata format +specification and usage notes. + +
+
+
+ +@since 1.9 + + diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/javax/imageio/spi/IIORegistry.java --- a/jdk/src/java.desktop/share/classes/javax/imageio/spi/IIORegistry.java Mon Nov 23 10:00:50 2015 -0800 +++ b/jdk/src/java.desktop/share/classes/javax/imageio/spi/IIORegistry.java Mon Nov 23 12:26:12 2015 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2015, 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 @@ -49,6 +49,8 @@ import com.sun.imageio.plugins.bmp.BMPImageWriterSpi; import com.sun.imageio.plugins.wbmp.WBMPImageReaderSpi; import com.sun.imageio.plugins.wbmp.WBMPImageWriterSpi; +import com.sun.imageio.plugins.tiff.TIFFImageReaderSpi; +import com.sun.imageio.plugins.tiff.TIFFImageWriterSpi; import sun.awt.AppContext; import java.util.ServiceLoader; import java.util.ServiceConfigurationError; @@ -168,6 +170,8 @@ registerServiceProvider(new BMPImageWriterSpi()); registerServiceProvider(new WBMPImageReaderSpi()); registerServiceProvider(new WBMPImageWriterSpi()); + registerServiceProvider(new TIFFImageReaderSpi()); + registerServiceProvider(new TIFFImageWriterSpi()); registerServiceProvider(new PNGImageReaderSpi()); registerServiceProvider(new PNGImageWriterSpi()); registerServiceProvider(new JPEGImageReaderSpi());