changeset 13241:07ae3247e988

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
author bpb
date Mon, 23 Nov 2015 12:26:12 -0800
parents 47095c5242d8
children 73b93c545ceb
files src/java.desktop/share/classes/com/sun/imageio/plugins/common/ImageUtil.java src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleCMYKColorSpace.java src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleRenderedImage.java src/java.desktop/share/classes/com/sun/imageio/plugins/common/SingleTileRenderedImage.java src/java.desktop/share/classes/com/sun/imageio/plugins/common/iio-plugin.properties src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFAttrInfo.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFBaseJPEGCompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFCIELabColorConverter.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFColorConverter.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFCompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDecompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflateCompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflateDecompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflater.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFElementInfo.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFExifJPEGCompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxCompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxDecompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFieldNode.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFIFD.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadata.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormat.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormatResources.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReader.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReaderSpi.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriteParam.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriter.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriterSpi.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGCompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGDecompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBCompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBDecompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWCompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWDecompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWUtil.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFMetadataFormat.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullCompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullDecompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFOldJPEGDecompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsCompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsDecompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsUtil.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRLECompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRenderedImage.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadata.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormat.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormatResources.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT4Compressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT6Compressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrColorConverter.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrDecompressor.java src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFZLibCompressor.java src/java.desktop/share/classes/javax/imageio/metadata/doc-files/tiff_metadata.html src/java.desktop/share/classes/javax/imageio/metadata/package.html src/java.desktop/share/classes/javax/imageio/plugins/tiff/BaselineTIFFTagSet.java src/java.desktop/share/classes/javax/imageio/plugins/tiff/ExifGPSTagSet.java src/java.desktop/share/classes/javax/imageio/plugins/tiff/ExifInteroperabilityTagSet.java src/java.desktop/share/classes/javax/imageio/plugins/tiff/ExifParentTIFFTagSet.java src/java.desktop/share/classes/javax/imageio/plugins/tiff/ExifTIFFTagSet.java src/java.desktop/share/classes/javax/imageio/plugins/tiff/FaxTIFFTagSet.java src/java.desktop/share/classes/javax/imageio/plugins/tiff/GeoTIFFTagSet.java src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFDirectory.java src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFField.java src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFImageReadParam.java src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFTag.java src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFTagSet.java src/java.desktop/share/classes/javax/imageio/plugins/tiff/package.html src/java.desktop/share/classes/javax/imageio/spi/IIORegistry.java
diffstat 68 files changed, 28827 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/ImageUtil.java	Mon Nov 23 10:00:50 2015 -0800
+++ b/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 <code>ColorModel</code> that may be used with the
      * specified <code>SampleModel</code>.  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<ImageTypeSpecifier> 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 <code>true</code> if the given <code>ColorSpace</code> object is
+     * an instance of <code>ICC_ColorSpace</code> but is not one of the standard
+     * <code>ColorSpace</code>s returned by <code>ColorSpace.getInstance()</code>.
+     *
+     * @param cs The <code>ColorSpace</code> 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;
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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<RenderedImage> sources = new Vector<RenderedImage>();
+
+    /** A Hashtable containing the image properties. */
+    protected Hashtable<String,Object> properties = new Hashtable<String,Object>();
+
+    /** 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,
+     * <code>java.awt.Image.UndefinedProperty</code> will be returned.
+     *
+     * @param name the name of the property to get, as a
+     * <code>String</code>.  @return a reference to the property
+     * <code>Object</code>, or the value
+     * <code>java.awt.Image.UndefinedProperty.</code>
+     */
+    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, <code>null</code> will be
+     * returned.
+     *
+     * @return an array of <code>String</code>s representing valid
+     *         property names.
+     */
+    public String[] getPropertyNames() {
+        String[] names = null;
+
+        if(properties.size() > 0) {
+            names = new String[properties.size()];
+            int index = 0;
+
+            Enumeration<String> e = properties.keys();
+            while (e.hasMoreElements()) {
+                String name = e.nextElement();
+                names[index++] = name;
+            }
+        }
+
+        return names;
+    }
+
+    /**
+     * Returns an array of <code>String</code>s recognized as names by
+     * this property source that begin with the supplied prefix.  If
+     * no property names match, <code>null</code> will be returned.
+     * The comparison is done in a case-independent manner.
+     *
+     * <p> The default implementation calls
+     * <code>getPropertyNames()</code> and searches the list of names
+     * for matches.
+     *
+     * @return an array of <code>String</code>s giving the valid
+     * property names.
+     */
+    public String[] getPropertyNames(String prefix) {
+        String propertyNames[] = getPropertyNames();
+        if (propertyNames == null) {
+            return null;
+        }
+
+        prefix = prefix.toLowerCase();
+
+        Vector<String> names = new Vector<String>();
+        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<String> 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<RenderedImage> getSources() {
+        return null;
+    }
+
+    /**
+     * Returns the entire image in a single Raster.  For images with
+     * multiple tiles this will require making a copy.
+     *
+     * <p> 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.
+     *
+     * <p> 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.
+     *
+     * <p> 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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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;
+    }
+}
--- a/src/java.desktop/share/classes/com/sun/imageio/plugins/common/iio-plugin.properties	Mon Nov 23 10:00:50 2015 -0800
+++ b/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.
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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() { }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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 <code>true</code> should also
+     * initialized {@link #JPEGStreamMetadata}.
+     */
+    protected boolean writeAbbreviatedStream = false;
+
+    /**
+     * Stream metadata equivalent to a tables-only stream such as in
+     * the <code>JPEGTables</code>. Default value is <code>null</code>.
+     * This should be set by any subclass which sets
+     * {@link writeAbbreviatedStream} to <code>true</code>.
+     */
+    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 <code>pruneTables</code> is
+     * <code>true</code> in which case the nodes derived from the DHT and
+     * DQT marker segments are also removed.
+     *
+     * @param tree A <tt>javax_imageio_jpeg_image_1.0</tt> tree.
+     * @param pruneTables Whether to prune Huffman and quantization tables.
+     * @throws NullPointerException if <code>tree</code> is
+     * <code>null</code>.
+     * @throws IllegalArgumentException if <code>tree</code> 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<String> wantedNodes = new ArrayList<String>();
+        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<Node> 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<Node> getAllNodes(IIOMetadataNode root, List<Node> nodes) {
+        if(nodes == null) nodes = new ArrayList<Node>();
+
+        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 <code>ByteArrayOutputStream</code> which allows writing to an
+     * <code>ImageOutputStream</code>.
+     */
+    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<ImageWriter> 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();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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 <code>TIFFColorConverter</code>.
+     */
+    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 <code>result</code> array.
+     *
+     * @param r the red value.
+     * @param g the green value.
+     * @param b the blue value.
+     * @param result an array of <code>float</code>s containing three elements.
+     * @throws NullPointerException if <code>result</code> is
+     * <code>null</code>.
+     * @throws ArrayIndexOutOfBoundsException if
+     * <code>result.length&nbsp;&lt;&nbsp;3</code>.
+     */
+    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 <code>rgb</code> 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 <code>float</code>s containing three elements.
+     * @throws NullPointerException if <code>rgb</code> is
+     * <code>null</code>.
+     * @throws ArrayIndexOutOfBoundsException if
+     * <code>rgb.length&nbsp;&lt;&nbsp;3</code>.
+     */
+    public abstract void toRGB(float x0, float x1, float x2, float[] rgb);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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 <code>ImageWriter</code> calling this
+     * <code>TIFFCompressor</code>.
+     */
+    protected ImageWriter writer;
+
+    /**
+     * The <code>IIOMetadata</code> 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 <i>Compression</i> tag in the
+     * TIFF image metadata.
+     */
+    protected int compressionTagValue;
+
+    /**
+     * Whether the compression is lossless.
+     */
+    protected boolean isCompressionLossless;
+
+    /**
+     * The <code>ImageOutputStream</code> 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.
+     *
+     * <p>The parameters <code>compressionTagValue</code> and
+     * <code>isCompressionLossless</code> 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 <code>compressionTagValue</code> and
+     * <code>isCompressionLossless</code> parameters are ignored.</p>
+     *
+     * @param compressionType The name of the compression type.
+     * @param compressionTagValue The value to be assigned to the TIFF
+     * <i>Compression</i> tag in the TIFF image metadata; ignored if
+     * <code>compressionType</code> is a known type.
+     * @param isCompressionLossless Whether the compression is lossless;
+     * ignored if <code>compressionType</code> is a known type.
+     *
+     * @throws NullPointerException if <code>compressionType</code> is
+     * <code>null</code>.
+     * @throws IllegalArgumentException if <code>compressionTagValue</code> is
+     * less <code>1</code>.
+     */
+    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 <i>Compression</i> tag
+     * in the TIFF image metadata.
+     *
+     * @return The <i>Compression</i> 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 <code>ImageOutputStream</code> to be written.
+     *
+     * @param stream an <code>ImageOutputStream</code> to be written.
+     *
+     * @see #getStream
+     */
+    public void setStream(ImageOutputStream stream) {
+        this.stream = stream;
+    }
+
+    /**
+     * Returns the <code>ImageOutputStream</code> that will be written.
+     *
+     * @return an <code>ImageOutputStream</code>.
+     *
+     * @see #setStream(ImageOutputStream)
+     */
+    public ImageOutputStream getStream() {
+        return stream;
+    }
+
+    /**
+     * Sets the value of the <code>writer</code> field.
+     *
+     * @param writer the current <code>ImageWriter</code>.
+     *
+     * @see #getWriter()
+     */
+    public void setWriter(ImageWriter writer) {
+        this.writer = writer;
+    }
+
+    /**
+     * Returns the current <code>ImageWriter</code>.
+     *
+     * @return an <code>ImageWriter</code>.
+     *
+     * @see #setWriter(ImageWriter)
+     */
+    public ImageWriter getWriter() {
+        return this.writer;
+    }
+
+    /**
+     * Sets the value of the <code>metadata</code> field.
+     *
+     * @param metadata the <code>IIOMetadata</code> object for the
+     * image being written.
+     *
+     * @see #getMetadata()
+     */
+    public void setMetadata(IIOMetadata metadata) {
+        this.metadata = metadata;
+    }
+
+    /**
+     * Returns the current <code>IIOMetadata</code> object.
+     *
+     * @return the <code>IIOMetadata</code> 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
+     * <code>ImageOutputStream</code>.
+     *
+     * @param b an array of <code>byte</code>s containing the packed
+     * but uncompressed image data.
+     * @param off the starting offset of the data to be written in the
+     * array <code>b</code>.
+     * @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 <code>int</code>s 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 <code>TIFFCompressor</code>, 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;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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.
+ *
+ * <p> The mapping between source and destination Y coordinates is
+ * given by the equations:
+ *
+ * <pre>
+ * dx = (sx - sourceXOffset)/subsampleX + dstXOffset;
+ * dy = (sy - sourceYOffset)/subsampleY + dstYOffset;
+ * </pre>
+ *
+ * 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:
+ *
+ * <pre>
+ * sx = (dx - dstXOffset)*subsampleX + sourceXOffset;
+ * sy = (dy - dstYOffset)*subsampleY + sourceYOffset;
+ * </pre>
+ *
+ * <p> Decompressors may be written with various levels of complexity.
+ * The most complex decompressors will override the
+ * <code>decode</code> 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.
+ *
+ * <p> Less ambitious decompressors may override the
+ * <code>decodeRaw</code> method, which is responsible for
+ * decompressing the entire tile or strip into a byte array (or other
+ * appropriate datatype).  The default implementation of
+ * <code>decode</code> will perform all necessary setup of buffers,
+ * call <code>decodeRaw</code> 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
+ * <code>decodeRaw</code> in order to avoid making an extra copy.
+ *
+ * <p> Slightly more ambitious decompressors may override
+ * <code>decodeRaw</code>, but avoid writing pixels that will be
+ * discarded in the subsampling phase.
+ */
+public abstract class TIFFDecompressor {
+
+    /**
+     * The <code>ImageReader</code> calling this
+     * <code>TIFFDecompressor</code>.
+     */
+    protected ImageReader reader;
+
+    /**
+     * The <code>IIOMetadata</code> object containing metadata for the
+     * current image.
+     */
+    protected IIOMetadata metadata;
+
+    /**
+     * The value of the <code>PhotometricInterpretation</code> 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 <code>Compression</code> 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;
+
+    /**
+     * <code>true</code> if the image is encoded using separate planes.
+     */
+    protected boolean planar;
+
+    /**
+     * The value of the <code>SamplesPerPixel</code> tag.
+     */
+    protected int samplesPerPixel;
+
+    /**
+     * The value of the <code>BitsPerSample</code> tag.
+     *
+     */
+    protected int[] bitsPerSample;
+
+    /**
+     * The value of the <code>SampleFormat</code> 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 <code>ExtraSamples</code> 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 <code>ColorMap</code> tag.
+     *
+     */
+    protected char[] colorMap;
+
+    // Region of input stream containing the data
+
+    /**
+     * The <code>ImageInputStream</code> containing the TIFF source
+     * data.
+     */
+    protected ImageInputStream stream;
+
+    /**
+     * The offset in the source <code>ImageInputStream</code> of the
+     * start of the data to be decompressed.
+     */
+    protected long offset;
+
+    /**
+     * The number of bytes of data from the source
+     * <code>ImageInputStream</code> 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 <code>dstXOffset</code>
+     * and <code>subsampleX</code>, to map between horizontal source
+     * and destination pixel coordinates.
+     */
+    protected int sourceXOffset;
+
+    /**
+     * The horizontal destination offset used, along with
+     * <code>sourceXOffset</code> and <code>subsampleX</code>, 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 <code>dstYOffset</code>
+     * and <code>subsampleY</code>, to map between vertical source and
+     * destination pixel coordinates.
+     */
+    protected int sourceYOffset;
+
+    /**
+     * The vertical destination offset used, along with
+     * <code>sourceYOffset</code> and <code>subsampleY</code>, 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 <code>BufferedImage</code> for the <code>decodeRaw</code>
+     * 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 (<code>activeSrcMinX</code>,
+     * <code>activeSrcMinY</code>) is to be copied into the
+     * destination pixel at (<code>dstMinX</code>,
+     * <code>dstMinY</code>).
+     *
+     * <p> The pixels in the source region to be copied are
+     * those with X coordinates of the form <code>activeSrcMinX +
+     * k*subsampleX</code>, where <code>k</code> is an integer such
+     * that <code>0 &le; k &lt; dstWidth</code>.
+     */
+    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.
+     *
+     * <p> The pixels in the source region to be copied are
+     * those with Y coordinates of the form <code>activeSrcMinY +
+     * k*subsampleY</code>, where <code>k</code> is an integer such
+     * that <code>0 &le; k &lt; dstHeight</code>.
+     */
+    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.
+     *
+     * <p> The active source width will always be equal to
+     * <code>(dstWidth - 1)*subsampleX + 1</code>.
+     */
+    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.
+     *
+     * <p> The active source height will always be equal to
+     * <code>(dstHeight - 1)*subsampleY + 1</code>.
+     */
+    protected int activeSrcHeight;
+
+    /**
+     * A <code>TIFFColorConverter</code> object describing the color space of
+     * the encoded pixel data, or <code>null</code>.
+     */
+    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 <code>PixelInterleavedSampleModel</code> for use in creating
+     * an <code>ImageTypeSpecifier</code>.  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 <code>PixelInterleavedSampleModel</code>.
+     */
+    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 <code>ComponentColorModel</code> for use in creating
+     * an <code>ImageTypeSpecifier</code>.
+     */
+    // 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 <code>dataType</code>
+     * which must be one of the <code>DataBuffer</code> <code>TYPE</code>s.
+     */
+    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 <code>DataBuffer</code> 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 <code>DataBuffer</code>
+     * of the supplied <code>WritableRaster</code>.
+     */
+    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
+     * <code>ImageTypeSpecifier</code> suitable for decoding an image
+     * with the given parameters.
+     *
+     * @param photometricInterpretation the value of the
+     * <code>PhotometricInterpretation</code> field.
+     * @param compression the value of the <code>Compression</code> field.
+     * @param samplesPerPixel the value of the
+     * <code>SamplesPerPixel</code> field.
+     * @param bitsPerSample the value of the <code>BitsPerSample</code> field.
+     * @param sampleFormat the value of the <code>SampleFormat</code> field.
+     * @param extraSamples the value of the <code>ExtraSamples</code> field.
+     * @param colorMap the value of the <code>ColorMap</code> field.
+     *
+     * @return a suitable <code>ImageTypeSpecifier</code>, or
+     * <code>null</code> 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 <code>reader</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * method must be called prior to calling any of the decode
+     * methods.
+     *
+     * @param reader the current <code>ImageReader</code>.
+     */
+    public void setReader(ImageReader reader) {
+        this.reader = reader;
+    }
+
+    /**
+     * Sets the value of the <code>metadata</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * method must be called prior to calling any of the decode
+     * methods.
+     *
+     * @param metadata the <code>IIOMetadata</code> object for the
+     * image being read.
+     */
+    public void setMetadata(IIOMetadata metadata) {
+        this.metadata = metadata;
+    }
+
+    /**
+     * Sets the value of the <code>photometricInterpretation</code>
+     * field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>compression</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>planar</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * method must be called prior to calling any of the decode
+     * methods.
+     *
+     * @param planar <code>true</code> if the image to be decoded is
+     * stored in planar format.
+     */
+    public void setPlanar(boolean planar) {
+        this.planar = planar;
+    }
+
+    /**
+     * Sets the value of the <code>samplesPerPixel</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>bitsPerSample</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>sampleFormat</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>extraSamples</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>colorMap</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>char</code>s.
+     */
+    public void setColorMap(char[] colorMap) {
+        this.colorMap = colorMap == null ?
+            null : colorMap.clone();
+    }
+
+    /**
+     * Sets the value of the <code>stream</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * method must be called prior to calling any of the decode
+     * methods.
+     *
+     * @param stream the <code>ImageInputStream</code> to be read.
+     */
+    public void setStream(ImageInputStream stream) {
+        this.stream = stream;
+    }
+
+    /**
+     * Sets the value of the <code>offset</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>byteCount</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>srcMinX</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>srcMinY</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>srcWidth</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>srcHeight</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>sourceXOffset</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>dstXOffset</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>sourceYOffset</code>.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>dstYOffset</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>subsampleX</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * method must be called prior to calling any of the decode
+     * methods.
+     *
+     * @param subsampleX the horizontal subsampling factor.
+     *
+     * @throws IllegalArgumentException if <code>subsampleX</code> 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 <code>subsampleY</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * method must be called prior to calling any of the decode
+     * methods.
+     *
+     * @param subsampleY the vertical subsampling factor.
+     *
+     * @throws IllegalArgumentException if <code>subsampleY</code> 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 <code>sourceBands</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * method must be called prior to calling any of the decode
+     * methods.
+     *
+     * @param sourceBands an array of <code>int</code>s
+     * specifying the source bands to be read.
+     */
+    public void setSourceBands(int[] sourceBands) {
+        this.sourceBands = sourceBands == null ?
+            null : sourceBands.clone();
+    }
+
+    /**
+     * Sets the value of the <code>destinationBands</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * method must be called prior to calling any of the decode
+     * methods.
+     *
+     * @param destinationBands an array of <code>int</code>s
+     * 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 <code>image</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * method must be called prior to calling any of the decode
+     * methods.
+     *
+     * @param image the destination <code>BufferedImage</code>.
+     */
+    public void setImage(BufferedImage image) {
+        this.image = image;
+    }
+
+    /**
+     * Sets the value of the <code>dstMinX</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>dstMinY</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>dstWidth</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>dstHeight</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>activeSrcMinX</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>activeSrcMinY</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>activeSrcWidth</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>activeSrcHeight</code> field.
+     *
+     * <p> If this method is called, the <code>beginDecoding</code>
+     * 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 <code>TIFFColorConverter</code> object describing the color
+     * space of the encoded data in the input stream.  If no
+     * <code>TIFFColorConverter</code> is set, no conversion will be performed.
+     *
+     * @param colorConverter a <code>TIFFColorConverter</code> object, or
+     * <code>null</code>.
+     */
+    public void setColorConverter(TIFFColorConverter colorConverter) {
+        this.colorConverter = colorConverter;
+    }
+
+    /**
+     * Returns an <code>ImageTypeSpecifier</code> describing an image
+     * whose underlying data array has the same format as the raw
+     * source pixel data.
+     *
+     * @return an <code>ImageTypeSpecifier</code>.
+     */
+    public ImageTypeSpecifier getRawImageType() {
+        ImageTypeSpecifier its =
+            getRawImageTypeSpecifier(photometricInterpretation,
+                                     compression,
+                                     samplesPerPixel,
+                                     bitsPerSample,
+                                     sampleFormat,
+                                     extraSamples,
+                                     colorMap);
+        return its;
+    }
+
+    /**
+     * Creates a <code>BufferedImage</code> whose underlying data
+     * array will be suitable for holding the raw decoded output of
+     * the <code>decodeRaw</code> method.
+     *
+     * <p> The default implementation calls
+     * <code>getRawImageType</code>, and calls the resulting
+     * <code>ImageTypeSpecifier</code>'s
+     * <code>createBufferedImage</code> method.
+     *
+     * @return a <code>BufferedImage</code> whose underlying data
+     * array has the same format as the raw source pixel data, or
+     * <code>null</code> 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 <code>byte</code>
+     * array <code>b</code>, starting at the offset given by
+     * <code>dstOffset</code>.  Each pixel occupies
+     * <code>bitsPerPixel</code> bits, with no padding between pixels.
+     * Scanlines are separated by <code>scanlineStride</code>
+     * <code>byte</code>s.
+     *
+     * @param b a <code>byte</code> array to be written.
+     * @param dstOffset the starting offset in <code>b</code> to be
+     * written.
+     * @param bitsPerPixel the number of bits for each pixel.
+     * @param scanlineStride the number of <code>byte</code>s to
+     * advance between that starting pixels of each scanline.
+     *
+     * @throws IOException if an error occurs reading from the source
+     * <code>ImageInputStream</code>.
+     */
+    public abstract void decodeRaw(byte[] b,
+                                   int dstOffset,
+                                   int bitsPerPixel,
+                                   int scanlineStride) throws IOException;
+
+    /**
+     * Decodes the source data into the provided <code>short</code>
+     * array <code>s</code>, starting at the offset given by
+     * <code>dstOffset</code>.  Each pixel occupies
+     * <code>bitsPerPixel</code> bits, with no padding between pixels.
+     * Scanlines are separated by <code>scanlineStride</code>
+     * <code>short</code>s
+     *
+     * <p> The default implementation calls <code>decodeRaw(byte[] b,
+     * ...)</code> and copies the resulting data into <code>s</code>.
+     *
+     * @param s a <code>short</code> array to be written.
+     * @param dstOffset the starting offset in <code>s</code> to be
+     * written.
+     * @param bitsPerPixel the number of bits for each pixel.
+     * @param scanlineStride the number of <code>short</code>s to
+     * advance between that starting pixels of each scanline.
+     *
+     * @throws IOException if an error occurs reading from the source
+     * <code>ImageInputStream</code>.
+     */
+    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 <code>int</code>
+     * array <code>i</code>, starting at the offset given by
+     * <code>dstOffset</code>.  Each pixel occupies
+     * <code>bitsPerPixel</code> bits, with no padding between pixels.
+     * Scanlines are separated by <code>scanlineStride</code>
+     * <code>int</code>s.
+     *
+     * <p> The default implementation calls <code>decodeRaw(byte[] b,
+     * ...)</code> and copies the resulting data into <code>i</code>.
+     *
+     * @param i an <code>int</code> array to be written.
+     * @param dstOffset the starting offset in <code>i</code> to be
+     * written.
+     * @param bitsPerPixel the number of bits for each pixel.
+     * @param scanlineStride the number of <code>int</code>s to
+     * advance between that starting pixels of each scanline.
+     *
+     * @throws IOException if an error occurs reading from the source
+     * <code>ImageInputStream</code>.
+     */
+    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 <code>float</code>
+     * array <code>f</code>, starting at the offset given by
+     * <code>dstOffset</code>.  Each pixel occupies
+     * <code>bitsPerPixel</code> bits, with no padding between pixels.
+     * Scanlines are separated by <code>scanlineStride</code>
+     * <code>float</code>s.
+     *
+     * <p> The default implementation calls <code>decodeRaw(byte[] b,
+     * ...)</code> and copies the resulting data into <code>f</code>.
+     *
+     * @param f a <code>float</code> array to be written.
+     * @param dstOffset the starting offset in <code>f</code> to be
+     * written.
+     * @param bitsPerPixel the number of bits for each pixel.
+     * @param scanlineStride the number of <code>float</code>s to
+     * advance between that starting pixels of each scanline.
+     *
+     * @throws IOException if an error occurs reading from the source
+     * <code>ImageInputStream</code>.
+     */
+    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 <code>double</code>
+     * array <code>f</code>, starting at the offset given by
+     * <code>dstOffset</code>.  Each pixel occupies
+     * <code>bitsPerPixel</code> bits, with no padding between pixels.
+     * Scanlines are separated by <code>scanlineStride</code>
+     * <code>double</code>s.
+     *
+     * <p> The default implementation calls <code>decodeRaw(byte[] b,
+     * ...)</code> and copies the resulting data into <code>f</code>.
+     *
+     * @param f a <code>double</code> array to be written.
+     * @param dstOffset the starting offset in <code>f</code> to be
+     * written.
+     * @param bitsPerPixel the number of bits for each pixel.
+     * @param scanlineStride the number of <code>double</code>s to
+     * advance between that starting pixels of each scanline.
+     *
+     * @throws IOException if an error occurs reading from the source
+     * <code>ImageInputStream</code>.
+     */
+    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
+     * <code>decode</code> 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.
+     *
+     * <p> The default implementation computes tables used by the
+     * <code>decode</code> method to rescale components to different
+     * bit depths.  Thus, if this method is overridden, it is
+     * important for the subclass method to call <code>super()</code>,
+     * unless it overrides <code>decode</code> 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
+     * <code>ImageInputStream</code> <code>stream</code>, at offset
+     * <code>offset</code>, and continuing for <code>byteCount</code>
+     * bytes) into the output <code>BufferedImage</code>
+     * <code>image</code>.
+     *
+     * <p> The default implementation analyzes the destination image
+     * to determine if it is suitable as the destination for the
+     * <code>decodeRaw</code> method.  If not, a suitable image is
+     * created.  Next, <code>decodeRaw</code> is called to perform the
+     * actual decoding, and the results are copied into the
+     * destination image if necessary.  Subsampling and offsetting are
+     * performed automatically.
+     *
+     * <p> The precise responsibilities of this routine are as
+     * follows.  The input bit stream is defined by the instance
+     * variables <code>stream</code>, <code>offset</code>, and
+     * <code>byteCount</code>.  These bits contain the data for the
+     * region of the source image defined by <code>srcMinX</code>,
+     * <code>srcMinY</code>, <code>srcWidth</code>, and
+     * <code>srcHeight</code>.
+     *
+     * <p> The source data is required to be subsampling, starting at
+     * the <code>sourceXOffset</code>th column and including
+     * every <code>subsampleX</code>th pixel thereafter (and similarly
+     * for <code>sourceYOffset</code> and
+     * <code>subsampleY</code>).
+     *
+     * <p> Pixels are copied into the destination with an addition shift of
+     * (<code>dstXOffset</code>, <code>dstYOffset</code>).  The complete
+     * set of formulas relating the source and destination coordinate spaces
+     * are:
+     *
+     * <pre>
+     * dx = (sx - sourceXOffset)/subsampleX + dstXOffset;
+     * dy = (sy - sourceYOffset)/subsampleY + dstYOffset;
+     * </pre>
+     *
+     * Only source pixels such that <code>(sx - sourceXOffset) %
+     * subsampleX == 0</code> and <code>(sy - sourceYOffset) %
+     * subsampleY == 0</code> are copied.
+     *
+     * <p> The inverse mapping, from destination to source coordinates,
+     * is one-to-one:
+     *
+     * <pre>
+     * sx = (dx - dstXOffset)*subsampleX + sourceXOffset;
+     * sy = (dy - dstYOffset)*subsampleY + sourceYOffset;
+     * </pre>
+     *
+     * <p> The region of the destination image to be updated is given
+     * by the instance variables <code>dstMinX</code>,
+     * <code>dstMinY</code>, <code>dstWidth</code>, and
+     * <code>dstHeight</code>.
+     *
+     * <p> 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 <code>activeSrcMinX</code>,
+     * <code>activeSrcMinY</code>, <code>activeSrcWidth</code> and
+     * <code>activeSrcHeight</code>.  Thus, the source pixel at
+     * (<code>activeSrcMinX</code>, <code>activeSrcMinY</code>) will
+     * map to the destination pixel (<code>dstMinX</code>,
+     * <code>dstMinY</code>).
+     *
+     * <p> The sequence of source bands given by
+     * <code>sourceBands</code> are to be copied into the sequence of
+     * bands in the destination given by
+     * <code>destinationBands</code>.
+     *
+     * <p> Some standard tag information is provided the instance
+     * variables <code>photometricInterpretation</code>,
+     * <code>compression</code>, <code>samplesPerPixel</code>,
+     * <code>bitsPerSample</code>, <code>sampleFormat</code>,
+     * <code>extraSamples</code>, and <code>colorMap</code>.
+     *
+     * <p> 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 <code>decodeRaw</code> and/or
+     * <code>getRawImageType</code> methods.
+     *
+     * @exception IOException if an error occurs in
+     * <code>decodeRaw</code>.
+     */
+    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;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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<srcWidth*samplesPerPixel; i++) {
+                    buf[count] += buf[count - samplesPerPixel];
+                    count++;
+                }
+            }
+        }
+
+        if(bytesPerRow != scanlineStride) {
+            int off = 0;
+            for (int y = 0; y < srcHeight; y++) {
+                System.arraycopy(buf, off, b, dstOffset, bytesPerRow);
+                off += bytesPerRow;
+                dstOffset += scanlineStride;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflater.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,120 @@
+/*
+ * 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 java.util.zip.Deflater;
+import javax.imageio.ImageWriteParam;
+
+/**
+ * Compressor superclass for Deflate and ZLib compression.
+ */
+public class TIFFDeflater extends TIFFCompressor {
+
+    Deflater deflater;
+    int predictor;
+
+    public TIFFDeflater(String compressionType,
+                        int compressionTagValue,
+                        ImageWriteParam param,
+                        int predictorValue) {
+        super(compressionType, compressionTagValue, true);
+
+        this.predictor = predictorValue;
+
+        // Set the deflate level.
+        int deflateLevel;
+        if(param != null &&
+           param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {
+            float quality = param.getCompressionQuality();
+            deflateLevel = (int)(1 + 8*quality);
+        } else {
+            deflateLevel = Deflater.DEFAULT_COMPRESSION;
+        }
+
+        this.deflater = new Deflater(deflateLevel);
+    }
+
+    public int encode(byte[] b, int off,
+                      int width, int height,
+                      int[] bitsPerSample,
+                      int scanlineStride) throws IOException {
+
+        int inputSize = height*scanlineStride;
+        int blocks = (inputSize + 32767)/32768;
+
+        // Worst case for Zlib deflate is input size + 5 bytes per 32k
+        // block, plus 6 header bytes
+        byte[] compData = new byte[inputSize + 5*blocks + 6];
+
+        int numCompressedBytes = 0;
+        if(predictor == BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) {
+            int samplesPerPixel = bitsPerSample.length;
+            int bitsPerPixel = 0;
+            for (int i = 0; i < samplesPerPixel; i++) {
+                bitsPerPixel += bitsPerSample[i];
+            }
+            int bytesPerRow = (bitsPerPixel*width + 7)/8;
+            byte[] rowBuf = new byte[bytesPerRow];
+
+            int maxRow = height - 1;
+            for(int i = 0; i < height; i++) {
+                // 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];
+                }
+
+                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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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<Object> objectMinValue = null;
+    Comparable<Object> 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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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 <code>TIFFCompressor</code> 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)
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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 <code>metadata</code> field.
+     *
+     * <p> The implementation in this class also sets local options
+     * from the FILL_ORDER field if it exists.</p>
+     *
+     * @param metadata the <code>IIOMetadata</code> 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 <code>maxOffset</code> or offset of first pixel
+     * different from pixel at <code>bitOffset</code>.
+     */
+    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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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.
+     * <pre>
+     * 1 = MSB-to-LSB
+     * 2 = LSB-to-MSB (flipped)
+     * </pre>
+     */
+    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);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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 <code>Node</code> representation of a <code>TIFFField</code>
+ * 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<TIFFTagSet> 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);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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<TIFFTagSet> tagSets) {
+        Iterator<TIFFTagSet> 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<TIFFTagSet> tagSets) {
+        Iterator<TIFFTagSet> 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<TIFFTagSet> tagSets, TIFFTag parentTag) {
+        super(tagSets.toArray(new TIFFTagSet[tagSets.size()]),
+              parentTag);
+    }
+
+    public TIFFIFD(List<TIFFTagSet> tagSets) {
+        this(tagSets, null);
+    }
+
+    public List<TIFFTagSet> getTagSetList() {
+        return Arrays.asList(getTagSets());
+    }
+
+    /**
+     * Returns an <code>Iterator</code> 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<TIFFField> iterator() {
+        return Arrays.asList(getTIFFFields()).iterator();
+    }
+
+    /**
+     * Read the value of a field. The <code>data</code> 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<String> 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<TIFFField> 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<TIFFField> 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<TIFFTagSet> tagSetList = getTagSetList();
+
+        List<Object> 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<TIFFTagSet> tagSets = new ArrayList<TIFFTagSet>(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<TIFFField> 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 <code>TIFFIFD</code> wherein all fields from the
+     * <code>BaselineTIFFTagSet</code> 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<TIFFTagSet> 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<Integer> baselineTagNumbers = baselineTagSet.getTagNumbers();
+
+        // Iterate over the fields in this IFD.
+        Iterator<TIFFField> 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;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/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<TIFFTagSet> tagSets;
+
+    TIFFIFD rootIFD;
+
+    public TIFFImageMetadata(List<TIFFTagSet> 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<TIFFTagSet> tagSets = ifd.getTagSetList();
+        if (tagSets.size() > 0) {
+            Iterator<TIFFTagSet> 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<TIFFField> 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<Integer> intList = new ArrayList<Integer>();
+        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<Integer> intList = new ArrayList<Integer>();
+        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<Integer,char[]> 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<Map.Entry<Integer,char[]>> paletteIter
+                                = palette.entrySet().iterator();
+                            while(paletteIter.hasNext()) {
+                                Map.Entry<Integer,char[]> 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 &&