8143342: Integrate Java Image I/O support for TIFF per JEP 262
authorbpb
Mon, 23 Nov 2015 12:26:12 -0800
changeset 34416 68c0d866db5d
parent 34415 098d54b4051d
child 34417 57a3863abbb4
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
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/ImageUtil.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleCMYKColorSpace.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleRenderedImage.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SingleTileRenderedImage.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/iio-plugin.properties
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFAttrInfo.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFBaseJPEGCompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFCIELabColorConverter.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFColorConverter.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFCompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDecompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflateCompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflateDecompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflater.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFElementInfo.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFExifJPEGCompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxCompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxDecompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFieldNode.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFIFD.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadata.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormat.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormatResources.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReader.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReaderSpi.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriteParam.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriter.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriterSpi.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGCompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGDecompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBCompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBDecompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWCompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWDecompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWUtil.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFMetadataFormat.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullCompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullDecompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFOldJPEGDecompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsCompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsDecompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsUtil.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRLECompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRenderedImage.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadata.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormat.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormatResources.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT4Compressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT6Compressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrColorConverter.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrDecompressor.java
jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFZLibCompressor.java
jdk/src/java.desktop/share/classes/javax/imageio/metadata/doc-files/tiff_metadata.html
jdk/src/java.desktop/share/classes/javax/imageio/metadata/package.html
jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/BaselineTIFFTagSet.java
jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/ExifGPSTagSet.java
jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/ExifInteroperabilityTagSet.java
jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/ExifParentTIFFTagSet.java
jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/ExifTIFFTagSet.java
jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/FaxTIFFTagSet.java
jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/GeoTIFFTagSet.java
jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFDirectory.java
jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFField.java
jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFImageReadParam.java
jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFTag.java
jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFTagSet.java
jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/package.html
jdk/src/java.desktop/share/classes/javax/imageio/spi/IIORegistry.java
--- a/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/ImageUtil.java	Mon Nov 23 10:00:50 2015 -0800
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/ImageUtil.java	Mon Nov 23 12:26:12 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -29,6 +29,7 @@
 import java.awt.Rectangle;
 import java.awt.Transparency;
 import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
 import java.awt.image.BufferedImage;
 import java.awt.image.ColorModel;
 import java.awt.image.ComponentColorModel;
@@ -47,64 +48,15 @@
 import java.awt.image.SinglePixelPackedSampleModel;
 import java.awt.image.WritableRaster;
 import java.util.Arrays;
-
-//import javax.imageio.ImageTypeSpecifier;
-
+import java.util.Iterator;
 import javax.imageio.IIOException;
 import javax.imageio.IIOImage;
+import javax.imageio.ImageReadParam;
 import javax.imageio.ImageTypeSpecifier;
 import javax.imageio.ImageWriter;
 import javax.imageio.spi.ImageWriterSpi;
 
 public class ImageUtil {
-    /* XXX testing only
-    public static void main(String[] args) {
-        ImageTypeSpecifier bilevel =
-            ImageTypeSpecifier.createIndexed(new byte[] {(byte)0, (byte)255},
-                                             new byte[] {(byte)0, (byte)255},
-                                             new byte[] {(byte)0, (byte)255},
-                                             null, 1,
-                                             DataBuffer.TYPE_BYTE);
-        ImageTypeSpecifier gray =
-            ImageTypeSpecifier.createGrayscale(8, DataBuffer.TYPE_BYTE, false);
-        ImageTypeSpecifier grayAlpha =
-            ImageTypeSpecifier.createGrayscale(8, DataBuffer.TYPE_BYTE, false,
-                                               false);
-        ImageTypeSpecifier rgb =
-            ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB),
-                                                 new int[] {0, 1, 2},
-                                                 DataBuffer.TYPE_BYTE,
-                                                 false,
-                                                 false);
-        ImageTypeSpecifier rgba =
-            ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB),
-                                                 new int[] {0, 1, 2, 3},
-                                                 DataBuffer.TYPE_BYTE,
-                                                 true,
-                                                 false);
-        ImageTypeSpecifier packed =
-            ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
-                                            0xff000000,
-                                            0x00ff0000,
-                                            0x0000ff00,
-                                            0x000000ff,
-                                            DataBuffer.TYPE_BYTE,
-                                            false);
-
-        SampleModel bandedSM =
-            new java.awt.image.BandedSampleModel(DataBuffer.TYPE_BYTE,
-                                                 1, 1, 15);
-
-        System.out.println(createColorModel(bilevel.getSampleModel()));
-        System.out.println(createColorModel(gray.getSampleModel()));
-        System.out.println(createColorModel(grayAlpha.getSampleModel()));
-        System.out.println(createColorModel(rgb.getSampleModel()));
-        System.out.println(createColorModel(rgba.getSampleModel()));
-        System.out.println(createColorModel(packed.getSampleModel()));
-        System.out.println(createColorModel(bandedSM));
-    }
-    */
-
     /**
      * Creates a <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/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleCMYKColorSpace.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.common;
+
+import java.awt.color.ColorSpace;
+
+/**
+ * Singleton class representing a simple, mathematically defined CMYK
+ * color space.
+ */
+public final class SimpleCMYKColorSpace extends ColorSpace {
+    private static final long serialVersionUID = 666L; // XXX Revise UID value
+
+    private static ColorSpace theInstance = null;
+    private ColorSpace csRGB;
+
+    /** The exponent for gamma correction. */
+    private static final double power1 = 1.0 / 2.4;
+
+    public static final synchronized ColorSpace getInstance() {
+        if(theInstance == null) {
+            theInstance = new SimpleCMYKColorSpace();
+        }
+        return theInstance;
+    }
+
+    private SimpleCMYKColorSpace() {
+        super(TYPE_CMYK, 4);
+        csRGB = ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
+    }
+
+    public boolean equals(Object o) {
+        return o != null && o instanceof SimpleCMYKColorSpace;
+    }
+
+    public int hashCode() {
+        return theInstance.hashCode();
+    }
+
+    public float[] toRGB(float[] colorvalue) {
+        float C = colorvalue[0];
+        float M = colorvalue[1];
+        float Y = colorvalue[2];
+        float K = colorvalue[3];
+
+        float K1 = 1.0F - K;
+
+        // Convert from CMYK to linear RGB.
+        float[] rgbvalue = new float[] {K1*(1.0F - C),
+                                        K1*(1.0F - M),
+                                        K1*(1.0F - Y)};
+
+        // Convert from linear RGB to sRGB.
+        for (int i = 0; i < 3; i++) {
+            float v = rgbvalue[i];
+
+            if (v < 0.0F) v = 0.0F;
+
+            if (v < 0.0031308F) {
+                rgbvalue[i] = 12.92F * v;
+            } else {
+                if (v > 1.0F) v = 1.0F;
+
+                rgbvalue[i] = (float)(1.055 * Math.pow(v, power1) - 0.055);
+            }
+        }
+
+        return rgbvalue;
+    }
+
+    public float[] fromRGB(float[] rgbvalue) {
+        // Convert from sRGB to linear RGB.
+        for (int i = 0; i < 3; i++) {
+            if (rgbvalue[i] < 0.040449936F) {
+                rgbvalue[i] /= 12.92F;
+            } else {
+                rgbvalue[i] =
+                (float)(Math.pow((rgbvalue[i] + 0.055)/1.055, 2.4));
+            }
+        }
+
+        // Convert from linear RGB to CMYK.
+        float C = 1.0F - rgbvalue[0];
+        float M = 1.0F - rgbvalue[1];
+        float Y = 1.0F - rgbvalue[2];
+        float K = Math.min(C, Math.min(M, Y));
+
+        // If K == 1.0F, then C = M = Y = 1.0F.
+        if(K != 1.0F) {
+            float K1 = 1.0F - K;
+
+            C = (C - K)/K1;
+            M = (M - K)/K1;
+            Y = (Y - K)/K1;
+        } else {
+            C = M = Y = 0.0F;
+        }
+
+        return new float[] {C, M, Y, K};
+    }
+
+    public float[] toCIEXYZ(float[] colorvalue) {
+        return csRGB.toCIEXYZ(toRGB(colorvalue));
+    }
+
+    public float[] fromCIEXYZ(float[] xyzvalue) {
+        return fromRGB(csRGB.fromCIEXYZ(xyzvalue));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SimpleRenderedImage.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,571 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.common;
+
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.image.RenderedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.SampleModel;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Vector;
+
+public abstract class SimpleRenderedImage implements RenderedImage {
+    /** The X coordinate of the image's upper-left pixel. */
+    protected int minX;
+
+    /** The Y coordinate of the image's upper-left pixel. */
+    protected int minY;
+
+    /** The image's width in pixels. */
+    protected int width;
+
+    /** The image's height in pixels. */
+    protected int height;
+
+    /** The width of a tile. */
+    protected int tileWidth;
+
+    /** The height of a tile. */
+    protected int tileHeight;
+
+    /** The X coordinate of the upper-left pixel of tile (0, 0). */
+    protected int tileGridXOffset = 0;
+
+    /** The Y coordinate of the upper-left pixel of tile (0, 0). */
+    protected int tileGridYOffset = 0;
+
+    /** The image's SampleModel. */
+    protected SampleModel sampleModel;
+
+    /** The image's ColorModel. */
+    protected ColorModel colorModel;
+
+    /** The image's sources, stored in a Vector. */
+    protected Vector<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/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/SingleTileRenderedImage.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.imageio.plugins.common;
+
+import java.awt.image.ColorModel;
+import java.awt.image.Raster;
+
+/**
+ * A simple class that provides RenderedImage functionality
+ * given a Raster and a ColorModel.
+ */
+public class SingleTileRenderedImage extends SimpleRenderedImage {
+
+    Raster ras;
+
+    /**
+     * Constructs a SingleTileRenderedImage based on a Raster
+     * and a ColorModel.
+     *
+     * @param ras A Raster that will define tile (0, 0) of the image.
+     * @param cm A ColorModel that will serve as the image's
+     *           ColorModel.
+     */
+    public SingleTileRenderedImage(Raster ras, ColorModel colorModel) {
+        this.ras = ras;
+
+        this.tileGridXOffset = this.minX = ras.getMinX();
+        this.tileGridYOffset = this.minY = ras.getMinY();
+        this.tileWidth = this.width = ras.getWidth();
+        this.tileHeight = this.height = ras.getHeight();
+        this.sampleModel = ras.getSampleModel();
+        this.colorModel = colorModel;
+    }
+
+    /**
+     * Returns the image's Raster as tile (0, 0).
+     */
+    public Raster getTile(int tileX, int tileY) {
+        if (tileX != 0 || tileY != 0) {
+            throw new IllegalArgumentException("tileX != 0 || tileY != 0");
+        }
+        return ras;
+    }
+}
--- a/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/iio-plugin.properties	Mon Nov 23 10:00:50 2015 -0800
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/common/iio-plugin.properties	Mon Nov 23 12:26:12 2015 -0800
@@ -8,7 +8,7 @@
 # Common properties
 ImageUtil0=The supplied Raster does not represent a binary data set.
 ImageUtil1=The provided sample model is null.
-SimpleRenderedImage0=The provided region doesn't intersect with the image bounds.
+ImageUtil2=The provided image cannot be encoded using: 
 GetNumImages0=Input has not been set.
 GetNumImages1=seekForwardOnly and allowSearch cannot both be true.
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFAttrInfo.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.metadata.IIOMetadataFormat;
+
+public class TIFFAttrInfo {
+    int valueType = IIOMetadataFormat.VALUE_ARBITRARY;
+    int dataType;
+    boolean isRequired = false;
+    int listMinLength = 0;
+    int listMaxLength = Integer.MAX_VALUE;
+
+    public TIFFAttrInfo() { }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFBaseJPEGCompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,444 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.awt.Point;
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.PixelInterleavedSampleModel;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+import java.awt.image.WritableRaster;
+import java.io.IOException;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Iterator;
+import javax.imageio.IIOException;
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.metadata.IIOInvalidTreeException;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.spi.ImageWriterSpi;
+import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
+import javax.imageio.stream.ImageOutputStream;
+import javax.imageio.stream.MemoryCacheImageOutputStream;
+import org.w3c.dom.Node;
+
+/**
+ * Base class for all possible forms of JPEG compression in TIFF.
+ */
+public abstract class TIFFBaseJPEGCompressor extends TIFFCompressor {
+
+    // Stream metadata format.
+    protected static final String STREAM_METADATA_NAME =
+        "javax_imageio_jpeg_stream_1.0";
+
+    // Image metadata format.
+    protected static final String IMAGE_METADATA_NAME =
+        "javax_imageio_jpeg_image_1.0";
+
+    // ImageWriteParam passed in.
+    private ImageWriteParam param = null;
+
+    /**
+     * ImageWriteParam for JPEG writer.
+     * May be initialized by {@link #initJPEGWriter()}.
+     */
+    protected JPEGImageWriteParam JPEGParam = null;
+
+    /**
+     * The JPEG writer.
+     * May be initialized by {@link #initJPEGWriter()}.
+     */
+    protected ImageWriter JPEGWriter = null;
+
+    /**
+     * Whether to write abbreviated JPEG streams (default == false).
+     * A subclass which sets this to <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/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFCIELabColorConverter.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+public class TIFFCIELabColorConverter extends TIFFColorConverter {
+
+    // XYZ coordinate or reference white (CIE D65)
+    private static final float Xn = 95.047f;
+    private static final float Yn = 100.0f;
+    private static final float Zn = 108.883f;
+
+    private static final float THRESHOLD = (float)Math.pow(0.008856, 1.0/3.0);
+
+    public TIFFCIELabColorConverter() {}
+
+
+    private float clamp(float x) {
+        if (x < 0.0f) {
+            return 0.0f;
+        } else if (x > 100.0f) {
+            return 255.0f;
+        } else {
+            return x*(255.0f/100.0f);
+        }
+    }
+
+    private float clamp2(float x) {
+        if (x < 0.0f) {
+            return 0.0f;
+        } else if (x > 255.0f) {
+            return 255.0f;
+        } else {
+            return x;
+        }
+    }
+
+    public void fromRGB(float r, float g, float b, float[] result) {
+        float X =  0.412453f*r + 0.357580f*g + 0.180423f*b;
+        float Y =  0.212671f*r + 0.715160f*g + 0.072169f*b;
+        float Z =  0.019334f*r + 0.119193f*g + 0.950227f*b;
+
+        float YYn = Y/Yn;
+        float XXn = X/Xn;
+        float ZZn = Z/Zn;
+
+        if (YYn < 0.008856f) {
+            YYn = 7.787f*YYn + 16.0f/116.0f;
+        } else {
+            YYn = (float)Math.pow(YYn, 1.0/3.0);
+        }
+
+        if (XXn < 0.008856f) {
+            XXn = 7.787f*XXn + 16.0f/116.0f;
+        } else {
+            XXn = (float)Math.pow(XXn, 1.0/3.0);
+        }
+
+        if (ZZn < 0.008856f) {
+            ZZn = 7.787f*ZZn + 16.0f/116.0f;
+        } else {
+            ZZn = (float)Math.pow(ZZn, 1.0/3.0);
+        }
+
+        float LStar = 116.0f*YYn - 16.0f;
+        float aStar = 500.0f*(XXn - YYn);
+        float bStar = 200.0f*(YYn - ZZn);
+
+        LStar *= 255.0f/100.0f;
+        if (aStar < 0.0f) {
+            aStar += 256.0f;
+        }
+        if (bStar < 0.0f) {
+            bStar += 256.0f;
+        }
+
+        result[0] = clamp2(LStar);
+        result[1] = clamp2(aStar);
+        result[2] = clamp2(bStar);
+    }
+
+    public void toRGB(float x0, float x1, float x2, float[] rgb) {
+        float LStar = x0*100.0f/255.0f;
+        float aStar = (x1 > 128.0f) ? (x1 - 256.0f) : x1;
+        float bStar = (x2 > 128.0f) ? (x2 - 256.0f) : x2;
+
+        float YYn; // Y/Yn
+        float fY; // 'F' value for Y
+
+        if (LStar < 8.0f) {
+            YYn = LStar/903.3f;
+            fY = 7.787f*YYn + 16.0f/116.0f;
+        } else {
+            float YYn_cubeRoot = (LStar + 16.0f)/116.0f;
+            YYn = YYn_cubeRoot*YYn_cubeRoot*YYn_cubeRoot;
+            fY = (float)Math.pow(YYn, 1.0/3.0);
+        }
+        float Y = YYn*Yn;
+
+        float fX = fY + (aStar/500.0f);
+        float X;
+        if (fX <= THRESHOLD) {
+            X = Xn*(fX - 16.0f/116.0f)/7.787f;
+        } else {
+            X = Xn*fX*fX*fX;
+        }
+
+        float fZ = fY - bStar/200.0f;
+        float Z;
+        if (fZ <= THRESHOLD) {
+            Z = Zn*(fZ - 16.0f/116.0f)/7.787f;
+        } else {
+            Z = Zn*fZ*fZ*fZ;
+        }
+
+        float R =  3.240479f*X - 1.537150f*Y - 0.498535f*Z;
+        float G = -0.969256f*X + 1.875992f*Y + 0.041556f*Z;
+        float B =  0.055648f*X - 0.204043f*Y + 1.057311f*Z;
+
+        rgb[0] = clamp(R);
+        rgb[1] = clamp(G);
+        rgb[2] = clamp(B);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFColorConverter.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+/**
+ * An abstract class that performs simple color conversion on 3-banded source
+ * images, for use with the TIFF Image I/O plug-in.
+ */
+public abstract class TIFFColorConverter {
+
+    /**
+     * Constructs an instance of a <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/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFCompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.io.IOException;
+import javax.imageio.ImageWriter;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.stream.ImageOutputStream;
+
+/**
+ * An abstract superclass for pluggable TIFF compressors.
+ */
+public abstract class TIFFCompressor {
+
+    /**
+     * The <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/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDecompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,2816 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.awt.Rectangle;
+import java.awt.Transparency;
+import java.awt.color.ColorSpace;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.ComponentSampleModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferDouble;
+import java.awt.image.DataBufferFloat;
+import java.awt.image.DataBufferInt;
+import java.awt.image.DataBufferShort;
+import java.awt.image.DataBufferUShort;
+import java.awt.image.MultiPixelPackedSampleModel;
+import java.awt.image.PixelInterleavedSampleModel;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+import java.awt.image.SinglePixelPackedSampleModel;
+import java.awt.image.WritableRaster;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import javax.imageio.IIOException;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.MemoryCacheImageInputStream;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import com.sun.imageio.plugins.common.ImageUtil;
+import com.sun.imageio.plugins.common.BogusColorSpace;
+import com.sun.imageio.plugins.common.SimpleCMYKColorSpace;
+
+/**
+ * A class defining a pluggable TIFF decompressor.
+ *
+ * <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/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflateCompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.ImageWriteParam;
+
+/**
+ * Compressor for Deflate compression.
+ */
+public class TIFFDeflateCompressor extends TIFFDeflater {
+    public TIFFDeflateCompressor(ImageWriteParam param, int predictor) {
+        super("Deflate", BaselineTIFFTagSet.COMPRESSION_DEFLATE, param,
+              predictor);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFDeflateDecompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.io.IOException;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+import javax.imageio.IIOException;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+
+public class TIFFDeflateDecompressor extends TIFFDecompressor {
+
+    Inflater inflater = null;
+    int predictor;
+
+    public TIFFDeflateDecompressor(int predictor) throws IIOException {
+        inflater = new Inflater();
+
+        if (predictor != BaselineTIFFTagSet.PREDICTOR_NONE &&
+            predictor !=
+            BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) {
+            throw new IIOException("Illegal value for Predictor in " +
+                                   "TIFF file");
+        }
+
+        this.predictor = predictor;
+    }
+
+    public synchronized void decodeRaw(byte[] b,
+                                       int dstOffset,
+                                       int bitsPerPixel,
+                                       int scanlineStride) throws IOException {
+
+        // Check bitsPerSample.
+        if (predictor ==
+            BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) {
+            int len = bitsPerSample.length;
+            for(int i = 0; i < len; i++) {
+                if(bitsPerSample[i] != 8) {
+                    throw new IIOException
+                        (bitsPerSample[i] + "-bit samples "+
+                         "are not supported for Horizontal "+
+                         "differencing Predictor");
+                }
+            }
+        }
+
+        // Seek to current tile data offset.
+        stream.seek(offset);
+
+        // Read the deflated data.
+        byte[] srcData = new byte[byteCount];
+        stream.readFully(srcData);
+
+        int bytesPerRow = (srcWidth*bitsPerPixel + 7)/8;
+        byte[] buf;
+        int bufOffset;
+        if(bytesPerRow == scanlineStride) {
+            buf = b;
+            bufOffset = dstOffset;
+        } else {
+            buf = new byte[bytesPerRow*srcHeight];
+            bufOffset = 0;
+        }
+
+        // Set the input to the Inflater.
+        inflater.setInput(srcData);
+
+        // Inflate the data.
+        try {
+            inflater.inflate(buf, bufOffset, bytesPerRow*srcHeight);
+        } catch(DataFormatException dfe) {
+            throw new IIOException("Error inflating data",
+                                   dfe);
+        }
+
+        // Reset the Inflater.
+        inflater.reset();
+
+        if (predictor ==
+            BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) {
+
+            for (int j = 0; j < srcHeight; j++) {
+                int count = bufOffset + samplesPerPixel * (j * srcWidth + 1);
+                for (int i=samplesPerPixel; i<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/jdk/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/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFElementInfo.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.metadata.IIOMetadataFormat;
+
+class TIFFElementInfo {
+    String[] childNames;
+    String[] attributeNames;
+    int childPolicy;
+
+    int minChildren = 0;
+    int maxChildren = Integer.MAX_VALUE;
+
+    int objectValueType = IIOMetadataFormat.VALUE_NONE;
+    Class<?> objectClass = null;
+    Object objectDefaultValue = null;
+    Object[] objectEnumerations = null;
+    Comparable<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/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFExifJPEGCompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.ImageWriteParam;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+
+/**
+ * A <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/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxCompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,513 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFField;
+
+/**
+ *
+ */
+abstract class TIFFFaxCompressor extends TIFFCompressor {
+
+     /**
+     * The CCITT numerical definition of white.
+     */
+    protected static final int WHITE = 0;
+
+    /**
+     * The CCITT numerical definition of black.
+     */
+    protected static final int BLACK = 1;
+
+    // --- Begin tables for CCITT compression ---
+
+    protected static final byte[] byteTable = new byte[] {
+        8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4,     // 0 to 15
+        3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,     // 16 to 31
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,     // 32 to 47
+        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,     // 48 to 63
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,     // 64 to 79
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,     // 80 to 95
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,     // 96 to 111
+        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,     // 112 to 127
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     // 128 to 143
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     // 144 to 159
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     // 160 to 175
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     // 176 to 191
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     // 192 to 207
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     // 208 to 223
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,     // 224 to 239
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0      // 240 to 255
+    };
+
+    /**
+     * Terminating codes for black runs.
+     */
+    protected static final int[] termCodesBlack = new int[] {
+        /*     0 0x0000 */     0x0dc0000a, 0x40000003, 0xc0000002, 0x80000002,
+        /*     4 0x0004 */     0x60000003, 0x30000004, 0x20000004, 0x18000005,
+        /*     8 0x0008 */     0x14000006, 0x10000006, 0x08000007, 0x0a000007,
+        /*    12 0x000c */     0x0e000007, 0x04000008, 0x07000008, 0x0c000009,
+        /*    16 0x0010 */     0x05c0000a, 0x0600000a, 0x0200000a, 0x0ce0000b,
+        /*    20 0x0014 */     0x0d00000b, 0x0d80000b, 0x06e0000b, 0x0500000b,
+        /*    24 0x0018 */     0x02e0000b, 0x0300000b, 0x0ca0000c, 0x0cb0000c,
+        /*    28 0x001c */     0x0cc0000c, 0x0cd0000c, 0x0680000c, 0x0690000c,
+        /*    32 0x0020 */     0x06a0000c, 0x06b0000c, 0x0d20000c, 0x0d30000c,
+        /*    36 0x0024 */     0x0d40000c, 0x0d50000c, 0x0d60000c, 0x0d70000c,
+        /*    40 0x0028 */     0x06c0000c, 0x06d0000c, 0x0da0000c, 0x0db0000c,
+        /*    44 0x002c */     0x0540000c, 0x0550000c, 0x0560000c, 0x0570000c,
+        /*    48 0x0030 */     0x0640000c, 0x0650000c, 0x0520000c, 0x0530000c,
+        /*    52 0x0034 */     0x0240000c, 0x0370000c, 0x0380000c, 0x0270000c,
+        /*    56 0x0038 */     0x0280000c, 0x0580000c, 0x0590000c, 0x02b0000c,
+        /*    60 0x003c */     0x02c0000c, 0x05a0000c, 0x0660000c, 0x0670000c
+    };
+
+    /**
+     * Terminating codes for white runs.
+     */
+    protected static final int[] termCodesWhite = new int[] {
+        /*     0 0x0000 */     0x35000008, 0x1c000006, 0x70000004, 0x80000004,
+        /*     4 0x0004 */     0xb0000004, 0xc0000004, 0xe0000004, 0xf0000004,
+        /*     8 0x0008 */     0x98000005, 0xa0000005, 0x38000005, 0x40000005,
+        /*    12 0x000c */     0x20000006, 0x0c000006, 0xd0000006, 0xd4000006,
+        /*    16 0x0010 */     0xa8000006, 0xac000006, 0x4e000007, 0x18000007,
+        /*    20 0x0014 */     0x10000007, 0x2e000007, 0x06000007, 0x08000007,
+        /*    24 0x0018 */     0x50000007, 0x56000007, 0x26000007, 0x48000007,
+        /*    28 0x001c */     0x30000007, 0x02000008, 0x03000008, 0x1a000008,
+        /*    32 0x0020 */     0x1b000008, 0x12000008, 0x13000008, 0x14000008,
+        /*    36 0x0024 */     0x15000008, 0x16000008, 0x17000008, 0x28000008,
+        /*    40 0x0028 */     0x29000008, 0x2a000008, 0x2b000008, 0x2c000008,
+        /*    44 0x002c */     0x2d000008, 0x04000008, 0x05000008, 0x0a000008,
+        /*    48 0x0030 */     0x0b000008, 0x52000008, 0x53000008, 0x54000008,
+        /*    52 0x0034 */     0x55000008, 0x24000008, 0x25000008, 0x58000008,
+        /*    56 0x0038 */     0x59000008, 0x5a000008, 0x5b000008, 0x4a000008,
+        /*    60 0x003c */     0x4b000008, 0x32000008, 0x33000008, 0x34000008
+    };
+
+    /**
+     * Make-up codes for black runs.
+     */
+    protected static final int[] makeupCodesBlack = new int[] {
+        /*     0 0x0000 */     0x00000000, 0x03c0000a, 0x0c80000c, 0x0c90000c,
+        /*     4 0x0004 */     0x05b0000c, 0x0330000c, 0x0340000c, 0x0350000c,
+        /*     8 0x0008 */     0x0360000d, 0x0368000d, 0x0250000d, 0x0258000d,
+        /*    12 0x000c */     0x0260000d, 0x0268000d, 0x0390000d, 0x0398000d,
+        /*    16 0x0010 */     0x03a0000d, 0x03a8000d, 0x03b0000d, 0x03b8000d,
+        /*    20 0x0014 */     0x0290000d, 0x0298000d, 0x02a0000d, 0x02a8000d,
+        /*    24 0x0018 */     0x02d0000d, 0x02d8000d, 0x0320000d, 0x0328000d,
+        /*    28 0x001c */     0x0100000b, 0x0180000b, 0x01a0000b, 0x0120000c,
+        /*    32 0x0020 */     0x0130000c, 0x0140000c, 0x0150000c, 0x0160000c,
+        /*    36 0x0024 */     0x0170000c, 0x01c0000c, 0x01d0000c, 0x01e0000c,
+        /*    40 0x0028 */     0x01f0000c, 0x00000000, 0x00000000, 0x00000000,
+        /*    44 0x002c */     0x00000000, 0x00000000, 0x00000000, 0x00000000,
+        /*    48 0x0030 */     0x00000000, 0x00000000, 0x00000000, 0x00000000,
+        /*    52 0x0034 */     0x00000000, 0x00000000, 0x00000000, 0x00000000,
+        /*    56 0x0038 */     0x00000000, 0x00000000, 0x00000000, 0x00000000
+    };
+
+    /**
+     * Make-up codes for white runs.
+     */
+    protected static final int[] makeupCodesWhite = new int[] {
+        /*     0 0x0000 */     0x00000000, 0xd8000005, 0x90000005, 0x5c000006,
+        /*     4 0x0004 */     0x6e000007, 0x36000008, 0x37000008, 0x64000008,
+        /*     8 0x0008 */     0x65000008, 0x68000008, 0x67000008, 0x66000009,
+        /*    12 0x000c */     0x66800009, 0x69000009, 0x69800009, 0x6a000009,
+        /*    16 0x0010 */     0x6a800009, 0x6b000009, 0x6b800009, 0x6c000009,
+        /*    20 0x0014 */     0x6c800009, 0x6d000009, 0x6d800009, 0x4c000009,
+        /*    24 0x0018 */     0x4c800009, 0x4d000009, 0x60000006, 0x4d800009,
+        /*    28 0x001c */     0x0100000b, 0x0180000b, 0x01a0000b, 0x0120000c,
+        /*    32 0x0020 */     0x0130000c, 0x0140000c, 0x0150000c, 0x0160000c,
+        /*    36 0x0024 */     0x0170000c, 0x01c0000c, 0x01d0000c, 0x01e0000c,
+        /*    40 0x0028 */     0x01f0000c, 0x00000000, 0x00000000, 0x00000000,
+        /*    44 0x002c */     0x00000000, 0x00000000, 0x00000000, 0x00000000,
+        /*    48 0x0030 */     0x00000000, 0x00000000, 0x00000000, 0x00000000,
+        /*    52 0x0034 */     0x00000000, 0x00000000, 0x00000000, 0x00000000,
+        /*    56 0x0038 */     0x00000000, 0x00000000, 0x00000000, 0x00000000
+    };
+
+    /**
+     * Pass mode table.
+     */
+    protected static final int[] passMode = new int[] {
+        0x10000004            // 0001
+    };
+
+    /**
+     * Vertical mode table.
+     */
+    protected static final int[] vertMode = new int[] {
+        0x06000007,            // 0000011
+        0x0c000006,            // 000011
+        0x60000003,            // 011
+        0x80000001,            // 1
+        0x40000003,            // 010
+        0x08000006,            // 000010
+        0x04000007            // 0000010
+    };
+
+    /**
+     * Horizontal mode table.
+     */
+    protected static final int[] horzMode = new int[] {
+        0x20000003            // 001
+    };
+
+    /**
+     * Black and white terminating code table.
+     */
+    protected static final int[][] termCodes =
+        new int[][] {termCodesWhite, termCodesBlack};
+
+    /**
+     * Black and white make-up code table.
+     */
+    protected static final int[][] makeupCodes =
+        new int[][] {makeupCodesWhite, makeupCodesBlack};
+
+    /**
+     * Black and white pass mode table.
+     */
+    protected static final int[][] pass = new int[][] {passMode, passMode};
+
+    /**
+     * Black and white vertical mode table.
+     */
+    protected static final int[][] vert = new int[][] {vertMode, vertMode};
+
+    /**
+     * Black and white horizontal mode table.
+     */
+    protected static final int[][] horz = new int[][] {horzMode, horzMode};
+
+    // --- End tables for CCITT compression ---
+
+    /**
+     * Whether bits are inserted in reverse order (TIFF FillOrder 2).
+     */
+    protected boolean inverseFill = false;
+
+    /**
+     * Output bit buffer.
+     */
+    protected int bits;
+
+    /**
+     * Number of bits in the output bit buffer.
+     */
+    protected int ndex;
+
+    /**
+     * Constructor. The superclass constructor is merely invoked with the
+     * same parameters.
+     */
+    protected TIFFFaxCompressor(String compressionType,
+                                int compressionTagValue,
+                                boolean isCompressionLossless) {
+        super(compressionType, compressionTagValue, isCompressionLossless);
+    }
+
+    /**
+     * Sets the value of the <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/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFaxDecompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,1594 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.io.IOException;
+import java.io.EOFException;
+import javax.imageio.IIOException;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFField;
+
+class TIFFFaxDecompressor extends TIFFDecompressor {
+
+    /**
+     * The logical order of bits within a byte.
+     * <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/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFFieldNode.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.util.Arrays;
+import java.util.List;
+import javax.imageio.metadata.IIOMetadataNode;
+import org.w3c.dom.Node;
+import javax.imageio.plugins.tiff.TIFFDirectory;
+import javax.imageio.plugins.tiff.TIFFField;
+import javax.imageio.plugins.tiff.TIFFTag;
+import javax.imageio.plugins.tiff.TIFFTagSet;
+
+/**
+ * The <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/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFIFD.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,837 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import javax.imageio.IIOException;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.ImageOutputStream;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFDirectory;
+import javax.imageio.plugins.tiff.TIFFField;
+import javax.imageio.plugins.tiff.TIFFTag;
+import javax.imageio.plugins.tiff.TIFFTagSet;
+
+public class TIFFIFD extends TIFFDirectory {
+    private static final long MAX_SAMPLES_PER_PIXEL = 0xffff;
+    private static final long MAX_ASCII_SIZE  = 0xffff;
+
+    private long stripOrTileByteCountsPosition = -1;
+    private long stripOrTileOffsetsPosition = -1;
+    private long lastPosition = -1;
+
+    public static TIFFTag getTag(int tagNumber, List<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/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadata.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,1630 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOInvalidTreeException;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.stream.ImageInputStream;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.ExifParentTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFField;
+import javax.imageio.plugins.tiff.TIFFTag;
+import javax.imageio.plugins.tiff.TIFFTagSet;
+
+public class TIFFImageMetadata extends IIOMetadata {
+
+    // package scope
+
+    public static final String NATIVE_METADATA_FORMAT_NAME =
+        "javax_imageio_tiff_image_1.0";
+
+    public static final String NATIVE_METADATA_FORMAT_CLASS_NAME =
+        "javax.imageio.plugins.tiff.TIFFImageMetadataFormat";
+
+    private List<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 &&
+                               !gotVerticalPixelSize) {
+                        horizontalPixelSize = pixelAspectRatio;
+                        verticalPixelSize = 1.0f;
+                        gotHorizontalPixelSize = true;
+                        gotVerticalPixelSize = true;
+                    }
+                }
+
+                // Compute pixels/centimeter
+                if (gotHorizontalPixelSize) {
+                    float xResolution =
+                        (sizeIsAbsolute ? 10.0f : 1.0f)/horizontalPixelSize;
+                    long[][] hData = new long[1][2];
+                    hData[0] = new long[2];
+                    hData[0][0] = (long)(xResolution*10000.0f);
+                    hData[0][1] = (long)10000;
+
+                    f = new TIFFField(
+                           rootIFD.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION),
+                           TIFFTag.TIFF_RATIONAL,
+                           1,
+                           hData);
+                    rootIFD.addTIFFField(f);
+                }
+
+                if (gotVerticalPixelSize) {
+                    float yResolution =
+                        (sizeIsAbsolute ? 10.0f : 1.0f)/verticalPixelSize;
+                    long[][] vData = new long[1][2];
+                    vData[0] = new long[2];
+                    vData[0][0] = (long)(yResolution*10000.0f);
+                    vData[0][1] = (long)10000;
+
+                    f = new TIFFField(
+                           rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION),
+                           TIFFTag.TIFF_RATIONAL,
+                           1,
+                           vData);
+                    rootIFD.addTIFFField(f);
+                }
+
+                // Emit ResolutionUnit tag
+                char[] res = new char[1];
+                res[0] = (char)(sizeIsAbsolute ?
+                                BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER :
+                                BaselineTIFFTagSet.RESOLUTION_UNIT_NONE);
+
+                f = new TIFFField(
+                        rootIFD.getTag(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
+                        TIFFTag.TIFF_SHORT,
+                        1,
+                        res);
+                rootIFD.addTIFFField(f);
+
+                // Position
+                if(sizeIsAbsolute) {
+                    if(gotHorizontalPosition) {
+                        // Convert from millimeters to centimeters via
+                        // numerator multiplier = denominator/10.
+                        long[][] hData = new long[1][2];
+                        hData[0][0] = (long)(horizontalPosition*10000.0f);
+                        hData[0][1] = (long)100000;
+
+                        f = new TIFFField(
+                           rootIFD.getTag(BaselineTIFFTagSet.TAG_X_POSITION),
+                           TIFFTag.TIFF_RATIONAL,
+                           1,
+                           hData);
+                        rootIFD.addTIFFField(f);
+                    }
+
+                    if(gotVerticalPosition) {
+                        // Convert from millimeters to centimeters via
+                        // numerator multiplier = denominator/10.
+                        long[][] vData = new long[1][2];
+                        vData[0][0] = (long)(verticalPosition*10000.0f);
+                        vData[0][1] = (long)100000;
+
+                        f = new TIFFField(
+                           rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_POSITION),
+                           TIFFTag.TIFF_RATIONAL,
+                           1,
+                           vData);
+                        rootIFD.addTIFFField(f);
+                    }
+                }
+            } else if (name.equals("Document")) {
+                Node child = node.getFirstChild();
+                while (child != null) {
+                    String childName = child.getNodeName();
+
+                    if (childName.equals("SubimageInterpretation")) {
+                        String si = getAttribute(child, "value");
+                        int newSubFileType = -1;
+                        if(si.equals("TransparencyMask")) {
+                            newSubFileType =
+                                BaselineTIFFTagSet.NEW_SUBFILE_TYPE_TRANSPARENCY;
+                        } else if(si.equals("ReducedResolution")) {
+                            newSubFileType =
+                                BaselineTIFFTagSet.NEW_SUBFILE_TYPE_REDUCED_RESOLUTION;
+                        } else if(si.equals("SinglePage")) {
+                            newSubFileType =
+                                BaselineTIFFTagSet.NEW_SUBFILE_TYPE_SINGLE_PAGE;
+                        }
+                        if(newSubFileType != -1) {
+                            tag =
+                                rootIFD.getTag(BaselineTIFFTagSet.TAG_NEW_SUBFILE_TYPE);
+                            f = new TIFFField(tag, newSubFileType);
+                            rootIFD.addTIFFField(f);
+                        }
+                    }
+
+                    if (childName.equals("ImageCreationTime")) {
+                        String year = getAttribute(child, "year");
+                        String month = getAttribute(child, "month");
+                        String day = getAttribute(child, "day");
+                        String hour = getAttribute(child, "hour");
+                        String minute = getAttribute(child, "minute");
+                        String second = getAttribute(child, "second");
+
+                        StringBuffer sb = new StringBuffer();
+                        sb.append(year);
+                        sb.append(":");
+                        if(month.length() == 1) {
+                            sb.append("0");
+                        }
+                        sb.append(month);
+                        sb.append(":");
+                        if(day.length() == 1) {
+                            sb.append("0");
+                        }
+                        sb.append(day);
+                        sb.append(" ");
+                        if(hour.length() == 1) {
+                            sb.append("0");
+                        }
+                        sb.append(hour);
+                        sb.append(":");
+                        if(minute.length() == 1) {
+                            sb.append("0");
+                        }
+                        sb.append(minute);
+                        sb.append(":");
+                        if(second.length() == 1) {
+                            sb.append("0");
+                        }
+                        sb.append(second);
+
+                        String[] dt = new String[1];
+                        dt[0] = sb.toString();
+
+                        f = new TIFFField(
+                              rootIFD.getTag(BaselineTIFFTagSet.TAG_DATE_TIME),
+                              TIFFTag.TIFF_ASCII,
+                              1,
+                              dt);
+                        rootIFD.addTIFFField(f);
+                    }
+
+                    child = child.getNextSibling();
+                }
+            } else if (name.equals("Text")) {
+                Node child = node.getFirstChild();
+                String theAuthor = null;
+                String theDescription = null;
+                String theTitle = null;
+                while (child != null) {
+                    String childName = child.getNodeName();
+                    if(childName.equals("TextEntry")) {
+                        int tagNumber = -1;
+                        NamedNodeMap childAttrs = child.getAttributes();
+                        Node keywordNode = childAttrs.getNamedItem("keyword");
+                        if(keywordNode != null) {
+                            String keyword = keywordNode.getNodeValue();
+                            String value = getAttribute(child, "value");
+                            if(!keyword.equals("") && !value.equals("")) {
+                                if(keyword.equalsIgnoreCase("DocumentName")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_DOCUMENT_NAME;
+                                } else if(keyword.equalsIgnoreCase("ImageDescription")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION;
+                                } else if(keyword.equalsIgnoreCase("Make")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_MAKE;
+                                } else if(keyword.equalsIgnoreCase("Model")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_MODEL;
+                                } else if(keyword.equalsIgnoreCase("PageName")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_PAGE_NAME;
+                                } else if(keyword.equalsIgnoreCase("Software")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_SOFTWARE;
+                                } else if(keyword.equalsIgnoreCase("Artist")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_ARTIST;
+                                } else if(keyword.equalsIgnoreCase("HostComputer")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_HOST_COMPUTER;
+                                } else if(keyword.equalsIgnoreCase("InkNames")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_INK_NAMES;
+                                } else if(keyword.equalsIgnoreCase("Copyright")) {
+                                    tagNumber =
+                                        BaselineTIFFTagSet.TAG_COPYRIGHT;
+                                } else if(keyword.equalsIgnoreCase("author")) {
+                                    theAuthor = value;
+                                } else if(keyword.equalsIgnoreCase("description")) {
+                                    theDescription = value;
+                                } else if(keyword.equalsIgnoreCase("title")) {
+                                    theTitle = value;
+                                }
+                                if(tagNumber != -1) {
+                                    f = new TIFFField(rootIFD.getTag(tagNumber),
+                                                      TIFFTag.TIFF_ASCII,
+                                                      1,
+                                                      new String[] {value});
+                                    rootIFD.addTIFFField(f);
+                                }
+                            }
+                        }
+                    }
+                    child = child.getNextSibling();
+                } // child != null
+                if(theAuthor != null &&
+                   getTIFFField(BaselineTIFFTagSet.TAG_ARTIST) == null) {
+                    f = new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_ARTIST),
+                                      TIFFTag.TIFF_ASCII,
+                                      1,
+                                      new String[] {theAuthor});
+                    rootIFD.addTIFFField(f);
+                }
+                if(theDescription != null &&
+                   getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION) == null) {
+                    f = new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_IMAGE_DESCRIPTION),
+                                      TIFFTag.TIFF_ASCII,
+                                      1,
+                                      new String[] {theDescription});
+                    rootIFD.addTIFFField(f);
+                }
+                if(theTitle != null &&
+                   getTIFFField(BaselineTIFFTagSet.TAG_DOCUMENT_NAME) == null) {
+                    f = new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_DOCUMENT_NAME),
+                                      TIFFTag.TIFF_ASCII,
+                                      1,
+                                      new String[] {theTitle});
+                    rootIFD.addTIFFField(f);
+                }
+            } else if (name.equals("Transparency")) {
+                 Node child = node.getFirstChild();
+                 while (child != null) {
+                     String childName = child.getNodeName();
+
+                     if (childName.equals("Alpha")) {
+                         String alpha = getAttribute(child, "value");
+
+                         f = null;
+                         if (alpha.equals("premultiplied")) {
+                             f = new TIFFField(
+                          rootIFD.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES),
+                          BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA);
+                         } else if (alpha.equals("nonpremultiplied")) {
+                             f = new TIFFField(
+                          rootIFD.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES),
+                          BaselineTIFFTagSet.EXTRA_SAMPLES_UNASSOCIATED_ALPHA);
+                         }
+                         if (f != null) {
+                             rootIFD.addTIFFField(f);
+                         }
+                     }
+
+                    child = child.getNextSibling();
+                 }
+            }
+
+            node = node.getNextSibling();
+        }
+
+        // Set SampleFormat.
+        if(sampleFormat != null) {
+            // Derive the value.
+            int sf = -1;
+            if(sampleFormat.equals("SignedIntegral")) {
+                sf = BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER;
+            } else if(sampleFormat.equals("UnsignedIntegral")) {
+                sf = BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER;
+            } else if(sampleFormat.equals("Real")) {
+                sf = BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT;
+            } else if(sampleFormat.equals("Index")) {
+                sf = BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER;
+            }
+
+            if(sf != -1) {
+                // Derive the count.
+                int count = 1;
+
+                // Try SamplesPerPixel first.
+                f = getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
+                if(f != null) {
+                    count = f.getAsInt(0);
+                } else {
+                    // Try BitsPerSample.
+                    f = getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
+                    if(f != null) {
+                        count = f.getCount();
+                    }
+                }
+
+                char[] sampleFormatArray = new char[count];
+                Arrays.fill(sampleFormatArray, (char)sf);
+
+                // Add SampleFormat.
+                tag = rootIFD.getTag(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
+                f = new TIFFField(tag, TIFFTag.TIFF_SHORT,
+                                  sampleFormatArray.length, sampleFormatArray);
+                rootIFD.addTIFFField(f);
+            }
+        }
+    }
+
+    private static String getAttribute(Node node, String attrName) {
+        NamedNodeMap attrs = node.getAttributes();
+        Node attr = attrs.getNamedItem(attrName);
+        return attr != null ? attr.getNodeValue() : null;
+    }
+
+    private Node getChildNode(Node node, String childName) {
+        Node childNode = null;
+        if(node.hasChildNodes()) {
+            NodeList childNodes = node.getChildNodes();
+            int length = childNodes.getLength();
+            for(int i = 0; i < length; i++) {
+                Node item = childNodes.item(i);
+                if(item.getNodeName().equals(childName)) {
+                    childNode = item;
+                    break;
+                }
+            }
+        }
+        return childNode;
+    }
+
+    public static TIFFIFD parseIFD(Node node) throws IIOInvalidTreeException {
+        if (!node.getNodeName().equals("TIFFIFD")) {
+            fatal(node, "Expected \"TIFFIFD\" node");
+        }
+
+        String tagSetNames = getAttribute(node, "tagSets");
+        List<TIFFTagSet> tagSets = new ArrayList<TIFFTagSet>(5);
+
+        if (tagSetNames != null) {
+            StringTokenizer st = new StringTokenizer(tagSetNames, ",");
+            while (st.hasMoreTokens()) {
+                String className = st.nextToken();
+
+                Object o = null;
+                try {
+                    Class<?> setClass = Class.forName(className);
+                    Method getInstanceMethod =
+                        setClass.getMethod("getInstance", (Class[])null);
+                    o = getInstanceMethod.invoke(null, (Object[])null);
+                } catch (NoSuchMethodException e) {
+                    throw new RuntimeException(e);
+                } catch (IllegalAccessException e) {
+                    throw new RuntimeException(e);
+                } catch (InvocationTargetException e) {
+                    throw new RuntimeException(e);
+                } catch (ClassNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
+
+                if (!(o instanceof TIFFTagSet)) {
+                    fatal(node, "Specified tag set class \"" +
+                          className +
+                          "\" is not an instance of TIFFTagSet");
+                } else {
+                    tagSets.add((TIFFTagSet)o);
+                }
+            }
+        }
+
+        TIFFIFD ifd = new TIFFIFD(tagSets);
+
+        node = node.getFirstChild();
+        while (node != null) {
+            String name = node.getNodeName();
+
+            TIFFField f = null;
+            if (name.equals("TIFFIFD")) {
+                TIFFIFD subIFD = parseIFD(node);
+                String parentTagName = getAttribute(node, "parentTagName");
+                String parentTagNumber = getAttribute(node, "parentTagNumber");
+                TIFFTag tag = null;
+                if(parentTagName != null) {
+                    tag = TIFFIFD.getTag(parentTagName, tagSets);
+                } else if(parentTagNumber != null) {
+                    int tagNumber = Integer.parseUnsignedInt(parentTagNumber);
+                    tag = TIFFIFD.getTag(tagNumber, tagSets);
+                }
+
+                int type;
+                if (tag == null) {
+                    type = TIFFTag.TIFF_LONG;
+                    tag = new TIFFTag(TIFFTag.UNKNOWN_TAG_NAME, 0, 1 << type);
+                } else {
+                    if (tag.isDataTypeOK(TIFFTag.TIFF_IFD_POINTER)) {
+                        type = TIFFTag.TIFF_IFD_POINTER;
+                    } else if (tag.isDataTypeOK(TIFFTag.TIFF_LONG)) {
+                        type = TIFFTag.TIFF_LONG;
+                    } else {
+                        for (type = TIFFTag.MAX_DATATYPE;
+                            type >= TIFFTag.MIN_DATATYPE;
+                            type--) {
+                            if (tag.isDataTypeOK(type)) {
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                f = new TIFFField(tag, type, 1L, subIFD);
+            } else if (name.equals("TIFFField")) {
+                int number = Integer.parseInt(getAttribute(node, "number"));
+
+                TIFFTagSet tagSet = null;
+                Iterator<TIFFTagSet> iter = tagSets.iterator();
+                while (iter.hasNext()) {
+                    TIFFTagSet t = iter.next();
+                    if (t.getTag(number) != null) {
+                        tagSet = t;
+                        break;
+                    }
+                }
+
+                f = TIFFField.createFromMetadataNode(tagSet, node);
+            } else {
+                fatal(node,
+                      "Expected either \"TIFFIFD\" or \"TIFFField\" node, got "
+                      + name);
+            }
+
+            ifd.addTIFFField(f);
+            node = node.getNextSibling();
+        }
+
+        return ifd;
+    }
+
+    private void mergeNativeTree(Node root) throws IIOInvalidTreeException {
+        Node node = root;
+        if (!node.getNodeName().equals(nativeMetadataFormatName)) {
+            fatal(node, "Root must be " + nativeMetadataFormatName);
+        }
+
+        node = node.getFirstChild();
+        if (node == null || !node.getNodeName().equals("TIFFIFD")) {
+            fatal(root, "Root must have \"TIFFIFD\" child");
+        }
+        TIFFIFD ifd = parseIFD(node);
+
+        List<TIFFTagSet> rootIFDTagSets = rootIFD.getTagSetList();
+        Iterator<TIFFTagSet> tagSetIter = ifd.getTagSetList().iterator();
+        while(tagSetIter.hasNext()) {
+            Object o = tagSetIter.next();
+            if(o instanceof TIFFTagSet && !rootIFDTagSets.contains(o)) {
+                rootIFD.addTagSet((TIFFTagSet)o);
+            }
+        }
+
+        Iterator<TIFFField> ifdIter = ifd.iterator();
+        while(ifdIter.hasNext()) {
+            TIFFField field = ifdIter.next();
+            rootIFD.addTIFFField(field);
+        }
+    }
+
+    public void mergeTree(String formatName, Node root)
+        throws IIOInvalidTreeException{
+        if (formatName.equals(nativeMetadataFormatName)) {
+            if (root == null) {
+                throw new NullPointerException("root == null!");
+            }
+            mergeNativeTree(root);
+        } else if (formatName.equals
+                   (IIOMetadataFormatImpl.standardMetadataFormatName)) {
+            if (root == null) {
+                throw new NullPointerException("root == null!");
+            }
+            mergeStandardTree(root);
+        } else {
+            throw new IllegalArgumentException("Not a recognized format!");
+        }
+    }
+
+    public void reset() {
+        rootIFD = new TIFFIFD(tagSets);
+    }
+
+    public TIFFIFD getRootIFD() {
+        return rootIFD;
+    }
+
+    public TIFFField getTIFFField(int tagNumber) {
+        return rootIFD.getTIFFField(tagNumber);
+    }
+
+    public void removeTIFFField(int tagNumber) {
+        rootIFD.removeTIFFField(tagNumber);
+    }
+
+    /**
+     * Returns a <code>TIFFImageMetadata</code> wherein all fields in the
+     * root IFD from the <code>BaselineTIFFTagSet</code> are copied by value
+     * and all other fields copied by reference.
+     */
+    public TIFFImageMetadata getShallowClone() {
+        return new TIFFImageMetadata(rootIFD.getShallowClone());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormat.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadataFormat;
+
+public class TIFFImageMetadataFormat extends TIFFMetadataFormat {
+
+    private static TIFFImageMetadataFormat theInstance = null;
+
+    static {
+    }
+
+    public boolean canNodeAppear(String elementName,
+                                 ImageTypeSpecifier imageType) {
+        return false;
+    }
+
+    private TIFFImageMetadataFormat() {
+        this.resourceBaseName =
+     "javax.imageio.plugins.tiff.TIFFImageMetadataFormatResources";
+        this.rootName = TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME;
+
+        TIFFElementInfo einfo;
+        TIFFAttrInfo ainfo;
+        String[] empty = new String[0];
+        String[] childNames;
+        String[] attrNames;
+
+        childNames = new String[] { "TIFFIFD" };
+        einfo = new TIFFElementInfo(childNames, empty, CHILD_POLICY_SEQUENCE);
+
+        elementInfoMap.put(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME,
+                           einfo);
+
+        childNames = new String[] { "TIFFField", "TIFFIFD" };
+        attrNames =
+            new String[] { "tagSets", "parentTagNumber", "parentTagName" };
+        einfo = new TIFFElementInfo(childNames, attrNames, CHILD_POLICY_SEQUENCE);
+        elementInfoMap.put("TIFFIFD", einfo);
+
+        ainfo = new TIFFAttrInfo();
+        ainfo.dataType = DATATYPE_STRING;
+        ainfo.isRequired = true;
+        attrInfoMap.put("TIFFIFD/tagSets", ainfo);
+
+        ainfo = new TIFFAttrInfo();
+        ainfo.dataType = DATATYPE_INTEGER;
+        ainfo.isRequired = false;
+        attrInfoMap.put("TIFFIFD/parentTagNumber", ainfo);
+
+        ainfo = new TIFFAttrInfo();
+        ainfo.dataType = DATATYPE_STRING;
+        ainfo.isRequired = false;
+        attrInfoMap.put("TIFFIFD/parentTagName", ainfo);
+
+        String[] types = {
+            "TIFFByte",
+            "TIFFAscii",
+            "TIFFShort",
+            "TIFFSShort",
+            "TIFFLong",
+            "TIFFSLong",
+            "TIFFRational",
+            "TIFFSRational",
+            "TIFFFloat",
+            "TIFFDouble",
+            "TIFFUndefined"
+        };
+
+        attrNames = new String[] { "value", "description" };
+        String[] attrNamesValueOnly = new String[] { "value" };
+        TIFFAttrInfo ainfoValue = new TIFFAttrInfo();
+        TIFFAttrInfo ainfoDescription = new TIFFAttrInfo();
+
+        for (int i = 0; i < types.length; i++) {
+            if (!types[i].equals("TIFFUndefined")) {
+                childNames = new String[1];
+                childNames[0] = types[i];
+                einfo =
+                    new TIFFElementInfo(childNames, empty, CHILD_POLICY_SEQUENCE);
+                elementInfoMap.put(types[i] + "s", einfo);
+            }
+
+            boolean hasDescription =
+                !types[i].equals("TIFFUndefined") &&
+                !types[i].equals("TIFFAscii") &&
+                !types[i].equals("TIFFRational") &&
+                !types[i].equals("TIFFSRational") &&
+                !types[i].equals("TIFFFloat") &&
+                !types[i].equals("TIFFDouble");
+
+            String[] anames = hasDescription ? attrNames : attrNamesValueOnly;
+            einfo = new TIFFElementInfo(empty, anames, CHILD_POLICY_EMPTY);
+            elementInfoMap.put(types[i], einfo);
+
+            attrInfoMap.put(types[i] + "/value", ainfoValue);
+            if (hasDescription) {
+                attrInfoMap.put(types[i] + "/description", ainfoDescription);
+            }
+        }
+
+        childNames = new String[2*types.length - 1];
+        for (int i = 0; i < types.length; i++) {
+            childNames[2*i] = types[i];
+            if (!types[i].equals("TIFFUndefined")) {
+                childNames[2*i + 1] = types[i] + "s";
+            }
+        }
+        attrNames = new String[] { "number", "name" };
+        einfo = new TIFFElementInfo(childNames, attrNames, CHILD_POLICY_CHOICE);
+        elementInfoMap.put("TIFFField", einfo);
+
+        ainfo = new TIFFAttrInfo();
+        ainfo.isRequired = true;
+        attrInfoMap.put("TIFFField/number", ainfo);
+
+        ainfo = new TIFFAttrInfo();
+        attrInfoMap.put("TIFFField/name", ainfo);
+    }
+
+    public static synchronized IIOMetadataFormat getInstance() {
+        if (theInstance == null) {
+            theInstance = new TIFFImageMetadataFormat();
+        }
+        return theInstance;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageMetadataFormatResources.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.util.ListResourceBundle;
+
+public class TIFFImageMetadataFormatResources extends ListResourceBundle {
+
+    private static final Object[][] contents = {
+        { "TIFFIFD", "An IFD (directory) containing fields" },
+        { "TIFFIFD/parentTagNumber",
+          "The tag number of the field pointing to this IFD" },
+        { "TIFFIFD/parentTagName",
+          "A mnemonic name for the field pointing to this IFD, if known" },
+        { "TIFFField", "A field containing data" },
+        { "TIFFField/number", "The tag number asociated with the field" },
+        { "TIFFField/name",
+          "A mnemonic name associated with the field, if known" },
+
+        { "TIFFUndefined", "Uninterpreted byte data" },
+        { "TIFFUndefined/value", "A list of comma-separated byte values" },
+
+        { "TIFFBytes", "A sequence of TIFFByte nodes" },
+        { "TIFFByte", "An integral value between 0 and 255" },
+        { "TIFFByte/value", "The value" },
+        { "TIFFByte/description", "A description, if available" },
+
+        { "TIFFAsciis", "A sequence of TIFFAscii nodes" },
+        { "TIFFAscii", "A String value" },
+        { "TIFFAscii/value", "The value" },
+
+        { "TIFFShorts", "A sequence of TIFFShort nodes" },
+        { "TIFFShort", "An integral value between 0 and 65535" },
+        { "TIFFShort/value", "The value" },
+        { "TIFFShort/description", "A description, if available" },
+
+        { "TIFFSShorts", "A sequence of TIFFSShort nodes" },
+        { "TIFFSShort", "An integral value between -32768 and 32767" },
+        { "TIFFSShort/value", "The value" },
+        { "TIFFSShort/description", "A description, if available" },
+
+        { "TIFFLongs", "A sequence of TIFFLong nodes" },
+        { "TIFFLong", "An integral value between 0 and 4294967295" },
+        { "TIFFLong/value", "The value" },
+        { "TIFFLong/description", "A description, if available" },
+
+        { "TIFFSLongs", "A sequence of TIFFSLong nodes" },
+        { "TIFFSLong", "An integral value between -2147483648 and 2147483647" },
+        { "TIFFSLong/value", "The value" },
+        { "TIFFSLong/description", "A description, if available" },
+
+        { "TIFFRationals", "A sequence of TIFFRational nodes" },
+        { "TIFFRational",
+          "A rational value consisting of an unsigned numerator and denominator" },
+        { "TIFFRational/value",
+          "The numerator and denominator, separated by a slash" },
+
+        { "TIFFSRationals", "A sequence of TIFFSRational nodes" },
+        { "TIFFSRational",
+          "A rational value consisting of a signed numerator and denominator" },
+        { "TIFFSRational/value",
+          "The numerator and denominator, separated by a slash" },
+
+        { "TIFFFloats", "A sequence of TIFFFloat nodes" },
+        { "TIFFFloat", "A single-precision floating-point value" },
+        { "TIFFFloat/value", "The value" },
+
+        { "TIFFDoubles", "A sequence of TIFFDouble nodes" },
+        { "TIFFDouble", "A double-precision floating-point value" },
+        { "TIFFDouble/value", "The value" },
+
+    };
+
+    public TIFFImageMetadataFormatResources() {
+    }
+
+    public Object[][] getContents() {
+        return contents.clone();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReader.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,1329 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
+import java.awt.color.ICC_Profile;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.Raster;
+import java.awt.image.RenderedImage;
+import java.awt.image.SampleModel;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import javax.imageio.IIOException;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+import org.w3c.dom.Node;
+import com.sun.imageio.plugins.common.ImageUtil;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFField;
+import javax.imageio.plugins.tiff.TIFFImageReadParam;
+import javax.imageio.plugins.tiff.TIFFTagSet;
+
+public class TIFFImageReader extends ImageReader {
+
+    // A somewhat arbitrary upper bound on SamplesPerPixel. Hyperspectral
+    // images as of this writing appear to be under 300 bands so this should
+    // account for those cases should they arise.
+    private static final int SAMPLES_PER_PIXEL_MAX = 1024;
+
+    // In baseline TIFF the largest data types are 64-bit long and double.
+    private static final int BITS_PER_SAMPLE_MAX = 64;
+
+    // The current ImageInputStream source.
+    private ImageInputStream stream = null;
+
+    // True if the file header has been read.
+    private boolean gotHeader = false;
+
+    private ImageReadParam imageReadParam = getDefaultReadParam();
+
+    // Stream metadata, or null.
+    private TIFFStreamMetadata streamMetadata = null;
+
+    // The current image index.
+    private int currIndex = -1;
+
+    // Metadata for image at 'currIndex', or null.
+    private TIFFImageMetadata imageMetadata = null;
+
+    // A <code>List</code> of <code>Long</code>s indicating the stream
+    // positions of the start of the IFD for each image.  Entries
+    // are added as needed.
+    private List<Long> imageStartPosition = new ArrayList<Long>();
+
+    // The number of images in the stream, if known, otherwise -1.
+    private int numImages = -1;
+
+    // The ImageTypeSpecifiers of the images in the stream.
+    // Contains a map of Integers to Lists.
+    private HashMap<Integer, List<ImageTypeSpecifier>> imageTypeMap
+            = new HashMap<Integer, List<ImageTypeSpecifier>>();
+
+    private BufferedImage theImage = null;
+
+    private int width = -1;
+    private int height = -1;
+    private int numBands = -1;
+    private int tileOrStripWidth = -1, tileOrStripHeight = -1;
+
+    private int planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
+
+    private int compression;
+    private int photometricInterpretation;
+    private int samplesPerPixel;
+    private int[] sampleFormat;
+    private int[] bitsPerSample;
+    private int[] extraSamples;
+    private char[] colorMap;
+
+    private int sourceXOffset;
+    private int sourceYOffset;
+    private int srcXSubsampling;
+    private int srcYSubsampling;
+
+    private int dstWidth;
+    private int dstHeight;
+    private int dstMinX;
+    private int dstMinY;
+    private int dstXOffset;
+    private int dstYOffset;
+
+    private int tilesAcross;
+    private int tilesDown;
+
+    private int pixelsRead;
+    private int pixelsToRead;
+
+    public TIFFImageReader(ImageReaderSpi originatingProvider) {
+        super(originatingProvider);
+    }
+
+    public void setInput(Object input,
+            boolean seekForwardOnly,
+            boolean ignoreMetadata) {
+        super.setInput(input, seekForwardOnly, ignoreMetadata);
+
+        // Clear all local values based on the previous stream contents.
+        resetLocal();
+
+        if (input != null) {
+            if (!(input instanceof ImageInputStream)) {
+                throw new IllegalArgumentException("input not an ImageInputStream!");
+            }
+            this.stream = (ImageInputStream) input;
+        } else {
+            this.stream = null;
+        }
+    }
+
+    // Do not seek to the beginning of the stream so as to allow users to
+    // point us at an IFD within some other file format
+    private void readHeader() throws IIOException {
+        if (gotHeader) {
+            return;
+        }
+        if (stream == null) {
+            throw new IllegalStateException("Input not set!");
+        }
+
+        // Create an object to store the stream metadata
+        this.streamMetadata = new TIFFStreamMetadata();
+
+        try {
+            int byteOrder = stream.readUnsignedShort();
+            if (byteOrder == 0x4d4d) {
+                streamMetadata.byteOrder = ByteOrder.BIG_ENDIAN;
+                stream.setByteOrder(ByteOrder.BIG_ENDIAN);
+            } else if (byteOrder == 0x4949) {
+                streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN;
+                stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+            } else {
+                processWarningOccurred(
+                        "Bad byte order in header, assuming little-endian");
+                streamMetadata.byteOrder = ByteOrder.LITTLE_ENDIAN;
+                stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+            }
+
+            int magic = stream.readUnsignedShort();
+            if (magic != 42) {
+                processWarningOccurred(
+                        "Bad magic number in header, continuing");
+            }
+
+            // Seek to start of first IFD
+            long offset = stream.readUnsignedInt();
+            imageStartPosition.add(Long.valueOf(offset));
+            stream.seek(offset);
+        } catch (IOException e) {
+            throw new IIOException("I/O error reading header!", e);
+        }
+
+        gotHeader = true;
+    }
+
+    private int locateImage(int imageIndex) throws IIOException {
+        readHeader();
+
+        try {
+            // Find closest known index
+            int index = Math.min(imageIndex, imageStartPosition.size() - 1);
+
+            // Seek to that position
+            Long l = imageStartPosition.get(index);
+            stream.seek(l.longValue());
+
+            // Skip IFDs until at desired index or last image found
+            while (index < imageIndex) {
+                int count = stream.readUnsignedShort();
+                stream.skipBytes(12 * count);
+
+                long offset = stream.readUnsignedInt();
+                if (offset == 0) {
+                    return index;
+                }
+
+                imageStartPosition.add(Long.valueOf(offset));
+                stream.seek(offset);
+                ++index;
+            }
+        } catch (IOException e) {
+            throw new IIOException("Couldn't seek!", e);
+        }
+
+        if (currIndex != imageIndex) {
+            imageMetadata = null;
+        }
+        currIndex = imageIndex;
+        return imageIndex;
+    }
+
+    public int getNumImages(boolean allowSearch) throws IOException {
+        if (stream == null) {
+            throw new IllegalStateException("Input not set!");
+        }
+        if (seekForwardOnly && allowSearch) {
+            throw new IllegalStateException("seekForwardOnly and allowSearch can't both be true!");
+        }
+
+        if (numImages > 0) {
+            return numImages;
+        }
+        if (allowSearch) {
+            this.numImages = locateImage(Integer.MAX_VALUE) + 1;
+        }
+        return numImages;
+    }
+
+    public IIOMetadata getStreamMetadata() throws IIOException {
+        readHeader();
+        return streamMetadata;
+    }
+
+    // Throw an IndexOutOfBoundsException if index < minIndex,
+    // and bump minIndex if required.
+    private void checkIndex(int imageIndex) {
+        if (imageIndex < minIndex) {
+            throw new IndexOutOfBoundsException("imageIndex < minIndex!");
+        }
+        if (seekForwardOnly) {
+            minIndex = imageIndex;
+        }
+    }
+
+    // Verify that imageIndex is in bounds, find the image IFD, read the
+    // image metadata, initialize instance variables from the metadata.
+    private void seekToImage(int imageIndex) throws IIOException {
+        checkIndex(imageIndex);
+
+        int index = locateImage(imageIndex);
+        if (index != imageIndex) {
+            throw new IndexOutOfBoundsException("imageIndex out of bounds!");
+        }
+
+        readMetadata();
+
+        initializeFromMetadata();
+    }
+
+    // Stream must be positioned at start of IFD for 'currIndex'
+    private void readMetadata() throws IIOException {
+        if (stream == null) {
+            throw new IllegalStateException("Input not set!");
+        }
+
+        if (imageMetadata != null) {
+            return;
+        }
+        try {
+            // Create an object to store the image metadata
+            List<TIFFTagSet> tagSets;
+            if (imageReadParam instanceof TIFFImageReadParam) {
+                tagSets
+                        = ((TIFFImageReadParam) imageReadParam).getAllowedTagSets();
+            } else {
+                tagSets = new ArrayList<TIFFTagSet>(1);
+                tagSets.add(BaselineTIFFTagSet.getInstance());
+            }
+
+            this.imageMetadata = new TIFFImageMetadata(tagSets);
+            imageMetadata.initializeFromStream(stream, ignoreMetadata);
+        } catch (IIOException iioe) {
+            throw iioe;
+        } catch (IOException ioe) {
+            throw new IIOException("I/O error reading image metadata!", ioe);
+        }
+    }
+
+    private int getWidth() {
+        return this.width;
+    }
+
+    private int getHeight() {
+        return this.height;
+    }
+
+    // Returns tile width if image is tiled, else image width
+    private int getTileOrStripWidth() {
+        TIFFField f
+                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
+        return (f == null) ? getWidth() : f.getAsInt(0);
+    }
+
+    // Returns tile height if image is tiled, else strip height
+    private int getTileOrStripHeight() {
+        TIFFField f
+                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH);
+        if (f != null) {
+            return f.getAsInt(0);
+        }
+
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
+        // Default for ROWS_PER_STRIP is 2^32 - 1, i.e., infinity
+        int h = (f == null) ? -1 : f.getAsInt(0);
+        return (h == -1) ? getHeight() : h;
+    }
+
+    private int getPlanarConfiguration() {
+        TIFFField f
+                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
+        if (f != null) {
+            int planarConfigurationValue = f.getAsInt(0);
+            if (planarConfigurationValue
+                    == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
+                // Some writers (e.g. Kofax standard Multi-Page TIFF
+                // Storage Filter v2.01.000; cf. bug 4929147) do not
+                // correctly set the value of this field. Attempt to
+                // ascertain whether the value is correctly Planar.
+                if (getCompression()
+                        == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG
+                        && imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT)
+                        != null) {
+                    // JPEG interchange format cannot have
+                    // PlanarConfiguration value Chunky so reset.
+                    processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with JPEGInterchangeFormat; resetting to \"Chunky\".");
+                    planarConfigurationValue
+                            = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
+                } else {
+                    TIFFField offsetField
+                            = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
+                    if (offsetField == null) {
+                        // Tiles
+                        offsetField
+                                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
+                        int tw = getTileOrStripWidth();
+                        int th = getTileOrStripHeight();
+                        int tAcross = (getWidth() + tw - 1) / tw;
+                        int tDown = (getHeight() + th - 1) / th;
+                        int tilesPerImage = tAcross * tDown;
+                        long[] offsetArray = offsetField.getAsLongs();
+                        if (offsetArray != null
+                                && offsetArray.length == tilesPerImage) {
+                            // Length of offsets array is
+                            // TilesPerImage for Chunky and
+                            // SamplesPerPixel*TilesPerImage for Planar.
+                            processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with TileOffsets field value count; resetting to \"Chunky\".");
+                            planarConfigurationValue
+                                    = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
+                        }
+                    } else {
+                        // Strips
+                        int rowsPerStrip = getTileOrStripHeight();
+                        int stripsPerImage
+                                = (getHeight() + rowsPerStrip - 1) / rowsPerStrip;
+                        long[] offsetArray = offsetField.getAsLongs();
+                        if (offsetArray != null
+                                && offsetArray.length == stripsPerImage) {
+                            // Length of offsets array is
+                            // StripsPerImage for Chunky and
+                            // SamplesPerPixel*StripsPerImage for Planar.
+                            processWarningOccurred("PlanarConfiguration \"Planar\" value inconsistent with StripOffsets field value count; resetting to \"Chunky\".");
+                            planarConfigurationValue
+                                    = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
+                        }
+                    }
+                }
+            }
+            return planarConfigurationValue;
+        }
+
+        return BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
+    }
+
+    private long getTileOrStripOffset(int tileIndex) throws IIOException {
+        TIFFField f
+                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
+        if (f == null) {
+            f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
+        }
+        if (f == null) {
+            f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
+        }
+
+        if (f == null) {
+            throw new IIOException("Missing required strip or tile offsets field.");
+        }
+
+        return f.getAsLong(tileIndex);
+    }
+
+    private long getTileOrStripByteCount(int tileIndex) throws IOException {
+        TIFFField f
+                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS);
+        if (f == null) {
+            f
+                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
+        }
+        if (f == null) {
+            f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+        }
+
+        long tileOrStripByteCount;
+        if (f != null) {
+            tileOrStripByteCount = f.getAsLong(tileIndex);
+        } else {
+            processWarningOccurred("TIFF directory contains neither StripByteCounts nor TileByteCounts field: attempting to calculate from strip or tile width and height.");
+
+            // Initialize to number of bytes per strip or tile assuming
+            // no compression.
+            int bitsPerPixel = bitsPerSample[0];
+            for (int i = 1; i < samplesPerPixel; i++) {
+                bitsPerPixel += bitsPerSample[i];
+            }
+            int bytesPerRow = (getTileOrStripWidth() * bitsPerPixel + 7) / 8;
+            tileOrStripByteCount = bytesPerRow * getTileOrStripHeight();
+
+            // Clamp to end of stream if possible.
+            long streamLength = stream.length();
+            if (streamLength != -1) {
+                tileOrStripByteCount
+                        = Math.min(tileOrStripByteCount,
+                                streamLength - getTileOrStripOffset(tileIndex));
+            } else {
+                processWarningOccurred("Stream length is unknown: cannot clamp estimated strip or tile byte count to EOF.");
+            }
+        }
+
+        return tileOrStripByteCount;
+    }
+
+    private int getCompression() {
+        TIFFField f
+                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
+        if (f == null) {
+            return BaselineTIFFTagSet.COMPRESSION_NONE;
+        } else {
+            return f.getAsInt(0);
+        }
+    }
+
+    public int getWidth(int imageIndex) throws IOException {
+        seekToImage(imageIndex);
+        return getWidth();
+    }
+
+    public int getHeight(int imageIndex) throws IOException {
+        seekToImage(imageIndex);
+        return getHeight();
+    }
+
+    /**
+     * Initializes these instance variables from the image metadata:
+     * <pre>
+     * compression
+     * width
+     * height
+     * samplesPerPixel
+     * numBands
+     * colorMap
+     * photometricInterpretation
+     * sampleFormat
+     * bitsPerSample
+     * extraSamples
+     * tileOrStripWidth
+     * tileOrStripHeight
+     * </pre>
+     */
+    private void initializeFromMetadata() throws IIOException {
+        TIFFField f;
+
+        // Compression
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
+        if (f == null) {
+            processWarningOccurred("Compression field is missing; assuming no compression");
+            compression = BaselineTIFFTagSet.COMPRESSION_NONE;
+        } else {
+            compression = f.getAsInt(0);
+        }
+
+        // Whether key dimensional information is absent.
+        boolean isMissingDimension = false;
+
+        // ImageWidth -> width
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
+        if (f != null) {
+            this.width = f.getAsInt(0);
+        } else {
+            processWarningOccurred("ImageWidth field is missing.");
+            isMissingDimension = true;
+        }
+
+        // ImageLength -> height
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
+        if (f != null) {
+            this.height = f.getAsInt(0);
+        } else {
+            processWarningOccurred("ImageLength field is missing.");
+            isMissingDimension = true;
+        }
+
+        // SamplesPerPixel
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
+        if (f != null) {
+            samplesPerPixel = f.getAsInt(0);
+        } else {
+            samplesPerPixel = 1;
+            isMissingDimension = true;
+        }
+
+        // If any dimension is missing and there is a JPEG stream available
+        // get the information from it.
+        int defaultBitDepth = 1;
+        if (isMissingDimension
+                && (f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT)) != null) {
+            Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName("JPEG");
+            if (iter != null && iter.hasNext()) {
+                ImageReader jreader = iter.next();
+                try {
+                    stream.mark();
+                    stream.seek(f.getAsLong(0));
+                    jreader.setInput(stream);
+                    if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH) == null) {
+                        this.width = jreader.getWidth(0);
+                    }
+                    if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH) == null) {
+                        this.height = jreader.getHeight(0);
+                    }
+                    ImageTypeSpecifier imageType = jreader.getRawImageType(0);
+                    if (imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL) == null) {
+                        this.samplesPerPixel =
+                            imageType != null ?
+                                imageType.getSampleModel().getNumBands() : 3;
+                    }
+                    stream.reset();
+                    defaultBitDepth =
+                        imageType != null ?
+                        imageType.getColorModel().getComponentSize(0) : 8;
+                } catch (IOException e) {
+                    // Ignore it and proceed: an error will occur later.
+                }
+                jreader.dispose();
+            }
+        }
+
+        if (samplesPerPixel < 1) {
+            throw new IIOException("Samples per pixel < 1!");
+        } else if (samplesPerPixel > SAMPLES_PER_PIXEL_MAX) {
+            throw new IIOException
+                ("Samples per pixel (" + samplesPerPixel
+                + ") greater than allowed maximum ("
+                + SAMPLES_PER_PIXEL_MAX + ")");
+        }
+
+        // SamplesPerPixel -> numBands
+        numBands = samplesPerPixel;
+
+        // ColorMap
+        this.colorMap = null;
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP);
+        if (f != null) {
+            // Grab color map
+            colorMap = f.getAsChars();
+        }
+
+        // PhotometricInterpretation
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
+        if (f == null) {
+            if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE
+                    || compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4
+                    || compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
+                processWarningOccurred("PhotometricInterpretation field is missing; "
+                        + "assuming WhiteIsZero");
+                photometricInterpretation
+                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
+            } else if (this.colorMap != null) {
+                photometricInterpretation
+                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR;
+            } else if (samplesPerPixel == 3 || samplesPerPixel == 4) {
+                photometricInterpretation
+                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
+            } else {
+                processWarningOccurred("PhotometricInterpretation field is missing; "
+                        + "assuming BlackIsZero");
+                photometricInterpretation
+                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
+            }
+        } else {
+            photometricInterpretation = f.getAsInt(0);
+        }
+
+        // SampleFormat
+        boolean replicateFirst = false;
+        int first = -1;
+
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
+        sampleFormat = new int[samplesPerPixel];
+        replicateFirst = false;
+        if (f == null) {
+            replicateFirst = true;
+            first = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
+        } else if (f.getCount() != samplesPerPixel) {
+            replicateFirst = true;
+            first = f.getAsInt(0);
+        }
+
+        for (int i = 0; i < samplesPerPixel; i++) {
+            sampleFormat[i] = replicateFirst ? first : f.getAsInt(i);
+            if (sampleFormat[i]
+                    != BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER
+                    && sampleFormat[i]
+                    != BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER
+                    && sampleFormat[i]
+                    != BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT
+                    && sampleFormat[i]
+                    != BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED) {
+                processWarningOccurred(
+                        "Illegal value for SAMPLE_FORMAT, assuming SAMPLE_FORMAT_UNDEFINED");
+                sampleFormat[i] = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
+            }
+        }
+
+        // BitsPerSample
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
+        this.bitsPerSample = new int[samplesPerPixel];
+        replicateFirst = false;
+        if (f == null) {
+            replicateFirst = true;
+            first = defaultBitDepth;
+        } else if (f.getCount() != samplesPerPixel) {
+            replicateFirst = true;
+            first = f.getAsInt(0);
+        }
+
+        for (int i = 0; i < samplesPerPixel; i++) {
+            // Replicate initial value if not enough values provided
+            bitsPerSample[i] = replicateFirst ? first : f.getAsInt(i);
+            if (bitsPerSample[i] > BITS_PER_SAMPLE_MAX) {
+                throw new IIOException
+                    ("Bits per sample (" + bitsPerSample[i]
+                    + ") greater than allowed maximum ("
+                    + BITS_PER_SAMPLE_MAX + ")");
+            }
+        }
+
+        // ExtraSamples
+        this.extraSamples = null;
+        f = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES);
+        if (f != null) {
+            extraSamples = f.getAsInts();
+        }
+    }
+
+    public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IIOException {
+        List<ImageTypeSpecifier> l; // List of ImageTypeSpecifiers
+
+        Integer imageIndexInteger = Integer.valueOf(imageIndex);
+        if (imageTypeMap.containsKey(imageIndexInteger)) {
+            // Return the cached ITS List.
+            l = imageTypeMap.get(imageIndexInteger);
+        } else {
+            // Create a new ITS List.
+            l = new ArrayList<ImageTypeSpecifier>(1);
+
+            // Create the ITS and cache if for later use so that this method
+            // always returns an Iterator containing the same ITS objects.
+            seekToImage(imageIndex);
+            ImageTypeSpecifier itsRaw
+                    = TIFFDecompressor.getRawImageTypeSpecifier(photometricInterpretation,
+                            compression,
+                            samplesPerPixel,
+                            bitsPerSample,
+                            sampleFormat,
+                            extraSamples,
+                            colorMap);
+
+            // Check for an ICCProfile field.
+            TIFFField iccProfileField
+                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE);
+
+            // If an ICCProfile field is present change the ImageTypeSpecifier
+            // to use it if the data layout is component type.
+            if (iccProfileField != null
+                    && itsRaw.getColorModel() instanceof ComponentColorModel) {
+                // Create a ColorSpace from the profile.
+                byte[] iccProfileValue = iccProfileField.getAsBytes();
+                ICC_Profile iccProfile
+                        = ICC_Profile.getInstance(iccProfileValue);
+                ICC_ColorSpace iccColorSpace
+                        = new ICC_ColorSpace(iccProfile);
+
+                // Get the raw sample and color information.
+                ColorModel cmRaw = itsRaw.getColorModel();
+                ColorSpace csRaw = cmRaw.getColorSpace();
+                SampleModel smRaw = itsRaw.getSampleModel();
+
+                // Get the number of samples per pixel and the number
+                // of color components.
+                int numBands = smRaw.getNumBands();
+                int numComponents = iccColorSpace.getNumComponents();
+
+                // Replace the ColorModel with the ICC ColorModel if the
+                // numbers of samples and color components are amenable.
+                if (numBands == numComponents
+                        || numBands == numComponents + 1) {
+                    // Set alpha flags.
+                    boolean hasAlpha = numComponents != numBands;
+                    boolean isAlphaPre
+                            = hasAlpha && cmRaw.isAlphaPremultiplied();
+
+                    // Create a ColorModel of the same class and with
+                    // the same transfer type.
+                    ColorModel iccColorModel
+                            = new ComponentColorModel(iccColorSpace,
+                                    cmRaw.getComponentSize(),
+                                    hasAlpha,
+                                    isAlphaPre,
+                                    cmRaw.getTransparency(),
+                                    cmRaw.getTransferType());
+
+                    // Prepend the ICC profile-based ITS to the List. The
+                    // ColorModel and SampleModel are guaranteed to be
+                    // compatible as the old and new ColorModels are both
+                    // ComponentColorModels with the same transfer type
+                    // and the same number of components.
+                    l.add(new ImageTypeSpecifier(iccColorModel, smRaw));
+
+                    // Append the raw ITS to the List if and only if its
+                    // ColorSpace has the same type and number of components
+                    // as the ICC ColorSpace.
+                    if (csRaw.getType() == iccColorSpace.getType()
+                            && csRaw.getNumComponents()
+                            == iccColorSpace.getNumComponents()) {
+                        l.add(itsRaw);
+                    }
+                } else { // ICCProfile not compatible with SampleModel.
+                    // Append the raw ITS to the List.
+                    l.add(itsRaw);
+                }
+            } else { // No ICCProfile field or raw ColorModel not component.
+                // Append the raw ITS to the List.
+                l.add(itsRaw);
+            }
+
+            // Cache the ITS List.
+            imageTypeMap.put(imageIndexInteger, l);
+        }
+
+        return l.iterator();
+    }
+
+    public IIOMetadata getImageMetadata(int imageIndex) throws IIOException {
+        seekToImage(imageIndex);
+        TIFFImageMetadata im
+                = new TIFFImageMetadata(imageMetadata.getRootIFD().getTagSetList());
+        Node root
+                = imageMetadata.getAsTree(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME);
+        im.setFromTree(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME, root);
+        return im;
+    }
+
+    public IIOMetadata getStreamMetadata(int imageIndex) throws IIOException {
+        readHeader();
+        TIFFStreamMetadata sm = new TIFFStreamMetadata();
+        Node root = sm.getAsTree(TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME);
+        sm.setFromTree(TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME, root);
+        return sm;
+    }
+
+    public boolean isRandomAccessEasy(int imageIndex) throws IOException {
+        if (currIndex != -1) {
+            seekToImage(currIndex);
+            return getCompression() == BaselineTIFFTagSet.COMPRESSION_NONE;
+        } else {
+            return false;
+        }
+    }
+
+    // Thumbnails
+    public boolean readSupportsThumbnails() {
+        return false;
+    }
+
+    public boolean hasThumbnails(int imageIndex) {
+        return false;
+    }
+
+    public int getNumThumbnails(int imageIndex) throws IOException {
+        return 0;
+    }
+
+    public ImageReadParam getDefaultReadParam() {
+        return new TIFFImageReadParam();
+    }
+
+    public boolean isImageTiled(int imageIndex) throws IOException {
+        seekToImage(imageIndex);
+
+        TIFFField f
+                = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
+        return f != null;
+    }
+
+    public int getTileWidth(int imageIndex) throws IOException {
+        seekToImage(imageIndex);
+        return getTileOrStripWidth();
+    }
+
+    public int getTileHeight(int imageIndex) throws IOException {
+        seekToImage(imageIndex);
+        return getTileOrStripHeight();
+    }
+
+    public BufferedImage readTile(int imageIndex, int tileX, int tileY)
+            throws IOException {
+
+        int w = getWidth(imageIndex);
+        int h = getHeight(imageIndex);
+        int tw = getTileWidth(imageIndex);
+        int th = getTileHeight(imageIndex);
+
+        int x = tw * tileX;
+        int y = th * tileY;
+
+        if (tileX < 0 || tileY < 0 || x >= w || y >= h) {
+            throw new IllegalArgumentException("Tile indices are out of bounds!");
+        }
+
+        if (x + tw > w) {
+            tw = w - x;
+        }
+
+        if (y + th > h) {
+            th = h - y;
+        }
+
+        ImageReadParam param = getDefaultReadParam();
+        Rectangle tileRect = new Rectangle(x, y, tw, th);
+        param.setSourceRegion(tileRect);
+
+        return read(imageIndex, param);
+    }
+
+    public boolean canReadRaster() {
+        return false;
+    }
+
+    public Raster readRaster(int imageIndex, ImageReadParam param)
+            throws IOException {
+        throw new UnsupportedOperationException();
+    }
+
+    private int[] sourceBands;
+    private int[] destinationBands;
+
+    private TIFFDecompressor decompressor;
+
+    // floor(num/den)
+    private static int ifloor(int num, int den) {
+        if (num < 0) {
+            num -= den - 1;
+        }
+        return num / den;
+    }
+
+    // ceil(num/den)
+    private static int iceil(int num, int den) {
+        if (num > 0) {
+            num += den - 1;
+        }
+        return num / den;
+    }
+
+    private void prepareRead(int imageIndex, ImageReadParam param)
+            throws IOException {
+        if (stream == null) {
+            throw new IllegalStateException("Input not set!");
+        }
+
+        // A null ImageReadParam means we use the default
+        if (param == null) {
+            param = getDefaultReadParam();
+        }
+
+        this.imageReadParam = param;
+
+        seekToImage(imageIndex);
+
+        this.tileOrStripWidth = getTileOrStripWidth();
+        this.tileOrStripHeight = getTileOrStripHeight();
+        this.planarConfiguration = getPlanarConfiguration();
+
+        this.sourceBands = param.getSourceBands();
+        if (sourceBands == null) {
+            sourceBands = new int[numBands];
+            for (int i = 0; i < numBands; i++) {
+                sourceBands[i] = i;
+            }
+        }
+
+        // Initialize the destination image
+        Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
+        ImageTypeSpecifier theImageType
+                = ImageUtil.getDestinationType(param, imageTypes);
+
+        int destNumBands = theImageType.getSampleModel().getNumBands();
+
+        this.destinationBands = param.getDestinationBands();
+        if (destinationBands == null) {
+            destinationBands = new int[destNumBands];
+            for (int i = 0; i < destNumBands; i++) {
+                destinationBands[i] = i;
+            }
+        }
+
+        if (sourceBands.length != destinationBands.length) {
+            throw new IllegalArgumentException(
+                    "sourceBands.length != destinationBands.length");
+        }
+
+        for (int i = 0; i < sourceBands.length; i++) {
+            int sb = sourceBands[i];
+            if (sb < 0 || sb >= numBands) {
+                throw new IllegalArgumentException(
+                        "Source band out of range!");
+            }
+            int db = destinationBands[i];
+            if (db < 0 || db >= destNumBands) {
+                throw new IllegalArgumentException(
+                        "Destination band out of range!");
+            }
+        }
+    }
+
+    public RenderedImage readAsRenderedImage(int imageIndex,
+            ImageReadParam param)
+            throws IOException {
+        prepareRead(imageIndex, param);
+        return new TIFFRenderedImage(this, imageIndex, imageReadParam,
+                width, height);
+    }
+
+    private void decodeTile(int ti, int tj, int band) throws IOException {
+        // Compute the region covered by the strip or tile
+        Rectangle tileRect = new Rectangle(ti * tileOrStripWidth,
+                tj * tileOrStripHeight,
+                tileOrStripWidth,
+                tileOrStripHeight);
+
+        // Clip against the image bounds if the image is not tiled. If it
+        // is tiled, the tile may legally extend beyond the image bounds.
+        if (!isImageTiled(currIndex)) {
+            tileRect
+                    = tileRect.intersection(new Rectangle(0, 0, width, height));
+        }
+
+        // Return if the intersection is empty.
+        if (tileRect.width <= 0 || tileRect.height <= 0) {
+            return;
+        }
+
+        int srcMinX = tileRect.x;
+        int srcMinY = tileRect.y;
+        int srcWidth = tileRect.width;
+        int srcHeight = tileRect.height;
+
+        // Determine dest region that can be derived from the
+        // source region
+        dstMinX = iceil(srcMinX - sourceXOffset, srcXSubsampling);
+        int dstMaxX = ifloor(srcMinX + srcWidth - 1 - sourceXOffset,
+                srcXSubsampling);
+
+        dstMinY = iceil(srcMinY - sourceYOffset, srcYSubsampling);
+        int dstMaxY = ifloor(srcMinY + srcHeight - 1 - sourceYOffset,
+                srcYSubsampling);
+
+        dstWidth = dstMaxX - dstMinX + 1;
+        dstHeight = dstMaxY - dstMinY + 1;
+
+        dstMinX += dstXOffset;
+        dstMinY += dstYOffset;
+
+        // Clip against image bounds
+        Rectangle dstRect = new Rectangle(dstMinX, dstMinY,
+                dstWidth, dstHeight);
+        dstRect
+                = dstRect.intersection(theImage.getRaster().getBounds());
+
+        dstMinX = dstRect.x;
+        dstMinY = dstRect.y;
+        dstWidth = dstRect.width;
+        dstHeight = dstRect.height;
+
+        if (dstWidth <= 0 || dstHeight <= 0) {
+            return;
+        }
+
+        // Backwards map dest region to source to determine
+        // active source region
+        int activeSrcMinX = (dstMinX - dstXOffset) * srcXSubsampling
+                + sourceXOffset;
+        int sxmax
+                = (dstMinX + dstWidth - 1 - dstXOffset) * srcXSubsampling
+                + sourceXOffset;
+        int activeSrcWidth = sxmax - activeSrcMinX + 1;
+
+        int activeSrcMinY = (dstMinY - dstYOffset) * srcYSubsampling
+                + sourceYOffset;
+        int symax
+                = (dstMinY + dstHeight - 1 - dstYOffset) * srcYSubsampling
+                + sourceYOffset;
+        int activeSrcHeight = symax - activeSrcMinY + 1;
+
+        decompressor.setSrcMinX(srcMinX);
+        decompressor.setSrcMinY(srcMinY);
+        decompressor.setSrcWidth(srcWidth);
+        decompressor.setSrcHeight(srcHeight);
+
+        decompressor.setDstMinX(dstMinX);
+        decompressor.setDstMinY(dstMinY);
+        decompressor.setDstWidth(dstWidth);
+        decompressor.setDstHeight(dstHeight);
+
+        decompressor.setActiveSrcMinX(activeSrcMinX);
+        decompressor.setActiveSrcMinY(activeSrcMinY);
+        decompressor.setActiveSrcWidth(activeSrcWidth);
+        decompressor.setActiveSrcHeight(activeSrcHeight);
+
+        int tileIndex = tj * tilesAcross + ti;
+
+        if (planarConfiguration
+                == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
+            tileIndex += band * tilesAcross * tilesDown;
+        }
+
+        long offset = getTileOrStripOffset(tileIndex);
+        long byteCount = getTileOrStripByteCount(tileIndex);
+
+        decompressor.setStream(stream);
+        decompressor.setOffset(offset);
+        decompressor.setByteCount((int) byteCount);
+
+        decompressor.beginDecoding();
+
+        stream.mark();
+        decompressor.decode();
+        stream.reset();
+    }
+
+    private void reportProgress() {
+        // Report image progress/update to listeners after each tile
+        pixelsRead += dstWidth * dstHeight;
+        processImageProgress(100.0f * pixelsRead / pixelsToRead);
+        processImageUpdate(theImage,
+                dstMinX, dstMinY, dstWidth, dstHeight,
+                1, 1,
+                destinationBands);
+    }
+
+    public BufferedImage read(int imageIndex, ImageReadParam param)
+            throws IOException {
+        prepareRead(imageIndex, param);
+        this.theImage = getDestination(param,
+                getImageTypes(imageIndex),
+                width, height);
+
+        srcXSubsampling = imageReadParam.getSourceXSubsampling();
+        srcYSubsampling = imageReadParam.getSourceYSubsampling();
+
+        Point p = imageReadParam.getDestinationOffset();
+        dstXOffset = p.x;
+        dstYOffset = p.y;
+
+        // This could probably be made more efficient...
+        Rectangle srcRegion = new Rectangle(0, 0, 0, 0);
+        Rectangle destRegion = new Rectangle(0, 0, 0, 0);
+
+        computeRegions(imageReadParam, width, height, theImage,
+                srcRegion, destRegion);
+
+        // Initial source pixel, taking source region and source
+        // subsamplimg offsets into account
+        sourceXOffset = srcRegion.x;
+        sourceYOffset = srcRegion.y;
+
+        pixelsToRead = destRegion.width * destRegion.height;
+        pixelsRead = 0;
+
+        processImageStarted(imageIndex);
+        processImageProgress(0.0f);
+
+        tilesAcross = (width + tileOrStripWidth - 1) / tileOrStripWidth;
+        tilesDown = (height + tileOrStripHeight - 1) / tileOrStripHeight;
+
+        int compression = getCompression();
+
+        // Set the decompressor
+        if (compression == BaselineTIFFTagSet.COMPRESSION_NONE) {
+            // Get the fillOrder field.
+            TIFFField fillOrderField
+                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER);
+
+            // Set the decompressor based on the fill order.
+            if (fillOrderField != null && fillOrderField.getAsInt(0) == 2) {
+                this.decompressor = new TIFFLSBDecompressor();
+            } else {
+                this.decompressor = new TIFFNullDecompressor();
+            }
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
+            this.decompressor = new TIFFFaxDecompressor();
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) {
+            this.decompressor = new TIFFFaxDecompressor();
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) {
+            this.decompressor = new TIFFFaxDecompressor();
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_PACKBITS) {
+            this.decompressor = new TIFFPackBitsDecompressor();
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_LZW) {
+            TIFFField predictorField
+                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
+            int predictor = ((predictorField == null)
+                    ? BaselineTIFFTagSet.PREDICTOR_NONE
+                    : predictorField.getAsInt(0));
+            this.decompressor = new TIFFLZWDecompressor(predictor);
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_JPEG) {
+            this.decompressor = new TIFFJPEGDecompressor();
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_ZLIB
+                || compression
+                == BaselineTIFFTagSet.COMPRESSION_DEFLATE) {
+            TIFFField predictorField
+                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
+            int predictor = ((predictorField == null)
+                    ? BaselineTIFFTagSet.PREDICTOR_NONE
+                    : predictorField.getAsInt(0));
+            this.decompressor = new TIFFDeflateDecompressor(predictor);
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
+            TIFFField JPEGProcField
+                    = imageMetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_PROC);
+            if (JPEGProcField == null) {
+                processWarningOccurred("JPEGProc field missing; assuming baseline sequential JPEG process.");
+            } else if (JPEGProcField.getAsInt(0)
+                    != BaselineTIFFTagSet.JPEG_PROC_BASELINE) {
+                throw new IIOException("Old-style JPEG supported for baseline sequential JPEG process only!");
+            }
+            this.decompressor = new TIFFOldJPEGDecompressor();
+            //throw new IIOException("Old-style JPEG not supported!");
+        } else {
+            throw new IIOException("Unsupported compression type (tag value = "
+                    + compression + ")!");
+        }
+
+        if (photometricInterpretation
+                == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR
+                && compression != BaselineTIFFTagSet.COMPRESSION_JPEG
+                && compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
+            boolean convertYCbCrToRGB
+                    = theImage.getColorModel().getColorSpace().getType()
+                    == ColorSpace.TYPE_RGB;
+            TIFFDecompressor wrappedDecompressor
+                    = this.decompressor instanceof TIFFNullDecompressor
+                            ? null : this.decompressor;
+            this.decompressor
+                    = new TIFFYCbCrDecompressor(wrappedDecompressor,
+                            convertYCbCrToRGB);
+        }
+
+        TIFFColorConverter colorConverter = null;
+        if (photometricInterpretation
+                == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB
+                && theImage.getColorModel().getColorSpace().getType()
+                == ColorSpace.TYPE_RGB) {
+            colorConverter = new TIFFCIELabColorConverter();
+        } else if (photometricInterpretation
+                == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR
+                && !(this.decompressor instanceof TIFFYCbCrDecompressor)
+                && compression != BaselineTIFFTagSet.COMPRESSION_JPEG
+                && compression != BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
+            colorConverter = new TIFFYCbCrColorConverter(imageMetadata);
+        }
+
+        decompressor.setReader(this);
+        decompressor.setMetadata(imageMetadata);
+        decompressor.setImage(theImage);
+
+        decompressor.setPhotometricInterpretation(photometricInterpretation);
+        decompressor.setCompression(compression);
+        decompressor.setSamplesPerPixel(samplesPerPixel);
+        decompressor.setBitsPerSample(bitsPerSample);
+        decompressor.setSampleFormat(sampleFormat);
+        decompressor.setExtraSamples(extraSamples);
+        decompressor.setColorMap(colorMap);
+
+        decompressor.setColorConverter(colorConverter);
+
+        decompressor.setSourceXOffset(sourceXOffset);
+        decompressor.setSourceYOffset(sourceYOffset);
+        decompressor.setSubsampleX(srcXSubsampling);
+        decompressor.setSubsampleY(srcYSubsampling);
+
+        decompressor.setDstXOffset(dstXOffset);
+        decompressor.setDstYOffset(dstYOffset);
+
+        decompressor.setSourceBands(sourceBands);
+        decompressor.setDestinationBands(destinationBands);
+
+        // Compute bounds on the tile indices for this source region.
+        int minTileX
+                = TIFFImageWriter.XToTileX(srcRegion.x, 0, tileOrStripWidth);
+        int minTileY
+                = TIFFImageWriter.YToTileY(srcRegion.y, 0, tileOrStripHeight);
+        int maxTileX
+                = TIFFImageWriter.XToTileX(srcRegion.x + srcRegion.width - 1,
+                        0, tileOrStripWidth);
+        int maxTileY
+                = TIFFImageWriter.YToTileY(srcRegion.y + srcRegion.height - 1,
+                        0, tileOrStripHeight);
+
+        if (planarConfiguration
+                == BaselineTIFFTagSet.PLANAR_CONFIGURATION_PLANAR) {
+
+            decompressor.setPlanar(true);
+
+            int[] sb = new int[1];
+            int[] db = new int[1];
+            for (int tj = minTileY; tj <= maxTileY; tj++) {
+                for (int ti = minTileX; ti <= maxTileX; ti++) {
+                    for (int band = 0; band < numBands; band++) {
+                        sb[0] = sourceBands[band];
+                        decompressor.setSourceBands(sb);
+                        db[0] = destinationBands[band];
+                        decompressor.setDestinationBands(db);
+
+                        decodeTile(ti, tj, band);
+                    }
+
+                    reportProgress();
+                }
+            }
+        } else {
+            for (int tj = minTileY; tj <= maxTileY; tj++) {
+                for (int ti = minTileX; ti <= maxTileX; ti++) {
+                    decodeTile(ti, tj, -1);
+
+                    reportProgress();
+                }
+            }
+        }
+
+        if (abortRequested()) {
+            processReadAborted();
+        } else {
+            processImageComplete();
+        }
+
+        return theImage;
+    }
+
+    public void reset() {
+        super.reset();
+        resetLocal();
+    }
+
+    protected void resetLocal() {
+        stream = null;
+        gotHeader = false;
+        imageReadParam = getDefaultReadParam();
+        streamMetadata = null;
+        currIndex = -1;
+        imageMetadata = null;
+        imageStartPosition = new ArrayList<Long>();
+        numImages = -1;
+        imageTypeMap = new HashMap<Integer, List<ImageTypeSpecifier>>();
+        width = -1;
+        height = -1;
+        numBands = -1;
+        tileOrStripWidth = -1;
+        tileOrStripHeight = -1;
+        planarConfiguration = BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY;
+    }
+
+    /**
+     * Package scope method to allow decompressors, for example, to emit warning
+     * messages.
+     */
+    void forwardWarningMessage(String warning) {
+        processWarningOccurred(warning);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageReaderSpi.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.io.IOException;
+import java.util.Locale;
+import javax.imageio.ImageReader;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.spi.ServiceRegistry;
+import javax.imageio.stream.ImageInputStream;
+
+public class TIFFImageReaderSpi extends ImageReaderSpi {
+
+    private boolean registered = false;
+
+    public TIFFImageReaderSpi() {
+        super("Oracle Corporation",
+              "1.0",
+              new String[] {"tif", "TIF", "tiff", "TIFF"},
+              new String[] {"tif", "tiff"},
+              new String[] {"image/tiff"},
+              "com.sun.imageio.plugins.tiff.TIFFImageReader",
+              new Class<?>[] { ImageInputStream.class },
+              new String[] {"com.sun.imageio.plugins.tiff.TIFFImageWriterSpi"},
+              false,
+              TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME,
+              "com.sun.imageio.plugins.tiff.TIFFStreamMetadataFormat",
+              null, null,
+              true,
+              TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME,
+              "com.sun.imageio.plugins.tiff.TIFFImageMetadataFormat",
+              null, null
+              );
+    }
+
+    public String getDescription(Locale locale) {
+        return "Standard TIFF image reader";
+    }
+
+    public boolean canDecodeInput(Object input) throws IOException {
+        if (!(input instanceof ImageInputStream)) {
+            return false;
+        }
+
+        ImageInputStream stream = (ImageInputStream)input;
+        byte[] b = new byte[4];
+        stream.mark();
+        stream.readFully(b);
+        stream.reset();
+
+        return ((b[0] == (byte)0x49 && b[1] == (byte)0x49 &&
+                 b[2] == (byte)0x2a && b[3] == (byte)0x00) ||
+                (b[0] == (byte)0x4d && b[1] == (byte)0x4d &&
+                 b[2] == (byte)0x00 && b[3] == (byte)0x2a));
+    }
+
+    public ImageReader createReaderInstance(Object extension) {
+        return new TIFFImageReader(this);
+    }
+
+    public void onRegistration(ServiceRegistry registry,
+                               Class<?> category) {
+        if (registered) {
+            return;
+        }
+
+        registered = true;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriteParam.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.util.Locale;
+import javax.imageio.ImageWriteParam;
+
+/**
+ * A subclass of {@link ImageWriteParam ImageWriteParam}
+ * allowing control over the TIFF writing process. The set of innately
+ * supported compression types is listed in the following table:
+ *
+ * <table border=1>
+ * <caption><b>Supported Compression Types</b></caption>
+ * <tr><th>Compression Type</th> <th>Description</th> <th>Reference</th></tr>
+ * <tr>
+ * <td>CCITT RLE</td>
+ * <td>Modified Huffman compression</td>
+ * <td>TIFF 6.0 Specification, Section 10</td>
+ * </tr>
+ * <tr>
+ * <td>CCITT T.4</td>
+ * <td>CCITT T.4 bilevel encoding/Group 3 facsimile compression</td>
+ * <td>TIFF 6.0 Specification, Section 11</td>
+ * </tr>
+ * <tr>
+ * <td>CCITT T.6</td>
+ * <td>CCITT T.6 bilevel encoding/Group 4 facsimile compression</td>
+ * <td>TIFF 6.0 Specification, Section 11</td></tr>
+ * <tr>
+ * <td>LZW</td>
+ * <td>LZW compression</td>
+ * <td>TIFF 6.0 Specification, Section 13</td></tr>
+ * <tr>
+ * <td>JPEG</td>
+ * <td>"New" JPEG-in-TIFF compression</td>
+ * <td><a href="ftp://ftp.sgi.com/graphics/tiff/TTN2.draft.txt">TIFF
+ * Technical Note #2</a></td>
+ * </tr>
+ * <tr>
+ * <td>ZLib</td>
+ * <td>"Deflate/Inflate" compression (see note following this table)</td>
+ * <td><a href="http://partners.adobe.com/asn/developer/pdfs/tn/TIFFphotoshop.pdf">
+ * Adobe Photoshop&#174; TIFF Technical Notes</a> (PDF)</td>
+ * </tr>
+ * <tr>
+ * <td>PackBits</td>
+ * <td>Byte-oriented, run length compression</td>
+ * <td>TIFF 6.0 Specification, Section 9</td>
+ * </tr>
+ * <tr>
+ * <td>Deflate</td>
+ * <td>"Zip-in-TIFF" compression (see note following this table)</td>
+ * <td><a href="http://www.isi.edu/in-notes/rfc1950.txt">
+ * ZLIB Compressed Data Format Specification</a>,
+ * <a href="http://www.isi.edu/in-notes/rfc1951.txt">
+ * DEFLATE Compressed Data Format Specification</a></td>
+ * </tr>
+ * <tr>
+ * <td>Exif JPEG</td>
+ * <td>Exif-specific JPEG compression (see note following this table)</td>
+ * <td><a href="http://www.exif.org/Exif2-2.PDF">Exif 2.2 Specification</a>
+ * (PDF), section 4.5.5, "Basic Structure of Thumbnail Data"</td>
+ * </table>
+ *
+ * <p>
+ * Old-style JPEG compression as described in section 22 of the TIFF 6.0
+ * Specification is <i>not</i> supported.
+ * </p>
+ *
+ * <p> The CCITT compression types are applicable to bilevel (1-bit)
+ * images only.  The JPEG compression type is applicable to byte
+ * grayscale (1-band) and RGB (3-band) images only.</p>
+ *
+ * <p>
+ * ZLib and Deflate compression are identical except for the value of the
+ * TIFF Compression field: for ZLib the Compression field has value 8
+ * whereas for Deflate it has value 32946 (0x80b2). In both cases each
+ * image segment (strip or tile) is written as a single complete zlib data
+ * stream.
+ * </p>
+ *
+ * <p>
+ * "Exif JPEG" is a compression type used when writing the contents of an
+ * APP1 Exif marker segment for inclusion in a JPEG native image metadata
+ * tree. The contents appended to the output when this compression type is
+ * used are a function of whether an empty or non-empty image is written.
+ * If the image is empty, then a TIFF IFD adhering to the specification of
+ * a compressed Exif primary IFD is appended. If the image is non-empty,
+ * then a complete IFD and image adhering to the specification of a
+ * compressed Exif thumbnail IFD and image are appended. Note that the
+ * data of the empty image may <i>not</i> later be appended using the pixel
+ * replacement capability of the TIFF writer.
+ * </p>
+ *
+ * <p> If ZLib/Deflate or JPEG compression is used, the compression quality
+ * may be set. For ZLib/Deflate the supplied floating point quality value is
+ * rescaled to the range <tt>[1,&nbsp;9]</tt> and truncated to an integer
+ * to derive the Deflate compression level. For JPEG the floating point
+ * quality value is passed directly to the JPEG writer plug-in which
+ * interprets it in the usual way.</p>
+ *
+ * <p> The <code>canWriteTiles</code> and
+ * <code>canWriteCompressed</code> methods will return
+ * <code>true</code>; the <code>canOffsetTiles</code> and
+ * <code>canWriteProgressive</code> methods will return
+ * <code>false</code>.</p>
+ *
+ * <p> If tiles are being written, then each of their dimensions will be
+ * rounded to the nearest multiple of 16 per the TIFF specification. If
+ * JPEG-in-TIFF compression is being used, and tiles are being written
+ * each tile dimension will be rounded to the nearest multiple of 8 times
+ * the JPEG minimum coded unit (MCU) in that dimension. If JPEG-in-TIFF
+ * compression is being used and strips are being written, the number of
+ * rows per strip is rounded to a multiple of 8 times the maximum MCU over
+ * both dimensions.</p>
+ */
+public class TIFFImageWriteParam extends ImageWriteParam {
+
+    /**
+     * Constructs a <code>TIFFImageWriteParam</code> instance
+     * for a given <code>Locale</code>.
+     *
+     * @param locale the <code>Locale</code> for which messages
+     * should be localized.
+     */
+    public TIFFImageWriteParam(Locale locale) {
+        super(locale);
+        this.canWriteCompressed = true;
+        this.canWriteTiles = true;
+        this.compressionTypes = TIFFImageWriter.TIFFCompressionTypes;
+    };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriter.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,3610 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.ComponentSampleModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.IndexColorModel;
+import java.awt.image.RenderedImage;
+import java.awt.image.Raster;
+import java.awt.image.SampleModel;
+import java.awt.image.WritableRaster;
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.imageio.IIOException;
+import javax.imageio.IIOImage;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOInvalidTreeException;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+import javax.imageio.spi.ImageWriterSpi;
+import javax.imageio.stream.ImageOutputStream;
+import org.w3c.dom.Node;
+import com.sun.imageio.plugins.common.ImageUtil;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.ExifParentTIFFTagSet;
+import javax.imageio.plugins.tiff.ExifTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFField;
+import javax.imageio.plugins.tiff.TIFFTag;
+import javax.imageio.plugins.tiff.TIFFTagSet;
+import com.sun.imageio.plugins.common.SimpleRenderedImage;
+import com.sun.imageio.plugins.common.SingleTileRenderedImage;
+import java.nio.charset.StandardCharsets;
+
+public class TIFFImageWriter extends ImageWriter {
+
+    static final String EXIF_JPEG_COMPRESSION_TYPE = "Exif JPEG";
+
+    private static final int DEFAULT_BYTES_PER_STRIP = 8192;
+
+    /**
+     * Supported TIFF compression types.
+     */
+    static final String[] TIFFCompressionTypes = {
+        "CCITT RLE",
+        "CCITT T.4",
+        "CCITT T.6",
+        "LZW",
+        // "Old JPEG",
+        "JPEG",
+        "ZLib",
+        "PackBits",
+        "Deflate",
+        EXIF_JPEG_COMPRESSION_TYPE
+    };
+
+    //
+    // The lengths of the arrays 'compressionTypes',
+    // 'isCompressionLossless', and 'compressionNumbers'
+    // must be equal.
+    //
+
+    /**
+     * Known TIFF compression types.
+     */
+    static final String[] compressionTypes = {
+        "CCITT RLE",
+        "CCITT T.4",
+        "CCITT T.6",
+        "LZW",
+        "Old JPEG",
+        "JPEG",
+        "ZLib",
+        "PackBits",
+        "Deflate",
+        EXIF_JPEG_COMPRESSION_TYPE
+    };
+
+    /**
+     * Lossless flag for known compression types.
+     */
+    static final boolean[] isCompressionLossless = {
+        true,  // RLE
+        true,  // T.4
+        true,  // T.6
+        true,  // LZW
+        false, // Old JPEG
+        false, // JPEG
+        true,  // ZLib
+        true,  // PackBits
+        true,  // DEFLATE
+        false  // Exif JPEG
+    };
+
+    /**
+     * Compression tag values for known compression types.
+     */
+    static final int[] compressionNumbers = {
+        BaselineTIFFTagSet.COMPRESSION_CCITT_RLE,
+        BaselineTIFFTagSet.COMPRESSION_CCITT_T_4,
+        BaselineTIFFTagSet.COMPRESSION_CCITT_T_6,
+        BaselineTIFFTagSet.COMPRESSION_LZW,
+        BaselineTIFFTagSet.COMPRESSION_OLD_JPEG,
+        BaselineTIFFTagSet.COMPRESSION_JPEG,
+        BaselineTIFFTagSet.COMPRESSION_ZLIB,
+        BaselineTIFFTagSet.COMPRESSION_PACKBITS,
+        BaselineTIFFTagSet.COMPRESSION_DEFLATE,
+        BaselineTIFFTagSet.COMPRESSION_OLD_JPEG, // Exif JPEG
+    };
+
+    private ImageOutputStream stream;
+    private long headerPosition;
+    private RenderedImage image;
+    private ImageTypeSpecifier imageType;
+    private ByteOrder byteOrder;
+    private ImageWriteParam param;
+    private TIFFCompressor compressor;
+    private TIFFColorConverter colorConverter;
+
+    private TIFFStreamMetadata streamMetadata;
+    private TIFFImageMetadata imageMetadata;
+
+    private int sourceXOffset;
+    private int sourceYOffset;
+    private int sourceWidth;
+    private int sourceHeight;
+    private int[] sourceBands;
+    private int periodX;
+    private int periodY;
+
+    private int bitDepth; // bits per channel
+    private int numBands;
+    private int tileWidth;
+    private int tileLength;
+    private int tilesAcross;
+    private int tilesDown;
+
+    private int[] sampleSize = null; // Input sample size per band, in bits
+    private int scalingBitDepth = -1; // Output bit depth of the scaling tables
+    private boolean isRescaling = false; // Whether rescaling is needed.
+
+    private boolean isBilevel; // Whether image is bilevel
+    private boolean isImageSimple; // Whether image can be copied into directly
+    private boolean isInverted; // Whether photometric inversion is required
+
+    private boolean isTiled; // Whether the image is tiled (true) or stipped (false).
+
+    private int nativePhotometricInterpretation;
+    private int photometricInterpretation;
+
+    private char[] bitsPerSample; // Output sample size per band
+    private int sampleFormat =
+        BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED; // Output sample format
+
+    // Tables for 1, 2, 4, or 8 bit output
+    private byte[][] scale = null; // 8 bit table
+    private byte[] scale0 = null; // equivalent to scale[0]
+
+    // Tables for 16 bit output
+    private byte[][] scaleh = null; // High bytes of output
+    private byte[][] scalel = null; // Low bytes of output
+
+    private int compression;
+    private int predictor;
+
+    private int totalPixels;
+    private int pixelsDone;
+
+    private long nextIFDPointerPos;
+
+    // Next available space.
+    private long nextSpace = 0L;
+
+    // Whether a sequence is being written.
+    private boolean isWritingSequence = false;
+    private boolean isInsertingEmpty = false;
+    private boolean isWritingEmpty = false;
+
+    private int currentImage = 0;
+
+    /**
+     * Converts a pixel's X coordinate into a horizontal tile index
+     * relative to a given tile grid layout specified by its X offset
+     * and tile width.
+     *
+     * <p> If <code>tileWidth < 0</code>, the results of this method
+     * are undefined.  If <code>tileWidth == 0</code>, an
+     * <code>ArithmeticException</code> will be thrown.
+     *
+     * @throws ArithmeticException  If <code>tileWidth == 0</code>.
+     */
+    public static int XToTileX(int x, int tileGridXOffset, int tileWidth) {
+        x -= tileGridXOffset;
+        if (x < 0) {
+            x += 1 - tileWidth;         // force round to -infinity (ceiling)
+        }
+        return x/tileWidth;
+    }
+
+    /**
+     * Converts a pixel's Y coordinate into a vertical tile index
+     * relative to a given tile grid layout specified by its Y offset
+     * and tile height.
+     *
+     * <p> If <code>tileHeight < 0</code>, the results of this method
+     * are undefined.  If <code>tileHeight == 0</code>, an
+     * <code>ArithmeticException</code> will be thrown.
+     *
+     * @throws ArithmeticException  If <code>tileHeight == 0</code>.
+     */
+    public static int YToTileY(int y, int tileGridYOffset, int tileHeight) {
+        y -= tileGridYOffset;
+        if (y < 0) {
+            y += 1 - tileHeight;         // force round to -infinity (ceiling)
+        }
+        return y/tileHeight;
+    }
+
+    public TIFFImageWriter(ImageWriterSpi originatingProvider) {
+        super(originatingProvider);
+    }
+
+    public ImageWriteParam getDefaultWriteParam() {
+        return new TIFFImageWriteParam(getLocale());
+    }
+
+    public void setOutput(Object output) {
+        super.setOutput(output);
+
+        if (output != null) {
+            if (!(output instanceof ImageOutputStream)) {
+                throw new IllegalArgumentException
+                    ("output not an ImageOutputStream!");
+            }
+            this.stream = (ImageOutputStream)output;
+
+            //
+            // The output is expected to be positioned at a TIFF header
+            // or at some arbitrary location which may or may not be
+            // the EOF. In the former case the writer should be able
+            // either to overwrite the existing sequence or append to it.
+            //
+
+            // Set the position of the header and the next available space.
+            try {
+                headerPosition = this.stream.getStreamPosition();
+                try {
+                    // Read byte order and magic number.
+                    byte[] b = new byte[4];
+                    stream.readFully(b);
+
+                    // Check bytes for TIFF header.
+                    if((b[0] == (byte)0x49 && b[1] == (byte)0x49 &&
+                        b[2] == (byte)0x2a && b[3] == (byte)0x00) ||
+                       (b[0] == (byte)0x4d && b[1] == (byte)0x4d &&
+                        b[2] == (byte)0x00 && b[3] == (byte)0x2a)) {
+                        // TIFF header.
+                        this.nextSpace = stream.length();
+                    } else {
+                        // Neither TIFF header nor EOF: overwrite.
+                        this.nextSpace = headerPosition;
+                    }
+                } catch(IOException io) { // thrown by readFully()
+                    // At EOF or not at a TIFF header.
+                    this.nextSpace = headerPosition;
+                }
+                stream.seek(headerPosition);
+            } catch(IOException ioe) { // thrown by getStreamPosition()
+                // Assume it's at zero.
+                this.nextSpace = headerPosition = 0L;
+            }
+        } else {
+            this.stream = null;
+        }
+    }
+
+    public IIOMetadata
+        getDefaultStreamMetadata(ImageWriteParam param) {
+        return new TIFFStreamMetadata();
+    }
+
+    public IIOMetadata
+        getDefaultImageMetadata(ImageTypeSpecifier imageType,
+                                ImageWriteParam param) {
+
+        List<TIFFTagSet> tagSets = new ArrayList<TIFFTagSet>(1);
+        tagSets.add(BaselineTIFFTagSet.getInstance());
+        TIFFImageMetadata imageMetadata = new TIFFImageMetadata(tagSets);
+
+        if(imageType != null) {
+            TIFFImageMetadata im =
+                (TIFFImageMetadata)convertImageMetadata(imageMetadata,
+                                                        imageType,
+                                                        param);
+            if(im != null) {
+                imageMetadata = im;
+            }
+        }
+
+        return imageMetadata;
+    }
+
+    public IIOMetadata convertStreamMetadata(IIOMetadata inData,
+                                             ImageWriteParam param) {
+        // Check arguments.
+        if(inData == null) {
+            throw new NullPointerException("inData == null!");
+        }
+
+        // Note: param is irrelevant as it does not contain byte order.
+
+        TIFFStreamMetadata outData = null;
+        if(inData instanceof TIFFStreamMetadata) {
+            outData = new TIFFStreamMetadata();
+            outData.byteOrder = ((TIFFStreamMetadata)inData).byteOrder;
+            return outData;
+        } else if(Arrays.asList(inData.getMetadataFormatNames()).contains(
+                      TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME)) {
+            outData = new TIFFStreamMetadata();
+            String format = TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME;
+            try {
+                outData.mergeTree(format, inData.getAsTree(format));
+            } catch(IIOInvalidTreeException e) {
+                return null;
+            }
+        }
+
+        return outData;
+    }
+
+    public IIOMetadata
+        convertImageMetadata(IIOMetadata inData,
+                             ImageTypeSpecifier imageType,
+                             ImageWriteParam param) {
+        // Check arguments.
+        if(inData == null) {
+            throw new NullPointerException("inData == null!");
+        }
+        if(imageType == null) {
+            throw new NullPointerException("imageType == null!");
+        }
+
+        TIFFImageMetadata outData = null;
+
+        // Obtain a TIFFImageMetadata object.
+        if(inData instanceof TIFFImageMetadata) {
+            // Create a new metadata object from a clone of the input IFD.
+            TIFFIFD inIFD = ((TIFFImageMetadata)inData).getRootIFD();
+            outData = new TIFFImageMetadata(inIFD.getShallowClone());
+        } else if(Arrays.asList(inData.getMetadataFormatNames()).contains(
+                      TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME)) {
+            // Initialize from the native metadata form of the input tree.
+            try {
+                outData = convertNativeImageMetadata(inData);
+            } catch(IIOInvalidTreeException e) {
+                return null;
+            }
+        } else if(inData.isStandardMetadataFormatSupported()) {
+            // Initialize from the standard metadata form of the input tree.
+            try {
+                outData = convertStandardImageMetadata(inData);
+            } catch(IIOInvalidTreeException e) {
+                return null;
+            }
+        }
+
+        // Update the metadata per the image type and param.
+        if(outData != null) {
+            TIFFImageWriter bogusWriter =
+                new TIFFImageWriter(this.originatingProvider);
+            bogusWriter.imageMetadata = outData;
+            bogusWriter.param = param;
+            SampleModel sm = imageType.getSampleModel();
+            try {
+                bogusWriter.setupMetadata(imageType.getColorModel(), sm,
+                                          sm.getWidth(), sm.getHeight());
+                return bogusWriter.imageMetadata;
+            } catch(IIOException e) {
+                return null;
+            }
+        }
+
+        return outData;
+    }
+
+    /**
+     * Converts a standard <code>javax_imageio_1.0</code> tree to a
+     * <code>TIFFImageMetadata</code> object.
+     *
+     * @param inData The metadata object.
+     * @return a <code>TIFFImageMetadata</code> or <code>null</code> if
+     * the standard tree derived from the input object is <code>null</code>.
+     * @throws IllegalArgumentException if <code>inData</code> is
+     * <code>null</code>.
+     * @throws IllegalArgumentException if <code>inData</code> does not support
+     * the standard metadata format.
+     * @throws IIOInvalidTreeException if <code>inData</code> generates an
+     * invalid standard metadata tree.
+     */
+    private TIFFImageMetadata convertStandardImageMetadata(IIOMetadata inData)
+        throws IIOInvalidTreeException {
+
+        if(inData == null) {
+            throw new NullPointerException("inData == null!");
+        } else if(!inData.isStandardMetadataFormatSupported()) {
+            throw new IllegalArgumentException
+                ("inData does not support standard metadata format!");
+        }
+
+        TIFFImageMetadata outData = null;
+
+        String formatName = IIOMetadataFormatImpl.standardMetadataFormatName;
+        Node tree = inData.getAsTree(formatName);
+        if (tree != null) {
+            List<TIFFTagSet> tagSets = new ArrayList<TIFFTagSet>(1);
+            tagSets.add(BaselineTIFFTagSet.getInstance());
+            outData = new TIFFImageMetadata(tagSets);
+            outData.setFromTree(formatName, tree);
+        }
+
+        return outData;
+    }
+
+    /**
+     * Converts a native
+     * <code>javax_imageio_tiff_image_1.0</code> tree to a
+     * <code>TIFFImageMetadata</code> object.
+     *
+     * @param inData The metadata object.
+     * @return a <code>TIFFImageMetadata</code> or <code>null</code> if
+     * the native tree derived from the input object is <code>null</code>.
+     * @throws IllegalArgumentException if <code>inData</code> is
+     * <code>null</code> or does not support the native metadata format.
+     * @throws IIOInvalidTreeException if <code>inData</code> generates an
+     * invalid native metadata tree.
+     */
+    private TIFFImageMetadata convertNativeImageMetadata(IIOMetadata inData)
+        throws IIOInvalidTreeException {
+
+        if(inData == null) {
+            throw new NullPointerException("inData == null!");
+        } else if(!Arrays.asList(inData.getMetadataFormatNames()).contains(
+                      TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME)) {
+            throw new IllegalArgumentException
+                ("inData does not support native metadata format!");
+        }
+
+        TIFFImageMetadata outData = null;
+
+        String formatName = TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME;
+        Node tree = inData.getAsTree(formatName);
+        if (tree != null) {
+            List<TIFFTagSet> tagSets = new ArrayList<TIFFTagSet>(1);
+            tagSets.add(BaselineTIFFTagSet.getInstance());
+            outData = new TIFFImageMetadata(tagSets);
+            outData.setFromTree(formatName, tree);
+        }
+
+        return outData;
+    }
+
+    /**
+     * Sets up the output metadata adding, removing, and overriding fields
+     * as needed. The destination image dimensions are provided as parameters
+     * because these might differ from those of the source due to subsampling.
+     *
+     * @param cm The <code>ColorModel</code> of the image being written.
+     * @param sm The <code>SampleModel</code> of the image being written.
+     * @param destWidth The width of the written image after subsampling.
+     * @param destHeight The height of the written image after subsampling.
+     */
+    void setupMetadata(ColorModel cm, SampleModel sm,
+                       int destWidth, int destHeight)
+        throws IIOException {
+        // Get initial IFD from metadata
+
+        // Always emit these fields:
+        //
+        // Override values from metadata:
+        //
+        //  planarConfiguration -> chunky (planar not supported on output)
+        //
+        // Override values from metadata with image-derived values:
+        //
+        //  bitsPerSample (if not bilivel)
+        //  colorMap (if palette color)
+        //  photometricInterpretation (derive from image)
+        //  imageLength
+        //  imageWidth
+        //
+        //  rowsPerStrip     \      /   tileLength
+        //  stripOffsets      | OR |   tileOffsets
+        //  stripByteCounts  /     |   tileByteCounts
+        //                          \   tileWidth
+        //
+        //
+        // Override values from metadata with write param values:
+        //
+        //  compression
+
+        // Use values from metadata if present for these fields,
+        // otherwise use defaults:
+        //
+        //  resolutionUnit
+        //  XResolution (take from metadata if present)
+        //  YResolution
+        //  rowsPerStrip
+        //  sampleFormat
+
+        TIFFIFD rootIFD = imageMetadata.getRootIFD();
+
+        BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();
+
+        // If PlanarConfiguration field present, set value to chunky.
+
+        TIFFField f =
+            rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
+        if(f != null &&
+           f.getAsInt(0) != BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY) {
+            TIFFField planarConfigurationField =
+                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION),
+                              BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY);
+            rootIFD.addTIFFField(planarConfigurationField);
+        }
+
+        char[] extraSamples = null;
+
+        this.photometricInterpretation = -1;
+        boolean forcePhotometricInterpretation = false;
+
+        f =
+       rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
+        if (f != null) {
+            photometricInterpretation = f.getAsInt(0);
+            if(photometricInterpretation ==
+               BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR &&
+               !(cm instanceof IndexColorModel)) {
+                photometricInterpretation = -1;
+            } else {
+                forcePhotometricInterpretation = true;
+            }
+        }
+
+        int[] sampleSize = sm.getSampleSize();
+
+        int numBands = sm.getNumBands();
+        int numExtraSamples = 0;
+
+        // Check that numBands > 1 here because TIFF requires that
+        // SamplesPerPixel = numBands + numExtraSamples and numBands
+        // cannot be zero.
+        if (numBands > 1 && cm != null && cm.hasAlpha()) {
+            --numBands;
+            numExtraSamples = 1;
+            extraSamples = new char[1];
+            if (cm.isAlphaPremultiplied()) {
+                extraSamples[0] =
+                    BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA;
+            } else {
+                extraSamples[0] =
+                    BaselineTIFFTagSet.EXTRA_SAMPLES_UNASSOCIATED_ALPHA;
+            }
+        }
+
+        if (numBands == 3) {
+            this.nativePhotometricInterpretation =
+                BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
+            if (photometricInterpretation == -1) {
+                photometricInterpretation =
+                    BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
+            }
+        } else if (sm.getNumBands() == 1 && cm instanceof IndexColorModel) {
+            IndexColorModel icm = (IndexColorModel)cm;
+            int r0 = icm.getRed(0);
+            int r1 = icm.getRed(1);
+            if (icm.getMapSize() == 2 &&
+                (r0 == icm.getGreen(0)) && (r0 == icm.getBlue(0)) &&
+                (r1 == icm.getGreen(1)) && (r1 == icm.getBlue(1)) &&
+                (r0 == 0 || r0 == 255) &&
+                (r1 == 0 || r1 == 255) &&
+                (r0 != r1)) {
+                // Black/white image
+
+                if (r0 == 0) {
+                    nativePhotometricInterpretation =
+                   BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
+                } else {
+                    nativePhotometricInterpretation =
+                   BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
+                }
+
+
+                // If photometricInterpretation is already set to
+                // WhiteIsZero or BlackIsZero, leave it alone
+                if (photometricInterpretation !=
+                 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO &&
+                    photometricInterpretation !=
+                 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) {
+                    photometricInterpretation =
+                        r0 == 0 ?
+                  BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO :
+                  BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
+                }
+            } else {
+                nativePhotometricInterpretation =
+                photometricInterpretation =
+                   BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR;
+            }
+        } else {
+            if(cm != null) {
+                switch(cm.getColorSpace().getType()) {
+                case ColorSpace.TYPE_Lab:
+                    nativePhotometricInterpretation =
+                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB;
+                    break;
+                case ColorSpace.TYPE_YCbCr:
+                    nativePhotometricInterpretation =
+                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR;
+                    break;
+                case ColorSpace.TYPE_CMYK:
+                    nativePhotometricInterpretation =
+                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK;
+                    break;
+                default:
+                    nativePhotometricInterpretation =
+                        BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
+                }
+            } else {
+                nativePhotometricInterpretation =
+                    BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
+            }
+            if (photometricInterpretation == -1) {
+                photometricInterpretation = nativePhotometricInterpretation;
+            }
+        }
+
+        // Emit compression tag
+
+        int compressionMode = param.getCompressionMode();
+        switch(compressionMode) {
+        case ImageWriteParam.MODE_EXPLICIT:
+            {
+                String compressionType = param.getCompressionType();
+                if (compressionType == null) {
+                    this.compression = BaselineTIFFTagSet.COMPRESSION_NONE;
+                } else {
+                    // Determine corresponding compression tag value.
+                    int len = compressionTypes.length;
+                    for (int i = 0; i < len; i++) {
+                        if (compressionType.equals(compressionTypes[i])) {
+                            this.compression = compressionNumbers[i];
+                        }
+                    }
+                }
+            }
+            break;
+        case ImageWriteParam.MODE_COPY_FROM_METADATA:
+            {
+                TIFFField compField =
+                    rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
+                if(compField != null) {
+                    this.compression = compField.getAsInt(0);
+                } else {
+                    this.compression = BaselineTIFFTagSet.COMPRESSION_NONE;
+                }
+            }
+            break;
+        default:
+            this.compression = BaselineTIFFTagSet.COMPRESSION_NONE;
+        }
+
+        TIFFField predictorField =
+            rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
+        if (predictorField != null) {
+            this.predictor = predictorField.getAsInt(0);
+
+            // We only support Horizontal Predictor for a bitDepth of 8
+            if (sampleSize[0] != 8 ||
+                // Check the value of the tag for validity
+                (predictor != BaselineTIFFTagSet.PREDICTOR_NONE &&
+                 predictor !=
+                 BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING)) {
+                // Set to default
+                predictor = BaselineTIFFTagSet.PREDICTOR_NONE;
+
+                // Emit this changed predictor value to metadata
+                TIFFField newPredictorField =
+                   new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_PREDICTOR),
+                                 predictor);
+                rootIFD.addTIFFField(newPredictorField);
+            }
+        }
+
+        TIFFField compressionField =
+            new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_COMPRESSION),
+                          compression);
+        rootIFD.addTIFFField(compressionField);
+
+        // Set Exif flag. Note that there is no way to determine definitively
+        // when an uncompressed thumbnail is being written as the Exif IFD
+        // pointer field is optional for thumbnails.
+        boolean isExif = false;
+        if(numBands == 3 &&
+           sampleSize[0] == 8 && sampleSize[1] == 8 && sampleSize[2] == 8) {
+            // Three bands with 8 bits per sample.
+            if(rootIFD.getTIFFField(ExifParentTIFFTagSet.TAG_EXIF_IFD_POINTER)
+               != null) {
+                // Exif IFD pointer present.
+                if(compression == BaselineTIFFTagSet.COMPRESSION_NONE &&
+                   (photometricInterpretation ==
+                    BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB ||
+                    photometricInterpretation ==
+                    BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR)) {
+                    // Uncompressed RGB or YCbCr.
+                    isExif = true;
+                } else if(compression ==
+                          BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
+                    // Compressed.
+                    isExif = true;
+                }
+            } else if(compressionMode == ImageWriteParam.MODE_EXPLICIT &&
+                      EXIF_JPEG_COMPRESSION_TYPE.equals
+                      (param.getCompressionType())) {
+                // Exif IFD pointer absent but Exif JPEG compression set.
+                isExif = true;
+            }
+        }
+
+        // Initialize JPEG interchange format flag which is used to
+        // indicate that the image is stored as a single JPEG stream.
+        // This flag is separated from the 'isExif' flag in case JPEG
+        // interchange format is eventually supported for non-Exif images.
+        boolean isJPEGInterchange =
+            isExif && compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG;
+
+        this.compressor = null;
+        if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) {
+            compressor = new TIFFRLECompressor();
+
+            if (!forcePhotometricInterpretation) {
+                photometricInterpretation
+                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
+            }
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) {
+            compressor = new TIFFT4Compressor();
+
+            if (!forcePhotometricInterpretation) {
+                photometricInterpretation
+                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
+            }
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
+            compressor = new TIFFT6Compressor();
+
+            if (!forcePhotometricInterpretation) {
+                photometricInterpretation
+                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
+            }
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_LZW) {
+            compressor = new TIFFLZWCompressor(predictor);
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
+            if (isExif) {
+                compressor = new TIFFExifJPEGCompressor(param);
+            } else {
+                throw new IIOException("Old JPEG compression not supported!");
+            }
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_JPEG) {
+            if (numBands == 3 && sampleSize[0] == 8
+                    && sampleSize[1] == 8 && sampleSize[2] == 8) {
+                photometricInterpretation
+                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR;
+            } else if (numBands == 1 && sampleSize[0] == 8) {
+                photometricInterpretation
+                        = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
+            } else {
+                throw new IIOException("JPEG compression supported for 1- and 3-band byte images only!");
+            }
+            compressor = new TIFFJPEGCompressor(param);
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_ZLIB) {
+            compressor = new TIFFZLibCompressor(param, predictor);
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_PACKBITS) {
+            compressor = new TIFFPackBitsCompressor();
+        } else if (compression
+                == BaselineTIFFTagSet.COMPRESSION_DEFLATE) {
+            compressor = new TIFFDeflateCompressor(param, predictor);
+        } else {
+            // Determine inverse fill setting.
+            f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER);
+            boolean inverseFill = (f != null && f.getAsInt(0) == 2);
+
+            if (inverseFill) {
+                compressor = new TIFFLSBCompressor();
+            } else {
+                compressor = new TIFFNullCompressor();
+            }
+        }
+
+
+        this.colorConverter = null;
+        if (cm != null
+                && cm.getColorSpace().getType() == ColorSpace.TYPE_RGB) {
+            //
+            // Perform color conversion only if image has RGB color space.
+            //
+            if (photometricInterpretation
+                    == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR
+                    && compression
+                    != BaselineTIFFTagSet.COMPRESSION_JPEG) {
+                //
+                // Convert RGB to YCbCr only if compression type is not
+                // JPEG in which case this is handled implicitly by the
+                // compressor.
+                //
+                colorConverter = new TIFFYCbCrColorConverter(imageMetadata);
+            } else if (photometricInterpretation
+                    == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB) {
+                colorConverter = new TIFFCIELabColorConverter();
+            }
+        }
+
+        //
+        // Cannot at this time do YCbCr subsampling so set the
+        // YCbCrSubsampling field value to [1, 1] and the YCbCrPositioning
+        // field value to "cosited".
+        //
+        if(photometricInterpretation ==
+           BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR &&
+           compression !=
+           BaselineTIFFTagSet.COMPRESSION_JPEG) {
+            // Remove old subsampling and positioning fields.
+            rootIFD.removeTIFFField
+                (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
+            rootIFD.removeTIFFField
+                (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING);
+
+            // Add unity chrominance subsampling factors.
+            rootIFD.addTIFFField
+                (new TIFFField
+                    (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING),
+                     TIFFTag.TIFF_SHORT,
+                     2,
+                     new char[] {(char)1, (char)1}));
+
+            // Add cosited positioning.
+            rootIFD.addTIFFField
+                (new TIFFField
+                    (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING),
+                     TIFFTag.TIFF_SHORT,
+                     1,
+                     new char[] {
+                         (char)BaselineTIFFTagSet.Y_CB_CR_POSITIONING_COSITED
+                     }));
+        }
+
+        TIFFField photometricInterpretationField =
+            new TIFFField(
+                base.getTag(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION),
+                          photometricInterpretation);
+        rootIFD.addTIFFField(photometricInterpretationField);
+
+        this.bitsPerSample = new char[numBands + numExtraSamples];
+        this.bitDepth = 0;
+        for (int i = 0; i < numBands; i++) {
+            this.bitDepth = Math.max(bitDepth, sampleSize[i]);
+        }
+        if (bitDepth == 3) {
+            bitDepth = 4;
+        } else if (bitDepth > 4 && bitDepth < 8) {
+            bitDepth = 8;
+        } else if (bitDepth > 8 && bitDepth < 16) {
+            bitDepth = 16;
+        } else if (bitDepth > 16 && bitDepth < 32) {
+            bitDepth = 32;
+        } else if (bitDepth > 32) {
+            bitDepth = 64;
+        }
+
+        for (int i = 0; i < bitsPerSample.length; i++) {
+            bitsPerSample[i] = (char)bitDepth;
+        }
+
+        // Emit BitsPerSample. If the image is bilevel, emit if and only
+        // if already in the metadata and correct (count and value == 1).
+        if (bitsPerSample.length != 1 || bitsPerSample[0] != 1) {
+            TIFFField bitsPerSampleField =
+                new TIFFField(
+                           base.getTag(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE),
+                           TIFFTag.TIFF_SHORT,
+                           bitsPerSample.length,
+                           bitsPerSample);
+            rootIFD.addTIFFField(bitsPerSampleField);
+        } else { // bitsPerSample.length == 1 && bitsPerSample[0] == 1
+            TIFFField bitsPerSampleField =
+                rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
+            if(bitsPerSampleField != null) {
+                int[] bps = bitsPerSampleField.getAsInts();
+                if(bps == null || bps.length != 1 || bps[0] != 1) {
+                    rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
+                }
+            }
+        }
+
+        // Prepare SampleFormat field.
+        f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
+        if(f == null && (bitDepth == 16 || bitDepth == 32 || bitDepth == 64)) {
+            // Set up default content for 16-, 32-, and 64-bit cases.
+            char sampleFormatValue;
+            int dataType = sm.getDataType();
+            if(bitDepth == 16 && dataType == DataBuffer.TYPE_USHORT) {
+               sampleFormatValue =
+                   (char)BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER;
+            } else if((bitDepth == 32 && dataType == DataBuffer.TYPE_FLOAT) ||
+                (bitDepth == 64 && dataType == DataBuffer.TYPE_DOUBLE)) {
+                sampleFormatValue =
+                    (char)BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT;
+            } else {
+                sampleFormatValue =
+                    BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER;
+            }
+            this.sampleFormat = (int)sampleFormatValue;
+            char[] sampleFormatArray = new char[bitsPerSample.length];
+            Arrays.fill(sampleFormatArray, sampleFormatValue);
+
+            // Update the metadata.
+            TIFFTag sampleFormatTag =
+                base.getTag(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
+
+            TIFFField sampleFormatField =
+                new TIFFField(sampleFormatTag, TIFFTag.TIFF_SHORT,
+                              sampleFormatArray.length, sampleFormatArray);
+
+            rootIFD.addTIFFField(sampleFormatField);
+        } else if(f != null) {
+            // Get whatever was provided.
+            sampleFormat = f.getAsInt(0);
+        } else {
+            // Set default value for internal use only.
+            sampleFormat = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
+        }
+
+        if (extraSamples != null) {
+            TIFFField extraSamplesField =
+                new TIFFField(
+                           base.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES),
+                           TIFFTag.TIFF_SHORT,
+                           extraSamples.length,
+                           extraSamples);
+            rootIFD.addTIFFField(extraSamplesField);
+        } else {
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES);
+        }
+
+        TIFFField samplesPerPixelField =
+            new TIFFField(
+                         base.getTag(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL),
+                         bitsPerSample.length);
+        rootIFD.addTIFFField(samplesPerPixelField);
+
+        // Emit ColorMap if image is of palette color type
+        if (photometricInterpretation ==
+            BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR &&
+            cm instanceof IndexColorModel) {
+            char[] colorMap = new char[3*(1 << bitsPerSample[0])];
+
+            IndexColorModel icm = (IndexColorModel)cm;
+
+            // mapSize is determined by BitsPerSample, not by incoming ICM.
+            int mapSize = 1 << bitsPerSample[0];
+            int indexBound = Math.min(mapSize, icm.getMapSize());
+            for (int i = 0; i < indexBound; i++) {
+                colorMap[i] = (char)((icm.getRed(i)*65535)/255);
+                colorMap[mapSize + i] = (char)((icm.getGreen(i)*65535)/255);
+                colorMap[2*mapSize + i] = (char)((icm.getBlue(i)*65535)/255);
+            }
+
+            TIFFField colorMapField =
+                new TIFFField(
+                           base.getTag(BaselineTIFFTagSet.TAG_COLOR_MAP),
+                           TIFFTag.TIFF_SHORT,
+                           colorMap.length,
+                           colorMap);
+            rootIFD.addTIFFField(colorMapField);
+        } else {
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP);
+        }
+
+        // Emit ICCProfile if there is no ICCProfile field already in the
+        // metadata and the ColorSpace is non-standard ICC.
+        if(cm != null &&
+           rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE) == null &&
+           ImageUtil.isNonStandardICCColorSpace(cm.getColorSpace())) {
+            ICC_ColorSpace iccColorSpace = (ICC_ColorSpace)cm.getColorSpace();
+            byte[] iccProfileData = iccColorSpace.getProfile().getData();
+            TIFFField iccProfileField =
+                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_ICC_PROFILE),
+                              TIFFTag.TIFF_UNDEFINED,
+                              iccProfileData.length,
+                              iccProfileData);
+            rootIFD.addTIFFField(iccProfileField);
+        }
+
+        // Always emit XResolution and YResolution.
+
+        TIFFField XResolutionField =
+            rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_X_RESOLUTION);
+        TIFFField YResolutionField =
+            rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_Y_RESOLUTION);
+
+        if(XResolutionField == null && YResolutionField == null) {
+            long[][] resRational = new long[1][2];
+            resRational[0] = new long[2];
+
+            TIFFField ResolutionUnitField =
+                rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT);
+
+            // Don't force dimensionless if one of the other dimensional
+            // quantities is present.
+            if(ResolutionUnitField == null &&
+               rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_X_POSITION) == null &&
+               rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_Y_POSITION) == null) {
+                // Set resolution to unit and units to dimensionless.
+                resRational[0][0] = 1;
+                resRational[0][1] = 1;
+
+                ResolutionUnitField =
+                    new TIFFField(rootIFD.getTag
+                                  (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
+                                  BaselineTIFFTagSet.RESOLUTION_UNIT_NONE);
+                rootIFD.addTIFFField(ResolutionUnitField);
+            } else {
+                // Set resolution to a value which would make the maximum
+                // image dimension equal to 4 inches as arbitrarily stated
+                // in the description of ResolutionUnit in the TIFF 6.0
+                // specification. If the ResolutionUnit field specifies
+                // "none" then set the resolution to unity (1/1).
+                int resolutionUnit = ResolutionUnitField != null ?
+                    ResolutionUnitField.getAsInt(0) :
+                    BaselineTIFFTagSet.RESOLUTION_UNIT_INCH;
+                int maxDimension = Math.max(destWidth, destHeight);
+                switch(resolutionUnit) {
+                case BaselineTIFFTagSet.RESOLUTION_UNIT_INCH:
+                    resRational[0][0] = maxDimension;
+                    resRational[0][1] = 4;
+                    break;
+                case BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER:
+                    resRational[0][0] = 100L*maxDimension; // divide out 100
+                    resRational[0][1] = 4*254; // 2.54 cm/inch * 100
+                    break;
+                default:
+                    resRational[0][0] = 1;
+                    resRational[0][1] = 1;
+                }
+            }
+
+            XResolutionField =
+                new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION),
+                              TIFFTag.TIFF_RATIONAL,
+                              1,
+                              resRational);
+            rootIFD.addTIFFField(XResolutionField);
+
+            YResolutionField =
+                new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION),
+                              TIFFTag.TIFF_RATIONAL,
+                              1,
+                              resRational);
+            rootIFD.addTIFFField(YResolutionField);
+        } else if(XResolutionField == null && YResolutionField != null) {
+            // Set XResolution to YResolution.
+            long[] yResolution =
+                YResolutionField.getAsRational(0).clone();
+            XResolutionField =
+             new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION),
+                              TIFFTag.TIFF_RATIONAL,
+                              1,
+                              yResolution);
+            rootIFD.addTIFFField(XResolutionField);
+        } else if(XResolutionField != null && YResolutionField == null) {
+            // Set YResolution to XResolution.
+            long[] xResolution =
+                XResolutionField.getAsRational(0).clone();
+            YResolutionField =
+             new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION),
+                              TIFFTag.TIFF_RATIONAL,
+                              1,
+                              xResolution);
+            rootIFD.addTIFFField(YResolutionField);
+        }
+
+        // Set mandatory fields, overriding metadata passed in
+
+        int width = destWidth;
+        TIFFField imageWidthField =
+            new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_WIDTH),
+                          width);
+        rootIFD.addTIFFField(imageWidthField);
+
+        int height = destHeight;
+        TIFFField imageLengthField =
+            new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_LENGTH),
+                          height);
+        rootIFD.addTIFFField(imageLengthField);
+
+        // Determine rowsPerStrip
+
+        int rowsPerStrip;
+
+        TIFFField rowsPerStripField =
+            rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
+        if (rowsPerStripField != null) {
+            rowsPerStrip = rowsPerStripField.getAsInt(0);
+            if(rowsPerStrip < 0) {
+                rowsPerStrip = height;
+            }
+        } else {
+            int bitsPerPixel = bitDepth*(numBands + numExtraSamples);
+            int bytesPerRow = (bitsPerPixel*width + 7)/8;
+            rowsPerStrip =
+                Math.max(Math.max(DEFAULT_BYTES_PER_STRIP/bytesPerRow, 1), 8);
+        }
+        rowsPerStrip = Math.min(rowsPerStrip, height);
+
+        // Tiling flag.
+        boolean useTiling = false;
+
+        // Analyze tiling parameters
+        int tilingMode = param.getTilingMode();
+        if (tilingMode == ImageWriteParam.MODE_DISABLED ||
+            tilingMode == ImageWriteParam.MODE_DEFAULT) {
+            this.tileWidth = width;
+            this.tileLength = rowsPerStrip;
+            useTiling = false;
+        } else if (tilingMode == ImageWriteParam.MODE_EXPLICIT) {
+            tileWidth = param.getTileWidth();
+            tileLength = param.getTileHeight();
+            useTiling = true;
+        } else if (tilingMode == ImageWriteParam.MODE_COPY_FROM_METADATA) {
+            f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
+            if (f == null) {
+                tileWidth = width;
+                useTiling = false;
+            } else {
+                tileWidth = f.getAsInt(0);
+                useTiling = true;
+            }
+
+            f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH);
+            if (f == null) {
+                tileLength = rowsPerStrip;
+            } else {
+                tileLength = f.getAsInt(0);
+                useTiling = true;
+            }
+        } else {
+            throw new IIOException("Illegal value of tilingMode!");
+        }
+
+        if(compression == BaselineTIFFTagSet.COMPRESSION_JPEG) {
+            // Reset tile size per TTN2 spec for JPEG compression.
+            int subX;
+            int subY;
+            if(numBands == 1) {
+                subX = subY = 1;
+            } else {
+                subX = subY = TIFFJPEGCompressor.CHROMA_SUBSAMPLING;
+            }
+            if(useTiling) {
+                int MCUMultipleX = 8*subX;
+                int MCUMultipleY = 8*subY;
+                tileWidth =
+                    Math.max(MCUMultipleX*((tileWidth +
+                                            MCUMultipleX/2)/MCUMultipleX),
+                             MCUMultipleX);
+                tileLength =
+                    Math.max(MCUMultipleY*((tileLength +
+                                            MCUMultipleY/2)/MCUMultipleY),
+                             MCUMultipleY);
+            } else if(rowsPerStrip < height) {
+                int MCUMultiple = 8*Math.max(subX, subY);
+                rowsPerStrip = tileLength =
+                    Math.max(MCUMultiple*((tileLength +
+                                           MCUMultiple/2)/MCUMultiple),
+                             MCUMultiple);
+            }
+
+            // The written image may be unreadable if these fields are present.
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+
+            // Also remove fields related to the old JPEG encoding scheme
+            // which may be misleading when the compression type is JPEG.
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_PROC);
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_RESTART_INTERVAL);
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_LOSSLESS_PREDICTORS);
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_POINT_TRANSFORMS);
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_Q_TABLES);
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_DC_TABLES);
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_AC_TABLES);
+        } else if(isJPEGInterchange) {
+            // Force tile size to equal image size.
+            tileWidth = width;
+            tileLength = height;
+        } else if(useTiling) {
+            // Round tile size to multiple of 16 per TIFF 6.0 specification
+            // (see pages 67-68 of version 6.0.1 from Adobe).
+            int tileWidthRemainder = tileWidth % 16;
+            if(tileWidthRemainder != 0) {
+                // Round to nearest multiple of 16 not less than 16.
+                tileWidth = Math.max(16*((tileWidth + 8)/16), 16);
+                processWarningOccurred(currentImage,
+                    "Tile width rounded to multiple of 16.");
+            }
+
+            int tileLengthRemainder = tileLength % 16;
+            if(tileLengthRemainder != 0) {
+                // Round to nearest multiple of 16 not less than 16.
+                tileLength = Math.max(16*((tileLength + 8)/16), 16);
+                processWarningOccurred(currentImage,
+                    "Tile height rounded to multiple of 16.");
+            }
+        }
+
+        this.tilesAcross = (width + tileWidth - 1)/tileWidth;
+        this.tilesDown = (height + tileLength - 1)/tileLength;
+
+        if (!useTiling) {
+            this.isTiled = false;
+
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH);
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS);
+
+            rowsPerStripField =
+              new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP),
+                            rowsPerStrip);
+            rootIFD.addTIFFField(rowsPerStripField);
+
+            TIFFField stripOffsetsField =
+                new TIFFField(
+                         base.getTag(BaselineTIFFTagSet.TAG_STRIP_OFFSETS),
+                         TIFFTag.TIFF_LONG,
+                         tilesDown);
+            rootIFD.addTIFFField(stripOffsetsField);
+
+            TIFFField stripByteCountsField =
+                new TIFFField(
+                         base.getTag(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS),
+                         TIFFTag.TIFF_LONG,
+                         tilesDown);
+            rootIFD.addTIFFField(stripByteCountsField);
+        } else {
+            this.isTiled = true;
+
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
+            rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
+
+            TIFFField tileWidthField =
+                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_TILE_WIDTH),
+                              tileWidth);
+            rootIFD.addTIFFField(tileWidthField);
+
+            TIFFField tileLengthField =
+                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_TILE_LENGTH),
+                              tileLength);
+            rootIFD.addTIFFField(tileLengthField);
+
+            TIFFField tileOffsetsField =
+                new TIFFField(
+                         base.getTag(BaselineTIFFTagSet.TAG_TILE_OFFSETS),
+                         TIFFTag.TIFF_LONG,
+                         tilesDown*tilesAcross);
+            rootIFD.addTIFFField(tileOffsetsField);
+
+            TIFFField tileByteCountsField =
+                new TIFFField(
+                         base.getTag(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS),
+                         TIFFTag.TIFF_LONG,
+                         tilesDown*tilesAcross);
+            rootIFD.addTIFFField(tileByteCountsField);
+        }
+
+        if(isExif) {
+            //
+            // Ensure presence of mandatory fields and absence of prohibited
+            // fields and those that duplicate information in JPEG marker
+            // segments per tables 14-18 of the Exif 2.2 specification.
+            //
+
+            // If an empty image is being written or inserted then infer
+            // that the primary IFD is being set up.
+            boolean isPrimaryIFD = isEncodingEmpty();
+
+            // Handle TIFF fields in order of increasing tag number.
+            if(compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
+                // ImageWidth
+                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
+
+                // ImageLength
+                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
+
+                // BitsPerSample
+                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
+                // Compression
+                if(isPrimaryIFD) {
+                    rootIFD.removeTIFFField
+                        (BaselineTIFFTagSet.TAG_COMPRESSION);
+                }
+
+                // PhotometricInterpretation
+                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
+
+                // StripOffsets
+                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
+
+                // SamplesPerPixel
+                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
+
+                // RowsPerStrip
+                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
+
+                // StripByteCounts
+                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
+                // XResolution and YResolution are handled above for all TIFFs.
+
+                // PlanarConfiguration
+                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
+
+                // ResolutionUnit
+                if(rootIFD.getTIFFField
+                   (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT) == null) {
+                    f = new TIFFField(base.getTag
+                                      (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
+                                      BaselineTIFFTagSet.RESOLUTION_UNIT_INCH);
+                    rootIFD.addTIFFField(f);
+                }
+
+                if(isPrimaryIFD) {
+                    // JPEGInterchangeFormat
+                    rootIFD.removeTIFFField
+                        (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
+
+                    // JPEGInterchangeFormatLength
+                    rootIFD.removeTIFFField
+                        (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+
+                    // YCbCrSubsampling
+                    rootIFD.removeTIFFField
+                        (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
+
+                    // YCbCrPositioning
+                    if(rootIFD.getTIFFField
+                       (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING) == null) {
+                        f = new TIFFField
+                            (base.getTag
+                             (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING),
+                             TIFFTag.TIFF_SHORT,
+                             1,
+                             new char[] {
+                                 (char)BaselineTIFFTagSet.Y_CB_CR_POSITIONING_CENTERED
+                             });
+                        rootIFD.addTIFFField(f);
+                    }
+                } else { // Thumbnail IFD
+                    // JPEGInterchangeFormat
+                    f = new TIFFField
+                        (base.getTag
+                         (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT),
+                         TIFFTag.TIFF_LONG,
+                         1);
+                    rootIFD.addTIFFField(f);
+
+                    // JPEGInterchangeFormatLength
+                    f = new TIFFField
+                        (base.getTag
+                         (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH),
+                         TIFFTag.TIFF_LONG,
+                         1);
+                    rootIFD.addTIFFField(f);
+
+                    // YCbCrSubsampling
+                    rootIFD.removeTIFFField
+                        (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
+                }
+            } else { // Uncompressed
+                // ImageWidth through PlanarConfiguration are set above.
+                // XResolution and YResolution are handled above for all TIFFs.
+
+                // ResolutionUnit
+                if(rootIFD.getTIFFField
+                   (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT) == null) {
+                    f = new TIFFField(base.getTag
+                                      (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
+                                      BaselineTIFFTagSet.RESOLUTION_UNIT_INCH);
+                    rootIFD.addTIFFField(f);
+                }
+
+
+                // JPEGInterchangeFormat
+                rootIFD.removeTIFFField
+                    (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
+
+                // JPEGInterchangeFormatLength
+                rootIFD.removeTIFFField
+                    (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+
+                if(photometricInterpretation ==
+                   BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB) {
+                    // YCbCrCoefficients
+                    rootIFD.removeTIFFField
+                        (BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS);
+
+                    // YCbCrSubsampling
+                    rootIFD.removeTIFFField
+                        (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
+
+                    // YCbCrPositioning
+                    rootIFD.removeTIFFField
+                        (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING);
+                }
+            }
+
+            // Get Exif tags.
+            TIFFTagSet exifTags = ExifTIFFTagSet.getInstance();
+
+            // Retrieve or create the Exif IFD.
+            TIFFIFD exifIFD = null;
+            f = rootIFD.getTIFFField
+                (ExifParentTIFFTagSet.TAG_EXIF_IFD_POINTER);
+            if(f != null && f.hasDirectory()) {
+                // Retrieve the Exif IFD.
+                exifIFD = (TIFFIFD)f.getDirectory();
+            } else if(isPrimaryIFD) {
+                // Create the Exif IFD.
+                List<TIFFTagSet> exifTagSets = new ArrayList<TIFFTagSet>(1);
+                exifTagSets.add(exifTags);
+                exifIFD = new TIFFIFD(exifTagSets);
+
+                // Add it to the root IFD.
+                TIFFTagSet tagSet = ExifParentTIFFTagSet.getInstance();
+                TIFFTag exifIFDTag =
+                    tagSet.getTag(ExifParentTIFFTagSet.TAG_EXIF_IFD_POINTER);
+                rootIFD.addTIFFField(new TIFFField(exifIFDTag,
+                                                   TIFFTag.TIFF_LONG,
+                                                   1L,
+                                                   exifIFD));
+            }
+
+            if(exifIFD != null) {
+                // Handle Exif private fields in order of increasing
+                // tag number.
+
+                // ExifVersion
+                if(exifIFD.getTIFFField
+                   (ExifTIFFTagSet.TAG_EXIF_VERSION) == null) {
+                    f = new TIFFField
+                        (exifTags.getTag(ExifTIFFTagSet.TAG_EXIF_VERSION),
+                         TIFFTag.TIFF_UNDEFINED,
+                         4,
+                         ExifTIFFTagSet.EXIF_VERSION_2_2.getBytes(StandardCharsets.US_ASCII));
+                    exifIFD.addTIFFField(f);
+                }
+
+                if(compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
+                    // ComponentsConfiguration
+                    if(exifIFD.getTIFFField
+                       (ExifTIFFTagSet.TAG_COMPONENTS_CONFIGURATION) == null) {
+                        f = new TIFFField
+                            (exifTags.getTag
+                             (ExifTIFFTagSet.TAG_COMPONENTS_CONFIGURATION),
+                             TIFFTag.TIFF_UNDEFINED,
+                             4,
+                             new byte[] {
+                                 (byte)ExifTIFFTagSet.COMPONENTS_CONFIGURATION_Y,
+                                 (byte)ExifTIFFTagSet.COMPONENTS_CONFIGURATION_CB,
+                                 (byte)ExifTIFFTagSet.COMPONENTS_CONFIGURATION_CR,
+                                 (byte)0
+                             });
+                        exifIFD.addTIFFField(f);
+                    }
+                } else {
+                    // ComponentsConfiguration
+                    exifIFD.removeTIFFField
+                        (ExifTIFFTagSet.TAG_COMPONENTS_CONFIGURATION);
+
+                    // CompressedBitsPerPixel
+                    exifIFD.removeTIFFField
+                        (ExifTIFFTagSet.TAG_COMPRESSED_BITS_PER_PIXEL);
+                }
+
+                // FlashpixVersion
+                if(exifIFD.getTIFFField
+                   (ExifTIFFTagSet.TAG_FLASHPIX_VERSION) == null) {
+                    f = new TIFFField
+                        (exifTags.getTag(ExifTIFFTagSet.TAG_FLASHPIX_VERSION),
+                         TIFFTag.TIFF_UNDEFINED,
+                         4,
+                     new byte[] {(byte)'0', (byte)'1', (byte)'0', (byte)'0'});
+                    exifIFD.addTIFFField(f);
+                }
+
+                // ColorSpace
+                if(exifIFD.getTIFFField
+                   (ExifTIFFTagSet.TAG_COLOR_SPACE) == null) {
+                    f = new TIFFField
+                        (exifTags.getTag(ExifTIFFTagSet.TAG_COLOR_SPACE),
+                         TIFFTag.TIFF_SHORT,
+                         1,
+                         new char[] {
+                             (char)ExifTIFFTagSet.COLOR_SPACE_SRGB
+                         });
+                    exifIFD.addTIFFField(f);
+                }
+
+                if(compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
+                    // PixelXDimension
+                    if(exifIFD.getTIFFField
+                       (ExifTIFFTagSet.TAG_PIXEL_X_DIMENSION) == null) {
+                        f = new TIFFField
+                            (exifTags.getTag(ExifTIFFTagSet.TAG_PIXEL_X_DIMENSION),
+                             width);
+                        exifIFD.addTIFFField(f);
+                    }
+
+                    // PixelYDimension
+                    if(exifIFD.getTIFFField
+                       (ExifTIFFTagSet.TAG_PIXEL_Y_DIMENSION) == null) {
+                        f = new TIFFField
+                            (exifTags.getTag(ExifTIFFTagSet.TAG_PIXEL_Y_DIMENSION),
+                             height);
+                        exifIFD.addTIFFField(f);
+                    }
+                } else {
+                    exifIFD.removeTIFFField
+                        (ExifTIFFTagSet.TAG_INTEROPERABILITY_IFD_POINTER);
+                }
+            }
+
+        } // if(isExif)
+    }
+
+    ImageTypeSpecifier getImageType() {
+        return imageType;
+    }
+
+    /**
+       @param tileRect The area to be written which might be outside the image.
+     */
+    private int writeTile(Rectangle tileRect, TIFFCompressor compressor)
+        throws IOException {
+        // Determine the rectangle which will actually be written
+        // and set the padding flag. Padding will occur only when the
+        // image is written as a tiled TIFF and the tile bounds are not
+        // contained within the image bounds.
+        Rectangle activeRect;
+        boolean isPadded;
+        Rectangle imageBounds =
+            new Rectangle(image.getMinX(), image.getMinY(),
+                          image.getWidth(), image.getHeight());
+        if(!isTiled) {
+            // Stripped
+            activeRect = tileRect.intersection(imageBounds);
+            tileRect = activeRect;
+            isPadded = false;
+        } else if(imageBounds.contains(tileRect)) {
+            // Tiled, tile within image bounds
+            activeRect = tileRect;
+            isPadded = false;
+        } else {
+            // Tiled, tile not within image bounds
+            activeRect = imageBounds.intersection(tileRect);
+            isPadded = true;
+        }
+
+        // Return early if empty intersection.
+        if(activeRect.isEmpty()) {
+            return 0;
+        }
+
+        int minX = tileRect.x;
+        int minY = tileRect.y;
+        int width = tileRect.width;
+        int height = tileRect.height;
+
+        if(isImageSimple) {
+
+            SampleModel sm = image.getSampleModel();
+
+            // Read only data from the active rectangle.
+            Raster raster = image.getData(activeRect);
+
+            // If padding is required, create a larger Raster and fill
+            // it from the active rectangle.
+            if(isPadded) {
+                WritableRaster wr =
+                    raster.createCompatibleWritableRaster(minX, minY,
+                                                          width, height);
+                wr.setRect(raster);
+                raster = wr;
+            }
+
+            if(isBilevel) {
+                byte[] buf = ImageUtil.getPackedBinaryData(raster,
+                                                           tileRect);
+
+                if(isInverted) {
+                    DataBuffer dbb = raster.getDataBuffer();
+                    if(dbb instanceof DataBufferByte &&
+                       buf == ((DataBufferByte)dbb).getData()) {
+                        byte[] bbuf = new byte[buf.length];
+                        int len = buf.length;
+                        for(int i = 0; i < len; i++) {
+                            bbuf[i] = (byte)(buf[i] ^ 0xff);
+                        }
+                        buf = bbuf;
+                    } else {
+                        int len = buf.length;
+                        for(int i = 0; i < len; i++) {
+                            buf[i] ^= 0xff;
+                        }
+                    }
+                }
+
+                return compressor.encode(buf, 0,
+                                         width, height, sampleSize,
+                                         (tileRect.width + 7)/8);
+            } else if(bitDepth == 8 &&
+                      sm.getDataType() == DataBuffer.TYPE_BYTE) {
+                ComponentSampleModel csm =
+                    (ComponentSampleModel)raster.getSampleModel();
+
+                byte[] buf =
+                    ((DataBufferByte)raster.getDataBuffer()).getData();
+
+                int off =
+                    csm.getOffset(minX -
+                                  raster.getSampleModelTranslateX(),
+                                  minY -
+                                  raster.getSampleModelTranslateY());
+
+                return compressor.encode(buf, off,
+                                         width, height, sampleSize,
+                                         csm.getScanlineStride());
+            }
+        }
+
+        // Set offsets and skips based on source subsampling factors
+        int xOffset = minX;
+        int xSkip = periodX;
+        int yOffset = minY;
+        int ySkip = periodY;
+
+        // Early exit if no data for this pass
+        int hpixels = (width + xSkip - 1)/xSkip;
+        int vpixels = (height + ySkip - 1)/ySkip;
+        if (hpixels == 0 || vpixels == 0) {
+            return 0;
+        }
+
+        // Convert X offset and skip from pixels to samples
+        xOffset *= numBands;
+        xSkip *= numBands;
+
+        // Initialize sizes
+        int samplesPerByte = 8/bitDepth;
+        int numSamples = width*numBands;
+        int bytesPerRow = hpixels*numBands;
+
+        // Update number of bytes per row.
+        if (bitDepth < 8) {
+            bytesPerRow = (bytesPerRow + samplesPerByte - 1)/samplesPerByte;
+        } else if (bitDepth == 16) {
+            bytesPerRow *= 2;
+        } else if (bitDepth == 32) {
+            bytesPerRow *= 4;
+        } else if (bitDepth == 64) {
+            bytesPerRow *= 8;
+        }
+
+        // Create row buffers
+        int[] samples = null;
+        float[] fsamples = null;
+        double[] dsamples = null;
+        if(sampleFormat == BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
+            if (bitDepth == 32) {
+                fsamples = new float[numSamples];
+            } else {
+                dsamples = new double[numSamples];
+            }
+        } else {
+            samples = new int[numSamples];
+        }
+
+        // Create tile buffer
+        byte[] currTile = new byte[bytesPerRow*vpixels];
+
+        // Sub-optimal case: shy of "isImageSimple" only by virtue of
+        // not being contiguous.
+        if(!isInverted &&                  // no inversion
+           !isRescaling &&                 // no value rescaling
+           sourceBands == null &&          // no subbanding
+           periodX == 1 && periodY == 1 && // no subsampling
+           colorConverter == null) {
+
+            SampleModel sm = image.getSampleModel();
+
+            if(sm instanceof ComponentSampleModel &&       // component
+               bitDepth == 8 &&                            // 8 bits/sample
+               sm.getDataType() == DataBuffer.TYPE_BYTE) { // byte type
+
+                // Read only data from the active rectangle.
+                Raster raster = image.getData(activeRect);
+
+                // If padding is required, create a larger Raster and fill
+                // it from the active rectangle.
+                if(isPadded) {
+                    WritableRaster wr =
+                        raster.createCompatibleWritableRaster(minX, minY,
+                                                              width, height);
+                    wr.setRect(raster);
+                    raster = wr;
+                }
+
+                // Get SampleModel info.
+                ComponentSampleModel csm =
+                    (ComponentSampleModel)raster.getSampleModel();
+                int[] bankIndices = csm.getBankIndices();
+                byte[][] bankData =
+                    ((DataBufferByte)raster.getDataBuffer()).getBankData();
+                int lineStride = csm.getScanlineStride();
+                int pixelStride = csm.getPixelStride();
+
+                // Copy the data into a contiguous pixel interleaved buffer.
+                for(int k = 0; k < numBands; k++) {
+                    byte[] bandData = bankData[bankIndices[k]];
+                    int lineOffset =
+                        csm.getOffset(raster.getMinX() -
+                                      raster.getSampleModelTranslateX(),
+                                      raster.getMinY() -
+                                      raster.getSampleModelTranslateY(), k);
+                    int idx = k;
+                    for(int j = 0; j < vpixels; j++) {
+                        int offset = lineOffset;
+                        for(int i = 0; i < hpixels; i++) {
+                            currTile[idx] = bandData[offset];
+                            idx += numBands;
+                            offset += pixelStride;
+                        }
+                        lineOffset += lineStride;
+                    }
+                }
+
+                // Compressor and return.
+                return compressor.encode(currTile, 0,
+                                         width, height, sampleSize,
+                                         width*numBands);
+            }
+        }
+
+        int tcount = 0;
+
+        // Save active rectangle variables.
+        int activeMinX = activeRect.x;
+        int activeMinY = activeRect.y;
+        int activeMaxY = activeMinY + activeRect.height - 1;
+        int activeWidth = activeRect.width;
+
+        // Set a SampleModel for use in padding.
+        SampleModel rowSampleModel = null;
+        if(isPadded) {
+           rowSampleModel =
+               image.getSampleModel().createCompatibleSampleModel(width, 1);
+        }
+
+        for (int row = yOffset; row < yOffset + height; row += ySkip) {
+            Raster ras = null;
+            if(isPadded) {
+                // Create a raster for the entire row.
+                WritableRaster wr =
+                    Raster.createWritableRaster(rowSampleModel,
+                                                new Point(minX, row));
+
+                // Populate the raster from the active sub-row, if any.
+                if(row >= activeMinY && row <= activeMaxY) {
+                    Rectangle rect =
+                        new Rectangle(activeMinX, row, activeWidth, 1);
+                    ras = image.getData(rect);
+                    wr.setRect(ras);
+                }
+
+                // Update the raster variable.
+                ras = wr;
+            } else {
+                Rectangle rect = new Rectangle(minX, row, width, 1);
+                ras = image.getData(rect);
+            }
+            if (sourceBands != null) {
+                ras = ras.createChild(minX, row, width, 1, minX, row,
+                                      sourceBands);
+            }
+
+            if(sampleFormat ==
+               BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
+                if (fsamples != null) {
+                    ras.getPixels(minX, row, width, 1, fsamples);
+                } else {
+                    ras.getPixels(minX, row, width, 1, dsamples);
+                }
+            } else {
+                ras.getPixels(minX, row, width, 1, samples);
+
+                if ((nativePhotometricInterpretation ==
+                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO &&
+                     photometricInterpretation ==
+                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) ||
+                    (nativePhotometricInterpretation ==
+                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO &&
+                     photometricInterpretation ==
+                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO)) {
+                    int bitMask = (1 << bitDepth) - 1;
+                    for (int s = 0; s < numSamples; s++) {
+                        samples[s] ^= bitMask;
+                    }
+                }
+            }
+
+            if (colorConverter != null) {
+                int idx = 0;
+                float[] result = new float[3];
+
+                if(sampleFormat ==
+                   BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
+                    if (bitDepth == 32) {
+                        for (int i = 0; i < width; i++) {
+                            float r = fsamples[idx];
+                            float g = fsamples[idx + 1];
+                            float b = fsamples[idx + 2];
+
+                            colorConverter.fromRGB(r, g, b, result);
+
+                            fsamples[idx] = result[0];
+                            fsamples[idx + 1] = result[1];
+                            fsamples[idx + 2] = result[2];
+
+                            idx += 3;
+                        }
+                    } else {
+                        for (int i = 0; i < width; i++) {
+                            // Note: Possible loss of precision.
+                            float r = (float)dsamples[idx];
+                            float g = (float)dsamples[idx + 1];
+                            float b = (float)dsamples[idx + 2];
+
+                            colorConverter.fromRGB(r, g, b, result);
+
+                            dsamples[idx] = result[0];
+                            dsamples[idx + 1] = result[1];
+                            dsamples[idx + 2] = result[2];
+
+                            idx += 3;
+                        }
+                    }
+                } else {
+                    for (int i = 0; i < width; i++) {
+                        float r = (float)samples[idx];
+                        float g = (float)samples[idx + 1];
+                        float b = (float)samples[idx + 2];
+
+                        colorConverter.fromRGB(r, g, b, result);
+
+                        samples[idx] = (int)(result[0]);
+                        samples[idx + 1] = (int)(result[1]);
+                        samples[idx + 2] = (int)(result[2]);
+
+                        idx += 3;
+                    }
+                }
+            }
+
+            int tmp = 0;
+            int pos = 0;
+
+            switch (bitDepth) {
+            case 1: case 2: case 4:
+                // Image can only have a single band
+
+                if(isRescaling) {
+                    for (int s = 0; s < numSamples; s += xSkip) {
+                        byte val = scale0[samples[s]];
+                        tmp = (tmp << bitDepth) | val;
+
+                        if (++pos == samplesPerByte) {
+                            currTile[tcount++] = (byte)tmp;
+                            tmp = 0;
+                            pos = 0;
+                        }
+                    }
+                } else {
+                    for (int s = 0; s < numSamples; s += xSkip) {
+                        byte val = (byte)samples[s];
+                        tmp = (tmp << bitDepth) | val;
+
+                        if (++pos == samplesPerByte) {
+                            currTile[tcount++] = (byte)tmp;
+                            tmp = 0;
+                            pos = 0;
+                        }
+                    }
+                }
+
+                // Left shift the last byte
+                if (pos != 0) {
+                    tmp <<= ((8/bitDepth) - pos)*bitDepth;
+                    currTile[tcount++] = (byte)tmp;
+                }
+                break;
+
+            case 8:
+                if (numBands == 1) {
+                    if(isRescaling) {
+                        for (int s = 0; s < numSamples; s += xSkip) {
+                            currTile[tcount++] = scale0[samples[s]];
+                        }
+                    } else {
+                        for (int s = 0; s < numSamples; s += xSkip) {
+                            currTile[tcount++] = (byte)samples[s];
+                        }
+                    }
+                } else {
+                    if(isRescaling) {
+                        for (int s = 0; s < numSamples; s += xSkip) {
+                            for (int b = 0; b < numBands; b++) {
+                                currTile[tcount++] = scale[b][samples[s + b]];
+                            }
+                        }
+                    } else {
+                        for (int s = 0; s < numSamples; s += xSkip) {
+                            for (int b = 0; b < numBands; b++) {
+                                currTile[tcount++] = (byte)samples[s + b];
+                            }
+                        }
+                    }
+                }
+                break;
+
+            case 16:
+                if(isRescaling) {
+                    if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
+                        for (int s = 0; s < numSamples; s += xSkip) {
+                            for (int b = 0; b < numBands; b++) {
+                                int sample = samples[s + b];
+                                currTile[tcount++] = scaleh[b][sample];
+                                currTile[tcount++] = scalel[b][sample];
+                            }
+                        }
+                    } else { // ByteOrder.LITLE_ENDIAN
+                        for (int s = 0; s < numSamples; s += xSkip) {
+                            for (int b = 0; b < numBands; b++) {
+                                int sample = samples[s + b];
+                                currTile[tcount++] = scalel[b][sample];
+                                currTile[tcount++] = scaleh[b][sample];
+                            }
+                        }
+                    }
+                } else {
+                    if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
+                        for (int s = 0; s < numSamples; s += xSkip) {
+                            for (int b = 0; b < numBands; b++) {
+                                int sample = samples[s + b];
+                                currTile[tcount++] =
+                                    (byte)((sample >>> 8) & 0xff);
+                                currTile[tcount++] =
+                                    (byte)(sample & 0xff);
+                            }
+                        }
+                    } else { // ByteOrder.LITLE_ENDIAN
+                        for (int s = 0; s < numSamples; s += xSkip) {
+                            for (int b = 0; b < numBands; b++) {
+                                int sample = samples[s + b];
+                                currTile[tcount++] =
+                                    (byte)(sample & 0xff);
+                                currTile[tcount++] =
+                                    (byte)((sample >>> 8) & 0xff);
+                            }
+                        }
+                    }
+                }
+                break;
+
+            case 32:
+                if(sampleFormat ==
+                   BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
+                    if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
+                        for (int s = 0; s < numSamples; s += xSkip) {
+                            for (int b = 0; b < numBands; b++) {
+                                float fsample = fsamples[s + b];
+                                int isample = Float.floatToIntBits(fsample);
+                                currTile[tcount++] =
+                                    (byte)((isample & 0xff000000) >> 24);
+                                currTile[tcount++] =
+                                    (byte)((isample & 0x00ff0000) >> 16);
+                                currTile[tcount++] =
+                                    (byte)((isample & 0x0000ff00) >> 8);
+                                currTile[tcount++] =
+                                    (byte)(isample & 0x000000ff);
+                            }
+                        }
+                    } else { // ByteOrder.LITLE_ENDIAN
+                        for (int s = 0; s < numSamples; s += xSkip) {
+                            for (int b = 0; b < numBands; b++) {
+                                float fsample = fsamples[s + b];
+                                int isample = Float.floatToIntBits(fsample);
+                                currTile[tcount++] =
+                                    (byte)(isample & 0x000000ff);
+                                currTile[tcount++] =
+                                    (byte)((isample & 0x0000ff00) >> 8);
+                                currTile[tcount++] =
+                                    (byte)((isample & 0x00ff0000) >> 16);
+                                currTile[tcount++] =
+                                    (byte)((isample & 0xff000000) >> 24);
+                            }
+                        }
+                    }
+                } else {
+                    if(isRescaling) {
+                        long[] maxIn = new long[numBands];
+                        long[] halfIn = new long[numBands];
+                        long maxOut = (1L << (long)bitDepth) - 1L;
+
+                        for (int b = 0; b < numBands; b++) {
+                            maxIn[b] = ((1L << (long)sampleSize[b]) - 1L);
+                            halfIn[b] = maxIn[b]/2;
+                        }
+
+                        if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
+                            for (int s = 0; s < numSamples; s += xSkip) {
+                                for (int b = 0; b < numBands; b++) {
+                                    long sampleOut =
+                                        (samples[s + b]*maxOut + halfIn[b])/
+                                        maxIn[b];
+                                    currTile[tcount++] =
+                                        (byte)((sampleOut & 0xff000000) >> 24);
+                                    currTile[tcount++] =
+                                        (byte)((sampleOut & 0x00ff0000) >> 16);
+                                    currTile[tcount++] =
+                                        (byte)((sampleOut & 0x0000ff00) >> 8);
+                                    currTile[tcount++] =
+                                        (byte)(sampleOut & 0x000000ff);
+                                }
+                            }
+                        } else { // ByteOrder.LITLE_ENDIAN
+                            for (int s = 0; s < numSamples; s += xSkip) {
+                                for (int b = 0; b < numBands; b++) {
+                                    long sampleOut =
+                                        (samples[s + b]*maxOut + halfIn[b])/
+                                        maxIn[b];
+                                    currTile[tcount++] =
+                                        (byte)(sampleOut & 0x000000ff);
+                                    currTile[tcount++] =
+                                        (byte)((sampleOut & 0x0000ff00) >> 8);
+                                    currTile[tcount++] =
+                                        (byte)((sampleOut & 0x00ff0000) >> 16);
+                                    currTile[tcount++] =
+                                        (byte)((sampleOut & 0xff000000) >> 24);
+                                }
+                            }
+                        }
+                    } else {
+                        if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
+                            for (int s = 0; s < numSamples; s += xSkip) {
+                                for (int b = 0; b < numBands; b++) {
+                                    int isample = samples[s + b];
+                                    currTile[tcount++] =
+                                        (byte)((isample & 0xff000000) >> 24);
+                                    currTile[tcount++] =
+                                        (byte)((isample & 0x00ff0000) >> 16);
+                                    currTile[tcount++] =
+                                        (byte)((isample & 0x0000ff00) >> 8);
+                                    currTile[tcount++] =
+                                        (byte)(isample & 0x000000ff);
+                                }
+                            }
+                        } else { // ByteOrder.LITLE_ENDIAN
+                            for (int s = 0; s < numSamples; s += xSkip) {
+                                for (int b = 0; b < numBands; b++) {
+                                    int isample = samples[s + b];
+                                    currTile[tcount++] =
+                                        (byte)(isample & 0x000000ff);
+                                    currTile[tcount++] =
+                                        (byte)((isample & 0x0000ff00) >> 8);
+                                    currTile[tcount++] =
+                                        (byte)((isample & 0x00ff0000) >> 16);
+                                    currTile[tcount++] =
+                                        (byte)((isample & 0xff000000) >> 24);
+                                }
+                            }
+                        }
+                    }
+                }
+                break;
+
+            case 64:
+                if(sampleFormat ==
+                   BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
+                    if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
+                        for (int s = 0; s < numSamples; s += xSkip) {
+                            for (int b = 0; b < numBands; b++) {
+                                double dsample = dsamples[s + b];
+                                long lsample = Double.doubleToLongBits(dsample);
+                                currTile[tcount++] =
+                                    (byte)((lsample & 0xff00000000000000L) >> 56);
+                                currTile[tcount++] =
+                                    (byte)((lsample & 0x00ff000000000000L) >> 48);
+                                currTile[tcount++] =
+                                    (byte)((lsample & 0x0000ff0000000000L) >> 40);
+                                currTile[tcount++] =
+                                    (byte)((lsample & 0x000000ff00000000L) >> 32);
+                                currTile[tcount++] =
+                                    (byte)((lsample & 0x00000000ff000000L) >> 24);
+                                currTile[tcount++] =
+                                    (byte)((lsample & 0x0000000000ff0000L) >> 16);
+                                currTile[tcount++] =
+                                    (byte)((lsample & 0x000000000000ff00L) >> 8);
+                                currTile[tcount++] =
+                                    (byte)(lsample & 0x00000000000000ffL);
+                            }
+                        }
+                    } else { // ByteOrder.LITLE_ENDIAN
+                        for (int s = 0; s < numSamples; s += xSkip) {
+                            for (int b = 0; b < numBands; b++) {
+                                double dsample = dsamples[s + b];
+                                long lsample = Double.doubleToLongBits(dsample);
+                                currTile[tcount++] =
+                                    (byte)(lsample & 0x00000000000000ffL);
+                                currTile[tcount++] =
+                                    (byte)((lsample & 0x000000000000ff00L) >> 8);
+                                currTile[tcount++] =
+                                    (byte)((lsample & 0x0000000000ff0000L) >> 16);
+                                currTile[tcount++] =
+                                    (byte)((lsample & 0x00000000ff000000L) >> 24);
+                                currTile[tcount++] =
+                                    (byte)((lsample & 0x000000ff00000000L) >> 32);
+                                currTile[tcount++] =
+                                    (byte)((lsample & 0x0000ff0000000000L) >> 40);
+                                currTile[tcount++] =
+                                    (byte)((lsample & 0x00ff000000000000L) >> 48);
+                                currTile[tcount++] =
+                                    (byte)((lsample & 0xff00000000000000L) >> 56);
+                            }
+                        }
+                    }
+                }
+                break;
+            }
+        }
+
+        int[] bitsPerSample = new int[numBands];
+        for (int i = 0; i < bitsPerSample.length; i++) {
+            bitsPerSample[i] = bitDepth;
+        }
+
+        int byteCount = compressor.encode(currTile, 0,
+                                          hpixels, vpixels,
+                                          bitsPerSample,
+                                          bytesPerRow);
+        return byteCount;
+    }
+
+    // Check two int arrays for value equality, always returns false
+    // if either array is null
+    private boolean equals(int[] s0, int[] s1) {
+        if (s0 == null || s1 == null) {
+            return false;
+        }
+        if (s0.length != s1.length) {
+            return false;
+        }
+        for (int i = 0; i < s0.length; i++) {
+            if (s0[i] != s1[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // Initialize the scale/scale0 or scaleh/scalel arrays to
+    // hold the results of scaling an input value to the desired
+    // output bit depth
+    private void initializeScaleTables(int[] sampleSize) {
+        // Save the sample size in the instance variable.
+
+        // If the existing tables are still valid, just return.
+        if (bitDepth == scalingBitDepth &&
+            equals(sampleSize, this.sampleSize)) {
+            return;
+        }
+
+        // Reset scaling variables.
+        isRescaling = false;
+        scalingBitDepth = -1;
+        scale = scalel = scaleh = null;
+        scale0 = null;
+
+        // Set global sample size to parameter.
+        this.sampleSize = sampleSize;
+
+        // Check whether rescaling is called for.
+        if(bitDepth <= 16) {
+            for(int b = 0; b < numBands; b++) {
+                if(sampleSize[b] != bitDepth) {
+                    isRescaling = true;
+                    break;
+                }
+            }
+        }
+
+        // If not rescaling then return after saving the sample size.
+        if(!isRescaling) {
+            return;
+        }
+
+        // Compute new tables
+        this.scalingBitDepth = bitDepth;
+        int maxOutSample = (1 << bitDepth) - 1;
+        if (bitDepth <= 8) {
+            scale = new byte[numBands][];
+            for (int b = 0; b < numBands; b++) {
+                int maxInSample = (1 << sampleSize[b]) - 1;
+                int halfMaxInSample = maxInSample/2;
+                scale[b] = new byte[maxInSample + 1];
+                for (int s = 0; s <= maxInSample; s++) {
+                    scale[b][s] =
+                        (byte)((s*maxOutSample + halfMaxInSample)/maxInSample);
+                }
+            }
+            scale0 = scale[0];
+            scaleh = scalel = null;
+        } else if(bitDepth <= 16) {
+            // Divide scaling table into high and low bytes
+            scaleh = new byte[numBands][];
+            scalel = new byte[numBands][];
+
+            for (int b = 0; b < numBands; b++) {
+                int maxInSample = (1 << sampleSize[b]) - 1;
+                int halfMaxInSample = maxInSample/2;
+                scaleh[b] = new byte[maxInSample + 1];
+                scalel[b] = new byte[maxInSample + 1];
+                for (int s = 0; s <= maxInSample; s++) {
+                    int val = (s*maxOutSample + halfMaxInSample)/maxInSample;
+                    scaleh[b][s] = (byte)(val >> 8);
+                    scalel[b][s] = (byte)(val & 0xff);
+                }
+            }
+            scale = null;
+            scale0 = null;
+        }
+    }
+
+    public void write(IIOMetadata sm,
+                      IIOImage iioimage,
+                      ImageWriteParam p) throws IOException {
+        write(sm, iioimage, p, true, true);
+    }
+
+    private void writeHeader() throws IOException {
+        if (streamMetadata != null) {
+            this.byteOrder = streamMetadata.byteOrder;
+        } else {
+            this.byteOrder = ByteOrder.BIG_ENDIAN;
+        }
+
+        stream.setByteOrder(byteOrder);
+        if (byteOrder == ByteOrder.BIG_ENDIAN) {
+            stream.writeShort(0x4d4d);
+        } else {
+            stream.writeShort(0x4949);
+        }
+
+        stream.writeShort(42); // Magic number
+        stream.writeInt(0); // Offset of first IFD (0 == none)
+
+        nextSpace = stream.getStreamPosition();
+        headerPosition = nextSpace - 8;
+    }
+
+    private void write(IIOMetadata sm,
+                       IIOImage iioimage,
+                       ImageWriteParam p,
+                       boolean writeHeader,
+                       boolean writeData) throws IOException {
+        if (stream == null) {
+            throw new IllegalStateException("output == null!");
+        }
+        if (iioimage == null) {
+            throw new NullPointerException("image == null!");
+        }
+        if(iioimage.hasRaster() && !canWriteRasters()) {
+            throw new UnsupportedOperationException
+                ("TIFF ImageWriter cannot write Rasters!");
+        }
+
+        this.image = iioimage.getRenderedImage();
+        SampleModel sampleModel = image.getSampleModel();
+
+        this.sourceXOffset = image.getMinX();
+        this.sourceYOffset = image.getMinY();
+        this.sourceWidth = image.getWidth();
+        this.sourceHeight = image.getHeight();
+
+        Rectangle imageBounds = new Rectangle(sourceXOffset,
+                                              sourceYOffset,
+                                              sourceWidth,
+                                              sourceHeight);
+
+        ColorModel colorModel = null;
+        if (p == null) {
+            this.param = getDefaultWriteParam();
+            this.sourceBands = null;
+            this.periodX = 1;
+            this.periodY = 1;
+            this.numBands = sampleModel.getNumBands();
+            colorModel = image.getColorModel();
+        } else {
+            this.param = p;
+
+            // Get source region and subsampling factors
+            Rectangle sourceRegion = param.getSourceRegion();
+            if (sourceRegion != null) {
+                // Clip to actual image bounds
+                sourceRegion = sourceRegion.intersection(imageBounds);
+
+                sourceXOffset = sourceRegion.x;
+                sourceYOffset = sourceRegion.y;
+                sourceWidth = sourceRegion.width;
+                sourceHeight = sourceRegion.height;
+            }
+
+            // Adjust for subsampling offsets
+            int gridX = param.getSubsamplingXOffset();
+            int gridY = param.getSubsamplingYOffset();
+            this.sourceXOffset += gridX;
+            this.sourceYOffset += gridY;
+            this.sourceWidth -= gridX;
+            this.sourceHeight -= gridY;
+
+            // Get subsampling factors
+            this.periodX = param.getSourceXSubsampling();
+            this.periodY = param.getSourceYSubsampling();
+
+            int[] sBands = param.getSourceBands();
+            if (sBands != null) {
+                sourceBands = sBands;
+                this.numBands = sourceBands.length;
+            } else {
+                this.numBands = sampleModel.getNumBands();
+            }
+
+            ImageTypeSpecifier destType = p.getDestinationType();
+            if(destType != null) {
+                ColorModel cm = destType.getColorModel();
+                if(cm.getNumComponents() == numBands) {
+                    colorModel = cm;
+                }
+            }
+
+            if(colorModel == null) {
+                colorModel = image.getColorModel();
+            }
+        }
+
+        this.imageType = new ImageTypeSpecifier(colorModel, sampleModel);
+
+        ImageUtil.canEncodeImage(this, this.imageType);
+
+        // Compute output dimensions
+        int destWidth = (sourceWidth + periodX - 1)/periodX;
+        int destHeight = (sourceHeight + periodY - 1)/periodY;
+        if (destWidth <= 0 || destHeight <= 0) {
+            throw new IllegalArgumentException("Empty source region!");
+        }
+
+        clearAbortRequest();
+        processImageStarted(0);
+
+        // Optionally write the header.
+        if (writeHeader) {
+            // Clear previous stream metadata.
+            this.streamMetadata = null;
+
+            // Try to convert non-null input stream metadata.
+            if (sm != null) {
+                this.streamMetadata =
+                    (TIFFStreamMetadata)convertStreamMetadata(sm, param);
+            }
+
+            // Set to default if not converted.
+            if(this.streamMetadata == null) {
+                this.streamMetadata =
+                    (TIFFStreamMetadata)getDefaultStreamMetadata(param);
+            }
+
+            // Write the header.
+            writeHeader();
+
+            // Seek to the position of the IFD pointer in the header.
+            stream.seek(headerPosition + 4);
+
+            // Ensure IFD is written on a word boundary
+            nextSpace = (nextSpace + 3) & ~0x3;
+
+            // Write the pointer to the first IFD after the header.
+            stream.writeInt((int)nextSpace);
+        }
+
+        // Write out the IFD and any sub IFDs, followed by a zero
+
+        // Clear previous image metadata.
+        this.imageMetadata = null;
+
+        // Initialize the metadata object.
+        IIOMetadata im = iioimage.getMetadata();
+        if(im != null) {
+            if (im instanceof TIFFImageMetadata) {
+                // Clone the one passed in.
+                this.imageMetadata = ((TIFFImageMetadata)im).getShallowClone();
+            } else if(Arrays.asList(im.getMetadataFormatNames()).contains(
+                   TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME)) {
+                this.imageMetadata = convertNativeImageMetadata(im);
+            } else if(im.isStandardMetadataFormatSupported()) {
+                // Convert standard metadata.
+                this.imageMetadata = convertStandardImageMetadata(im);
+            }
+            if (this.imageMetadata == null) {
+                processWarningOccurred(currentImage,
+                    "Could not initialize image metadata");
+            }
+        }
+
+        // Use default metadata if still null.
+        if(this.imageMetadata == null) {
+            this.imageMetadata =
+                (TIFFImageMetadata)getDefaultImageMetadata(this.imageType,
+                                                           this.param);
+        }
+
+        // Set or overwrite mandatory fields in the root IFD
+        setupMetadata(colorModel, sampleModel, destWidth, destHeight);
+
+        // Set compressor fields.
+        compressor.setWriter(this);
+        // Metadata needs to be set on the compressor before the IFD is
+        // written as the compressor could modify the metadata.
+        compressor.setMetadata(imageMetadata);
+        compressor.setStream(stream);
+
+        // Initialize scaling tables for this image
+        sampleSize = sampleModel.getSampleSize();
+        initializeScaleTables(sampleModel.getSampleSize());
+
+        // Determine whether bilevel.
+        this.isBilevel = ImageUtil.isBinary(this.image.getSampleModel());
+
+        // Check for photometric inversion.
+        this.isInverted =
+            (nativePhotometricInterpretation ==
+             BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO &&
+             photometricInterpretation ==
+             BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) ||
+            (nativePhotometricInterpretation ==
+             BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO &&
+             photometricInterpretation ==
+             BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO);
+
+        // Analyze image data suitability for direct copy.
+        this.isImageSimple =
+            (isBilevel ||
+             (!isInverted && ImageUtil.imageIsContiguous(this.image))) &&
+            !isRescaling &&                 // no value rescaling
+            sourceBands == null &&          // no subbanding
+            periodX == 1 && periodY == 1 && // no subsampling
+            colorConverter == null;
+
+        TIFFIFD rootIFD = imageMetadata.getRootIFD();
+
+        rootIFD.writeToStream(stream);
+
+        this.nextIFDPointerPos = stream.getStreamPosition();
+        stream.writeInt(0);
+
+        // Seek to end of IFD data
+        long lastIFDPosition = rootIFD.getLastPosition();
+        stream.seek(lastIFDPosition);
+        if(lastIFDPosition > this.nextSpace) {
+            this.nextSpace = lastIFDPosition;
+        }
+
+        // If not writing the image data, i.e., if writing or inserting an
+        // empty image, return.
+        if(!writeData) {
+            return;
+        }
+
+        // Get positions of fields within the IFD to update as we write
+        // each strip or tile
+        long stripOrTileByteCountsPosition =
+            rootIFD.getStripOrTileByteCountsPosition();
+        long stripOrTileOffsetsPosition =
+            rootIFD.getStripOrTileOffsetsPosition();
+
+        // Compute total number of pixels for progress notification
+        this.totalPixels = tileWidth*tileLength*tilesDown*tilesAcross;
+        this.pixelsDone = 0;
+
+        // Write the image, a strip or tile at a time
+        for (int tj = 0; tj < tilesDown; tj++) {
+            for (int ti = 0; ti < tilesAcross; ti++) {
+                long pos = stream.getStreamPosition();
+
+                // Write the (possibly compressed) tile data
+
+                Rectangle tileRect =
+                    new Rectangle(sourceXOffset + ti*tileWidth*periodX,
+                                  sourceYOffset + tj*tileLength*periodY,
+                                  tileWidth*periodX,
+                                  tileLength*periodY);
+
+                try {
+                    int byteCount = writeTile(tileRect, compressor);
+
+                    if(pos + byteCount > nextSpace) {
+                        nextSpace = pos + byteCount;
+                    }
+
+                    pixelsDone += tileRect.width*tileRect.height;
+                    processImageProgress(100.0F*pixelsDone/totalPixels);
+
+                    // Fill in the offset and byte count for the file
+                    stream.mark();
+                    stream.seek(stripOrTileOffsetsPosition);
+                    stream.writeInt((int)pos);
+                    stripOrTileOffsetsPosition += 4;
+
+                    stream.seek(stripOrTileByteCountsPosition);
+                    stream.writeInt(byteCount);
+                    stripOrTileByteCountsPosition += 4;
+                    stream.reset();
+                } catch (IOException e) {
+                    throw new IIOException("I/O error writing TIFF file!", e);
+                }
+
+                if (abortRequested()) {
+                    processWriteAborted();
+                    return;
+                }
+            }
+        }
+
+        processImageComplete();
+        currentImage++;
+    }
+
+    public boolean canWriteSequence() {
+        return true;
+    }
+
+    public void prepareWriteSequence(IIOMetadata streamMetadata)
+        throws IOException {
+        if (getOutput() == null) {
+            throw new IllegalStateException("getOutput() == null!");
+        }
+
+        // Set up stream metadata.
+        if (streamMetadata != null) {
+            streamMetadata = convertStreamMetadata(streamMetadata, null);
+        }
+        if(streamMetadata == null) {
+            streamMetadata = getDefaultStreamMetadata(null);
+        }
+        this.streamMetadata = (TIFFStreamMetadata)streamMetadata;
+
+        // Write the header.
+        writeHeader();
+
+        // Set the sequence flag.
+        this.isWritingSequence = true;
+    }
+
+    public void writeToSequence(IIOImage image, ImageWriteParam param)
+        throws IOException {
+        // Check sequence flag.
+        if(!this.isWritingSequence) {
+            throw new IllegalStateException
+                ("prepareWriteSequence() has not been called!");
+        }
+
+        // Append image.
+        writeInsert(-1, image, param);
+    }
+
+    public void endWriteSequence() throws IOException {
+        // Check output.
+        if (getOutput() == null) {
+            throw new IllegalStateException("getOutput() == null!");
+        }
+
+        // Check sequence flag.
+        if(!isWritingSequence) {
+            throw new IllegalStateException
+                ("prepareWriteSequence() has not been called!");
+        }
+
+        // Unset sequence flag.
+        this.isWritingSequence = false;
+
+        // Position the stream at the end, not at the next IFD pointer position.
+        long streamLength = this.stream.length();
+        if (streamLength != -1) {
+            stream.seek(streamLength);
+        }
+    }
+
+    public boolean canInsertImage(int imageIndex) throws IOException {
+        if (getOutput() == null) {
+            throw new IllegalStateException("getOutput() == null!");
+        }
+
+        // Mark position as locateIFD() will seek to IFD at imageIndex.
+        stream.mark();
+
+        // locateIFD() will throw an IndexOutOfBoundsException if
+        // imageIndex is < -1 or is too big thereby satisfying the spec.
+        long[] ifdpos = new long[1];
+        long[] ifd = new long[1];
+        locateIFD(imageIndex, ifdpos, ifd);
+
+        // Reset to position before locateIFD().
+        stream.reset();
+
+        return true;
+    }
+
+    // Locate start of IFD for image.
+    // Throws IIOException if not at a TIFF header and
+    // IndexOutOfBoundsException if imageIndex is < -1 or is too big.
+    private void locateIFD(int imageIndex, long[] ifdpos, long[] ifd)
+        throws IOException {
+
+        if(imageIndex < -1) {
+            throw new IndexOutOfBoundsException("imageIndex < -1!");
+        }
+
+        long startPos = stream.getStreamPosition();
+
+        stream.seek(headerPosition);
+        int byteOrder = stream.readUnsignedShort();
+        if (byteOrder == 0x4d4d) {
+            stream.setByteOrder(ByteOrder.BIG_ENDIAN);
+        } else if (byteOrder == 0x4949) {
+            stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+        } else {
+            stream.seek(startPos);
+            throw new IIOException("Illegal byte order");
+        }
+        if (stream.readUnsignedShort() != 42) {
+            stream.seek(startPos);
+            throw new IIOException("Illegal magic number");
+        }
+
+        ifdpos[0] = stream.getStreamPosition();
+        ifd[0] = stream.readUnsignedInt();
+        if (ifd[0] == 0) {
+            // imageIndex has to be >= -1 due to check above.
+            if(imageIndex > 0) {
+                stream.seek(startPos);
+                throw new IndexOutOfBoundsException
+                    ("imageIndex is greater than the largest available index!");
+            }
+            return;
+        }
+        stream.seek(ifd[0]);
+
+        for (int i = 0; imageIndex == -1 || i < imageIndex; i++) {
+            int numFields;
+            try {
+                numFields = stream.readShort();
+            } catch (EOFException eof) {
+                stream.seek(startPos);
+                ifd[0] = 0;
+                return;
+            }
+
+            stream.skipBytes(12*numFields);
+
+            ifdpos[0] = stream.getStreamPosition();
+            ifd[0] = stream.readUnsignedInt();
+            if (ifd[0] == 0) {
+                if (imageIndex != -1 && i < imageIndex - 1) {
+                    stream.seek(startPos);
+                    throw new IndexOutOfBoundsException(
+                    "imageIndex is greater than the largest available index!");
+                }
+                break;
+            }
+            stream.seek(ifd[0]);
+        }
+    }
+
+    public void writeInsert(int imageIndex,
+                            IIOImage image,
+                            ImageWriteParam param) throws IOException {
+        int currentImageCached = currentImage;
+        try {
+            insert(imageIndex, image, param, true);
+        } catch (Exception e) {
+            throw e;
+        } finally {
+            currentImage = currentImageCached;
+        }
+    }
+
+    private void insert(int imageIndex,
+                        IIOImage image,
+                        ImageWriteParam param,
+                        boolean writeData) throws IOException {
+        if (stream == null) {
+            throw new IllegalStateException("Output not set!");
+        }
+        if (image == null) {
+            throw new NullPointerException("image == null!");
+        }
+
+        // Locate the position of the old IFD (ifd) and the location
+        // of the pointer to that position (ifdpos).
+        long[] ifdpos = new long[1];
+        long[] ifd = new long[1];
+
+        // locateIFD() will throw an IndexOutOfBoundsException if
+        // imageIndex is < -1 or is too big thereby satisfying the spec.
+        locateIFD(imageIndex, ifdpos, ifd);
+
+        // Seek to the position containing the pointer to the old IFD.
+        stream.seek(ifdpos[0]);
+
+        // Update next space pointer in anticipation of next write.
+        if(ifdpos[0] + 4 > nextSpace) {
+            nextSpace = ifdpos[0] + 4;
+        }
+
+        // Ensure IFD is written on a word boundary
+        nextSpace = (nextSpace + 3) & ~0x3;
+
+        // Update the value to point to the next available space.
+        stream.writeInt((int)nextSpace);
+
+        // Seek to the next available space.
+        stream.seek(nextSpace);
+
+        // Write the image (IFD and data).
+        write(null, image, param, false, writeData);
+
+        // Seek to the position containing the pointer in the new IFD.
+        stream.seek(nextIFDPointerPos);
+
+        // Update the new IFD to point to the old IFD.
+        stream.writeInt((int)ifd[0]);
+        // Don't need to update nextSpace here as already done in write().
+    }
+
+    // ----- BEGIN insert/writeEmpty methods -----
+
+    private boolean isEncodingEmpty() {
+        return isInsertingEmpty || isWritingEmpty;
+    }
+
+    public boolean canInsertEmpty(int imageIndex) throws IOException {
+        return canInsertImage(imageIndex);
+    }
+
+    public boolean canWriteEmpty() throws IOException {
+        if (getOutput() == null) {
+            throw new IllegalStateException("getOutput() == null!");
+        }
+        return true;
+    }
+
+    // Check state and parameters for writing or inserting empty images.
+    private void checkParamsEmpty(ImageTypeSpecifier imageType,
+                                  int width,
+                                  int height,
+                                  List<? extends BufferedImage> thumbnails) {
+        if (getOutput() == null) {
+            throw new IllegalStateException("getOutput() == null!");
+        }
+
+        if(imageType == null) {
+            throw new NullPointerException("imageType == null!");
+        }
+
+        if(width < 1 || height < 1) {
+            throw new IllegalArgumentException("width < 1 || height < 1!");
+        }
+
+        if(thumbnails != null) {
+            int numThumbs = thumbnails.size();
+            for(int i = 0; i < numThumbs; i++) {
+                Object thumb = thumbnails.get(i);
+                if(thumb == null || !(thumb instanceof BufferedImage)) {
+                    throw new IllegalArgumentException
+                        ("thumbnails contains null references or objects other than BufferedImages!");
+                }
+            }
+        }
+
+        if(this.isInsertingEmpty) {
+            throw new IllegalStateException
+                ("Previous call to prepareInsertEmpty() without corresponding call to endInsertEmpty()!");
+        }
+
+        if(this.isWritingEmpty) {
+            throw new IllegalStateException
+                ("Previous call to prepareWriteEmpty() without corresponding call to endWriteEmpty()!");
+        }
+    }
+
+    public void prepareInsertEmpty(int imageIndex,
+                                   ImageTypeSpecifier imageType,
+                                   int width,
+                                   int height,
+                                   IIOMetadata imageMetadata,
+                                   List<? extends BufferedImage> thumbnails,
+                                   ImageWriteParam param) throws IOException {
+        checkParamsEmpty(imageType, width, height, thumbnails);
+
+        this.isInsertingEmpty = true;
+
+        SampleModel emptySM = imageType.getSampleModel();
+        RenderedImage emptyImage =
+            new EmptyImage(0, 0, width, height,
+                           0, 0, emptySM.getWidth(), emptySM.getHeight(),
+                           emptySM, imageType.getColorModel());
+
+        insert(imageIndex, new IIOImage(emptyImage, null, imageMetadata),
+               param, false);
+    }
+
+    public void prepareWriteEmpty(IIOMetadata streamMetadata,
+                                  ImageTypeSpecifier imageType,
+                                  int width,
+                                  int height,
+                                  IIOMetadata imageMetadata,
+                                  List<? extends BufferedImage> thumbnails,
+                                  ImageWriteParam param) throws IOException {
+        checkParamsEmpty(imageType, width, height, thumbnails);
+
+        this.isWritingEmpty = true;
+
+        SampleModel emptySM = imageType.getSampleModel();
+        RenderedImage emptyImage =
+            new EmptyImage(0, 0, width, height,
+                           0, 0, emptySM.getWidth(), emptySM.getHeight(),
+                           emptySM, imageType.getColorModel());
+
+        write(streamMetadata, new IIOImage(emptyImage, null, imageMetadata),
+              param, true, false);
+    }
+
+    public void endInsertEmpty() throws IOException {
+        if (getOutput() == null) {
+            throw new IllegalStateException("getOutput() == null!");
+        }
+
+        if(!this.isInsertingEmpty) {
+            throw new IllegalStateException
+                ("No previous call to prepareInsertEmpty()!");
+        }
+
+        if(this.isWritingEmpty) {
+            throw new IllegalStateException
+                ("Previous call to prepareWriteEmpty() without corresponding call to endWriteEmpty()!");
+        }
+
+        if (inReplacePixelsNest) {
+            throw new IllegalStateException
+                ("In nested call to prepareReplacePixels!");
+        }
+
+        this.isInsertingEmpty = false;
+    }
+
+    public void endWriteEmpty() throws IOException {
+        if (getOutput() == null) {
+            throw new IllegalStateException("getOutput() == null!");
+        }
+
+        if(!this.isWritingEmpty) {
+            throw new IllegalStateException
+                ("No previous call to prepareWriteEmpty()!");
+        }
+
+        if(this.isInsertingEmpty) {
+            throw new IllegalStateException
+                ("Previous call to prepareInsertEmpty() without corresponding call to endInsertEmpty()!");
+        }
+
+        if (inReplacePixelsNest) {
+            throw new IllegalStateException
+                ("In nested call to prepareReplacePixels!");
+        }
+
+        this.isWritingEmpty = false;
+    }
+
+    // ----- END insert/writeEmpty methods -----
+
+    // ----- BEGIN replacePixels methods -----
+
+    private TIFFIFD readIFD(int imageIndex) throws IOException {
+        if (stream == null) {
+            throw new IllegalStateException("Output not set!");
+        }
+        if (imageIndex < 0) {
+            throw new IndexOutOfBoundsException("imageIndex < 0!");
+        }
+
+        stream.mark();
+        long[] ifdpos = new long[1];
+        long[] ifd = new long[1];
+        locateIFD(imageIndex, ifdpos, ifd);
+        if (ifd[0] == 0) {
+            stream.reset();
+            throw new IndexOutOfBoundsException
+                ("imageIndex out of bounds!");
+        }
+
+        List<TIFFTagSet> tagSets = new ArrayList<TIFFTagSet>(1);
+        tagSets.add(BaselineTIFFTagSet.getInstance());
+        TIFFIFD rootIFD = new TIFFIFD(tagSets);
+        rootIFD.initialize(stream, true, true);
+        stream.reset();
+
+        return rootIFD;
+    }
+
+    public boolean canReplacePixels(int imageIndex) throws IOException {
+        if (getOutput() == null) {
+            throw new IllegalStateException("getOutput() == null!");
+        }
+
+        TIFFIFD rootIFD = readIFD(imageIndex);
+        TIFFField f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
+        int compression = f.getAsInt(0);
+
+        return compression == BaselineTIFFTagSet.COMPRESSION_NONE;
+    }
+
+    private Object replacePixelsLock = new Object();
+
+    private int replacePixelsIndex = -1;
+    private TIFFImageMetadata replacePixelsMetadata = null;
+    private long[] replacePixelsTileOffsets = null;
+    private long[] replacePixelsByteCounts = null;
+    private long replacePixelsOffsetsPosition = 0L;
+    private long replacePixelsByteCountsPosition = 0L;
+    private Rectangle replacePixelsRegion = null;
+    private boolean inReplacePixelsNest = false;
+
+    private TIFFImageReader reader = null;
+
+    public void prepareReplacePixels(int imageIndex,
+                                     Rectangle region) throws IOException {
+        synchronized(replacePixelsLock) {
+            // Check state and parameters vis-a-vis ImageWriter specification.
+            if (stream == null) {
+                throw new IllegalStateException("Output not set!");
+            }
+            if (region == null) {
+                throw new NullPointerException("region == null!");
+            }
+            if (region.getWidth() < 1) {
+                throw new IllegalArgumentException("region.getWidth() < 1!");
+            }
+            if (region.getHeight() < 1) {
+                throw new IllegalArgumentException("region.getHeight() < 1!");
+            }
+            if (inReplacePixelsNest) {
+                throw new IllegalStateException
+                    ("In nested call to prepareReplacePixels!");
+            }
+
+            // Read the IFD for the pixel replacement index.
+            TIFFIFD replacePixelsIFD = readIFD(imageIndex);
+
+            // Ensure that compression is "none".
+            TIFFField f =
+                replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
+            int compression = f.getAsInt(0);
+            if (compression != BaselineTIFFTagSet.COMPRESSION_NONE) {
+                throw new UnsupportedOperationException
+                    ("canReplacePixels(imageIndex) == false!");
+            }
+
+            // Get the image dimensions.
+            f =
+                replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
+            if(f == null) {
+                throw new IIOException("Cannot read ImageWidth field.");
+            }
+            int w = f.getAsInt(0);
+
+            f =
+                replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
+            if(f == null) {
+                throw new IIOException("Cannot read ImageHeight field.");
+            }
+            int h = f.getAsInt(0);
+
+            // Create image bounds.
+            Rectangle bounds = new Rectangle(0, 0, w, h);
+
+            // Intersect region with bounds.
+            region = region.intersection(bounds);
+
+            // Check for empty intersection.
+            if(region.isEmpty()) {
+                throw new IIOException("Region does not intersect image bounds");
+            }
+
+            // Save the region.
+            replacePixelsRegion = region;
+
+            // Get the tile offsets.
+            f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
+            if(f == null) {
+                f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
+            }
+            replacePixelsTileOffsets = f.getAsLongs();
+
+            // Get the byte counts.
+            f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS);
+            if(f == null) {
+                f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
+            }
+            replacePixelsByteCounts = f.getAsLongs();
+
+            replacePixelsOffsetsPosition =
+                replacePixelsIFD.getStripOrTileOffsetsPosition();
+            replacePixelsByteCountsPosition =
+                replacePixelsIFD.getStripOrTileByteCountsPosition();
+
+            // Get the image metadata.
+            replacePixelsMetadata = new TIFFImageMetadata(replacePixelsIFD);
+
+            // Save the image index.
+            replacePixelsIndex = imageIndex;
+
+            // Set the pixel replacement flag.
+            inReplacePixelsNest = true;
+        }
+    }
+
+    private Raster subsample(Raster raster, int[] sourceBands,
+                             int subOriginX, int subOriginY,
+                             int subPeriodX, int subPeriodY,
+                             int dstOffsetX, int dstOffsetY,
+                             Rectangle target) {
+
+        int x = raster.getMinX();
+        int y = raster.getMinY();
+        int w = raster.getWidth();
+        int h = raster.getHeight();
+        int b = raster.getSampleModel().getNumBands();
+        int t = raster.getSampleModel().getDataType();
+
+        int outMinX = XToTileX(x, subOriginX, subPeriodX) + dstOffsetX;
+        int outMinY = YToTileY(y, subOriginY, subPeriodY) + dstOffsetY;
+        int outMaxX = XToTileX(x + w - 1, subOriginX, subPeriodX) + dstOffsetX;
+        int outMaxY = YToTileY(y + h - 1, subOriginY, subPeriodY) + dstOffsetY;
+        int outWidth = outMaxX - outMinX + 1;
+        int outHeight = outMaxY - outMinY + 1;
+
+        if(outWidth <= 0 || outHeight <= 0) return null;
+
+        int inMinX = (outMinX - dstOffsetX)*subPeriodX + subOriginX;
+        int inMaxX = (outMaxX - dstOffsetX)*subPeriodX + subOriginX;
+        int inWidth = inMaxX - inMinX + 1;
+        int inMinY = (outMinY - dstOffsetY)*subPeriodY + subOriginY;
+        int inMaxY = (outMaxY - dstOffsetY)*subPeriodY + subOriginY;
+        int inHeight = inMaxY - inMinY + 1;
+
+        WritableRaster wr =
+            raster.createCompatibleWritableRaster(outMinX, outMinY,
+                                                  outWidth, outHeight);
+
+        int jMax = inMinY + inHeight;
+
+        if(t == DataBuffer.TYPE_FLOAT) {
+            float[] fsamples = new float[inWidth];
+            float[] fsubsamples = new float[outWidth];
+
+            for(int k = 0; k < b; k++) {
+                int outY = outMinY;
+                for(int j = inMinY; j < jMax; j += subPeriodY) {
+                    raster.getSamples(inMinX, j, inWidth, 1, k, fsamples);
+                    int s = 0;
+                    for(int i = 0; i < inWidth; i += subPeriodX) {
+                        fsubsamples[s++] = fsamples[i];
+                    }
+                    wr.setSamples(outMinX, outY++, outWidth, 1, k,
+                                  fsubsamples);
+                }
+            }
+        } else if (t == DataBuffer.TYPE_DOUBLE) {
+            double[] dsamples = new double[inWidth];
+            double[] dsubsamples = new double[outWidth];
+
+            for(int k = 0; k < b; k++) {
+                int outY = outMinY;
+                for(int j = inMinY; j < jMax; j += subPeriodY) {
+                    raster.getSamples(inMinX, j, inWidth, 1, k, dsamples);
+                    int s = 0;
+                    for(int i = 0; i < inWidth; i += subPeriodX) {
+                        dsubsamples[s++] = dsamples[i];
+                    }
+                    wr.setSamples(outMinX, outY++, outWidth, 1, k,
+                                  dsubsamples);
+                }
+            }
+        } else {
+            int[] samples = new int[inWidth];
+            int[] subsamples = new int[outWidth];
+
+            for(int k = 0; k < b; k++) {
+                int outY = outMinY;
+                for(int j = inMinY; j < jMax; j += subPeriodY) {
+                    raster.getSamples(inMinX, j, inWidth, 1, k, samples);
+                    int s = 0;
+                    for(int i = 0; i < inWidth; i += subPeriodX) {
+                        subsamples[s++] = samples[i];
+                    }
+                    wr.setSamples(outMinX, outY++, outWidth, 1, k,
+                                  subsamples);
+                }
+            }
+        }
+
+        return wr.createChild(outMinX, outMinY,
+                              target.width, target.height,
+                              target.x, target.y,
+                              sourceBands);
+    }
+
+    public void replacePixels(RenderedImage image, ImageWriteParam param)
+        throws IOException {
+
+        synchronized(replacePixelsLock) {
+            // Check state and parameters vis-a-vis ImageWriter specification.
+            if (stream == null) {
+                throw new IllegalStateException("stream == null!");
+            }
+
+            if (image == null) {
+                throw new NullPointerException("image == null!");
+            }
+
+            if (!inReplacePixelsNest) {
+                throw new IllegalStateException
+                    ("No previous call to prepareReplacePixels!");
+            }
+
+            // Subsampling values.
+            int stepX = 1, stepY = 1, gridX = 0, gridY = 0;
+
+            // Initialize the ImageWriteParam.
+            if (param == null) {
+                // Use the default.
+                param = getDefaultWriteParam();
+            } else {
+                // Make a copy of the ImageWriteParam.
+                ImageWriteParam paramCopy = getDefaultWriteParam();
+
+                // Force uncompressed.
+                paramCopy.setCompressionMode(ImageWriteParam.MODE_DISABLED);
+
+                // Force tiling to remain as in the already written image.
+                paramCopy.setTilingMode(ImageWriteParam.MODE_COPY_FROM_METADATA);
+
+                // Retain source and destination region and band settings.
+                paramCopy.setDestinationOffset(param.getDestinationOffset());
+                paramCopy.setSourceBands(param.getSourceBands());
+                paramCopy.setSourceRegion(param.getSourceRegion());
+
+                // Save original subsampling values for subsampling the
+                // replacement data - not the data re-read from the image.
+                stepX = param.getSourceXSubsampling();
+                stepY = param.getSourceYSubsampling();
+                gridX = param.getSubsamplingXOffset();
+                gridY = param.getSubsamplingYOffset();
+
+                // Replace the param.
+                param = paramCopy;
+            }
+
+            // Check band count and bit depth compatibility.
+            TIFFField f =
+                replacePixelsMetadata.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
+            if(f == null) {
+                throw new IIOException
+                    ("Cannot read destination BitsPerSample");
+            }
+            int[] dstBitsPerSample = f.getAsInts();
+            int[] srcBitsPerSample = image.getSampleModel().getSampleSize();
+            int[] sourceBands = param.getSourceBands();
+            if(sourceBands != null) {
+                if(sourceBands.length != dstBitsPerSample.length) {
+                    throw new IIOException
+                        ("Source and destination have different SamplesPerPixel");
+                }
+                for(int i = 0; i < sourceBands.length; i++) {
+                    if(dstBitsPerSample[i] !=
+                       srcBitsPerSample[sourceBands[i]]) {
+                        throw new IIOException
+                            ("Source and destination have different BitsPerSample");
+                    }
+                }
+            } else {
+                int srcNumBands = image.getSampleModel().getNumBands();
+                if(srcNumBands != dstBitsPerSample.length) {
+                    throw new IIOException
+                        ("Source and destination have different SamplesPerPixel");
+                }
+                for(int i = 0; i < srcNumBands; i++) {
+                    if(dstBitsPerSample[i] != srcBitsPerSample[i]) {
+                        throw new IIOException
+                            ("Source and destination have different BitsPerSample");
+                    }
+                }
+            }
+
+            // Get the source image bounds.
+            Rectangle srcImageBounds =
+                new Rectangle(image.getMinX(), image.getMinY(),
+                              image.getWidth(), image.getHeight());
+
+            // Initialize the source rect.
+            Rectangle srcRect = param.getSourceRegion();
+            if(srcRect == null) {
+                srcRect = srcImageBounds;
+            }
+
+            // Set subsampling grid parameters.
+            int subPeriodX = stepX;
+            int subPeriodY = stepY;
+            int subOriginX = gridX + srcRect.x;
+            int subOriginY = gridY + srcRect.y;
+
+            // Intersect with the source bounds.
+            if(!srcRect.equals(srcImageBounds)) {
+                srcRect = srcRect.intersection(srcImageBounds);
+                if(srcRect.isEmpty()) {
+                    throw new IllegalArgumentException
+                        ("Source region does not intersect source image!");
+                }
+            }
+
+            // Get the destination offset.
+            Point dstOffset = param.getDestinationOffset();
+
+            // Forward map source rectangle to determine destination width.
+            int dMinX = XToTileX(srcRect.x, subOriginX, subPeriodX) +
+                dstOffset.x;
+            int dMinY = YToTileY(srcRect.y, subOriginY, subPeriodY) +
+                dstOffset.y;
+            int dMaxX = XToTileX(srcRect.x + srcRect.width,
+                                 subOriginX, subPeriodX) + dstOffset.x;
+            int dMaxY = YToTileY(srcRect.y + srcRect.height,
+                                 subOriginY, subPeriodY) + dstOffset.y;
+
+            // Initialize the destination rectangle.
+            Rectangle dstRect =
+                new Rectangle(dstOffset.x, dstOffset.y,
+                              dMaxX - dMinX, dMaxY - dMinY);
+
+            // Intersect with the replacement region.
+            dstRect = dstRect.intersection(replacePixelsRegion);
+            if(dstRect.isEmpty()) {
+                throw new IllegalArgumentException
+                    ("Forward mapped source region does not intersect destination region!");
+            }
+
+            // Backward map to the active source region.
+            int activeSrcMinX = (dstRect.x - dstOffset.x)*subPeriodX +
+                subOriginX;
+            int sxmax =
+                (dstRect.x + dstRect.width - 1 - dstOffset.x)*subPeriodX +
+                subOriginX;
+            int activeSrcWidth = sxmax - activeSrcMinX + 1;
+
+            int activeSrcMinY = (dstRect.y - dstOffset.y)*subPeriodY +
+                subOriginY;
+            int symax =
+                (dstRect.y + dstRect.height - 1 - dstOffset.y)*subPeriodY +
+                subOriginY;
+            int activeSrcHeight = symax - activeSrcMinY + 1;
+            Rectangle activeSrcRect =
+                new Rectangle(activeSrcMinX, activeSrcMinY,
+                              activeSrcWidth, activeSrcHeight);
+            if(activeSrcRect.intersection(srcImageBounds).isEmpty()) {
+                throw new IllegalArgumentException
+                    ("Backward mapped destination region does not intersect source image!");
+            }
+
+            if(reader == null) {
+                reader = new TIFFImageReader(new TIFFImageReaderSpi());
+            } else {
+                reader.reset();
+            }
+
+            stream.mark();
+
+            try {
+                stream.seek(headerPosition);
+                reader.setInput(stream);
+
+                this.imageMetadata = replacePixelsMetadata;
+                this.param = param;
+                SampleModel sm = image.getSampleModel();
+                ColorModel cm = image.getColorModel();
+                this.numBands = sm.getNumBands();
+                this.imageType = new ImageTypeSpecifier(image);
+                this.periodX = param.getSourceXSubsampling();
+                this.periodY = param.getSourceYSubsampling();
+                this.sourceBands = null;
+                int[] sBands = param.getSourceBands();
+                if (sBands != null) {
+                    this.sourceBands = sBands;
+                    this.numBands = sourceBands.length;
+                }
+                setupMetadata(cm, sm,
+                              reader.getWidth(replacePixelsIndex),
+                              reader.getHeight(replacePixelsIndex));
+                int[] scaleSampleSize = sm.getSampleSize();
+                initializeScaleTables(scaleSampleSize);
+
+                // Determine whether bilevel.
+                this.isBilevel = ImageUtil.isBinary(image.getSampleModel());
+
+                // Check for photometric inversion.
+                this.isInverted =
+                    (nativePhotometricInterpretation ==
+                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO &&
+                     photometricInterpretation ==
+                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) ||
+                    (nativePhotometricInterpretation ==
+                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO &&
+                     photometricInterpretation ==
+                     BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO);
+
+                // Analyze image data suitability for direct copy.
+                this.isImageSimple =
+                    (isBilevel ||
+                     (!isInverted && ImageUtil.imageIsContiguous(image))) &&
+                    !isRescaling &&                 // no value rescaling
+                    sourceBands == null &&          // no subbanding
+                    periodX == 1 && periodY == 1 && // no subsampling
+                    colorConverter == null;
+
+                int minTileX = XToTileX(dstRect.x, 0, tileWidth);
+                int minTileY = YToTileY(dstRect.y, 0, tileLength);
+                int maxTileX = XToTileX(dstRect.x + dstRect.width - 1,
+                                        0, tileWidth);
+                int maxTileY = YToTileY(dstRect.y + dstRect.height - 1,
+                                        0, tileLength);
+
+                TIFFCompressor encoder = new TIFFNullCompressor();
+                encoder.setWriter(this);
+                encoder.setStream(stream);
+                encoder.setMetadata(this.imageMetadata);
+
+                Rectangle tileRect = new Rectangle();
+                for(int ty = minTileY; ty <= maxTileY; ty++) {
+                    for(int tx = minTileX; tx <= maxTileX; tx++) {
+                        int tileIndex = ty*tilesAcross + tx;
+                        boolean isEmpty =
+                            replacePixelsByteCounts[tileIndex] == 0L;
+                        WritableRaster raster;
+                        if(isEmpty) {
+                            SampleModel tileSM =
+                                sm.createCompatibleSampleModel(tileWidth,
+                                                               tileLength);
+                            raster = Raster.createWritableRaster(tileSM, null);
+                        } else {
+                            BufferedImage tileImage =
+                                reader.readTile(replacePixelsIndex, tx, ty);
+                            raster = tileImage.getRaster();
+                        }
+
+                        tileRect.setLocation(tx*tileWidth,
+                                             ty*tileLength);
+                        tileRect.setSize(raster.getWidth(),
+                                         raster.getHeight());
+                        raster =
+                            raster.createWritableTranslatedChild(tileRect.x,
+                                                                 tileRect.y);
+
+                        Rectangle replacementRect =
+                            tileRect.intersection(dstRect);
+
+                        int srcMinX =
+                            (replacementRect.x - dstOffset.x)*subPeriodX +
+                            subOriginX;
+                        int srcXmax =
+                            (replacementRect.x + replacementRect.width - 1 -
+                             dstOffset.x)*subPeriodX + subOriginX;
+                        int srcWidth = srcXmax - srcMinX + 1;
+
+                        int srcMinY =
+                            (replacementRect.y - dstOffset.y)*subPeriodY +
+                            subOriginY;
+                        int srcYMax =
+                            (replacementRect.y + replacementRect.height - 1 -
+                             dstOffset.y)*subPeriodY + subOriginY;
+                        int srcHeight = srcYMax - srcMinY + 1;
+                        Rectangle srcTileRect =
+                            new Rectangle(srcMinX, srcMinY,
+                                          srcWidth, srcHeight);
+
+                        Raster replacementData = image.getData(srcTileRect);
+                        if(subPeriodX == 1 && subPeriodY == 1 &&
+                           subOriginX == 0 && subOriginY == 0) {
+                            replacementData =
+                                replacementData.createChild(srcTileRect.x,
+                                                            srcTileRect.y,
+                                                            srcTileRect.width,
+                                                            srcTileRect.height,
+                                                            replacementRect.x,
+                                                            replacementRect.y,
+                                                            sourceBands);
+                        } else {
+                            replacementData = subsample(replacementData,
+                                                        sourceBands,
+                                                        subOriginX,
+                                                        subOriginY,
+                                                        subPeriodX,
+                                                        subPeriodY,
+                                                        dstOffset.x,
+                                                        dstOffset.y,
+                                                        replacementRect);
+                            if(replacementData == null) {
+                                continue;
+                            }
+                        }
+
+                        raster.setRect(replacementData);
+
+                        if(isEmpty) {
+                            stream.seek(nextSpace);
+                        } else {
+                            stream.seek(replacePixelsTileOffsets[tileIndex]);
+                        }
+
+                        this.image = new SingleTileRenderedImage(raster, cm);
+
+                        int numBytes = writeTile(tileRect, encoder);
+
+                        if(isEmpty) {
+                            // Update Strip/TileOffsets and
+                            // Strip/TileByteCounts fields.
+                            stream.mark();
+                            stream.seek(replacePixelsOffsetsPosition +
+                                        4*tileIndex);
+                            stream.writeInt((int)nextSpace);
+                            stream.seek(replacePixelsByteCountsPosition +
+                                        4*tileIndex);
+                            stream.writeInt(numBytes);
+                            stream.reset();
+
+                            // Increment location of next available space.
+                            nextSpace += numBytes;
+                        }
+                    }
+                }
+
+            } catch(IOException e) {
+                throw e;
+            } finally {
+                stream.reset();
+            }
+        }
+    }
+
+    public void replacePixels(Raster raster, ImageWriteParam param)
+        throws IOException {
+        if (raster == null) {
+            throw new NullPointerException("raster == null!");
+        }
+
+        replacePixels(new SingleTileRenderedImage(raster,
+                                                  image.getColorModel()),
+                      param);
+    }
+
+    public void endReplacePixels() throws IOException {
+        synchronized(replacePixelsLock) {
+            if(!this.inReplacePixelsNest) {
+                throw new IllegalStateException
+                    ("No previous call to prepareReplacePixels()!");
+            }
+            replacePixelsIndex = -1;
+            replacePixelsMetadata = null;
+            replacePixelsTileOffsets = null;
+            replacePixelsByteCounts = null;
+            replacePixelsOffsetsPosition = 0L;
+            replacePixelsByteCountsPosition = 0L;
+            replacePixelsRegion = null;
+            inReplacePixelsNest = false;
+        }
+    }
+
+    // ----- END replacePixels methods -----
+
+    public void reset() {
+        super.reset();
+
+        stream = null;
+        image = null;
+        imageType = null;
+        byteOrder = null;
+        param = null;
+        compressor = null;
+        colorConverter = null;
+        streamMetadata = null;
+        imageMetadata = null;
+
+        isWritingSequence = false;
+        isWritingEmpty = false;
+        isInsertingEmpty = false;
+
+        replacePixelsIndex = -1;
+        replacePixelsMetadata = null;
+        replacePixelsTileOffsets = null;
+        replacePixelsByteCounts = null;
+        replacePixelsOffsetsPosition = 0L;
+        replacePixelsByteCountsPosition = 0L;
+        replacePixelsRegion = null;
+        inReplacePixelsNest = false;
+    }
+}
+
+class EmptyImage extends SimpleRenderedImage {
+    EmptyImage(int minX, int minY, int width, int height,
+               int tileGridXOffset, int tileGridYOffset,
+               int tileWidth, int tileHeight,
+               SampleModel sampleModel, ColorModel colorModel) {
+        this.minX = minX;
+        this.minY = minY;
+        this.width = width;
+        this.height = height;
+        this.tileGridXOffset = tileGridXOffset;
+        this.tileGridYOffset = tileGridYOffset;
+        this.tileWidth = tileWidth;
+        this.tileHeight = tileHeight;
+        this.sampleModel = sampleModel;
+        this.colorModel = colorModel;
+    }
+
+    public Raster getTile(int tileX, int tileY) {
+        return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriterSpi.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.util.Locale;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.ImageWriter;
+import javax.imageio.spi.ImageWriterSpi;
+import javax.imageio.spi.ServiceRegistry;
+import javax.imageio.stream.ImageOutputStream;
+
+public class TIFFImageWriterSpi extends ImageWriterSpi {
+
+    private boolean registered = false;
+
+    public TIFFImageWriterSpi() {
+        super("Oracle Corporation",
+              "1.0",
+              new String[] {"tif", "TIF", "tiff", "TIFF"},
+              new String[] {"tif", "tiff"},
+              new String[] {"image/tiff"},
+              "com.sun.imageio.plugins.tiff.TIFFImageWriter",
+              new Class<?>[] {ImageOutputStream.class},
+              new String[] {"com.sun.imageio.plugins.tiff.TIFFImageReaderSpi"},
+              false,
+              TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME,
+              "com.sun.imageio.plugins.tiff.TIFFStreamMetadataFormat",
+              null, null,
+              false,
+              TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME,
+              "com.sun.imageio.plugins.tiff.TIFFImageMetadataFormat",
+              null, null
+              );
+    }
+
+    public boolean canEncodeImage(ImageTypeSpecifier type) {
+        return true;
+    }
+
+    public String getDescription(Locale locale) {
+        return "Standard TIFF image writer";
+    }
+
+    public ImageWriter createWriterInstance(Object extension) {
+        return new TIFFImageWriter(this);
+    }
+
+    public void onRegistration(ServiceRegistry registry,
+                               Class<?> category) {
+        if (registered) {
+            return;
+        }
+
+        registered = true;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGCompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFField;
+import javax.imageio.plugins.tiff.TIFFTag;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.Iterator;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.spi.ServiceRegistry;
+import javax.imageio.stream.MemoryCacheImageInputStream;
+import javax.imageio.stream.MemoryCacheImageOutputStream;
+
+/**
+ * Compressor for encoding compression type 7, TTN2/Adobe JPEG-in-TIFF.
+ */
+public class TIFFJPEGCompressor extends TIFFBaseJPEGCompressor {
+
+    // Subsampling factor for chroma bands (Cb Cr).
+    static final int CHROMA_SUBSAMPLING = 2;
+
+    /**
+     * A filter which identifies the ImageReaderSpi of a JPEG reader
+     * which supports JPEG native stream metadata.
+     */
+    private static class JPEGSPIFilter implements ServiceRegistry.Filter {
+        JPEGSPIFilter() {}
+
+        public boolean filter(Object provider) {
+            ImageReaderSpi readerSPI = (ImageReaderSpi)provider;
+
+            if(readerSPI != null) {
+                String streamMetadataName =
+                    readerSPI.getNativeStreamMetadataFormatName();
+                if(streamMetadataName != null) {
+                    return streamMetadataName.equals(STREAM_METADATA_NAME);
+                } else {
+                    return false;
+                }
+            }
+
+            return false;
+        }
+    }
+
+    /**
+     * Retrieves a JPEG reader which supports native JPEG stream metadata.
+     */
+    private static ImageReader getJPEGTablesReader() {
+        ImageReader jpegReader = null;
+
+        try {
+            IIORegistry registry = IIORegistry.getDefaultInstance();
+            Class<?> imageReaderClass =
+                Class.forName("javax.imageio.spi.ImageReaderSpi");
+            Iterator<?> readerSPIs =
+                registry.getServiceProviders(imageReaderClass,
+                                             new JPEGSPIFilter(),
+                                             true);
+            if(readerSPIs.hasNext()) {
+                ImageReaderSpi jpegReaderSPI =
+                    (ImageReaderSpi)readerSPIs.next();
+                jpegReader = jpegReaderSPI.createReaderInstance();
+            }
+        } catch(Exception e) {
+            // Ignore it ...
+        }
+
+        return jpegReader;
+    }
+
+    public TIFFJPEGCompressor(ImageWriteParam param) {
+        super("JPEG", BaselineTIFFTagSet.COMPRESSION_JPEG, false, param);
+    }
+
+    /**
+     * Sets the value of the <code>metadata</code> field.
+     *
+     * <p>The implementation in this class also adds the TIFF fields
+     * JPEGTables, YCbCrSubSampling, YCbCrPositioning, and
+     * ReferenceBlackWhite superseding any prior settings of those
+     * fields.</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;
+            TIFFIFD rootIFD = tim.getRootIFD();
+            BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();
+
+            TIFFField f =
+                tim.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
+            int numBands = f.getAsInt(0);
+
+            if(numBands == 1) {
+                // Remove YCbCr fields not relevant for grayscale.
+
+                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
+                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING);
+                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE);
+            } else { // numBands == 3
+                // Replace YCbCr fields.
+
+                // YCbCrSubSampling
+                TIFFField YCbCrSubSamplingField = new TIFFField
+                    (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING),
+                     TIFFTag.TIFF_SHORT, 2,
+                     new char[] {CHROMA_SUBSAMPLING, CHROMA_SUBSAMPLING});
+                rootIFD.addTIFFField(YCbCrSubSamplingField);
+
+                // YCbCrPositioning
+                TIFFField YCbCrPositioningField = new TIFFField
+                    (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING),
+                     TIFFTag.TIFF_SHORT, 1,
+                     new char[]
+                        {BaselineTIFFTagSet.Y_CB_CR_POSITIONING_CENTERED});
+                rootIFD.addTIFFField(YCbCrPositioningField);
+
+                // ReferenceBlackWhite
+                TIFFField referenceBlackWhiteField = new TIFFField
+                    (base.getTag(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE),
+                     TIFFTag.TIFF_RATIONAL, 6,
+                     new long[][] { // no headroon/footroom
+                         {0, 1}, {255, 1},
+                         {128, 1}, {255, 1},
+                         {128, 1}, {255, 1}
+                     });
+                rootIFD.addTIFFField(referenceBlackWhiteField);
+            }
+
+            // JPEGTables field is written if and only if one is
+            // already present in the metadata. If one is present
+            // and has either zero length or does not represent a
+            // valid tables-only stream, then a JPEGTables field
+            // will be written initialized to the standard tables-
+            // only stream written by the JPEG writer.
+
+            // Retrieve the JPEGTables field.
+            TIFFField JPEGTablesField =
+                tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_TABLES);
+
+            // Initialize JPEG writer to one supporting abbreviated streams.
+            if(JPEGTablesField != null) {
+                // Intialize the JPEG writer to one that supports stream
+                // metadata, i.e., abbreviated streams, and may or may not
+                // support image metadata.
+                initJPEGWriter(true, false);
+            }
+
+            // Write JPEGTables field if a writer supporting abbreviated
+            // streams was available.
+            if(JPEGTablesField != null && JPEGWriter != null) {
+                // Set the abbreviated stream flag.
+                this.writeAbbreviatedStream = true;
+
+                //Branch based on field value count.
+                if(JPEGTablesField.getCount() > 0) {
+                    // Derive the stream metadata from the field.
+
+                    // Get the field values.
+                    byte[] tables = JPEGTablesField.getAsBytes();
+
+                    // Create an input stream for the tables.
+                    ByteArrayInputStream bais =
+                        new ByteArrayInputStream(tables);
+                    MemoryCacheImageInputStream iis =
+                        new MemoryCacheImageInputStream(bais);
+
+                    // Read the tables stream using the JPEG reader.
+                    ImageReader jpegReader = getJPEGTablesReader();
+                    jpegReader.setInput(iis);
+
+                    // Initialize the stream metadata object.
+                    try {
+                        JPEGStreamMetadata = jpegReader.getStreamMetadata();
+                    } catch(Exception e) {
+                        // Fall back to default tables.
+                        JPEGStreamMetadata = null;
+                    } finally {
+                        jpegReader.reset();
+                    }
+                }
+
+                if(JPEGStreamMetadata == null) {
+                    // Derive the field from default stream metadata.
+
+                    // Get default stream metadata.
+                    JPEGStreamMetadata =
+                        JPEGWriter.getDefaultStreamMetadata(JPEGParam);
+
+                    // Create an output stream for the tables.
+                    ByteArrayOutputStream tableByteStream =
+                        new ByteArrayOutputStream();
+                    MemoryCacheImageOutputStream tableStream =
+                        new MemoryCacheImageOutputStream(tableByteStream);
+
+                    // Write a tables-only stream.
+                    JPEGWriter.setOutput(tableStream);
+                    try {
+                        JPEGWriter.prepareWriteSequence(JPEGStreamMetadata);
+                        tableStream.flush();
+                        JPEGWriter.endWriteSequence();
+
+                        // Get the tables-only stream content.
+                        byte[] tables = tableByteStream.toByteArray();
+
+                        // Add the JPEGTables field.
+                        JPEGTablesField = new TIFFField
+                            (base.getTag(BaselineTIFFTagSet.TAG_JPEG_TABLES),
+                             TIFFTag.TIFF_UNDEFINED,
+                             tables.length,
+                             tables);
+                        rootIFD.addTIFFField(JPEGTablesField);
+                    } catch(Exception e) {
+                        // Do not write JPEGTables field.
+                        rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_TABLES);
+                        this.writeAbbreviatedStream = false;
+                    }
+                }
+            } else { // Do not write JPEGTables field.
+                // Remove any field present.
+                rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_TABLES);
+
+                // Initialize the writer preferring codecLib.
+                initJPEGWriter(false, false);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFJPEGDecompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.io.IOException;
+import java.io.ByteArrayInputStream;
+import java.util.Iterator;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageReadParam;
+import javax.imageio.stream.MemoryCacheImageInputStream;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFField;
+
+public class TIFFJPEGDecompressor extends TIFFDecompressor {
+    // Start of Image
+    protected static final int SOI = 0xD8;
+
+    // End of Image
+    protected static final int EOI = 0xD9;
+
+    protected ImageReader JPEGReader = null;
+    protected ImageReadParam JPEGParam;
+
+    protected boolean hasJPEGTables = false;
+    protected byte[] tables = null;
+
+    private byte[] data = new byte[0];
+
+    public TIFFJPEGDecompressor() {}
+
+    public void beginDecoding() {
+        // Initialize the JPEG reader if needed.
+        if(this.JPEGReader == null) {
+            // Get all JPEG readers.
+            Iterator<ImageReader> iter = ImageIO.getImageReadersByFormatName("jpeg");
+
+            if(!iter.hasNext()) {
+                throw new IllegalStateException("No JPEG readers found!");
+            }
+
+            // Initialize reader to the first one.
+            this.JPEGReader = iter.next();
+
+            this.JPEGParam = JPEGReader.getDefaultReadParam();
+        }
+
+        // Get the JPEGTables field.
+        TIFFImageMetadata tmetadata = (TIFFImageMetadata)metadata;
+        TIFFField f =
+            tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_TABLES);
+
+        if (f != null) {
+            this.hasJPEGTables = true;
+            this.tables = f.getAsBytes();
+        } else {
+            this.hasJPEGTables = false;
+        }
+    }
+
+    public void decodeRaw(byte[] b,
+                          int dstOffset,
+                          int bitsPerPixel,
+                          int scanlineStride) throws IOException {
+        // Seek to the data position for this segment.
+        stream.seek(offset);
+
+        // Set the stream variable depending on presence of JPEGTables.
+        ImageInputStream is;
+        if(this.hasJPEGTables) {
+            // The current strip or tile is an abbreviated JPEG stream.
+
+            // Reallocate memory if there is not enough already.
+            int dataLength = tables.length + byteCount;
+            if(data.length < dataLength) {
+                data = new byte[dataLength];
+            }
+
+            // Copy the tables ignoring any EOI and subsequent bytes.
+            int dataOffset = tables.length;
+            for(int i = tables.length - 2; i > 0; i--) {
+                if((tables[i] & 0xff) == 0xff &&
+                   (tables[i+1] & 0xff) == EOI) {
+                    dataOffset = i;
+                    break;
+                }
+            }
+            System.arraycopy(tables, 0, data, 0, dataOffset);
+
+            // Check for SOI and skip it if present.
+            byte byte1 = (byte)stream.read();
+            byte byte2 = (byte)stream.read();
+            if(!((byte1 & 0xff) == 0xff && (byte2 & 0xff) == SOI)) {
+                data[dataOffset++] = byte1;
+                data[dataOffset++] = byte2;
+            }
+
+            // Read remaining data.
+            stream.readFully(data, dataOffset, byteCount - 2);
+
+            // Create ImageInputStream.
+            ByteArrayInputStream bais = new ByteArrayInputStream(data);
+            is = new MemoryCacheImageInputStream(bais);
+        } else {
+            // The current strip or tile is a complete JPEG stream.
+            is = stream;
+        }
+
+        // Set the stream on the reader.
+        JPEGReader.setInput(is, false, true);
+
+        // Set the destination to the raw image ignoring the parameters.
+        JPEGParam.setDestination(rawImage);
+
+        // Read the strip or tile.
+        JPEGReader.read(0, JPEGParam);
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        JPEGReader.dispose();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBCompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import java.io.IOException;
+
+/**
+ * Uncompressed data with LSB-to-MSB fill order.
+ */
+public class TIFFLSBCompressor extends TIFFCompressor {
+
+    public TIFFLSBCompressor() {
+        super("", BaselineTIFFTagSet.COMPRESSION_NONE, true);
+    }
+
+    public int encode(byte[] b, int off,
+                      int width, int height,
+                      int[] bitsPerSample,
+                      int scanlineStride) throws IOException {
+        int bitsPerPixel = 0;
+        for (int i = 0; i < bitsPerSample.length; i++) {
+            bitsPerPixel += bitsPerSample[i];
+        }
+        int bytesPerRow = (bitsPerPixel*width + 7)/8;
+        byte[] compData = new byte[bytesPerRow];
+        byte[] flipTable = TIFFFaxDecompressor.flipTable;
+        for (int row = 0; row < height; row++) {
+            System.arraycopy(b, off, compData, 0, bytesPerRow);
+            for(int j = 0; j < bytesPerRow; j++) {
+                compData[j] = flipTable[compData[j]&0xff];
+            }
+            stream.write(compData, 0, bytesPerRow);
+            off += scanlineStride;
+        }
+        return height*bytesPerRow;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLSBDecompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.io.IOException;
+
+public class TIFFLSBDecompressor extends TIFFDecompressor {
+
+    /**
+     * Table for flipping bytes from LSB-to-MSB to MSB-to-LSB.
+     */
+    private static final byte[] flipTable = TIFFFaxDecompressor.flipTable;
+
+    public TIFFLSBDecompressor() {}
+
+    public void decodeRaw(byte[] b,
+                          int dstOffset,
+                          int bitsPerPixel,
+                          int scanlineStride) throws IOException {
+        stream.seek(offset);
+
+        int bytesPerRow = (srcWidth*bitsPerPixel + 7)/8;
+        if(bytesPerRow == scanlineStride) {
+            int numBytes = bytesPerRow*srcHeight;
+            stream.readFully(b, dstOffset, numBytes);
+            int xMax = dstOffset + numBytes;
+            for (int x = dstOffset; x < xMax; x++) {
+                b[x] = flipTable[b[x]&0xff];
+            }
+        } else {
+            for (int y = 0; y < srcHeight; y++) {
+                stream.readFully(b, dstOffset, bytesPerRow);
+                int xMax = dstOffset + bytesPerRow;
+                for (int x = dstOffset; x < xMax; x++) {
+                    b[x] = flipTable[b[x]&0xff];
+                }
+                dstOffset += scanlineStride;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWCompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import com.sun.imageio.plugins.common.LZWCompressor;
+import java.io.IOException;
+import javax.imageio.stream.ImageOutputStream;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+
+/**
+ * LZW Compressor.
+ */
+public class TIFFLZWCompressor extends TIFFCompressor {
+
+    private final int predictor;
+
+    public TIFFLZWCompressor(int predictorValue) {
+        super("LZW", BaselineTIFFTagSet.COMPRESSION_LZW, true);
+        this.predictor = predictorValue;
+    }
+
+    public void setStream(ImageOutputStream stream) {
+        super.setStream(stream);
+    }
+
+    public int encode(byte[] b, int off,
+                      int width, int height,
+                      int[] bitsPerSample,
+                      int scanlineStride) throws IOException {
+
+        LZWCompressor lzwCompressor = new LZWCompressor(stream, 8, true);
+
+        int samplesPerPixel = bitsPerSample.length;
+        int bitsPerPixel = 0;
+        for (int i = 0; i < samplesPerPixel; i++) {
+            bitsPerPixel += bitsPerSample[i];
+        }
+        int bytesPerRow = (bitsPerPixel*width + 7)/8;
+
+        long initialStreamPosition = stream.getStreamPosition();
+
+        boolean usePredictor =
+            predictor == BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING;
+
+        if(bytesPerRow == scanlineStride && !usePredictor) {
+            lzwCompressor.compress(b, off, bytesPerRow*height);
+        } else {
+            byte[] rowBuf = usePredictor ? new byte[bytesPerRow] : null;
+            for(int i = 0; i < height; i++) {
+                if(usePredictor) {
+                    // Cannot modify b[] in place as it might be a data
+                    // array from the image being written so make a copy.
+                    System.arraycopy(b, off, rowBuf, 0, bytesPerRow);
+                    for(int j = bytesPerRow - 1; j >= samplesPerPixel; j--) {
+                        rowBuf[j] -= rowBuf[j - samplesPerPixel];
+                    }
+                    lzwCompressor.compress(rowBuf, 0, bytesPerRow);
+                } else {
+                    lzwCompressor.compress(b, off, bytesPerRow);
+                }
+                off += scanlineStride;
+            }
+        }
+
+        lzwCompressor.flush();
+
+        int bytesWritten =
+            (int)(stream.getStreamPosition() - initialStreamPosition);
+
+        return bytesWritten;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWDecompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.io.IOException;
+import javax.imageio.IIOException;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+
+class TIFFLZWDecompressor extends TIFFDecompressor {
+
+    private static final int andTable[] = {
+        511,
+        1023,
+        2047,
+        4095
+    };
+
+    private int predictor;
+
+    private byte[] srcData;
+    private byte[] dstData;
+
+    private int srcIndex;
+    private int dstIndex;
+
+    private byte stringTable[][];
+    private int tableIndex, bitsToGet = 9;
+
+    private int nextData = 0;
+    private int nextBits = 0;
+
+    public TIFFLZWDecompressor(int predictor) throws IIOException {
+        super();
+
+        if (predictor != BaselineTIFFTagSet.PREDICTOR_NONE &&
+            predictor !=
+            BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) {
+            throw new IIOException("Illegal value for Predictor in " +
+                                   "TIFF file");
+        }
+
+        this.predictor = predictor;
+    }
+
+    public void decodeRaw(byte[] b,
+                          int dstOffset,
+                          int bitsPerPixel,
+                          int scanlineStride) throws IOException {
+
+        // Check bitsPerSample.
+        if (predictor ==
+            BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) {
+            int len = bitsPerSample.length;
+            for(int i = 0; i < len; i++) {
+                if(bitsPerSample[i] != 8) {
+                    throw new IIOException
+                        (bitsPerSample[i] + "-bit samples "+
+                         "are not supported for Horizontal "+
+                         "differencing Predictor");
+                }
+            }
+        }
+
+        stream.seek(offset);
+
+        byte[] sdata = new byte[byteCount];
+        stream.readFully(sdata);
+
+        int bytesPerRow = (srcWidth*bitsPerPixel + 7)/8;
+        byte[] buf;
+        int bufOffset;
+        if(bytesPerRow == scanlineStride) {
+            buf = b;
+            bufOffset = dstOffset;
+        } else {
+            buf = new byte[bytesPerRow*srcHeight];
+            bufOffset = 0;
+        }
+
+        int numBytesDecoded = decode(sdata, 0, buf, bufOffset);
+
+        if(bytesPerRow != scanlineStride) {
+            int off = 0;
+            for (int y = 0; y < srcHeight; y++) {
+                System.arraycopy(buf, off, b, dstOffset, bytesPerRow);
+                off += bytesPerRow;
+                dstOffset += scanlineStride;
+            }
+        }
+    }
+
+    public int decode(byte[] sdata, int srcOffset,
+                      byte[] ddata, int dstOffset)
+        throws IOException {
+        if (sdata[0] == (byte)0x00 && sdata[1] == (byte)0x01) {
+            throw new IIOException
+                ("TIFF 5.0-style LZW compression is not supported!");
+        }
+
+        this.srcData = sdata;
+        this.dstData = ddata;
+
+        this.srcIndex = srcOffset;
+        this.dstIndex = dstOffset;
+
+        this.nextData = 0;
+        this.nextBits = 0;
+
+        initializeStringTable();
+
+        int code, oldCode = 0;
+        byte[] string;
+
+        while ((code = getNextCode()) != 257) {
+            if (code == 256) {
+                initializeStringTable();
+                code = getNextCode();
+                if (code == 257) {
+                    break;
+                }
+
+                writeString(stringTable[code]);
+                oldCode = code;
+            } else {
+                if (code < tableIndex) {
+                    string = stringTable[code];
+
+                    writeString(string);
+                    addStringToTable(stringTable[oldCode], string[0]);
+                    oldCode = code;
+                } else {
+                    string = stringTable[oldCode];
+                    string = composeString(string, string[0]);
+                    writeString(string);
+                    addStringToTable(string);
+                    oldCode = code;
+                }
+            }
+        }
+
+        if (predictor ==
+            BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING) {
+
+            for (int j = 0; j < srcHeight; j++) {
+
+                int count = dstOffset + samplesPerPixel * (j * srcWidth + 1);
+
+                for (int i = samplesPerPixel; i < srcWidth * samplesPerPixel; i++) {
+
+                    dstData[count] += dstData[count - samplesPerPixel];
+                    count++;
+                }
+            }
+        }
+
+        return dstIndex - dstOffset;
+    }
+
+    /**
+     * Initialize the string table.
+     */
+    public void initializeStringTable() {
+        stringTable = new byte[4096][];
+
+        for (int i = 0; i < 256; i++) {
+            stringTable[i] = new byte[1];
+            stringTable[i][0] = (byte)i;
+        }
+
+        tableIndex = 258;
+        bitsToGet = 9;
+    }
+
+    /**
+     * Write out the string just uncompressed.
+     */
+    public void writeString(byte string[]) {
+        if(dstIndex < dstData.length) {
+            int maxIndex = Math.min(string.length,
+                                    dstData.length - dstIndex);
+
+            for (int i=0; i < maxIndex; i++) {
+                dstData[dstIndex++] = string[i];
+            }
+        }
+    }
+
+    /**
+     * Add a new string to the string table.
+     */
+    public void addStringToTable(byte oldString[], byte newString) {
+        int length = oldString.length;
+        byte string[] = new byte[length + 1];
+        System.arraycopy(oldString, 0, string, 0, length);
+        string[length] = newString;
+
+        // Add this new String to the table
+        stringTable[tableIndex++] = string;
+
+        if (tableIndex == 511) {
+            bitsToGet = 10;
+        } else if (tableIndex == 1023) {
+            bitsToGet = 11;
+        } else if (tableIndex == 2047) {
+            bitsToGet = 12;
+        }
+    }
+
+    /**
+     * Add a new string to the string table.
+     */
+    public void addStringToTable(byte string[]) {
+        // Add this new String to the table
+        stringTable[tableIndex++] = string;
+
+        if (tableIndex == 511) {
+            bitsToGet = 10;
+        } else if (tableIndex == 1023) {
+            bitsToGet = 11;
+        } else if (tableIndex == 2047) {
+            bitsToGet = 12;
+        }
+    }
+
+    /**
+     * Append <code>newString</code> to the end of <code>oldString</code>.
+     */
+    public byte[] composeString(byte oldString[], byte newString) {
+        int length = oldString.length;
+        byte string[] = new byte[length + 1];
+        System.arraycopy(oldString, 0, string, 0, length);
+        string[length] = newString;
+
+        return string;
+    }
+
+    // Returns the next 9, 10, 11 or 12 bits
+    public int getNextCode() {
+        // Attempt to get the next code. The exception is caught to make
+        // this robust to cases wherein the EndOfInformation code has been
+        // omitted from a strip. Examples of such cases have been observed
+        // in practice.
+
+        try {
+            nextData = (nextData << 8) | (srcData[srcIndex++] & 0xff);
+            nextBits += 8;
+
+            if (nextBits < bitsToGet) {
+                nextData = (nextData << 8) | (srcData[srcIndex++] & 0xff);
+                nextBits += 8;
+            }
+
+            int code =
+                (nextData >> (nextBits - bitsToGet)) & andTable[bitsToGet - 9];
+            nextBits -= bitsToGet;
+
+            return code;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            // Strip not terminated as expected: return EndOfInformation code.
+            return 257;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFLZWUtil.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.io.IOException;
+import javax.imageio.IIOException;
+
+class TIFFLZWUtil {
+    public TIFFLZWUtil() {
+    }
+
+    byte[] srcData;
+    int srcIndex;
+
+    byte[] dstData;
+    int dstIndex = 0;
+
+    byte stringTable[][];
+    int tableIndex, bitsToGet = 9;
+
+    int nextData = 0;
+    int nextBits = 0;
+
+    private static final int andTable[] = {
+        511,
+        1023,
+        2047,
+        4095
+    };
+
+    public byte[] decode(byte[] data, int predictor, int samplesPerPixel,
+                         int width, int height) throws IOException {
+        if (data[0] == (byte)0x00 && data[1] == (byte)0x01) {
+            throw new IIOException("TIFF 5.0-style LZW compression is not supported!");
+        }
+
+        this.srcData = data;
+        this.srcIndex = 0;
+        this.nextData = 0;
+        this.nextBits = 0;
+
+        this.dstData = new byte[8192];
+        this.dstIndex = 0;
+
+        initializeStringTable();
+
+        int code, oldCode = 0;
+        byte[] string;
+
+        while ((code = getNextCode()) != 257) {
+            if (code == 256) {
+                initializeStringTable();
+                code = getNextCode();
+                if (code == 257) {
+                    break;
+                }
+
+                writeString(stringTable[code]);
+                oldCode = code;
+            } else {
+                if (code < tableIndex) {
+                    string = stringTable[code];
+
+                    writeString(string);
+                    addStringToTable(stringTable[oldCode], string[0]);
+                    oldCode = code;
+                } else {
+                    string = stringTable[oldCode];
+                    string = composeString(string, string[0]);
+                    writeString(string);
+                    addStringToTable(string);
+                    oldCode = code;
+                }
+            }
+        }
+
+        if (predictor == 2) {
+
+            int count;
+            for (int j = 0; j < height; j++) {
+
+                count = samplesPerPixel * (j * width + 1);
+
+                for (int i = samplesPerPixel; i < width * samplesPerPixel; i++) {
+
+                    dstData[count] += dstData[count - samplesPerPixel];
+                    count++;
+                }
+            }
+        }
+
+        byte[] newDstData = new byte[dstIndex];
+        System.arraycopy(dstData, 0, newDstData, 0, dstIndex);
+        return newDstData;
+    }
+
+    /**
+     * Initialize the string table.
+     */
+    public void initializeStringTable() {
+        stringTable = new byte[4096][];
+
+        for (int i = 0; i < 256; i++) {
+            stringTable[i] = new byte[1];
+            stringTable[i][0] = (byte)i;
+        }
+
+        tableIndex = 258;
+        bitsToGet = 9;
+    }
+
+    private void ensureCapacity(int bytesToAdd) {
+        if (dstIndex + bytesToAdd > dstData.length) {
+            byte[] newDstData = new byte[Math.max((int)(dstData.length*1.2f),
+                                                  dstIndex + bytesToAdd)];
+            System.arraycopy(dstData, 0, newDstData, 0, dstData.length);
+            dstData = newDstData;
+        }
+    }
+
+    /**
+     * Write out the string just uncompressed.
+     */
+    public void writeString(byte string[]) {
+        ensureCapacity(string.length);
+        for (int i = 0; i < string.length; i++) {
+            dstData[dstIndex++] = string[i];
+        }
+    }
+
+    /**
+     * Add a new string to the string table.
+     */
+    public void addStringToTable(byte oldString[], byte newString) {
+        int length = oldString.length;
+        byte string[] = new byte[length + 1];
+        System.arraycopy(oldString, 0, string, 0, length);
+        string[length] = newString;
+
+        // Add this new String to the table
+        stringTable[tableIndex++] = string;
+
+        if (tableIndex == 511) {
+            bitsToGet = 10;
+        } else if (tableIndex == 1023) {
+            bitsToGet = 11;
+        } else if (tableIndex == 2047) {
+            bitsToGet = 12;
+        }
+    }
+
+    /**
+     * Add a new string to the string table.
+     */
+    public void addStringToTable(byte string[]) {
+        // Add this new String to the table
+        stringTable[tableIndex++] = string;
+
+        if (tableIndex == 511) {
+            bitsToGet = 10;
+        } else if (tableIndex == 1023) {
+            bitsToGet = 11;
+        } else if (tableIndex == 2047) {
+            bitsToGet = 12;
+        }
+    }
+
+    /**
+     * Append <code>newString</code> to the end of <code>oldString</code>.
+     */
+    public byte[] composeString(byte oldString[], byte newString) {
+        int length = oldString.length;
+        byte string[] = new byte[length + 1];
+        System.arraycopy(oldString, 0, string, 0, length);
+        string[length] = newString;
+
+        return string;
+    }
+
+    // Returns the next 9, 10, 11 or 12 bits
+    public int getNextCode() {
+        // Attempt to get the next code. The exception is caught to make
+        // this robust to cases wherein the EndOfInformation code has been
+        // omitted from a strip. Examples of such cases have been observed
+        // in practice.
+
+        try {
+            nextData = (nextData << 8) | (srcData[srcIndex++] & 0xff);
+            nextBits += 8;
+
+            if (nextBits < bitsToGet) {
+                nextData = (nextData << 8) | (srcData[srcIndex++] & 0xff);
+                nextBits += 8;
+            }
+
+            int code =
+                (nextData >> (nextBits - bitsToGet)) & andTable[bitsToGet - 9];
+            nextBits -= bitsToGet;
+
+            return code;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            // Strip not terminated as expected: return EndOfInformation code.
+            return 257;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFMetadataFormat.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import javax.imageio.metadata.IIOMetadataFormat;
+
+public abstract class TIFFMetadataFormat implements IIOMetadataFormat {
+
+    protected Map<String,TIFFElementInfo> elementInfoMap = new HashMap<String,TIFFElementInfo>();
+    protected Map<String,TIFFAttrInfo> attrInfoMap = new HashMap<String,TIFFAttrInfo>();
+
+    protected String resourceBaseName;
+    protected String rootName;
+
+    public String getRootName() {
+        return rootName;
+    }
+
+    private String getResource(String key, Locale locale) {
+        if (locale == null) {
+            locale = Locale.getDefault();
+        }
+        try {
+            ResourceBundle bundle =
+                ResourceBundle.getBundle(resourceBaseName, locale);
+            return bundle.getString(key);
+        } catch (MissingResourceException e) {
+            return null;
+        }
+    }
+
+    private TIFFElementInfo getElementInfo(String elementName) {
+        if (elementName == null) {
+            throw new NullPointerException("elementName == null!");
+        }
+        TIFFElementInfo info =
+            elementInfoMap.get(elementName);
+        if (info == null) {
+            throw new IllegalArgumentException("No such element: " +
+                                               elementName);
+        }
+        return info;
+    }
+
+    private TIFFAttrInfo getAttrInfo(String elementName, String attrName) {
+        if (elementName == null) {
+            throw new NullPointerException("elementName == null!");
+        }
+        if (attrName == null) {
+            throw new NullPointerException("attrName == null!");
+        }
+        String key = elementName + "/" + attrName;
+        TIFFAttrInfo info = attrInfoMap.get(key);
+        if (info == null) {
+            throw new IllegalArgumentException("No such attribute: " + key);
+        }
+        return info;
+    }
+
+    public int getElementMinChildren(String elementName) {
+        TIFFElementInfo info = getElementInfo(elementName);
+        return info.minChildren;
+    }
+
+    public int getElementMaxChildren(String elementName) {
+        TIFFElementInfo info = getElementInfo(elementName);
+        return info.maxChildren;
+    }
+
+    public String getElementDescription(String elementName, Locale locale) {
+        if (!elementInfoMap.containsKey(elementName)) {
+            throw new IllegalArgumentException("No such element: " +
+                                               elementName);
+        }
+        return getResource(elementName, locale);
+    }
+
+    public int getChildPolicy(String elementName) {
+        TIFFElementInfo info = getElementInfo(elementName);
+        return info.childPolicy;
+    }
+
+    public String[] getChildNames(String elementName) {
+        TIFFElementInfo info = getElementInfo(elementName);
+        return info.childNames;
+    }
+
+    public String[] getAttributeNames(String elementName) {
+        TIFFElementInfo info = getElementInfo(elementName);
+        return info.attributeNames;
+    }
+
+    public int getAttributeValueType(String elementName, String attrName) {
+        TIFFAttrInfo info = getAttrInfo(elementName, attrName);
+        return info.valueType;
+    }
+
+    public int getAttributeDataType(String elementName, String attrName) {
+        TIFFAttrInfo info = getAttrInfo(elementName, attrName);
+        return info.dataType;
+    }
+
+    public boolean isAttributeRequired(String elementName, String attrName) {
+        TIFFAttrInfo info = getAttrInfo(elementName, attrName);
+        return info.isRequired;
+    }
+
+    public String getAttributeDefaultValue(String elementName,
+                                           String attrName) {
+        return null;
+    }
+
+    public String[] getAttributeEnumerations(String elementName,
+                                             String attrName) {
+        throw new IllegalArgumentException("The attribute is not an enumeration.");
+    }
+
+    public String getAttributeMinValue(String elementName, String attrName) {
+        throw new IllegalArgumentException("The attribute is not a range.");
+    }
+
+    public String getAttributeMaxValue(String elementName, String attrName) {
+        throw new IllegalArgumentException("The attribute is not a range.");
+    }
+
+    public int getAttributeListMinLength(String elementName, String attrName) {
+        TIFFAttrInfo info = getAttrInfo(elementName, attrName);
+        return info.listMinLength;
+    }
+
+    public int getAttributeListMaxLength(String elementName, String attrName) {
+        TIFFAttrInfo info = getAttrInfo(elementName, attrName);
+        return info.listMaxLength;
+    }
+
+    public String getAttributeDescription(String elementName, String attrName,
+                                          Locale locale) {
+        String key = elementName + "/" + attrName;
+        if (!attrInfoMap.containsKey(key)) {
+            throw new IllegalArgumentException("No such attribute: " + key);
+        }
+        return getResource(key, locale);
+    }
+
+    public int getObjectValueType(String elementName) {
+        TIFFElementInfo info = getElementInfo(elementName);
+        return info.objectValueType;
+    }
+
+    public Class<?> getObjectClass(String elementName) {
+        TIFFElementInfo info = getElementInfo(elementName);
+        if (info.objectValueType == VALUE_NONE) {
+            throw new IllegalArgumentException(
+                     "Element cannot contain an object value: " + elementName);
+        }
+        return info.objectClass;
+    }
+
+    public Object getObjectDefaultValue(String elementName) {
+        TIFFElementInfo info = getElementInfo(elementName);
+        if (info.objectValueType == VALUE_NONE) {
+            throw new IllegalArgumentException(
+                     "Element cannot contain an object value: " + elementName);
+        }
+        return info.objectDefaultValue;
+    }
+
+    public Object[] getObjectEnumerations(String elementName) {
+        TIFFElementInfo info = getElementInfo(elementName);
+        if (info.objectValueType == VALUE_NONE) {
+            throw new IllegalArgumentException(
+                     "Element cannot contain an object value: " + elementName);
+        }
+        return info.objectEnumerations;
+    }
+
+    public Comparable<Object> getObjectMinValue(String elementName) {
+        TIFFElementInfo info = getElementInfo(elementName);
+        if (info.objectValueType == VALUE_NONE) {
+            throw new IllegalArgumentException(
+                     "Element cannot contain an object value: " + elementName);
+        }
+        return info.objectMinValue;
+    }
+
+    public Comparable<Object> getObjectMaxValue(String elementName) {
+        TIFFElementInfo info = getElementInfo(elementName);
+        if (info.objectValueType == VALUE_NONE) {
+            throw new IllegalArgumentException(
+                     "Element cannot contain an object value: " + elementName);
+        }
+        return info.objectMaxValue;
+    }
+
+    public int getObjectArrayMinLength(String elementName) {
+        TIFFElementInfo info = getElementInfo(elementName);
+        if (info.objectValueType == VALUE_NONE) {
+            throw new IllegalArgumentException(
+                     "Element cannot contain an object value: " + elementName);
+        }
+        return info.objectArrayMinLength;
+    }
+
+    public int getObjectArrayMaxLength(String elementName) {
+        TIFFElementInfo info = getElementInfo(elementName);
+        if (info.objectValueType == VALUE_NONE) {
+            throw new IllegalArgumentException(
+                     "Element cannot contain an object value: " + elementName);
+        }
+        return info.objectArrayMaxLength;
+    }
+
+    public TIFFMetadataFormat() {}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullCompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import java.io.IOException;
+
+public class TIFFNullCompressor extends TIFFCompressor {
+
+    public TIFFNullCompressor() {
+        super("", BaselineTIFFTagSet.COMPRESSION_NONE, true);
+    }
+
+    public int encode(byte[] b, int off,
+                      int width, int height,
+                      int[] bitsPerSample,
+                      int scanlineStride) throws IOException {
+        int bitsPerPixel = 0;
+        for (int i = 0; i < bitsPerSample.length; i++) {
+            bitsPerPixel += bitsPerSample[i];
+        }
+
+        int bytesPerRow = (bitsPerPixel*width + 7)/8;
+        int numBytes = height*bytesPerRow;
+
+        if(bytesPerRow == scanlineStride) {
+            stream.write(b, off, numBytes);
+        } else {
+            for (int row = 0; row < height; row++) {
+                stream.write(b, off, bytesPerRow);
+                off += scanlineStride;
+            }
+        }
+
+        return numBytes;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFNullDecompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.io.EOFException;
+import java.io.IOException;
+
+public class TIFFNullDecompressor extends TIFFDecompressor {
+
+    /**
+     * Whether to read the active source region only.
+     */
+    private boolean isReadActiveOnly = false;
+
+    /** The original value of <code>srcMinX</code>. */
+    private int originalSrcMinX;
+
+    /** The original value of <code>srcMinY</code>. */
+    private int originalSrcMinY;
+
+    /** The original value of <code>srcWidth</code>. */
+    private int originalSrcWidth;
+
+    /** The original value of <code>srcHeight</code>. */
+    private int originalSrcHeight;
+
+    public TIFFNullDecompressor() {}
+
+    //
+    // This approach to reading the active region is a not the best
+    // as the original values of the entire source region are stored,
+    // overwritten, and then restored. It would probably be better to
+    // revise TIFFDecompressor such that this were not necessary, i.e.,
+    // change beginDecoding() and decode() to use the active region values
+    // when random access is easy and the entire region values otherwise.
+    //
+    public void beginDecoding() {
+        // Determine number of bits per pixel.
+        int bitsPerPixel = 0;
+        for(int i = 0; i < bitsPerSample.length; i++) {
+            bitsPerPixel += bitsPerSample[i];
+        }
+
+        // Can read active region only if row starts on a byte boundary.
+        if((activeSrcMinX != srcMinX || activeSrcMinY != srcMinY ||
+            activeSrcWidth != srcWidth || activeSrcHeight != srcHeight) &&
+           ((activeSrcMinX - srcMinX)*bitsPerPixel) % 8 == 0) {
+            // Set flag.
+            isReadActiveOnly = true;
+
+            // Cache original region.
+            originalSrcMinX = srcMinX;
+            originalSrcMinY = srcMinY;
+            originalSrcWidth = srcWidth;
+            originalSrcHeight = srcHeight;
+
+            // Replace region with active region.
+            srcMinX = activeSrcMinX;
+            srcMinY = activeSrcMinY;
+            srcWidth = activeSrcWidth;
+            srcHeight = activeSrcHeight;
+        } else {
+            // Clear flag.
+            isReadActiveOnly = false;
+        }
+
+        super.beginDecoding();
+    }
+
+    public void decode() throws IOException {
+        super.decode();
+
+        // Reset state.
+        if(isReadActiveOnly) {
+            // Restore original source region values.
+            srcMinX = originalSrcMinX;
+            srcMinY = originalSrcMinY;
+            srcWidth = originalSrcWidth;
+            srcHeight = originalSrcHeight;
+
+            // Unset flag.
+            isReadActiveOnly = false;
+        }
+    }
+
+    public void decodeRaw(byte[] b,
+                          int dstOffset,
+                          int bitsPerPixel,
+                          int scanlineStride) throws IOException {
+        if(isReadActiveOnly) {
+            // Read the active source region only.
+
+            int activeBytesPerRow = (activeSrcWidth*bitsPerPixel + 7)/8;
+            int totalBytesPerRow = (originalSrcWidth*bitsPerPixel + 7)/8;
+            int bytesToSkipPerRow = totalBytesPerRow - activeBytesPerRow;
+
+            //
+            // Seek to the start of the active region:
+            //
+            // active offset = original offset +
+            //                 number of bytes to start of first active row +
+            //                 number of bytes to first active pixel within row
+            //
+            // Since the condition for reading from the active region only is
+            //
+            //     ((activeSrcMinX - srcMinX)*bitsPerPixel) % 8 == 0
+            //
+            // the bit offset to the first active pixel within the first
+            // active row is a multiple of 8.
+            //
+            stream.seek(offset +
+                        (activeSrcMinY - originalSrcMinY)*totalBytesPerRow +
+                        ((activeSrcMinX - originalSrcMinX)*bitsPerPixel)/8);
+
+            int lastRow = activeSrcHeight - 1;
+            for (int y = 0; y < activeSrcHeight; y++) {
+                int bytesRead = stream.read(b, dstOffset, activeBytesPerRow);
+                if (bytesRead < 0) {
+                    throw new EOFException();
+                } else if (bytesRead != activeBytesPerRow) {
+                    break;
+                }
+                dstOffset += scanlineStride;
+
+                // Skip unneeded bytes (row suffix + row prefix).
+                if(y != lastRow) {
+                    stream.skipBytes(bytesToSkipPerRow);
+                }
+            }
+        } else {
+            // Read the entire source region.
+            stream.seek(offset);
+            int bytesPerRow = (srcWidth*bitsPerPixel + 7)/8;
+            if(bytesPerRow == scanlineStride) {
+                if (stream.read(b, dstOffset, bytesPerRow*srcHeight) < 0) {
+                    throw new EOFException();
+                }
+            } else {
+                for (int y = 0; y < srcHeight; y++) {
+                    int bytesRead = stream.read(b, dstOffset, bytesPerRow);
+                    if (bytesRead < 0) {
+                        throw new EOFException();
+                    } else if (bytesRead != bytesPerRow) {
+                        break;
+                    }
+                    dstOffset += scanlineStride;
+                }
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFOldJPEGDecompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,617 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.io.IOException;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import javax.imageio.IIOException;
+import javax.imageio.stream.MemoryCacheImageInputStream;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFField;
+
+/**
+ * <code>TIFFDecompressor</code> for "Old JPEG" compression.
+ */
+public class TIFFOldJPEGDecompressor extends TIFFJPEGDecompressor {
+
+    // Define Huffman Tables
+    private static final int DHT = 0xC4;
+
+    // Define Quantisation Tables
+    private static final int DQT = 0xDB;
+
+    // Define Restart Interval
+    private static final int DRI = 0xDD;
+
+    // Baseline DCT
+    private static final int SOF0 = 0xC0;
+
+    // Start of Scan
+    private static final int SOS = 0xDA;
+
+    // End of Image
+    // private static final int EOI = 0xD9; // now defined in superclass
+
+    // Whether the decompressor has been initialized.
+    private boolean isInitialized = false;
+
+    //
+    // Instance variables set by the initialize() method.
+    //
+    // Offset to a complete, contiguous JPEG stream.
+    private Long JPEGStreamOffset = null;
+    // Offset to the SOF marker.
+    private int SOFPosition = -1;
+    // Value of the SOS marker.
+    private byte[] SOSMarker = null;
+
+    // Horizontal chroma subsampling factor.
+    private int subsamplingX = 2;
+
+    // Vertical chroma subsampling factor.
+    private int subsamplingY = 2;
+
+    public TIFFOldJPEGDecompressor() {}
+
+    //
+    // Intialize instance variables according to an analysis of the
+    // TIFF field content. See bug 4929147 for test image information.
+    //
+    // Case 1: Image contains a single strip or tile and the offset to
+    //         that strip or tile points to an SOI marker.
+    //
+    //         Example:
+    //         "Visionshape Inc. Compression Software, version 2.5"
+    //         ColorTiffWithBarcode.tif
+    //         Color2.tif (pages 2-5 (indexes 1-4)
+    //         color3.tif (pages 2-5 (indexes 1-4)
+    //
+    //         "Kofax standard Multi-Page TIFF Storage Filter v2.01.000"
+    //         01.tif (pages 1 and 3(indexes 0 and 2))
+    //
+    //         Instance variables set: JPEGStreamOffset
+    //
+    // Case 2: Image contains a single strip or tile and a
+    //         JPEGInterchangeFormat field is present but the
+    //         JPEGInterchangeFormatLength is erroneously missing.
+    //
+    //         Example:
+    //         "Kofax standard Multi-Page TIFF Storage Filter v2.01.000"
+    //         01.tif (pages 1 and 3(indexes 0 and 2))
+    //         (but this example also satisfies case 1)
+    //
+    //         Instance variables set: JPEGStreamOffset
+    //
+    // Case 3: Image contains a single strip or tile, the
+    //         JPEGInterchangeFormat and JPEGInterchangeFormatLength
+    //         fields are both present, the value of JPEGInterchangeFormat
+    //         is less than the offset to the strip or tile, and the sum
+    //         of the values of JPEGInterchangeFormat and
+    //         JPEGInterchangeFormatLength is greater than the offset to
+    //         the strip or tile.
+    //
+    //         Instance variables set: JPEGStreamOffset
+    //
+    //         Example:
+    //         "HP IL v1.1"
+    //         smallliz.tif from libtiff test data.
+    //
+    //         Instance variables set: JPEGStreamOffset
+    //
+    // Cases 4-5 apply if none of cases 1-3 applies or the image has multiple
+    // strips or tiles.
+    //
+    // Case 4: JPEGInterchangeFormat and JPEGInterchangeFormatLength are
+    //         present, the value of JPEGInterchangeFormatLength is at least 2,
+    //         and the sum of the values of these two fields is at most the
+    //         value of the offset to the first strip or tile.
+    //
+    //         Instance variables set: tables, SOFPosition, SOSMarker
+    //
+    //         Example:
+    //         "Oi/GFS, writer v00.06.00P, (c) Wang Labs, Inc. 1990, 1991"
+    //         03.tif (pages 1 and 3(indexes 0 and 2))
+    //
+    //         "Oi/GFS, writer v00.06.02"
+    //         Color2.tif (page 1 (index 0))
+    //         color3.tif (page 1 (index 0))
+    //
+    // Case 5: If none of the foregoing cases apply. For this case the
+    //         JPEGQTables, JPEGACTables, and JPEGDCTables must be valid.
+    //
+    //         Instance variables set: tables, SOFPosition, SOSMarker
+    //
+    //         Example:
+    //         "NeXT"
+    //         zackthecat.tif from libtiff test data.
+    //
+    private synchronized void initialize() throws IOException {
+        if(isInitialized) {
+            return;
+        }
+
+        // Get the TIFF metadata object.
+        TIFFImageMetadata tim = (TIFFImageMetadata)metadata;
+
+        // Get the JPEGInterchangeFormat field.
+        TIFFField JPEGInterchangeFormatField =
+            tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
+
+        // Get the tile or strip offsets.
+        TIFFField segmentOffsetField =
+            tim.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
+        if(segmentOffsetField == null) {
+            segmentOffsetField =
+                tim.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
+            if(segmentOffsetField == null) {
+                segmentOffsetField = JPEGInterchangeFormatField;
+            }
+        }
+        long[] segmentOffsets = segmentOffsetField.getAsLongs();
+
+            // Determine whether the image has more than one strip or tile.
+        boolean isTiled = segmentOffsets.length > 1;
+
+        if(!isTiled) {
+            //
+            // If the image has only a single strip or tile and it looks
+            // as if a complete JPEG stream is present then set the value
+            // of JPEGStreamOffset to the offset of the JPEG stream;
+            // otherwise leave JPEGStreamOffset set to null.
+            //
+
+            stream.seek(offset);
+            stream.mark();
+            if(stream.read() == 0xff && stream.read() == SOI) {
+                // Tile or strip offset points to SOI.
+                JPEGStreamOffset = Long.valueOf(offset);
+
+                // Set initialization flag and return.
+                ((TIFFImageReader)reader).forwardWarningMessage("SOI marker detected at start of strip or tile.");
+                isInitialized = true;
+                return;
+            }
+            stream.reset();
+
+            if(JPEGInterchangeFormatField != null) {
+                // Get the value of JPEGInterchangeFormat.
+                long jpegInterchangeOffset =
+                    JPEGInterchangeFormatField.getAsLong(0);
+
+                // Get the JPEGInterchangeFormatLength field.
+                TIFFField JPEGInterchangeFormatLengthField =
+                    tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+
+                if(JPEGInterchangeFormatLengthField == null) {
+                    // JPEGInterchangeFormat stream is of indeterminate
+                    // length so use it as a complete JPEG stream.
+                    JPEGStreamOffset = Long.valueOf(jpegInterchangeOffset);
+
+                    // Set initialization flag and return.
+                    ((TIFFImageReader)reader).forwardWarningMessage("JPEGInterchangeFormatLength field is missing");
+                    isInitialized = true;
+                    return;
+                } else {
+                    // Get the JPEGInterchangeFormatLength field's value.
+                    long jpegInterchangeLength =
+                        JPEGInterchangeFormatLengthField.getAsLong(0);
+
+                    if(jpegInterchangeOffset < segmentOffsets[0] &&
+                       (jpegInterchangeOffset + jpegInterchangeLength) >
+                       segmentOffsets[0]) {
+                        // JPEGInterchangeFormat points to a position
+                        // below the segment start position and ends at
+                        // a position after the segment start position.
+                        JPEGStreamOffset = Long.valueOf(jpegInterchangeOffset);
+
+                        // Set initialization flag and return.
+                        isInitialized = true;
+                        return;
+                    }
+                }
+            }
+        }
+
+        // Get the subsampling factors.
+        TIFFField YCbCrSubsamplingField =
+            tim.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
+        if(YCbCrSubsamplingField != null) {
+            subsamplingX = YCbCrSubsamplingField.getAsChars()[0];
+            subsamplingY = YCbCrSubsamplingField.getAsChars()[1];
+        }
+
+        //
+        // Initialize the 'tables' instance variable either for later
+        // use in prepending to individual abbreviated strips or tiles.
+        //
+        if(JPEGInterchangeFormatField != null) {
+            // Get the value of JPEGInterchangeFormat.
+            long jpegInterchangeOffset =
+                JPEGInterchangeFormatField.getAsLong(0);
+
+            // Get the JPEGInterchangeFormatLength field.
+            TIFFField JPEGInterchangeFormatLengthField =
+                tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+
+            if(JPEGInterchangeFormatLengthField != null) {
+                // Get the JPEGInterchangeFormatLength field's value.
+                long jpegInterchangeLength =
+                    JPEGInterchangeFormatLengthField.getAsLong(0);
+
+                if(jpegInterchangeLength >= 2 &&
+                   jpegInterchangeOffset + jpegInterchangeLength <=
+                   segmentOffsets[0]) {
+                    // Determine the length excluding any terminal EOI marker
+                    // and allocate table memory.
+                    stream.mark();
+                    stream.seek(jpegInterchangeOffset+jpegInterchangeLength-2);
+                    if(stream.read() == 0xff && stream.read() == EOI) {
+                        this.tables = new byte[(int)(jpegInterchangeLength-2)];
+                    } else {
+                        this.tables = new byte[(int)jpegInterchangeLength];
+                    }
+                    stream.reset();
+
+                    // Read the tables.
+                    stream.mark();
+                    stream.seek(jpegInterchangeOffset);
+                    stream.readFully(tables);
+                    stream.reset();
+
+                    ((TIFFImageReader)reader).forwardWarningMessage("Incorrect JPEG interchange format: using JPEGInterchangeFormat offset to derive tables.");
+                } else {
+                    ((TIFFImageReader)reader).forwardWarningMessage("JPEGInterchangeFormat+JPEGInterchangeFormatLength > offset to first strip or tile.");
+                }
+            }
+        }
+
+        if(this.tables == null) {
+            //
+            // Create tables-only stream in tables[] consisting of
+            // SOI+DQTs+DHTs
+            //
+
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+            // Save stream length;
+            long streamLength = stream.length();
+
+            // SOI
+            baos.write(0xff);
+            baos.write(SOI);
+
+            // Quantization Tables
+            TIFFField f =
+                tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_Q_TABLES);
+            if(f == null) {
+                throw new IIOException("JPEGQTables field missing!");
+            }
+            long[] off = f.getAsLongs();
+
+            for(int i = 0; i < off.length; i++) {
+                baos.write(0xff); // Marker ID
+                baos.write(DQT);
+
+                char markerLength = (char)67;
+                baos.write((markerLength >>> 8) & 0xff); // Length
+                baos.write(markerLength & 0xff);
+
+                baos.write(i); // Table ID and precision
+
+                byte[] qtable = new byte[64];
+                if(streamLength != -1 && off[i] > streamLength) {
+                    throw new IIOException("JPEGQTables offset for index "+
+                                           i+" is not in the stream!");
+                }
+                stream.seek(off[i]);
+                stream.readFully(qtable);
+
+                baos.write(qtable); // Table data
+            }
+
+            // Huffman Tables (k == 0 ? DC : AC).
+            for(int k = 0; k < 2; k++) {
+                int tableTagNumber = k == 0 ?
+                    BaselineTIFFTagSet.TAG_JPEG_DC_TABLES :
+                    BaselineTIFFTagSet.TAG_JPEG_AC_TABLES;
+                f = tim.getTIFFField(tableTagNumber);
+                String fieldName =
+                    tableTagNumber ==
+                    BaselineTIFFTagSet.TAG_JPEG_DC_TABLES ?
+                    "JPEGDCTables" : "JPEGACTables";
+
+                if(f == null) {
+                    throw new IIOException(fieldName+" field missing!");
+                }
+                off = f.getAsLongs();
+
+                for(int i = 0; i < off.length; i++) {
+                    baos.write(0xff); // Marker ID
+                    baos.write(DHT);
+
+                    byte[] blengths = new byte[16];
+                    if(streamLength != -1 && off[i] > streamLength) {
+                        throw new IIOException(fieldName+" offset for index "+
+                                               i+" is not in the stream!");
+                    }
+                    stream.seek(off[i]);
+                    stream.readFully(blengths);
+                    int numCodes = 0;
+                    for(int j = 0; j < 16; j++) {
+                        numCodes += blengths[j]&0xff;
+                    }
+
+                    char markerLength = (char)(19 + numCodes);
+
+                    baos.write((markerLength >>> 8) & 0xff); // Length
+                    baos.write(markerLength & 0xff);
+
+                    baos.write(i | (k << 4)); // Table ID and type
+
+                    baos.write(blengths); // Number of codes
+
+                    byte[] bcodes = new byte[numCodes];
+                    stream.readFully(bcodes);
+                    baos.write(bcodes); // Codes
+                }
+            }
+
+            // SOF0
+            baos.write((byte)0xff); // Marker identifier
+            baos.write((byte)SOF0);
+            short sval = (short)(8 + 3*samplesPerPixel); // Length
+            baos.write((byte)((sval >>> 8) & 0xff));
+            baos.write((byte)(sval & 0xff));
+            baos.write((byte)8); // Data precision
+            sval = (short)srcHeight; // Tile/strip height
+            baos.write((byte)((sval >>> 8) & 0xff));
+            baos.write((byte)(sval & 0xff));
+            sval = (short)srcWidth; // Tile/strip width
+            baos.write((byte)((sval >>> 8) & 0xff));
+            baos.write((byte)(sval & 0xff));
+            baos.write((byte)samplesPerPixel); // Number of components
+            if(samplesPerPixel == 1) {
+                baos.write((byte)1); // Component ID
+                baos.write((byte)0x11); // Subsampling factor
+                baos.write((byte)0); // Quantization table ID
+            } else { // 3
+                for(int i = 0; i < 3; i++) {
+                    baos.write((byte)(i + 1)); // Component ID
+                    baos.write((i != 0) ?
+                               (byte)0x11 :
+                               (byte)(((subsamplingX & 0x0f) << 4) |
+                                      (subsamplingY & 0x0f)));
+
+                    baos.write((byte)i); // Quantization table ID
+                }
+            };
+
+
+            // DRI (optional).
+            f = tim.getTIFFField(BaselineTIFFTagSet.TAG_JPEG_RESTART_INTERVAL);
+            if(f != null) {
+                char restartInterval = f.getAsChars()[0];
+
+                if(restartInterval != 0) {
+                    baos.write((byte)0xff); // Marker identifier
+                    baos.write((byte)DRI);
+
+                    sval = 4;
+                    baos.write((byte)((sval >>> 8) & 0xff)); // Length
+                    baos.write((byte)(sval & 0xff));
+
+                    // RestartInterval
+                    baos.write((byte)((restartInterval >>> 8) & 0xff));
+                    baos.write((byte)(restartInterval & 0xff));
+                }
+            }
+
+            tables = baos.toByteArray();
+        }
+
+        //
+        // Check for presence of SOF marker and save its position.
+        //
+        int idx = 0;
+        int idxMax = tables.length - 1;
+        while(idx < idxMax) {
+            if((tables[idx]&0xff) == 0xff &&
+               (tables[idx+1]&0xff) == SOF0) {
+                SOFPosition = idx;
+                break;
+            }
+            idx++;
+        }
+
+        //
+        // If no SOF marker, add one.
+        //
+        if(SOFPosition == -1) {
+            byte[] tmpTables =
+                new byte[tables.length + 10 + 3*samplesPerPixel];
+            System.arraycopy(tables, 0, tmpTables, 0, tables.length);
+            int tmpOffset = tables.length;
+            SOFPosition = tables.length;
+            tables = tmpTables;
+
+            tables[tmpOffset++] = (byte)0xff; // Marker identifier
+            tables[tmpOffset++] = (byte)SOF0;
+            short sval = (short)(8 + 3*samplesPerPixel); // Length
+            tables[tmpOffset++] = (byte)((sval >>> 8) & 0xff);
+            tables[tmpOffset++] = (byte)(sval & 0xff);
+            tables[tmpOffset++] = (byte)8; // Data precision
+            sval = (short)srcHeight; // Tile/strip height
+            tables[tmpOffset++] = (byte)((sval >>> 8) & 0xff);
+            tables[tmpOffset++] = (byte)(sval & 0xff);
+            sval = (short)srcWidth; // Tile/strip width
+            tables[tmpOffset++] = (byte)((sval >>> 8) & 0xff);
+            tables[tmpOffset++] = (byte)(sval & 0xff);
+            tables[tmpOffset++] = (byte)samplesPerPixel; // Number of components
+            if(samplesPerPixel == 1) {
+                tables[tmpOffset++] = (byte)1; // Component ID
+                tables[tmpOffset++] = (byte)0x11; // Subsampling factor
+                tables[tmpOffset++] = (byte)0; // Quantization table ID
+            } else { // 3
+                for(int i = 0; i < 3; i++) {
+                    tables[tmpOffset++] = (byte)(i + 1); // Component ID
+                    tables[tmpOffset++] = (i != 0) ?
+                        (byte)0x11 :
+                        (byte)(((subsamplingX & 0x0f) << 4) |
+                               (subsamplingY & 0x0f));
+
+                    tables[tmpOffset++] = (byte)i; // Quantization table ID
+                }
+            };
+        }
+
+        //
+        // Initialize SOSMarker.
+        //
+        stream.mark();
+        stream.seek(segmentOffsets[0]);
+        if(stream.read() == 0xff && stream.read() == SOS) {
+            //
+            // If the first segment starts with an SOS marker save it.
+            //
+            int SOSLength = (stream.read()<<8)|stream.read();
+            SOSMarker = new byte[SOSLength+2];
+            SOSMarker[0] = (byte)0xff;
+            SOSMarker[1] = (byte)SOS;
+            SOSMarker[2] = (byte)((SOSLength & 0xff00) >> 8);
+            SOSMarker[3] = (byte)(SOSLength & 0xff);
+            stream.readFully(SOSMarker, 4, SOSLength - 2);
+        } else {
+            //
+            // Manufacture an SOS marker.
+            //
+            SOSMarker = new byte[2 + 6 + 2*samplesPerPixel];
+            int SOSMarkerIndex = 0;
+            SOSMarker[SOSMarkerIndex++] = (byte)0xff; // Marker identifier
+            SOSMarker[SOSMarkerIndex++] = (byte)SOS;
+            short sval = (short)(6 + 2*samplesPerPixel); // Length
+            SOSMarker[SOSMarkerIndex++] = (byte)((sval >>> 8) & 0xff);
+            SOSMarker[SOSMarkerIndex++] = (byte)(sval & 0xff);
+            // Number of components in scan
+            SOSMarker[SOSMarkerIndex++] = (byte)samplesPerPixel;
+            if(samplesPerPixel == 1) {
+                SOSMarker[SOSMarkerIndex++] = (byte)1; // Component ID
+                SOSMarker[SOSMarkerIndex++] = (byte)0; // Huffman table ID
+            } else { // 3
+                for(int i = 0; i < 3; i++) {
+                    SOSMarker[SOSMarkerIndex++] =
+                        (byte)(i + 1); // Component ID
+                    SOSMarker[SOSMarkerIndex++] =
+                        (byte)((i << 4) | i); // Huffman table IDs
+                }
+            };
+            SOSMarker[SOSMarkerIndex++] = (byte)0;
+            SOSMarker[SOSMarkerIndex++] = (byte)0x3f;
+            SOSMarker[SOSMarkerIndex++] = (byte)0;
+        }
+        stream.reset();
+
+        // Set initialization flag.
+        isInitialized = true;
+    }
+
+    //
+    // The strategy for reading cases 1-3 is to treat the data as a complete
+    // JPEG interchange stream located at JPEGStreamOffset.
+    //
+    // The strategy for cases 4-5 is to concatenate a tables stream created
+    // in initialize() with the entropy coded data in each strip or tile.
+    //
+    public void decodeRaw(byte[] b,
+                          int dstOffset,
+                          int bitsPerPixel,
+                          int scanlineStride) throws IOException {
+
+        initialize();
+
+        TIFFImageMetadata tim = (TIFFImageMetadata)metadata;
+
+        if(JPEGStreamOffset != null) {
+            stream.seek(JPEGStreamOffset.longValue());
+            JPEGReader.setInput(stream, false, true);
+        } else {
+            // Determine buffer length and allocate.
+            int tableLength = tables.length;
+            int bufLength =
+                tableLength + SOSMarker.length + byteCount + 2; // 2 for EOI.
+            byte[] buf = new byte[bufLength];
+            System.arraycopy(tables, 0, buf, 0, tableLength);
+            int bufOffset = tableLength;
+
+            // Update the SOF dimensions.
+            short sval = (short)srcHeight; // Tile/strip height
+            buf[SOFPosition + 5] = (byte)((sval >>> 8) & 0xff);
+            buf[SOFPosition + 6] = (byte)(sval & 0xff);
+            sval = (short)srcWidth; // Tile/strip width
+            buf[SOFPosition + 7] = (byte)((sval >>> 8) & 0xff);
+            buf[SOFPosition + 8] = (byte)(sval & 0xff);
+
+            // Seek to data.
+            stream.seek(offset);
+
+            // Add SOS marker if data segment does not start with one.
+            byte[] twoBytes = new byte[2];
+            stream.readFully(twoBytes);
+            if(!((twoBytes[0]&0xff) == 0xff && (twoBytes[1]&0xff) == SOS)) {
+                // Segment does not start with SOS marker;
+                // use the pre-calculated SOS marker.
+                System.arraycopy(SOSMarker, 0, buf, bufOffset,
+                                 SOSMarker.length);
+                bufOffset += SOSMarker.length;
+            }
+
+            // Copy the segment data into the buffer.
+            buf[bufOffset++] = twoBytes[0];
+            buf[bufOffset++] = twoBytes[1];
+            stream.readFully(buf, bufOffset, byteCount - 2);
+            bufOffset += byteCount - 2;
+
+            // EOI.
+            buf[bufOffset++] = (byte)0xff; // Marker identifier
+            buf[bufOffset++] = (byte)EOI;
+
+            ByteArrayInputStream bais =
+                new ByteArrayInputStream(buf, 0, bufOffset);
+            ImageInputStream is = new MemoryCacheImageInputStream(bais);
+
+            JPEGReader.setInput(is, true, true);
+        }
+
+        // Read real image
+        JPEGParam.setDestination(rawImage);
+        JPEGReader.read(0, JPEGParam);
+    }
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        JPEGReader.dispose();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsCompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import java.io.IOException;
+
+public class TIFFPackBitsCompressor extends TIFFCompressor {
+
+    public TIFFPackBitsCompressor() {
+        super("PackBits", BaselineTIFFTagSet.COMPRESSION_PACKBITS, true);
+    }
+
+    /**
+     * Performs PackBits compression for a single buffer of data.
+     * This should be called for each row of each tile. The returned
+     * value is the offset into the output buffer after compression.
+     */
+    private static int packBits(byte[] input, int inOffset, int inCount,
+                                byte[] output, int outOffset) {
+        int inMax = inOffset + inCount - 1;
+        int inMaxMinus1 = inMax - 1;
+
+        while(inOffset <= inMax) {
+            int run = 1;
+            byte replicate = input[inOffset];
+            while(run < 127 && inOffset < inMax &&
+                  input[inOffset] == input[inOffset+1]) {
+                run++;
+                inOffset++;
+            }
+            if(run > 1) {
+                inOffset++;
+                output[outOffset++] = (byte)(-(run - 1));
+                output[outOffset++] = replicate;
+            }
+
+            run = 0;
+            int saveOffset = outOffset;
+            while(run < 128 &&
+                  ((inOffset < inMax &&
+                    input[inOffset] != input[inOffset+1]) ||
+                   (inOffset < inMaxMinus1 &&
+                    input[inOffset] != input[inOffset+2]))) {
+                run++;
+                output[++outOffset] = input[inOffset++];
+            }
+            if(run > 0) {
+                output[saveOffset] = (byte)(run - 1);
+                outOffset++;
+            }
+
+            if(inOffset == inMax) {
+                if(run > 0 && run < 128) {
+                    output[saveOffset]++;
+                    output[outOffset++] = input[inOffset++];
+                } else {
+                    output[outOffset++] = (byte)0;
+                    output[outOffset++] = input[inOffset++];
+                }
+            }
+        }
+
+        return outOffset;
+    }
+
+    public int encode(byte[] b, int off,
+                      int width, int height,
+                      int[] bitsPerSample,
+                      int scanlineStride) throws IOException {
+        int bitsPerPixel = 0;
+        for (int i = 0; i < bitsPerSample.length; i++) {
+            bitsPerPixel += bitsPerSample[i];
+        }
+        int bytesPerRow = (bitsPerPixel*width + 7)/8;
+        int bufSize = (bytesPerRow + (bytesPerRow + 127)/128);
+        byte[] compData = new byte[bufSize];
+
+        int bytesWritten = 0;
+
+        for(int i = 0; i < height; i++) {
+            int bytes = packBits(b, off, scanlineStride, compData, 0);
+            off += scanlineStride;
+            bytesWritten += bytes;
+            stream.write(compData, 0, bytes);
+        }
+
+        return bytesWritten;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsDecompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.io.IOException;
+
+public class TIFFPackBitsDecompressor extends TIFFDecompressor {
+
+    public TIFFPackBitsDecompressor() {
+    }
+
+    public int decode(byte[] srcData, int srcOffset,
+                      byte[] dstData, int dstOffset)
+        throws IOException {
+
+        int srcIndex = srcOffset;
+        int dstIndex = dstOffset;
+
+        int dstArraySize = dstData.length;
+        int srcArraySize = srcData.length;
+        try {
+            while (dstIndex < dstArraySize && srcIndex < srcArraySize) {
+                byte b = srcData[srcIndex++];
+
+                if (b >= 0 && b <= 127) {
+                    // Literal run packet
+
+                    for (int i = 0; i < b + 1; i++) {
+                        dstData[dstIndex++] = srcData[srcIndex++];
+                    }
+                } else if (b <= -1 && b >= -127) {
+                    // 2-byte encoded run packet
+                    byte repeat = srcData[srcIndex++];
+                    for (int i = 0; i < (-b + 1); i++) {
+                        dstData[dstIndex++] = repeat;
+                    }
+                } else {
+                    // No-op packet, do nothing
+                    ++srcIndex;
+                }
+            }
+        } catch(ArrayIndexOutOfBoundsException e) {
+            if(reader instanceof TIFFImageReader) {
+                ((TIFFImageReader)reader).forwardWarningMessage
+                    ("ArrayIndexOutOfBoundsException ignored in TIFFPackBitsDecompressor.decode()");
+            }
+        }
+
+        return dstIndex - dstOffset;
+    }
+
+    public void decodeRaw(byte[] b,
+                          int dstOffset,
+                          int bitsPerPixel,
+                          int scanlineStride) throws IOException {
+        stream.seek(offset);
+
+        byte[] srcData = new byte[byteCount];
+        stream.readFully(srcData);
+
+        int bytesPerRow = (srcWidth*bitsPerPixel + 7)/8;
+        byte[] buf;
+        int bufOffset;
+        if(bytesPerRow == scanlineStride) {
+            buf = b;
+            bufOffset = dstOffset;
+        } else {
+            buf = new byte[bytesPerRow*srcHeight];
+            bufOffset = 0;
+        }
+
+        decode(srcData, 0, buf, bufOffset);
+
+        if(bytesPerRow != scanlineStride) {
+            int off = 0;
+            for (int y = 0; y < srcHeight; y++) {
+                System.arraycopy(buf, off, b, dstOffset, bytesPerRow);
+                off += bytesPerRow;
+                dstOffset += scanlineStride;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFPackBitsUtil.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.io.IOException;
+
+public class TIFFPackBitsUtil {
+
+    byte[] dstData = new byte[8192];
+    int dstIndex = 0;
+
+    public TIFFPackBitsUtil() {
+    }
+
+    private void ensureCapacity(int bytesToAdd) {
+        if (dstIndex + bytesToAdd > dstData.length) {
+            byte[] newDstData = new byte[Math.max((int)(dstData.length*1.2f),
+                                                  dstIndex + bytesToAdd)];
+            System.arraycopy(dstData, 0, newDstData, 0, dstData.length);
+            dstData = newDstData;
+        }
+    }
+
+    public byte[] decode(byte[] srcData) throws IOException {
+        int inIndex = 0;
+        while (inIndex < srcData.length) {
+            byte b = srcData[inIndex++];
+
+            if (b >= 0 && b <= 127) {
+                // Literal run packet
+
+                ensureCapacity(b + 1);
+                for (int i = 0; i < b + 1; i++) {
+                    dstData[dstIndex++] = srcData[inIndex++];
+                }
+            } else if (b <= -1 && b >= -127) {
+                // 2-byte encoded run packet
+                byte repeat = srcData[inIndex++];
+                ensureCapacity(-b + 1);
+                for (int i = 0; i < (-b + 1); i++) {
+                    dstData[dstIndex++] = repeat;
+                }
+            } else {
+                // No-op packet, do nothing
+                ++inIndex;
+            }
+        }
+
+        byte[] newDstData = new byte[dstIndex];
+        System.arraycopy(dstData, 0, newDstData, 0, dstIndex);
+        return newDstData;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRLECompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import java.io.IOException;
+import javax.imageio.IIOException;
+
+/**
+ *
+ */
+public class TIFFRLECompressor extends TIFFFaxCompressor {
+
+    public TIFFRLECompressor() {
+        super("CCITT RLE", BaselineTIFFTagSet.COMPRESSION_CCITT_RLE, true);
+    }
+
+    /**
+     * Encode a row of data using Modified Huffman Compression also known as
+     * CCITT RLE (Run Lenth Encoding).
+     *
+     * @param data        The row of data to compress.
+     * @param rowOffset   Starting index in <code>data</code>.
+     * @param colOffset   Bit offset within first <code>data[rowOffset]</code>.
+     * @param rowLength   Number of bits in the row.
+     * @param compData    The compressed data.
+     *
+     * @return The number of bytes saved in the compressed data array.
+     */
+    public int encodeRLE(byte[] data,
+                         int rowOffset,
+                         int colOffset,
+                         int rowLength,
+                         byte[] compData) {
+        //
+        // Initialize bit buffer machinery.
+        //
+        initBitBuf();
+
+        //
+        // Run-length encode line.
+        //
+        int outIndex =
+            encode1D(data, rowOffset, colOffset, rowLength, compData, 0);
+
+        //
+        // Flush pending bits
+        //
+        while (ndex > 0) {
+            compData[outIndex++] = (byte)(bits >>> 24);
+            bits <<= 8;
+            ndex -= 8;
+        }
+
+        //
+        // Flip the bytes if inverse fill was requested.
+        //
+        if (inverseFill) {
+            byte[] flipTable = TIFFFaxDecompressor.flipTable;
+            for(int i = 0; i < outIndex; i++) {
+                compData[i] = flipTable[compData[i] & 0xff];
+            }
+        }
+
+        return outIndex;
+    }
+
+    public int encode(byte[] b, int off,
+                      int width, int height,
+                      int[] bitsPerSample,
+                      int scanlineStride) throws IOException {
+        if (bitsPerSample.length != 1 || bitsPerSample[0] != 1) {
+            throw new IIOException(
+                            "Bits per sample must be 1 for RLE compression!");
+        }
+
+        // In the worst case, 2 bits of input will result in 9 bits of output,
+        // plus 2 extra bits if the row starts with black.
+        int maxBits = 9*((width + 1)/2) + 2;
+        byte[] compData = new byte[(maxBits + 7)/8];
+
+        int bytes = 0;
+        int rowOffset = off;
+
+        for (int i = 0; i < height; i++) {
+            int rowBytes = encodeRLE(b, rowOffset, 0, width, compData);
+            stream.write(compData, 0, rowBytes);
+
+            rowOffset += scanlineStride;
+            bytes += rowBytes;
+        }
+
+        return bytes;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFRenderedImage.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.Raster;
+import java.awt.image.RenderedImage;
+import java.awt.image.SampleModel;
+import java.awt.image.WritableRaster;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.plugins.tiff.TIFFImageReadParam;
+import javax.imageio.plugins.tiff.TIFFTagSet;
+
+public class TIFFRenderedImage implements RenderedImage {
+
+    private TIFFImageReader reader;
+    private int imageIndex;
+    private ImageReadParam tileParam;
+
+    private int subsampleX;
+    private int subsampleY;
+
+    private boolean isSubsampling;
+
+    private int width;
+    private int height;
+    private int tileWidth;
+    private int tileHeight;
+
+    private ImageTypeSpecifier its;
+
+    public TIFFRenderedImage(TIFFImageReader reader,
+                             int imageIndex,
+                             ImageReadParam readParam,
+                             int width, int height) throws IOException {
+        this.reader = reader;
+        this.imageIndex = imageIndex;
+        this.tileParam = cloneImageReadParam(readParam, false);
+
+        this.subsampleX = tileParam.getSourceXSubsampling();
+        this.subsampleY = tileParam.getSourceYSubsampling();
+
+        this.isSubsampling = this.subsampleX != 1 || this.subsampleY != 1;
+
+        this.width = width/subsampleX;
+        this.height = height/subsampleY;
+
+        // If subsampling is being used, we may not match the
+        // true tile grid exactly, but everything should still work
+        this.tileWidth = reader.getTileWidth(imageIndex)/subsampleX;
+        this.tileHeight = reader.getTileHeight(imageIndex)/subsampleY;
+
+        Iterator<ImageTypeSpecifier> iter = reader.getImageTypes(imageIndex);
+        this.its = iter.next();
+        tileParam.setDestinationType(its);
+    }
+
+    /**
+     * Creates a copy of <code>param</code>. The source subsampling and
+     * and bands settings and the destination bands and offset settings
+     * are copied. If <code>param</code> is a <code>TIFFImageReadParam</code>
+     * then the <code>TIFFDecompressor</code> and
+     * <code>TIFFColorConverter</code> settings are also copied; otherwise
+     * they are explicitly set to <code>null</code>.
+     *
+     * @param param the parameters to be copied.
+     * @param copyTagSets whether the <code>TIFFTagSet</code> settings
+     * should be copied if set.
+     * @return copied parameters.
+     */
+    private ImageReadParam cloneImageReadParam(ImageReadParam param,
+                                               boolean copyTagSets) {
+        // Create a new TIFFImageReadParam.
+        TIFFImageReadParam newParam = new TIFFImageReadParam();
+
+        // Copy the basic settings.
+        newParam.setSourceSubsampling(param.getSourceXSubsampling(),
+                                      param.getSourceYSubsampling(),
+                                      param.getSubsamplingXOffset(),
+                                      param.getSubsamplingYOffset());
+        newParam.setSourceBands(param.getSourceBands());
+        newParam.setDestinationBands(param.getDestinationBands());
+        newParam.setDestinationOffset(param.getDestinationOffset());
+
+        if (param instanceof TIFFImageReadParam && copyTagSets) {
+            // Copy the settings from the input parameter.
+            TIFFImageReadParam tparam = (TIFFImageReadParam) param;
+
+            List<TIFFTagSet> tagSets = tparam.getAllowedTagSets();
+            if (tagSets != null) {
+                Iterator<TIFFTagSet> tagSetIter = tagSets.iterator();
+                if (tagSetIter != null) {
+                    while (tagSetIter.hasNext()) {
+                        TIFFTagSet tagSet = tagSetIter.next();
+                        newParam.addAllowedTagSet(tagSet);
+                    }
+                }
+            }
+        }
+
+        return newParam;
+    }
+
+    public Vector<RenderedImage> getSources() {
+        return null;
+    }
+
+    public Object getProperty(String name) {
+        return java.awt.Image.UndefinedProperty;
+    }
+
+    public String[] getPropertyNames() {
+        return null;
+    }
+
+    public ColorModel getColorModel() {
+        return its.getColorModel();
+    }
+
+    public SampleModel getSampleModel() {
+        return its.getSampleModel();
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public int getMinX() {
+        return 0;
+    }
+
+    public int getMinY() {
+        return 0;
+    }
+
+    public int getNumXTiles() {
+        return (width + tileWidth - 1)/tileWidth;
+    }
+
+    public int getNumYTiles() {
+        return (height + tileHeight - 1)/tileHeight;
+    }
+
+    public int getMinTileX() {
+        return 0;
+    }
+
+    public int getMinTileY() {
+        return 0;
+    }
+
+    public int getTileWidth() {
+        return tileWidth;
+    }
+
+    public int getTileHeight() {
+        return tileHeight;
+    }
+
+    public int getTileGridXOffset() {
+        return 0;
+    }
+
+    public int getTileGridYOffset() {
+        return 0;
+    }
+
+    public Raster getTile(int tileX, int tileY) {
+        Rectangle tileRect = new Rectangle(tileX*tileWidth,
+                                           tileY*tileHeight,
+                                           tileWidth,
+                                           tileHeight);
+        return getData(tileRect);
+    }
+
+    public Raster getData() {
+        return read(new Rectangle(0, 0, getWidth(), getHeight()));
+    }
+
+    public Raster getData(Rectangle rect) {
+        return read(rect);
+    }
+
+    // This method needs to be synchronized as it updates the instance
+    // variable 'tileParam'.
+    public synchronized WritableRaster read(Rectangle rect) {
+        tileParam.setSourceRegion(isSubsampling ?
+                                  new Rectangle(subsampleX*rect.x,
+                                                subsampleY*rect.y,
+                                                subsampleX*rect.width,
+                                                subsampleY*rect.height) :
+                                  rect);
+
+        try {
+            BufferedImage bi = reader.read(imageIndex, tileParam);
+            WritableRaster ras = bi.getRaster();
+            return ras.createWritableChild(0, 0,
+                                           ras.getWidth(), ras.getHeight(),
+                                           rect.x, rect.y,
+                                           null);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public WritableRaster copyData(WritableRaster raster) {
+        if (raster == null) {
+            return read(new Rectangle(0, 0, getWidth(), getHeight()));
+        } else {
+            Raster src = read(raster.getBounds());
+            raster.setRect(src);
+            return raster;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadata.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.nio.ByteOrder;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.metadata.IIOInvalidTreeException;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+public class TIFFStreamMetadata extends IIOMetadata {
+
+    // package scope
+    static final String NATIVE_METADATA_FORMAT_NAME =
+        "javax_imageio_tiff_stream_1.0";
+
+    static final String NATIVE_METADATA_FORMAT_CLASS_NAME =
+        "javax.imageio.plugins.tiff.TIFFStreamMetadataFormat";
+
+    private static final String bigEndianString =
+        ByteOrder.BIG_ENDIAN.toString();
+    private static final String littleEndianString =
+        ByteOrder.LITTLE_ENDIAN.toString();
+
+    public ByteOrder byteOrder = ByteOrder.BIG_ENDIAN;
+
+    public TIFFStreamMetadata() {
+        super(false,
+              NATIVE_METADATA_FORMAT_NAME,
+              NATIVE_METADATA_FORMAT_CLASS_NAME,
+              null, null);
+    }
+
+    public boolean isReadOnly() {
+        return false;
+    }
+
+    // Shorthand for throwing an IIOInvalidTreeException
+    private static void fatal(Node node, String reason)
+        throws IIOInvalidTreeException {
+        throw new IIOInvalidTreeException(reason, node);
+    }
+
+    public Node getAsTree(String formatName) {
+        IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
+
+        IIOMetadataNode byteOrderNode = new IIOMetadataNode("ByteOrder");
+        byteOrderNode.setAttribute("value", byteOrder.toString());
+
+        root.appendChild(byteOrderNode);
+        return root;
+    }
+
+//     public void setFromTree(String formatName, Node root) {
+//     }
+
+    private void mergeNativeTree(Node root) throws IIOInvalidTreeException {
+        Node node = root;
+        if (!node.getNodeName().equals(nativeMetadataFormatName)) {
+            fatal(node, "Root must be " + nativeMetadataFormatName);
+        }
+
+        node = node.getFirstChild();
+        if (node == null || !node.getNodeName().equals("ByteOrder")) {
+            fatal(node, "Root must have \"ByteOrder\" child");
+        }
+
+        NamedNodeMap attrs = node.getAttributes();
+        String order = attrs.getNamedItem("value").getNodeValue();
+
+        if (order == null) {
+            fatal(node, "ByteOrder node must have a \"value\" attribute");
+        }
+        if (order.equals(bigEndianString)) {
+            this.byteOrder = ByteOrder.BIG_ENDIAN;
+        } else if (order.equals(littleEndianString)) {
+            this.byteOrder = ByteOrder.LITTLE_ENDIAN;
+        } else {
+            fatal(node, "Incorrect value for ByteOrder \"value\" attribute");
+        }
+    }
+
+    public void mergeTree(String formatName, Node root)
+        throws IIOInvalidTreeException {
+        if (formatName.equals(nativeMetadataFormatName)) {
+            if (root == null) {
+                throw new NullPointerException("root == null!");
+            }
+            mergeNativeTree(root);
+        } else {
+            throw new IllegalArgumentException("Not a recognized format!");
+        }
+    }
+
+    public void reset() {
+        this.byteOrder = ByteOrder.BIG_ENDIAN;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormat.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadataFormat;
+
+public class TIFFStreamMetadataFormat extends TIFFMetadataFormat {
+
+    private static TIFFStreamMetadataFormat theInstance = null;
+
+    public boolean canNodeAppear(String elementName,
+                                 ImageTypeSpecifier imageType) {
+        return false;
+    }
+
+    private TIFFStreamMetadataFormat() {
+        this.resourceBaseName =
+    "javax.imageio.plugins.tiff.TIFFStreamMetadataFormatResources";
+        this.rootName = TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME;
+
+        TIFFElementInfo einfo;
+        TIFFAttrInfo ainfo;
+        String[] empty = new String[0];
+        String[] childNames;
+        String[] attrNames;
+
+        childNames = new String[] { "ByteOrder" };
+        einfo = new TIFFElementInfo(childNames, empty, CHILD_POLICY_ALL);
+
+        elementInfoMap.put(TIFFStreamMetadata.NATIVE_METADATA_FORMAT_NAME,
+                           einfo);
+
+        childNames = empty;
+        attrNames = new String[] { "value" };
+        einfo = new TIFFElementInfo(childNames, attrNames, CHILD_POLICY_EMPTY);
+        elementInfoMap.put("ByteOrder", einfo);
+
+        ainfo = new TIFFAttrInfo();
+        ainfo.dataType = DATATYPE_STRING;
+        ainfo.isRequired = true;
+        attrInfoMap.put("ByteOrder/value", ainfo);
+    }
+
+    public static synchronized IIOMetadataFormat getInstance() {
+        if (theInstance == null) {
+            theInstance = new TIFFStreamMetadataFormat();
+        }
+        return theInstance;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFStreamMetadataFormatResources.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.util.ListResourceBundle;
+
+public class TIFFStreamMetadataFormatResources extends ListResourceBundle {
+
+    private static final Object[][] contents = {
+        { "ByteOrder", "The stream byte order" },
+        { "ByteOrder/value", "One of \"BIG_ENDIAN\" or \"LITTLE_ENDIAN\"" }
+    };
+
+    public TIFFStreamMetadataFormatResources() {
+    }
+
+    public Object[][] getContents() {
+        return contents.clone();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT4Compressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,254 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFField;
+import javax.imageio.plugins.tiff.TIFFTag;
+import java.io.IOException;
+import javax.imageio.IIOException;
+import javax.imageio.metadata.IIOMetadata;
+
+public class TIFFT4Compressor extends TIFFFaxCompressor {
+
+    private boolean is1DMode = false;
+    private boolean isEOLAligned = false;
+
+    public TIFFT4Compressor() {
+        super("CCITT T.4", BaselineTIFFTagSet.COMPRESSION_CCITT_T_4, true);
+    }
+
+    /**
+     * Sets the value of the <code>metadata</code> field.
+     *
+     * <p> The implementation in this class also sets local options
+     * from the T4_OPTIONS field if it exists, and if it doesn't, adds
+     * it with default values.</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_T4_OPTIONS);
+            if (f != null) {
+                int options = f.getAsInt(0);
+                is1DMode = (options & 0x1) == 0;
+                isEOLAligned = (options & 0x4) == 0x4;
+            } else {
+                long[] oarray = new long[1];
+                oarray[0] = (isEOLAligned ? 0x4 : 0x0) |
+                    (is1DMode ? 0x0 : 0x1);
+
+                BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();
+                TIFFField T4Options =
+                  new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_T4_OPTIONS),
+                                TIFFTag.TIFF_LONG,
+                                1,
+                                oarray);
+                tim.rootIFD.addTIFFField(T4Options);
+            }
+        }
+    }
+
+    /**
+     * Encode a buffer of data using CCITT T.4 Compression also known as
+     * Group 3 facsimile compression.
+     *
+     * @param is1DMode     Whether to perform one-dimensional encoding.
+     * @param isEOLAligned Whether EOL bit sequences should be padded.
+     * @param data         The row of data to compress.
+     * @param lineStride   Byte step between the same sample in different rows.
+     * @param colOffset    Bit offset within first <code>data[rowOffset]</code>.
+     * @param width        Number of bits in the row.
+     * @param height       Number of rows in the buffer.
+     * @param compData     The compressed data.
+     *
+     * @return The number of bytes saved in the compressed data array.
+     */
+    public int encodeT4(boolean is1DMode,
+                        boolean isEOLAligned,
+                        byte[] data,
+                        int lineStride,
+                        int colOffset,
+                        int width,
+                        int height,
+                        byte[] compData)
+    {
+        //
+        // ao, a1, a2 are bit indices in the current line
+        // b1 and b2  are bit indices in the reference line (line above)
+        // color is the current color (WHITE or BLACK)
+        //
+        byte[] refData = data;
+        int lineAddr = 0;
+        int outIndex = 0;
+
+        initBitBuf();
+
+        int KParameter = 2;
+        for(int numRows = 0; numRows < height; numRows++) {
+            if(is1DMode || (numRows % KParameter) == 0) { // 1D encoding
+                // Write EOL+1
+                outIndex += addEOL(is1DMode, isEOLAligned, true,
+                                   compData, outIndex);
+
+                // Encode row
+                outIndex += encode1D(data, lineAddr, colOffset, width,
+                                      compData, outIndex);
+            } else { // 2D encoding.
+                // Write EOL+0
+                outIndex += addEOL(is1DMode, isEOLAligned, false,
+                                   compData, outIndex);
+
+                // Set reference to previous line
+                int refAddr = lineAddr - lineStride;
+
+                // Encode row
+                int a0   = colOffset;
+                int last = a0 + width;
+
+                int testbit =
+                    ((data[lineAddr + (a0>>>3)]&0xff) >>>
+                     (7-(a0 & 0x7))) & 0x1;
+                int a1 = testbit != 0 ?
+                    a0 : nextState(data, lineAddr, a0, last);
+
+                testbit = ((refData[refAddr + (a0>>>3)]&0xff) >>>
+                           (7-(a0 & 0x7))) & 0x1;
+                int b1 = testbit != 0 ?
+                    a0 : nextState(refData, refAddr, a0, last);
+
+                // The current color is set to WHITE at line start
+                int color = WHITE;
+
+                while(true) {
+                    int b2 = nextState(refData, refAddr, b1, last);
+                    if(b2 < a1) {          // pass mode
+                        outIndex += add2DBits(compData, outIndex, pass, 0);
+                        a0 = b2;
+                    } else {
+                        int tmp = b1 - a1 + 3;
+                        if((tmp <= 6) && (tmp >= 0)) { // vertical mode
+                            outIndex +=
+                                add2DBits(compData, outIndex, vert, tmp);
+                            a0 = a1;
+                        } else {            // horizontal mode
+                            int a2 = nextState(data, lineAddr, a1, last);
+                            outIndex +=
+                                add2DBits(compData, outIndex, horz, 0);
+                            outIndex +=
+                                add1DBits(compData, outIndex, a1-a0, color);
+                            outIndex +=
+                                add1DBits(compData, outIndex, a2-a1, color^1);
+                            a0 = a2;
+                        }
+                    }
+                    if(a0 >= last) {
+                        break;
+                    }
+                    color = ((data[lineAddr + (a0>>>3)]&0xff) >>>
+                             (7-(a0 & 0x7))) & 0x1;
+                    a1 = nextState(data, lineAddr, a0, last);
+                    b1 = nextState(refData, refAddr, a0, last);
+                    testbit = ((refData[refAddr + (b1>>>3)]&0xff) >>>
+                               (7-(b1 & 0x7))) & 0x1;
+                    if(testbit == color) {
+                        b1 = nextState(refData, refAddr, b1, last);
+                    }
+                }
+            }
+
+            // Skip to next line.
+            lineAddr += lineStride;
+        }
+
+        for(int i = 0; i < 6; i++) {
+            outIndex += addEOL(is1DMode, isEOLAligned, true,
+                               compData, outIndex);
+        }
+
+        //
+        // flush all pending bits
+        //
+        while(ndex > 0) {
+            compData[outIndex++] = (byte)(bits >>> 24);
+            bits <<= 8;
+            ndex -= 8;
+        }
+
+        // Flip the bytes if inverse fill was requested.
+        if(inverseFill) {
+            for(int i = 0; i < outIndex; i++) {
+                compData[i] = TIFFFaxDecompressor.flipTable[compData[i]&0xff];
+            }
+        }
+
+        return outIndex;
+    }
+
+    public int encode(byte[] b, int off,
+                      int width, int height,
+                      int[] bitsPerSample,
+                      int scanlineStride) throws IOException {
+        if (bitsPerSample.length != 1 || bitsPerSample[0] != 1) {
+            throw new IIOException(
+                             "Bits per sample must be 1 for T4 compression!");
+        }
+
+        // This initial buffer size is based on an alternating 1-0
+        // pattern generating the most bits when converted to code
+        // words: 9 bits out for each pair of bits in. So the number
+        // of bit pairs is determined, multiplied by 9, converted to
+        // bytes, and a ceil() is taken to account for fill bits at the
+        // end of each line.  The "2" addend accounts for the case
+        // of the pattern beginning with black.  The buffer is intended
+        // to hold only a single row.
+
+        int maxBits = 9*((width + 1)/2) + 2;
+        int bufSize = (maxBits + 7)/8;
+
+        // Calculate the maximum row as the G3-1D size plus the EOL,
+        // multiply this by the number of rows in the tile, and add
+        // 6 EOLs for the RTC (return to control).
+        bufSize = height*(bufSize + 2) + 12;
+
+        byte[] compData = new byte[bufSize];
+
+        int bytes = encodeT4(is1DMode,
+                             isEOLAligned,
+                             b, scanlineStride, 8*off,
+                             width, height,
+                             compData);
+
+        stream.write(compData, 0, bytes);
+        return bytes;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFT6Compressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFField;
+import javax.imageio.plugins.tiff.TIFFTag;
+import java.io.IOException;
+import javax.imageio.IIOException;
+
+public class TIFFT6Compressor extends TIFFFaxCompressor {
+
+    public TIFFT6Compressor() {
+        super("CCITT T.6", BaselineTIFFTagSet.COMPRESSION_CCITT_T_6, true);
+    }
+
+    /**
+     * Encode a buffer of data using CCITT T.6 Compression also known as
+     * Group 4 facsimile compression.
+     *
+     * @param data        The row of data to compress.
+     * @param lineStride  Byte step between the same sample in different rows.
+     * @param colOffset   Bit offset within first <code>data[rowOffset]</code>.
+     * @param width       Number of bits in the row.
+     * @param height      Number of rows in the buffer.
+     * @param compData    The compressed data.
+     *
+     * @return The number of bytes saved in the compressed data array.
+     */
+    public synchronized int encodeT6(byte[] data,
+                                     int lineStride,
+                                     int colOffset,
+                                     int width,
+                                     int height,
+                                     byte[] compData)
+    {
+        //
+        // ao, a1, a2 are bit indices in the current line
+        // b1 and b2  are bit indices in the reference line (line above)
+        // color is the current color (WHITE or BLACK)
+        //
+        byte[] refData = null;
+        int refAddr  = 0;
+        int lineAddr = 0;
+        int  outIndex = 0;
+
+        initBitBuf();
+
+        //
+        // Iterate over all lines
+        //
+        while(height-- != 0) {
+            int a0   = colOffset;
+            int last = a0 + width;
+
+            int testbit =
+                ((data[lineAddr + (a0>>>3)]&0xff) >>>
+                 (7-(a0 & 0x7))) & 0x1;
+            int a1 = testbit != 0 ?
+                a0 : nextState(data, lineAddr, a0, last);
+
+            testbit = refData == null ?
+                0: ((refData[refAddr + (a0>>>3)]&0xff) >>>
+                       (7-(a0 & 0x7))) & 0x1;
+            int b1 = testbit != 0 ?
+                a0 : nextState(refData, refAddr, a0, last);
+
+            //
+            // The current color is set to WHITE at line start
+            //
+            int color = WHITE;
+
+            while(true) {
+                int b2 = nextState(refData, refAddr, b1, last);
+                if(b2 < a1) {          // pass mode
+                    outIndex += add2DBits(compData, outIndex, pass, 0);
+                    a0 = b2;
+                } else {
+                    int tmp = b1 - a1 + 3;
+                    if((tmp <= 6) && (tmp >= 0)) { // vertical mode
+                        outIndex += add2DBits(compData, outIndex, vert, tmp);
+                        a0 = a1;
+                    } else {            // horizontal mode
+                        int a2 = nextState(data, lineAddr, a1, last);
+                        outIndex += add2DBits(compData, outIndex, horz, 0);
+                        outIndex += add1DBits(compData, outIndex, a1-a0, color);
+                        outIndex += add1DBits(compData, outIndex, a2-a1, color^1);
+                        a0 = a2;
+                    }
+                }
+                if(a0 >= last) {
+                    break;
+                }
+                color = ((data[lineAddr + (a0>>>3)]&0xff) >>>
+                         (7-(a0 & 0x7))) & 0x1;
+                a1 = nextState(data, lineAddr, a0, last);
+                b1 = nextState(refData, refAddr, a0, last);
+                testbit = refData == null ?
+                    0: ((refData[refAddr + (b1>>>3)]&0xff) >>>
+                           (7-(b1 & 0x7))) & 0x1;
+                if(testbit == color) {
+                    b1 = nextState(refData, refAddr, b1, last);
+                }
+            }
+
+            refData = data;
+            refAddr = lineAddr;
+            lineAddr += lineStride;
+
+        } // End while(height--)
+
+        //
+        // append eofb
+        //
+        outIndex += addEOFB(compData, outIndex);
+
+        // Flip the bytes if inverse fill was requested.
+        if(inverseFill) {
+            for(int i = 0; i < outIndex; i++) {
+                compData[i] = TIFFFaxDecompressor.flipTable[compData[i]&0xff];
+            }
+        }
+
+        return outIndex;
+    }
+
+    public int encode(byte[] b, int off,
+                      int width, int height,
+                      int[] bitsPerSample,
+                      int scanlineStride) throws IOException {
+        if (bitsPerSample.length != 1 || bitsPerSample[0] != 1) {
+            throw new IIOException(
+                             "Bits per sample must be 1 for T6 compression!");
+        }
+
+
+        if (metadata instanceof TIFFImageMetadata) {
+            TIFFImageMetadata tim = (TIFFImageMetadata)metadata;
+
+            long[] options = new long[1];
+            options[0] = 0;
+
+            BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();
+            TIFFField T6Options =
+                new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_T6_OPTIONS),
+                              TIFFTag.TIFF_LONG,
+                              1,
+                              options);
+            tim.rootIFD.addTIFFField(T6Options);
+        }
+
+        // See comment in TIFFT4Compressor
+        int maxBits = 9*((width + 1)/2) + 2;
+        int bufSize = (maxBits + 7)/8;
+        bufSize = height*(bufSize + 2) + 12;
+
+        byte[] compData = new byte[bufSize];
+        int bytes = encodeT6(b, scanlineStride, 8*off, width, height,
+                             compData);
+        stream.write(compData, 0, bytes);
+        return bytes;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrColorConverter.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFField;
+
+public class TIFFYCbCrColorConverter extends TIFFColorConverter {
+
+    private static final float CODING_RANGE_Y = 255.0f;
+    private static final float CODING_RANGE_CB_CR = 127.0f;
+
+    private float lumaRed = 0.299f;
+    private float lumaGreen = 0.587f;
+    private float lumaBlue = 0.114f;
+
+    private float referenceBlackY = 0.0f;
+    private float referenceWhiteY = 255.0f;
+
+    private float referenceBlackCb = 128.0f;
+    private float referenceWhiteCb = 255.0f;
+
+    private float referenceBlackCr = 128.0f;
+    private float referenceWhiteCr = 255.0f;
+
+    public TIFFYCbCrColorConverter(TIFFImageMetadata metadata) {
+        TIFFImageMetadata tmetadata = metadata;
+
+        TIFFField f =
+           tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS);
+        if (f != null && f.getCount() == 3) {
+            this.lumaRed = f.getAsFloat(0);
+            this.lumaGreen = f.getAsFloat(1);
+            this.lumaBlue = f.getAsFloat(2);
+        }
+
+        f =
+          tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE);
+        if (f != null && f.getCount() == 6) {
+            this.referenceBlackY = f.getAsFloat(0);
+            this.referenceWhiteY = f.getAsFloat(1);
+            this.referenceBlackCb = f.getAsFloat(2);
+            this.referenceWhiteCb = f.getAsFloat(3);
+            this.referenceBlackCr = f.getAsFloat(4);
+            this.referenceWhiteCr = f.getAsFloat(5);
+        }
+    }
+
+    /*
+      The full range component value is converted from the code by:
+
+      FullRangeValue = (code - ReferenceBlack) * CodingRange
+                / (ReferenceWhite - ReferenceBlack);
+
+      The code is converted from the full-range component value by:
+
+      code = (FullRangeValue * (ReferenceWhite - ReferenceBlack)
+                / CodingRange) + ReferenceBlack;
+
+     */
+    public void fromRGB(float r, float g, float b, float[] result) {
+        // Convert RGB to full-range YCbCr.
+        float Y = (lumaRed*r + lumaGreen*g + lumaBlue*b);
+        float Cb = (b - Y)/(2 - 2*lumaBlue);
+        float Cr = (r - Y)/(2 - 2*lumaRed);
+
+        // Convert full-range YCbCr to code.
+        result[0] = Y*(referenceWhiteY - referenceBlackY)/CODING_RANGE_Y +
+            referenceBlackY;
+        result[1] = Cb*(referenceWhiteCb - referenceBlackCb)/CODING_RANGE_CB_CR +
+            referenceBlackCb;
+        result[2] = Cr*(referenceWhiteCr - referenceBlackCr)/CODING_RANGE_CB_CR +
+            referenceBlackCr;
+    }
+
+    public void toRGB(float x0, float x1, float x2, float[] rgb) {
+        // Convert YCbCr code to full-range YCbCr.
+        float Y = (x0 - referenceBlackY)*CODING_RANGE_Y/
+            (referenceWhiteY - referenceBlackY);
+        float Cb = (x1 - referenceBlackCb)*CODING_RANGE_CB_CR/
+            (referenceWhiteCb - referenceBlackCb);
+        float Cr = (x2 - referenceBlackCr)*CODING_RANGE_CB_CR/
+            (referenceWhiteCr - referenceBlackCr);
+
+        // Convert YCbCr to RGB.
+        rgb[0] = Cr*(2 - 2*lumaRed) + Y;
+        rgb[2] = Cb*(2 - 2*lumaBlue) + Y;
+        rgb[1] = (Y - lumaBlue*rgb[2] - lumaRed*rgb[0])/lumaGreen;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFYCbCrDecompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,538 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import javax.imageio.ImageReader;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.stream.MemoryCacheImageInputStream;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.plugins.tiff.TIFFField;
+
+public class TIFFYCbCrDecompressor extends TIFFDecompressor {
+    // Store constants in S15.16 format
+    private static final int FRAC_BITS = 16;
+    private static final float FRAC_SCALE = (float)(1 << FRAC_BITS);
+
+    private float lumaRed = 0.299f;
+    private float lumaGreen = 0.587f;
+    private float lumaBlue = 0.114f;
+
+    private float referenceBlackY = 0.0f;
+    private float referenceWhiteY = 255.0f;
+
+    private float referenceBlackCb = 128.0f;
+    private float referenceWhiteCb = 255.0f;
+
+    private float referenceBlackCr = 128.0f;
+    private float referenceWhiteCr = 255.0f;
+
+    private float codingRangeY = 255.0f;
+
+    private int[] iYTab = new int[256];
+    private int[] iCbTab = new int[256];
+    private int[] iCrTab = new int[256];
+
+    private int[] iGYTab = new int[256];
+    private int[] iGCbTab = new int[256];
+    private int[] iGCrTab = new int[256];
+
+    private int chromaSubsampleH = 2;
+    private int chromaSubsampleV = 2;
+
+    private boolean colorConvert;
+
+    private TIFFDecompressor decompressor;
+
+    private BufferedImage tmpImage;
+
+    //
+    // If 'decompressor' is not null then it reads the data from the
+    // actual stream first and passes the result on to YCrCr decompression
+    // and possibly color conversion.
+    //
+
+    public TIFFYCbCrDecompressor(TIFFDecompressor decompressor,
+                                 boolean colorConvert) {
+        this.decompressor = decompressor;
+        this.colorConvert = colorConvert;
+    }
+
+    private void warning(String message) {
+        if(this.reader instanceof TIFFImageReader) {
+            ((TIFFImageReader)reader).forwardWarningMessage(message);
+        }
+    }
+
+    //
+    // "Chained" decompressor methods.
+    //
+
+    public void setReader(ImageReader reader) {
+        if(decompressor != null) {
+            decompressor.setReader(reader);
+        }
+        super.setReader(reader);
+    }
+
+    public void setMetadata(IIOMetadata metadata) {
+        if(decompressor != null) {
+            decompressor.setMetadata(metadata);
+        }
+        super.setMetadata(metadata);
+    }
+
+    public void setPhotometricInterpretation(int photometricInterpretation) {
+        if(decompressor != null) {
+            decompressor.setPhotometricInterpretation(photometricInterpretation);
+        }
+        super.setPhotometricInterpretation(photometricInterpretation);
+    }
+
+    public void setCompression(int compression) {
+        if(decompressor != null) {
+            decompressor.setCompression(compression);
+        }
+        super.setCompression(compression);
+    }
+
+    public void setPlanar(boolean planar) {
+        if(decompressor != null) {
+            decompressor.setPlanar(planar);
+        }
+        super.setPlanar(planar);
+    }
+
+    public void setSamplesPerPixel(int samplesPerPixel) {
+        if(decompressor != null) {
+            decompressor.setSamplesPerPixel(samplesPerPixel);
+        }
+        super.setSamplesPerPixel(samplesPerPixel);
+    }
+
+    public void setBitsPerSample(int[] bitsPerSample) {
+        if(decompressor != null) {
+            decompressor.setBitsPerSample(bitsPerSample);
+        }
+        super.setBitsPerSample(bitsPerSample);
+    }
+
+    public void setSampleFormat(int[] sampleFormat) {
+        if(decompressor != null) {
+            decompressor.setSampleFormat(sampleFormat);
+        }
+        super.setSampleFormat(sampleFormat);
+    }
+
+    public void setExtraSamples(int[] extraSamples) {
+        if(decompressor != null) {
+            decompressor.setExtraSamples(extraSamples);
+        }
+        super.setExtraSamples(extraSamples);
+    }
+
+    public void setColorMap(char[] colorMap) {
+        if(decompressor != null) {
+            decompressor.setColorMap(colorMap);
+        }
+        super.setColorMap(colorMap);
+    }
+
+    public void setStream(ImageInputStream stream) {
+        if(decompressor != null) {
+            decompressor.setStream(stream);
+        } else {
+            super.setStream(stream);
+        }
+    }
+
+    public void setOffset(long offset) {
+        if(decompressor != null) {
+            decompressor.setOffset(offset);
+        }
+        super.setOffset(offset);
+    }
+
+    public void setByteCount(int byteCount) {
+        if(decompressor != null) {
+            decompressor.setByteCount(byteCount);
+        }
+        super.setByteCount(byteCount);
+    }
+
+    public void setSrcMinX(int srcMinX) {
+        if(decompressor != null) {
+            decompressor.setSrcMinX(srcMinX);
+        }
+        super.setSrcMinX(srcMinX);
+    }
+
+    public void setSrcMinY(int srcMinY) {
+        if(decompressor != null) {
+            decompressor.setSrcMinY(srcMinY);
+        }
+        super.setSrcMinY(srcMinY);
+    }
+
+    public void setSrcWidth(int srcWidth) {
+        if(decompressor != null) {
+            decompressor.setSrcWidth(srcWidth);
+        }
+        super.setSrcWidth(srcWidth);
+    }
+
+    public void setSrcHeight(int srcHeight) {
+        if(decompressor != null) {
+            decompressor.setSrcHeight(srcHeight);
+        }
+        super.setSrcHeight(srcHeight);
+    }
+
+    public void setSourceXOffset(int sourceXOffset) {
+        if(decompressor != null) {
+            decompressor.setSourceXOffset(sourceXOffset);
+        }
+        super.setSourceXOffset(sourceXOffset);
+    }
+
+    public void setDstXOffset(int dstXOffset) {
+        if(decompressor != null) {
+            decompressor.setDstXOffset(dstXOffset);
+        }
+        super.setDstXOffset(dstXOffset);
+    }
+
+    public void setSourceYOffset(int sourceYOffset) {
+        if(decompressor != null) {
+            decompressor.setSourceYOffset(sourceYOffset);
+        }
+        super.setSourceYOffset(sourceYOffset);
+    }
+
+    public void setDstYOffset(int dstYOffset) {
+        if(decompressor != null) {
+            decompressor.setDstYOffset(dstYOffset);
+        }
+        super.setDstYOffset(dstYOffset);
+    }
+
+    /* Should not need to override these mutators as subsampling
+       should not be done by the wrapped decompressor.
+    public void setSubsampleX(int subsampleX) {
+        if(decompressor != null) {
+            decompressor.setSubsampleX(subsampleX);
+        }
+        super.setSubsampleX(subsampleX);
+    }
+
+    public void setSubsampleY(int subsampleY) {
+        if(decompressor != null) {
+            decompressor.setSubsampleY(subsampleY);
+        }
+        super.setSubsampleY(subsampleY);
+    }
+    */
+
+    public void setSourceBands(int[] sourceBands) {
+        if(decompressor != null) {
+            decompressor.setSourceBands(sourceBands);
+        }
+        super.setSourceBands(sourceBands);
+    }
+
+    public void setDestinationBands(int[] destinationBands) {
+        if(decompressor != null) {
+            decompressor.setDestinationBands(destinationBands);
+        }
+        super.setDestinationBands(destinationBands);
+    }
+
+    public void setImage(BufferedImage image) {
+        if(decompressor != null) {
+            ColorModel cm = image.getColorModel();
+            tmpImage =
+                new BufferedImage(cm,
+                                  image.getRaster().createCompatibleWritableRaster(1, 1),
+                                  cm.isAlphaPremultiplied(),
+                                  null);
+            decompressor.setImage(tmpImage);
+        }
+        super.setImage(image);
+    }
+
+    public void setDstMinX(int dstMinX) {
+        if(decompressor != null) {
+            decompressor.setDstMinX(dstMinX);
+        }
+        super.setDstMinX(dstMinX);
+    }
+
+    public void setDstMinY(int dstMinY) {
+        if(decompressor != null) {
+            decompressor.setDstMinY(dstMinY);
+        }
+        super.setDstMinY(dstMinY);
+    }
+
+    public void setDstWidth(int dstWidth) {
+        if(decompressor != null) {
+            decompressor.setDstWidth(dstWidth);
+        }
+        super.setDstWidth(dstWidth);
+    }
+
+    public void setDstHeight(int dstHeight) {
+        if(decompressor != null) {
+            decompressor.setDstHeight(dstHeight);
+        }
+        super.setDstHeight(dstHeight);
+    }
+
+    public void setActiveSrcMinX(int activeSrcMinX) {
+        if(decompressor != null) {
+            decompressor.setActiveSrcMinX(activeSrcMinX);
+        }
+        super.setActiveSrcMinX(activeSrcMinX);
+    }
+
+    public void setActiveSrcMinY(int activeSrcMinY) {
+        if(decompressor != null) {
+            decompressor.setActiveSrcMinY(activeSrcMinY);
+        }
+        super.setActiveSrcMinY(activeSrcMinY);
+    }
+
+    public void setActiveSrcWidth(int activeSrcWidth) {
+        if(decompressor != null) {
+            decompressor.setActiveSrcWidth(activeSrcWidth);
+        }
+        super.setActiveSrcWidth(activeSrcWidth);
+    }
+
+    public void setActiveSrcHeight(int activeSrcHeight) {
+        if(decompressor != null) {
+            decompressor.setActiveSrcHeight(activeSrcHeight);
+        }
+        super.setActiveSrcHeight(activeSrcHeight);
+    }
+
+    private byte clamp(int f) {
+        if (f < 0) {
+            return (byte)0;
+        } else if (f > 255*65536) {
+            return (byte)255;
+        } else {
+            return (byte)(f >> 16);
+        }
+    }
+
+    public void beginDecoding() {
+        if(decompressor != null) {
+            decompressor.beginDecoding();
+        }
+
+        TIFFImageMetadata tmetadata = (TIFFImageMetadata)metadata;
+        TIFFField f;
+
+        f = tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
+        if (f != null) {
+            if (f.getCount() == 2) {
+                this.chromaSubsampleH = f.getAsInt(0);
+                this.chromaSubsampleV = f.getAsInt(1);
+
+                if (chromaSubsampleH != 1 && chromaSubsampleH != 2 &&
+                    chromaSubsampleH != 4) {
+                    warning("Y_CB_CR_SUBSAMPLING[0] has illegal value " +
+                            chromaSubsampleH +
+                            " (should be 1, 2, or 4), setting to 1");
+                    chromaSubsampleH = 1;
+                }
+
+                if (chromaSubsampleV != 1 && chromaSubsampleV != 2 &&
+                    chromaSubsampleV != 4) {
+                    warning("Y_CB_CR_SUBSAMPLING[1] has illegal value " +
+                            chromaSubsampleV +
+                            " (should be 1, 2, or 4), setting to 1");
+                    chromaSubsampleV = 1;
+                }
+            } else {
+                warning("Y_CB_CR_SUBSAMPLING count != 2, " +
+                        "assuming no subsampling");
+            }
+        }
+
+        f =
+           tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS);
+        if (f != null) {
+            if (f.getCount() == 3) {
+                this.lumaRed = f.getAsFloat(0);
+                this.lumaGreen = f.getAsFloat(1);
+                this.lumaBlue = f.getAsFloat(2);
+            } else {
+                warning("Y_CB_CR_COEFFICIENTS count != 3, " +
+                        "assuming default values for CCIR 601-1");
+            }
+        }
+
+        f =
+          tmetadata.getTIFFField(BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE);
+        if (f != null) {
+            if (f.getCount() == 6) {
+                this.referenceBlackY = f.getAsFloat(0);
+                this.referenceWhiteY = f.getAsFloat(1);
+                this.referenceBlackCb = f.getAsFloat(2);
+                this.referenceWhiteCb = f.getAsFloat(3);
+                this.referenceBlackCr = f.getAsFloat(4);
+                this.referenceWhiteCr = f.getAsFloat(5);
+            } else {
+                warning("REFERENCE_BLACK_WHITE count != 6, ignoring it");
+            }
+        } else {
+                warning("REFERENCE_BLACK_WHITE not found, assuming 0-255/128-255/128-255");
+        }
+
+        this.colorConvert = true;
+
+        float BCb = (2.0f - 2.0f*lumaBlue);
+        float RCr = (2.0f - 2.0f*lumaRed);
+
+        float GY = (1.0f - lumaBlue - lumaRed)/lumaGreen;
+        float GCb = 2.0f*lumaBlue*(lumaBlue - 1.0f)/lumaGreen;
+        float GCr = 2.0f*lumaRed*(lumaRed - 1.0f)/lumaGreen;
+
+        for (int i = 0; i < 256; i++) {
+            float fY = (i - referenceBlackY)*codingRangeY/
+                (referenceWhiteY - referenceBlackY);
+            float fCb = (i - referenceBlackCb)*127.0f/
+                (referenceWhiteCb - referenceBlackCb);
+            float fCr = (i - referenceBlackCr)*127.0f/
+                (referenceWhiteCr - referenceBlackCr);
+
+            iYTab[i] = (int)(fY*FRAC_SCALE);
+            iCbTab[i] = (int)(fCb*BCb*FRAC_SCALE);
+            iCrTab[i] = (int)(fCr*RCr*FRAC_SCALE);
+
+            iGYTab[i] = (int)(fY*GY*FRAC_SCALE);
+            iGCbTab[i] = (int)(fCb*GCb*FRAC_SCALE);
+            iGCrTab[i] = (int)(fCr*GCr*FRAC_SCALE);
+        }
+    }
+
+    public void decodeRaw(byte[] buf,
+                          int dstOffset,
+                          int bitsPerPixel,
+                          int scanlineStride) throws IOException {
+        int elementsPerPacket = chromaSubsampleH*chromaSubsampleV + 2;
+        byte[] packet = new byte[elementsPerPacket];
+
+        if(decompressor != null) {
+            int bytesPerRow = 3*srcWidth;
+            byte[] tmpBuf = new byte[bytesPerRow*srcHeight];
+            decompressor.decodeRaw(tmpBuf, dstOffset, bitsPerPixel,
+                                   bytesPerRow);
+            ByteArrayInputStream byteStream =
+                new ByteArrayInputStream(tmpBuf);
+            stream = new MemoryCacheImageInputStream(byteStream);
+        } else {
+            stream.seek(offset);
+        }
+
+        for (int y = srcMinY; y < srcMinY + srcHeight; y += chromaSubsampleV) {
+            // Decode chromaSubsampleV rows
+            for (int x = srcMinX; x < srcMinX + srcWidth;
+                 x += chromaSubsampleH) {
+                try {
+                    stream.readFully(packet);
+                } catch (EOFException e) {
+                    return;
+                }
+
+                byte Cb = packet[elementsPerPacket - 2];
+                byte Cr = packet[elementsPerPacket - 1];
+
+                int iCb  = 0, iCr = 0, iGCb = 0, iGCr = 0;
+
+                if (colorConvert) {
+                    int Cbp = Cb & 0xff;
+                    int Crp = Cr & 0xff;
+
+                    iCb = iCbTab[Cbp];
+                    iCr = iCrTab[Crp];
+
+                    iGCb = iGCbTab[Cbp];
+                    iGCr = iGCrTab[Crp];
+                }
+
+                int yIndex = 0;
+                for (int v = 0; v < chromaSubsampleV; v++) {
+                    int idx = dstOffset + 3*(x - srcMinX) +
+                        scanlineStride*(y - srcMinY + v);
+
+                    // Check if we reached the last scanline
+                    if (y + v >= srcMinY + srcHeight) {
+                        break;
+                    }
+
+                    for (int h = 0; h < chromaSubsampleH; h++) {
+                        if (x + h >= srcMinX + srcWidth) {
+                            break;
+                        }
+
+                        byte Y = packet[yIndex++];
+
+                        if (colorConvert) {
+                            int Yp = Y & 0xff;
+                            int iY = iYTab[Yp];
+                            int iGY = iGYTab[Yp];
+
+                            int iR = iY + iCr;
+                            int iG = iGY + iGCb + iGCr;
+                            int iB = iY + iCb;
+
+                            byte r = clamp(iR);
+                            byte g = clamp(iG);
+                            byte b = clamp(iB);
+
+                            buf[idx] = r;
+                            buf[idx + 1] = g;
+                            buf[idx + 2] = b;
+                        } else {
+                            buf[idx] = Y;
+                            buf[idx + 1] = Cb;
+                            buf[idx + 2] = Cr;
+                        }
+
+                        idx += 3;
+                    }
+                }
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFZLibCompressor.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.sun.imageio.plugins.tiff;
+
+import javax.imageio.plugins.tiff.BaselineTIFFTagSet;
+import javax.imageio.ImageWriteParam;
+
+/**
+ * Compressor for ZLib compression.
+ */
+public class TIFFZLibCompressor extends TIFFDeflater {
+    public TIFFZLibCompressor(ImageWriteParam param, int predictor) {
+        super("ZLib", BaselineTIFFTagSet.COMPRESSION_ZLIB, param, predictor);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/metadata/doc-files/tiff_metadata.html	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,1200 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+Copyright (c) 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.
+-->
+
+<title>TIFF Metadata Format Specification and Usage Notes</title>
+</head>
+
+<body bgcolor="white">
+
+<center><h1>
+TIFF Metadata Format Specification and Usage Notes
+</h1></center>
+
+    <p>
+<a href="#Reading">Reading Images</a><br/>
+<font size="-1">
+<ul>
+<li><a href="#ColorConversionRead">Color Conversion</a></li>
+<li><a href="#ColorSpacesRead">Color Spaces</a></li>
+<li><a href="#ICCProfilesRead">ICC Profiles</a></li>
+<li><a href="#MetadataIssuesRead">Metadata Issues</a>
+<font size="-2">
+<ul>
+<li><a href="#MapNativeStandard">Native to Standard Metadata Mapping</a></li>
+</ul>
+</font>
+</li>
+<li><a href="#ExifRead">Reading Exif Images</a>
+<font size="-2">
+<ul>
+<li><a href="#ExifReadTIFF">Reading Uncompressed Exif Images</a></li>
+<li><a href="#ExifReadJPEG">Reading Compressed Exif Images</a></li>
+</ul>
+</font>
+</li>
+</ul>
+</font>
+<a href="#Writing">Writing Images</a><br/>
+<font size="-1">
+<ul>
+<li><a href="#Compression">Compression</a></li>
+<li><a href="#ColorConversionWrite">Color Conversion</a></li>
+<li><a href="#ICCProfilesWrite">ICC Profiles</a></li>
+<li><a href="#MetadataIssuesWrite">Metadata Issues</a></li>
+<font size="-2">
+<ul>
+<li><a href="#MapStandardNative">Standard to Native Metadata Mapping</a></li>
+</ul>
+</font>
+<li><a href="#ExifWrite">Writing Exif Images</a>
+<font size="-2">
+<ul>
+<li><a href="#ExifWriteTIFF">Writing Uncompressed Exif Images</a></li>
+<li><a href="#ExifWriteJPEG">Writing Compressed Exif Images</a></li>
+</ul>
+</font>
+</li>
+</ul>
+</font>
+<a href="#StreamMetadata">Native Stream Metadata Format</a><br/>
+<a href="#ImageMetadata">Native Image Metadata Format</a>
+</p>
+
+<h3><a name="Reading"/>Reading Images</h3>
+
+TIFF images are read by an <a href="../../ImageReader.html">ImageReader</a>
+which may be controlled by its public interface as well as via a supplied
+<a href="../../plugins/tiff/TIFFImageReadParam.html">TIFFImageReadParam</a>.
+
+<!-- <h4>Supported Image Types</h4> -->
+
+<!-- Table? -->
+
+<h4><a name="ColorConversionRead"/>Color Conversion</h4>
+
+<p>If the source image data
+have photometric type CIE L*a*b* or YCbCr, and the destination color space
+type is RGB, then the source image data will be automatically converted to
+RGB using an internal color converter.</p>
+
+<h4><a name="ColorSpacesRead"/>Color Spaces</h4>
+
+The raw color space assigned by default, i.e., in the absence of a
+user-supplied <a href="../../ImageTypeSpecifier.html">ImageTypeSpecifier</a>,
+will be the first among the following which applies:
+
+<ul>
+<li>A color space created from the <a href="#ICCProfilesRead">ICC Profile</a>
+metadata field if it is present and compatible with the image data
+layout.</li>
+<a name="nonICCProfile"><li>sRGB if the image is monochrome/bilevel
+(a two-level color map is created internally).</li>
+<li>sRGB if the image is palette-color.</li>
+<li>Linear RGB if the image has three samples per pixel, has photometric type
+CIE L*a*b*, or has photometric type YCbCr and is <i>not</i>
+JPEG-compressed.</li>
+<li>A <a href="#DefaultCMYK">default CMYK color space</a> if the image has
+photometric type CMYK and four samples per pixel.</li>
+<li>Grayscale if the image has one or two samples per pixel and uniformly
+1, 2, 4, 8, 16, or 32 bits per sample or is floating point.</li>
+<li>sRGB if the image has three or four samples per pixel and uniformly
+1, 2, 4, 8, 16, or 32 bits per sample or is floating point.</li>
+<li>A fabricated, <a href="#GenericCS">generic color space</a> if the image
+has more than four samples per pixel and the number of bits per sample for
+all bands is the same and is a multiple of 8.</li>
+<li>Grayscale if the image has one or two samples per pixel regardless of
+the number of bits per sample.</li>
+<li>sRGB if the image has three or four samples per pixel regardless of
+the number of bits per sample.</li>
+<li>A fabricated, <a href="#GenericCS">generic color space</a> if the image
+has more than four samples per pixel regardless of the number of bits per
+sample.</li>
+</ul>
+
+<p><a name="DefaultCMYK"/>The normalized color coordinate transformations
+used for the default CMYK color space are defined as follows:
+
+<ul>
+<li>CMYK to linear RGB
+<pre>
+R = (1 - K)*(1 - C)
+G = (1 - K)*(1 - M)
+B = (1 - K)*(1 - Y)
+</pre>
+</li>
+<li>Linear RGB to CMYK
+<pre>
+K = min{1 - R, 1 - G, 1 - B}
+if(K != 1) {
+    C = (1 - R - K)/(1 - K)
+    M = (1 - G - K)/(1 - K)
+    Y = (1 - B - K)/(1 - K)
+} else {
+    C = M = Y = 0
+}
+</pre>
+</li>
+</ul>
+</p>
+
+<p><a name="GenericCS"/>The generic color space used when no other color space
+can be inferred is provided merely to enable the data to be loaded. It is not
+intended to provide accurate conversions of any kind.</p>
+
+<p>If the data are known to be in a color space not correctly handled by the
+foregoing, then an <code>ImageTypeSpecifier</code> should be
+supplied to the reader and should be derived from a color space which is correct
+for the data in question.</p>
+
+<h4><a name="ICCProfilesRead"/>ICC Profiles</h4>
+
+If an ICC profile is contained in the image metadata
+(<a href="../../plugins/tiff/BaselineTIFFTagSet.html">
+BaselineTIFFTagSet</a>.TAG_ICC_PROFILE, tag number 34675),
+an attempt will be made to use it to create the color space
+of the loaded image. It will be used if the data layout is of component type
+and the number of samples per pixel equals or is one greater than the number
+of components described by the ICC profile. If the ICC profile is not used
+then the color space will be inferred in one of the subsequent steps described
+<a href="#nonICCProfile">above</a>.
+
+<p>If for some reason the embedded ICC profile is not used automatically, then
+it may be used manually by following this procedure:
+
+<ol>
+<li>Obtain the image metadata from
+<code>ImageReader.getImageMetadata</code></li>
+<li>Extract the ICC profile field and its value.</li>
+<li>Create an <a href="../../../../java/awt/color/ICC_ColorSpace.html">
+ICC_ColorSpace</a> from an
+<a href="../../../../java/awt/color/ICC_Profile.html">
+ICC_Profile</a> created from the ICC profile field data
+using <code>ICC_Profile.getInstance(byte[])</code>.</li>
+<li>Create an <code>ImageTypeSpecifier</code> from the new color
+space using one of its factory methods which accepts an
+<code>ICC_ColorSpace</code>.
+<li>Create a compatible <a href="../../ImageReadParam.html">ImageReadParam</a>
+and set the <code>ImageTypeSpecifier</code> using
+<code>ImageReadParam.setDestinationType</code>.</li>
+<li>Pass the parameter object to the appropriate <code>read</code> method.</li>
+</ol>
+</p>
+
+<p>If the inferred color space not based on the ICC Profile field is compatible
+with the ICC profile-based color space, then a second
+<code>ImageTypeSpecifier</code> derived from this inferred color
+space will be included in the
+<a href="../../../../java/util/Iterator.html">Iterator</a> returned by
+<code>ImageReader.getImageTypes</code>. If the iterator contains
+more than one type, the first one will be based on the ICC profile and the
+second on the inferred color space.</p>
+
+<h4><a name="MetadataIssuesRead"/>Metadata Issues</h4>
+
+By default all fields in the TIFF image file directory (IFD) are loaded into
+the native image metadata object. In cases where the IFD includes fields which
+contain large amounts of data this could be very inefficient. Which fields
+are loaded may be controlled by setting which TIFF tags the reader is allowed
+to recognize and whether it is ignoring metadata. The reader is informed to
+disregard metadata as usual via the <code>ignoreMetadata</code> parameter of
+<code>ImageReader.setInput(Object,boolean,boolean)</code>. It is
+informed of which <a href="../../plugins/tiff/TIFFTag.html">TIFFTag</a>s to
+recognize or not to recognize via
+<code>TIFFImageReadParam.addAllowedTagSet(TIFFTagSet)</code>
+and
+<code>TIFFImageReadParam.removeAllowedTagSet(TIFFTagSet)</code>.
+If <code>ignoreMetadata</code> is <code>true</code>, then the reader will
+load into the native image metadata object only those fields which have a
+<code>TIFFTag</code> contained in the one of the allowed
+<code>TIFFTagSet</code>s.
+
+<p>Use of a <a href="../../plugins/tiff/TIFFDirectory.html">TIFFDirectory</a>
+object may simplify gaining access to metadata values. An instance of
+<code>TIFFDirectory</code> may be created from the <code>IIOMetadata</code>
+object returned by the TIFF reader using the
+<code>TIFFDirectory.createFromMetadata</code> method.</p>
+
+<h5><a name="MapNativeStandard"/>
+Mapping of TIFF Native Image Metadata to the Standard Metadata Format</h5>
+
+The derivation of standard metadata format
+<a href="standard_metadata.html">javax_imageio_1.0</a>
+elements from <a href="#ImageMetadata">TIFF native image metadata</a> is given
+in the following table.
+
+<p>
+<table border="1">
+<tr>
+<th>Standard Metadata Element</th>
+<th>Derivation from TIFF Fields</th>
+</tr>
+<tr>
+<td>/Chroma/ColorSpaceType@name</td>
+<td>PhotometricInterpretation: WhiteIsZero, BlackIsZero, TransparencyMask =
+"GRAY"; RGB, PaletteColor => "RGB"; CMYK => "CMYK";
+YCbCr => "YCbCr";
+CIELab, ICCLab => "Lab".</td>
+</tr>
+<tr>
+<td>/Chroma/NumChannels@value</td>
+<td>SamplesPerPixel</td>
+</tr>
+<tr>
+<td>/Chroma/BlackIsZero@value</td>
+<td>"TRUE" <=> PhotometricInterpretation => WhiteIsZero</td>
+</tr>
+<tr>
+<td>/Chroma/Palette</td>
+<td>ColorMap</td>
+</tr>
+<tr>
+<td>/Compression/CompressionTypeName@value</td>
+<td>Compression: Uncompressed => "none"; CCITT 1D => "CCITT
+RLE";
+Group 3 Fax => "CCITT T.4"; Group 4 Fax => "CCITT T.6";
+LZW => "LZW";
+JPEG => "Old JPEG"; New JPEG => "JPEG"; Zlib =>> "ZLib"; PackBits =>
+"PackBits";
+Deflate => "Deflate"; Exif JPEG => "JPEG".</td>
+</tr>
+<tr>
+<td>/Compression/Lossless@value</td>
+<td>Compression: JPEG or New JPEG => "FALSE"; otherwise "TRUE".</td>
+</tr>
+<tr>
+<td>/Data/PlanarConfiguration@value</td>
+<td>Chunky => "PixelInterleaved"; Planar => "PlaneInterleaved".</td>
+</tr>
+<tr>
+<td>/Data/SampleFormat@value</td>
+<td>PhotometricInterpretation PaletteColor => "Index";
+SampleFormat unsigned integer data => "UnsignedIntegral";
+SampleFormat two's complement signed integer data => "SignedIntegral";
+SampleFormat IEEE floating point data => "Real";
+otherwise element not emitted.
+</td>
+</tr>
+<tr>
+<td>/Data/BitsPerSample@value</td>
+<td>BitsPerSample as a space-separated list.</td>
+</tr>
+<tr>
+<td>/Data/SampleMSB@value</td>
+<td>FillOrder: left-to-right => space-separated list of BitsPerSample-1;
+right-to-left => space-separated list of 0s.</td>
+</tr>
+<tr>
+<td>/Dimension/PixelAspectRatio@value</td>
+<td>(1/XResolution)/(1/YResolution)</td>
+</tr>
+<tr>
+<td>/Dimension/ImageOrientation@value</td>
+<td>Orientation</td>
+</tr>
+<tr>
+<td>/Dimension/HorizontalPixelSize@value</td>
+<td>1/XResolution in millimeters if ResolutionUnit is not None.</td>
+</tr>
+<tr>
+<td>/Dimension/VerticalPixelSize@value</td>
+<td>1/YResolution in millimeters if ResolutionUnit is not None.</td>
+</tr>
+<tr>
+<td>/Dimension/HorizontalPosition@value</td>
+<td>XPosition in millimeters if ResolutionUnit is not None.</td>
+</tr>
+<tr>
+<td>/Dimension/VerticalPosition@value</td>
+<td>YPosition in millimeters if ResolutionUnit is not None.</td>
+</tr>
+<tr>
+<td>/Document/FormatVersion@value</td>
+<td>6.0</td>
+</tr>
+<tr>
+<td>/Document/SubimageInterpretation@value</td>
+<td>NewSubFileType: transparency => "TransparencyMask";
+reduced-resolution => "ReducedResolution";
+single page => "SinglePage".</td>
+</tr>
+<tr>
+<td>/Document/ImageCreationTime@value</td>
+<td>DateTime</td>
+</tr>
+<tr>
+<td>/Text/TextEntry</td>
+<td>DocumentName, ImageDescription, Make, Model, PageName, Software,
+Artist, HostComputer, InkNames, Copyright:
+/Text/TextEntry@keyword = field name,
+/Text/TextEntry@value = field value.<br>
+Example: TIFF Software field => /Text/TextEntry@keyword = "Software",
+/Text/TextEntry@value = Name and version number of the software package(s)
+used to create the image.</td>
+</tr>
+<tr>
+<td>/Transparency/Alpha@value</td>
+<td>ExtraSamples: associated alpha => "premultiplied";
+unassociated alpha => "nonpremultiplied".</td>
+</tr>
+</table>
+</p>
+
+<h4><a name="ExifRead"/>Reading Exif Images</h4>
+
+The TIFF reader may be used to read an uncompressed Exif image or the
+contents of the <tt>APP1</tt> marker segment of a compressed Exif image.
+
+<h5><a name="ExifReadTIFF"/>Reading Uncompressed Exif Images</h5>
+
+An uncompressed Exif image is a one- or two-page uncompressed TIFF image
+with a specific ordering of its IFD and image data content. Each pixel
+has three 8-bit samples with photometric interpretation RGB or YCbCr.
+The image stream must contain a single primary image and may contain a
+single thumbnail which if present must also be uncompressed. The usual
+<code>ImageReader</code> methods may be used to read the image
+data and metadata:
+
+<pre><code>
+    ImageInputStream input;
+    ImageReader tiffReader;
+    ImageReadParam tiffReadParam;
+
+    tiffReader.setInput(input);
+
+    // Read primary image and IFD.
+    BufferedImage image = tiffReader.read(0, tiffReadParam);
+    IIOMetadata primaryIFD = tiffReader.getImageMetadata(0);
+
+    // Read thumbnail if present.
+    BufferedImage thumbnail = null;
+    if (tiffReader.getNumImages(true) > 1) {
+        thumbnail = tiffReader.read(1, tiffReadParam);
+    }
+</code></pre>
+
+Note that the Exif thumbnail is treated as a separate page in the TIFF
+stream and not as a thumbnail, i.e.,
+<code>tiffReader.hasThumbnails(0)</code> will return <code>false</code>.
+
+<h5><a name="ExifReadJPEG"/>Reading Compressed Exif Images</h5>
+
+A compressed Exif image is a 3-band ISO/IEC 10918-1 baseline DCT JPEG stream
+with an inserted <tt>APP1</tt> marker segment. The parameters of the marker
+segment after the length are the 6-byte sequence
+<code>{'E',&nbsp;'x',&nbsp;'i',&nbsp;'f',&nbsp;0x00,&nbsp;0x00}</code></code>
+followed by a complete TIFF stream. The embedded TIFF stream contains a primary
+IFD describing the JPEG image optionally followed by a thumbnail IFD and
+compressed or uncompressed thumbnail image data. Note that the embedded TIFF
+stream does not contain any image data associated with the primary IFD
+nor any descriptive fields which duplicate information found in the JPEG
+stream itself.
+
+<p>The parameter content of the <tt>APP1</tt> marker segment may be obtained
+from the user object of the associated <code>Node</code> in a
+<tt>javax_imageio_jpeg_image_1.0</tt> native image metadata tree extracted
+from the image metadata object returned by the JPEG reader. This APP1 Exif
+node will be a child of the node named "markerSequence" and will
+have name <tt>unknown</tt> and an attribute named <tt>MarkerTag</tt> with
+integral value <code>0xE1</code> (<code>String</code> value
+<code>"225"</code>). The user object of this node will be a byte array
+which starts with the six bytes <code>{'E', 'x', 'i', 'f', '0', '0'}</code>.
+The primary IFD and the thumbnail IFD and image may be
+read from the user object by the usual <code>ImageReader</code>
+methods:
+
+<pre><code>
+    ImageReader jpegReader;
+    ImageReader tiffReader;
+
+    // Obtain the APP1 Exif marker data from the JPEG image metadata.
+    IIOMetadata jpegImageMetadata = jpegReader.getImageMetadata(0);
+    String nativeFormat = jpegImageMetadata.getNativeMetadataFormatName();
+    Node jpegImageMetadataTree = jpegImageMetadata.getAsTree(nativeFormat);
+
+    // getExifMarkerData() returns the byte array which is the user object
+    // of the APP1 Exif marker node.
+    byte[] app1Params = getExifMarkerData(jpegImageMetadataTree);
+    if (app1Params == null) {
+        throw new IIOException("APP1 Exif marker not found.");
+    }
+
+    // Set up input, skipping Exif ID 6-byte sequence.
+    MemoryCacheImageInputStream app1ExifInput
+        = new MemoryCacheImageInputStream
+            (new ByteArrayInputStream(app1Params, 6, app1Params.length - 6));
+    tiffReader.setInput(app1ExifInput);
+
+    // Read primary IFD.
+    IIOMetadata primaryIFD = tiffReader.getImageMetadata(0);
+
+    // Read thumbnail if present.
+    BufferedImage thumbnail = null;
+    if (tiffReader.getNumImages(true) > 1) {
+        thumbnail = tiffReader.read(1, tiffReadParam);
+    }
+
+    // Read the primary image.
+    BufferedImage image = jpegReader.read(0);
+</code></pre>
+
+Note that <code>tiffReader.getNumImages(true)</code> returns the number of
+IFDs in the embedded TIFF stream including those corresponding to empty
+images. Calling <code>tiffReader.read(0,&nbsp;readParam)</code> will throw
+an exception as the primary image in the embedded TIFF stream is always
+empty; the primary image should be obtained using the JPEG reader itself.
+</p>
+
+<h3><a name="Writing"/>Writing Images</h3>
+
+TIFF images are written by a <a href="../../ImageWriter.html">ImageWriter</a> which may be
+controlled by its public interface as well as via a supplied
+<a href="../../ImageWriteParam.html">ImageWriteParam</a>.  For an <code>ImageWriteParam</code> returned
+by the <code>getDefaultWriteParam()</code> method of the TIFF <code>ImageWriter</code>,
+the <code>canWriteTiles()</code> and <code>canWriteCompressed()</code> methods
+will return <code>true</code>; the <code>canOffsetTiles()</code> and
+<code>canWriteProgressive()</code> methods will return <code>false</code>.</p>
+
+The TIFF writer supports many optional capabilities including writing tiled
+images, inserting images, writing or inserting empty images, and replacing image
+data. Pixels may be replaced in either empty or non-empty images but if and
+only if the data are not compressed.
+
+<p> If tiles are being written, then each of their dimensions will be
+rounded to the nearest multiple of 16 per the TIFF specification. If
+JPEG-in-TIFF compression is being used, and tiles are being written
+each tile dimension will be rounded to the nearest multiple of 8 times
+the JPEG minimum coded unit (MCU) in that dimension. If JPEG-in-TIFF
+compression is being used and strips are being written, the number of
+rows per strip is rounded to a multiple of 8 times the maximum MCU over
+both dimensions.</p>
+ 
+ <!-- <h4>Supported Image Types</h4> -->
+
+<!-- Table? -->
+
+<h4><a name="Compression"/>Compression</h4>
+
+The compression type may be set via the <code>setCompressionType()</code> method of
+the <code>ImageWriteParam</code> after setting the compression mode to
+<code>MODE_EXPLICIT</code>. The set of innately
+supported compression types is listed in the following table:
+
+<table border=1>
+<caption><b>Supported Compression Types</b></caption>
+<tr><th>Compression Type</th> <th>Description</th> <th>Reference</th></tr>
+<tr>
+<td>CCITT RLE</td>
+<td>Modified Huffman compression</td>
+<td>TIFF 6.0 Specification, Section 10</td>
+</tr>
+<tr>
+<td>CCITT T.4</td>
+<td>CCITT T.4 bilevel encoding/Group 3 facsimile compression</td>
+<td>TIFF 6.0 Specification, Section 11</td>
+</tr>
+<tr>
+<td>CCITT T.6</td>
+<td>CCITT T.6 bilevel encoding/Group 4 facsimile compression</td>
+<td>TIFF 6.0 Specification, Section 11</td></tr>
+<tr>
+<td>LZW</td>
+<td>LZW compression</td>
+<td>TIFF 6.0 Specification, Section 13</td></tr>
+<tr>
+<td>JPEG</td>
+<td>"New" JPEG-in-TIFF compression</td>
+<td><a href="ftp://ftp.sgi.com/graphics/tiff/TTN2.draft.txt">TIFF
+Technical Note #2</a></td>
+</tr>
+<tr>
+<td>ZLib</td>
+<td>"Deflate/Inflate" compression (see note following this table)</td>
+<td><a href="http://partners.adobe.com/asn/developer/pdfs/tn/TIFFphotoshop.pdf">
+Adobe Photoshop&#174; TIFF Technical Notes</a> (PDF)</td>
+</tr>
+<tr>
+<td>PackBits</td>
+<td>Byte-oriented, run length compression</td>
+<td>TIFF 6.0 Specification, Section 9</td>
+</tr>
+<tr>
+<td>Deflate</td>
+<td>"Zip-in-TIFF" compression (see note following this table)</td>
+<td><a href="http://www.isi.edu/in-notes/rfc1950.txt">
+ZLIB Compressed Data Format Specification</a>,
+<a href="http://www.isi.edu/in-notes/rfc1951.txt">
+DEFLATE Compressed Data Format Specification</a></td>
+</tr>
+<tr>
+<td>Exif JPEG</td>
+<td>Exif-specific JPEG compression (see note following this table)</td>
+<td><a href="http://www.exif.org/Exif2-2.PDF">Exif 2.2 Specification</a>
+(PDF), section 4.5.5, "Basic Structure of Thumbnail Data"</td>
+</table>
+
+<p>
+Old-style JPEG compression as described in section 22 of the TIFF 6.0
+Specification is <i>not</i> supported.
+</p>
+
+<p> The CCITT compression types are applicable to bilevel (1-bit)
+images only.  The JPEG compression type is applicable to byte
+grayscale (1-band) and RGB (3-band) images only.</p>
+
+<p>
+ZLib and Deflate compression are identical except for the value of the
+TIFF Compression field: for ZLib the Compression field has value 8
+whereas for Deflate it has value 32946 (0x80b2). In both cases each
+image segment (strip or tile) is written as a single complete zlib data
+stream.
+</p>
+
+<p>
+"Exif JPEG" is a compression type used when writing the contents of an
+APP1 Exif marker segment for inclusion in a JPEG native image metadata
+tree. The contents appended to the output when this compression type is
+used are a function of whether an empty or non-empty image is written.
+If the image is empty, then a TIFF IFD adhering to the specification of
+a compressed Exif primary IFD is appended. If the image is non-empty,
+then a complete IFD and image adhering to the specification of a
+compressed Exif thumbnail IFD and image are appended. Note that the
+data of the empty image may <i>not</i> later be appended using the pixel
+replacement capability of the TIFF writer.
+</p>
+
+<p> If ZLib/Deflate or JPEG compression is used, the compression quality
+may be set. For ZLib/Deflate the supplied floating point quality value is
+rescaled to the range <tt>[1,&nbsp;9]</tt> and truncated to an integer
+to derive the Deflate compression level. For JPEG the floating point
+quality value is passed directly to the JPEG writer plug-in which
+interprets it in the usual way.</p>
+
+<h4><a name="ColorConversionWrite"/>Color Conversion</h4>
+
+<p>If the source image data
+color space type is RGB, and the destination photometric type is CIE L*a*b* or
+YCbCr, then the source image data will be automatically converted from
+RGB using an internal color converter.</p>
+
+<h4><a name="ICCProfilesWrite"/>ICC Profiles</h4>
+
+An <tt>ICC Profile</tt> field will be written if either:
+<ul>
+<li>one is present in the native image metadata
+<a href="../IIOMetadata.html">IIOMetadata</a> instance supplied to the writer,
+or</li>
+<li>the <a href="../../../../java/awt/color/ColorSpace.html">ColorSpace</a>
+of the destination <code>ImageTypeSpecifier</code> is an instance of
+<code>ICC_ColorSpace</code> which is not one of the standard
+color spaces defined by the <tt>CS_*</tt> constants in the
+<code>ColorSpace</code> class. The destination type is set via
+<code>ImageWriteParam.setDestinationType(ImageTypeSpecifier)</code> and defaults
+to the <code>ImageTypeSpecifier</code> of the image being written.
+</li>
+</ul>
+
+<h4><a name="MetadataIssuesWrite"/>Metadata Issues</h4>
+
+Some behavior of the writer is affected by or may affect the contents of
+the image metadata which may be supplied by the user.
+
+<p>For bilevel images, the <tt>FillOrder</tt>, and <tt>T4Options</tt>
+fields affect the output data. The data will be filled right-to-left if
+<tt>FillOrder</tt> is present with a value of 2
+(<code>BaselineTIFFTagSet.FILL_ORDER_RIGHT_TO_LEFT</code>)
+and will be filled left-to-right otherwise. The value of <tt>T4Options</tt>
+specifies whether the data should be 1D- or 2D-encoded and whether EOL
+padding should be used.</p>
+
+<p>For all images the value of the <tt>RowsPerStrip</tt> field is used
+to the set the number of rows per strip if the image is not tiled. The
+default number of rows per strip is either 8 or the number of rows which
+would fill no more than 8 kilobytes, whichever is larger.</p>
+
+<p>For all images the tile dimensions may be set using the <tt>TileWidth</tt>
+and <tt>TileLength</tt> field values if the tiling mode is
+<code>ImageWriteParam.MODE_COPY_FROM_METADATA</code>. If this mode
+is set but the fields are not, their respective default values are the image
+width and height.</p>
+
+<p>When using JPEG-in-TIFF compression, a <tt>JPEGTables</tt> field will be
+written to the IFD and abbreviated JPEG streams to each strip or tile if and
+only if a <tt>JPEGTables</tt> field is contained in the metadata object
+provided to the writer. If the contents of the <tt>JPEGTables</tt> field is
+a valid tables-only JPEG stream, then it will be used; otherwise the contents
+of the field will be replaced with default visually lossless tables. If no
+such <tt>JPEGTables</tt> field is present in the metadata, then no
+<tt>JPEGTables</tt> field will be written to the output and each strip or
+tile will be written as a separate, self-contained JPEG stream.</p>
+
+<p>When using Deflate/ZLib or LZW compression, if the image has 8 bits per
+sample, a horizontal differencing predictor will be used if the
+<tt>Predictor</tt> field is present with a value of 2
+(<code>BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING</code>).
+If prediction is so requested but the image does not have
+8 bits per sample the field will be reset to have the value 1
+(<code>BaselineTIFFTagSet.PREDICTOR_NONE</code>).
+</p>
+
+<p>Some fields may be added or modified:
+
+<ul>
+<li><tt>PhotometricInterpretation</tt> if not present.</li>
+<li><tt>PlanarConfiguration</tt> if this field is present with value
+<tt>Planar</tt> is is reset to <tt>Chunky</tt>.</li>
+<li><tt>Compression</tt> always.</li>
+<li><tt>BitsPerSample</tt> if the image is not bilevel.</li>
+<li><tt>SamplesPerPixel</tt> always.</li>
+<li><tt>ExtraSamples</tt> if an alpha channel is present.</li>
+<li><tt>SampleFormat</tt> if not present and the data are 16- or 32-bit
+integers or floating point.</li>
+<li><tt>ColorMap</tt> if the <tt>PhotometricInterpretation</tt> is
+<tt>RGBPalette</tt>.</li>
+<li><tt>ImageWidth</tt> and <tt>ImageLength</tt> always.</li>
+<li><tt>TileWidth</tt>, <tt>TileLength</tt>, <tt>TileOffsets</tt>, and
+<tt>TileByteCounts</tt> if a tiled image is being written.</li>
+<li><tt>RowsPerStrip</tt>, <tt>StripOffsets</tt>, and <tt>StripByteCounts</tt>
+if a tiled image is <i>not</i> being written.</li>
+<li><tt>XResolution</tt>, <tt>YResolution</tt>, and <tt>ResolutionUnit</tt>
+if none of these is present.</li>
+<li><tt>YCbCrSubsampling</tt> and <tt>YCbCrPositioning</tt> if the
+photometric interpretation is YCbCr and the compression type is not JPEG
+(only [1,&nbsp;1] subsampling and cosited positioning are supported for
+non-JPEG YCbCr output).</li>
+<li><tt>YCbCrSubsampling</tt>, <tt>YCbCrPositioning</tt>, and
+<tt>ReferenceBlackWhite</tt>: if the compression type is JPEG and the color
+space is RGB these will be reset to [2,&nbsp;2] centered subsampling with no
+headroom/footroom (0:255,128:255,128:255).</li>
+</ul>
+
+<p>Some fields may be removed:
+
+<ul>
+<li><tt>BitsPerSample</tt> if the image is bilevel.</li>
+<li><tt>ExtraSamples</tt> if the image does not have an alpha channel.</li>
+<li><tt>ColorMap</tt> if the photometric interpretation is not
+<tt>RGBPalette</tt>.</li>
+<li><tt>TileWidth</tt>, <tt>TileLength</tt>, <tt>TileOffsets</tt>, and
+<tt>TileByteCounts</tt> if tiling <i>is not</i> being used.</li>
+<li><tt>RowsPerStrip</tt>, <tt>StripOffsets</tt>, and <tt>StripByteCounts</tt>
+if tiling <i>is</i> being used.</li>
+<li><tt>YCbCrSubsampling</tt>, <tt>YCbCrPositioning</tt>, and
+<tt>ReferenceBlackWhite</tt> if the compression type is JPEG and the
+color space is grayscale.</li>
+<li><tt>JPEGProc</tt>, <tt>JPEGInterchangeFormat</tt>,
+<tt>JPEGInterchangeFormatLength</tt>, <tt>JPEGRestartInterval</tt>,
+<tt>JPEGLosslessPredictors</tt>, <tt>JPEGPointTransforms</tt>,
+<tt>JPEGQTables</tt>, <tt>JPEGDCTables</tt>, and
+<tt>JPEGACTables</tt> if the compression type is JPEG.</li>
+</ul>
+</p>
+
+<p>Other fields present in the supplied metadata are uninterpreted and will
+be written as supplied.</p>
+
+<p>If an Exif image is being written, the set of fields present and their
+values will be modified such that the result is in accord with the Exif 2.2
+specification.</p>
+
+<p>Setting up the image metadata to write to a TIFF stream may be simplified
+by using the <code>TIFFDirectory</code> class
+which represents a TIFF IFD. A field in a TIFF IFD is represented by an
+instance of <a href="../../plugins/tiff/TIFFField.html">TIFFField</a>. For each
+field to be written a <code>TIFFField</code> may be added to the
+<code>TIFFDirectory</code> and the latter converted to an
+<code>IIOMetadata</code> object by invoking
+<code>TIFFDirectory.getAsMetadata</code>. The
+<code>IIOMetadata</code> object so obtained may then be passed to the TIFF
+writer.</p>
+
+<h5><a name="MapStandardNative"/>
+Mapping of the Standard Metadata Format to TIFF Native Image Metadata</h5>
+
+The derivation of <a href="#ImageMetadata">TIFF native image metadata</a>
+elements from the standard metadata format
+<a href="standard_metadata.html">javax_imageio_1.0</a> is
+given in the following table.
+
+<p>
+<table border="1">
+<tr>
+<th>TIFF Field</th>
+<th>Derivation from Standard Metadata Elements</th>
+</tr>
+<tr>
+<td>
+PhotometricInterpretation
+</td>
+<td>/Chroma/ColorSpaceType@name: "GRAY" and /Chroma/BlackIsZero@value = "FALSE"
+=> WhiteIsZero; "GRAY" and /Document/SubimageInterpretation@value =
+"TransparencyMask" => TransparencyMask; "RGB" and /Chroma/Palette present =>
+PaletteColor; "GRAY" => BlackIsZero; "RGB" => RGB; "YCbCr" => YCbCr;
+"CMYK" => CMYK; "Lab" => CIELab.</td>
+</tr>
+<tr>
+<td>SamplesPerPixel</td>
+<td>/Chroma/NumChannels@value</td>
+</tr>
+<tr>
+<td>ColorMap</td>
+<td>/Chroma/Palette</td>
+</tr>
+<tr>
+<td>Compression</td>
+<td>/Compression/CompressionTypeName@value: "none" => Uncompressed;
+"CCITT RLE" => CCITT 1D; "CCITT T.4" => Group 3 Fax; "CCITT T.6" => Group 4
+Fax; "LZW" => LZW; "Old JPEG" => JPEG; "JPEG" => New JPEG; "ZLib" => ZLib;
+"PackBits" => PackBits; "Deflate" => Deflate.</td>
+</tr>
+<tr>
+<td>PlanarConfiguration</td>
+<td>/Data/PlanarConfiguration@value: "PixelInterleaved" => Chunky;
+"PlaneInterleaved" => Planar.</td>
+</tr>
+<tr>
+<td>SampleFormat</td>
+<td>/Data/SampleFormat@value: "SignedIntegral" => two's complement signed
+integer data; "UnsignedIntegral" => unsigned integer data; "Real" =>
+IEEE floating point data; "Index" => unsigned integer data.
+</td>
+</tr>
+<tr>
+<td>BitsPerSample</td>
+<td>/Data/BitsPerSample@value: space-separated list parsed to char array.</td>
+</tr>
+<tr>
+<td>FillOrder</td>
+<td>/Data/SampleMSB@value: if all values in space-separated list are 0s =>
+right-to-left; otherwise => left-to-right.
+</td>
+</tr>
+<tr>
+<td>XResolution</td>
+<td>(10 / /Dimension/HorizontalPixelSize@value) or
+(10 / (/Dimension/VerticalPixelSize@value *
+/Dimension/PixelAspectRatio@value))</td>
+</tr>
+<tr>
+<td>YResolution</td>
+<td>(10 / /Dimension/VerticalPixelSize@value) or
+(10 / (/Dimension/HorizontalPixelSize@value /
+/Dimension/PixelAspectRatio@value))</td>
+</tr>
+<tr>
+<td>ResolutionUnit</td>
+<td>Centimeter if XResolution or YResolution set; otherwise None.</td>
+</tr>
+<tr>
+<td>Orientation</td>
+<td>/Dimension/ImageOrientation@value</td>
+</tr>
+<tr>
+<td>XPosition</td>
+<td>/Dimension/HorizontalPosition@value / 10</td>
+</tr>
+<tr>
+<td>YPosition</td>
+<td>/Dimension/VerticalPosition@value / 10</td>
+</tr>
+<tr>
+<td>NewSubFileType</td>
+<td>/Document/SubimageInterpretation@value: "TransparencyMask" =>
+transparency mask; "ReducedResolution" => reduced-resolution;
+"SinglePage" => single page.</td>
+</tr>
+<tr>
+<td>DateTime</td>
+<td>/Document/ImageCreationTime@value</td>
+</tr>
+<tr>
+<td>DocumentName, ImageDescription, Make, Model, PageName, Software,
+Artist, HostComputer, InkNames, Copyright</td>
+<td>/Text/TextEntry: if /Text/TextEntry@keyword is the name of any of the
+TIFF Fields, e.g., "Software", then the field is added with content
+/Text/TextEntry@value and count 1.</td>
+</tr>
+<tr>
+<td>ExtraSamples</td>
+<td>/Transparency/Alpha@value: "premultiplied" => associated alpha, count 1;
+"nonpremultiplied" => unassociated alpha, count 1.</td>
+</tr>
+<tr>
+<td></td>
+<td></td>
+</tr>
+</table>
+</p>
+
+<h4><a name="ExifWrite"/>Writing Exif Images</h4>
+
+The TIFF writer may be used to write an uncompressed Exif image or the
+contents of the <tt>APP1</tt> marker segment of a compressed Exif image.
+
+<h5><a name="ExifWriteTIFF"/>Writing Uncompressed Exif Images</h5>
+
+When writing a sequence of images each image is normally recorded as
+{IFD,&nbsp;IFD Value,&nbsp;Image Data}. The Exif specification requires
+that an uncompressed Exif image be structured as follows:
+
+<ol>
+<a name="ExifStructure"/>
+<li>Image File Header</li>
+<li>Primary IFD</li>
+<li>Primary IFD Value</li>
+<li>Thumbnail IFD</li>
+<li>Thumbnail IFD Value</li>
+<li>Thumbnail Image Data</li>
+<li>Primary Image Data</li>
+</ol>
+
+To meet the requirement of the primary image data being recorded last, the
+primary image must be written initially as an empty image and have its data
+added via pixel replacement after the thumbnail IFD and image data have been
+written:
+
+<pre><code>
+    ImageWriter tiffWriter;
+    ImageWriteParam tiffWriteParam;
+    IIOMetadata tiffStreamMetadata;
+    IIOMetadata primaryIFD;
+    BufferedImage image;
+    BufferedImage thumbnail;
+
+    // Specify uncompressed output.
+    tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);
+
+    if (thumbnail != null) {
+        // Write the TIFF header.
+        tiffWriter.prepareWriteSequence(tiffStreamMetadata);
+
+        // Append the primary IFD.
+        tiffWriter.prepareInsertEmpty(-1, // append
+                new ImageTypeSpecifier(image),
+                image.getWidth(),
+                image.getHeight(),
+                primaryIFD,
+                null, // thumbnails
+                tiffWriteParam);
+        tiffWriter.endInsertEmpty();
+
+        // Append the thumbnail image data.
+        tiffWriter.writeToSequence(new IIOImage(thumbnail, null, null),
+                tiffWriteParam);
+
+        // Insert the primary image data.
+        tiffWriter.prepareReplacePixels(0, new Rectangle(image.getWidth(),
+                image.getHeight()));
+        tiffWriter.replacePixels(image, tiffWriteParam);
+        tiffWriter.endReplacePixels();
+
+        // End writing.
+        tiffWriter.endWriteSequence();
+    } else {
+        // Write only the primary IFD and image data.
+        tiffWriter.write(tiffStreamMetadata,
+                new IIOImage(image, null, primaryIFD),
+                tiffWriteParam);
+    }
+</code></pre>
+
+<h5><a name="ExifWriteJPEG"/>Writing Compressed Exif Images</h5>
+
+The structure of the embedded TIFF stream in the <tt>APP1</tt> segment of a
+compressed Exif image is identical to the <a href="#ExifStructure">
+uncompressed Exif image structure</a> except that there are no primary
+image data, i.e., the primary IFD does not refer to any image data.
+
+<pre><code>
+    ImageWriter tiffWriter;
+    ImageWriteParam tiffWriteParam;
+    IIOMetadata tiffStreamMetadata;
+    BufferedImage image;
+    BufferedImage thumbnail;
+    IIOMetadata primaryIFD;
+    ImageOutputStream output;
+
+    // Set up an output to contain the APP1 Exif TIFF stream.
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    MemoryCacheImageOutputStream app1ExifOutput =
+        new MemoryCacheImageOutputStream(baos);
+    tiffWriter.setOutput(app1ExifOutput);
+
+    // Set compression for the thumbnail.
+    tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+    tiffWriteParam.setCompressionType("Exif JPEG");
+
+    // Write the APP1 Exif TIFF stream.
+    if (thumbnail != null) {
+        // Write the TIFF header.
+        tiffWriter.prepareWriteSequence(tiffStreamMetadata);
+
+        // Append the primary IFD.
+        tiffWriter.prepareInsertEmpty(-1, // append
+                new ImageTypeSpecifier(image),
+                image.getWidth(),
+                image.getHeight(),
+                primaryIFD,
+                null, // thumbnails
+                tiffWriteParam);
+        tiffWriter.endInsertEmpty();
+
+        // Append the thumbnail IFD and image data.
+        tiffWriter.writeToSequence(new IIOImage(thumbnail, null,
+                null), tiffWriteParam);
+
+        // End writing.
+        tiffWriter.endWriteSequence();
+    } else {
+        // Write only the primary IFD.
+        tiffWriter.prepareWriteEmpty(tiffStreamMetadata,
+                new ImageTypeSpecifier(image),
+                image.getWidth(),
+                image.getHeight(),
+                primaryIFD,
+                null, // thumbnails
+                tiffWriteParam);
+        tiffWriter.endWriteEmpty();
+    }
+
+    // Flush data into byte stream.
+    app1ExifOutput.flush();
+
+    // Create APP1 parameter array.
+    byte[] app1Parameters = new byte[6 + baos.size()];
+
+    // Add APP1 Exif ID bytes.
+    app1Parameters[0] = (byte) 'E';
+    app1Parameters[1] = (byte) 'x';
+    app1Parameters[2] = (byte) 'i';
+    app1Parameters[3] = (byte) 'f';
+    app1Parameters[4] = app1Parameters[5] = (byte) 0;
+
+    // Append TIFF stream to APP1 parameters.
+    System.arraycopy(baos.toByteArray(), 0, app1Parameters, 6, baos.size());
+
+    // Create the APP1 Exif node to be added to native JPEG image metadata.
+    IIOMetadataNode app1Node = new IIOMetadataNode("unknown");
+    app1Node.setAttribute("MarkerTag", String.valueOf(0xE1));
+    app1Node.setUserObject(app1Parameters);
+
+    // Append the APP1 Exif marker to the "markerSequence" node.
+    IIOMetadata jpegImageMetadata =
+        jpegWriter.getDefaultImageMetadata(new ImageTypeSpecifier(image),
+            jpegWriteParam);
+    String nativeFormat = jpegImageMetadata.getNativeMetadataFormatName();
+    Node tree = jpegImageMetadata.getAsTree(nativeFormat);
+    NodeList children = tree.getChildNodes();
+    int numChildren = children.getLength();
+    for (int i = 0; i < numChildren; i++) {
+        Node child = children.item(i);
+        if (child.getNodeName().equals("markerSequence")) {
+            child.appendChild(app1Node);
+            break;
+        }
+    }
+    jpegImageMetadata.setFromTree(nativeFormat, tree);
+
+    // Write the JPEG image data including the APP1 Exif marker.
+    jpegWriter.setOutput(output);
+    jpegWriter.write(new IIOImage(image, null, jpegImageMetadata));
+</code></pre>
+
+The <code>"unknown"</code> node created above would be appended to the
+<code>"markerSequence"</code> node of the native JPEG image metadata
+and written to the JPEG stream when the primary image is written using
+the JPEG writer.
+
+<h3><a name="StreamMetadata"/>Stream Metadata</h3>
+
+The DTD for the TIFF native stream metadata format is as follows:
+
+<pre>
+&lt;!DOCTYPE "javax_imageio_tiff_stream_1.0" [
+
+  &lt;!ELEMENT "javax_imageio_tiff_stream_1.0" (ByteOrder)>
+
+    &lt;!ELEMENT "ByteOrder" EMPTY&gt;
+      &lt;!-- The stream byte order --&gt; 
+      &lt;!ATTLIST "ByteOrder" "value" #CDATA #REQUIRED&gt;
+        &lt;!-- One of "BIG_ENDIAN" or "LITTLE_ENDIAN" --&gt; 
+        &lt;!-- Data type: String --&gt;
+]&gt;
+</pre>
+
+<h3><a name="ImageMetadata"/>Image Metadata</h3>
+
+The DTD for the TIFF native image metadata format is as follows:
+
+<pre>
+&lt;!DOCTYPE "javax_imageio_tiff_image_1.0" [
+
+  &lt;!ELEMENT "javax_imageio_tiff_image_1.0" (TIFFIFD)*&gt;
+
+    &lt;!ELEMENT "TIFFIFD" (TIFFField | TIFFIFD)*&gt;
+      &lt;!-- An IFD (directory) containing fields --&gt; 
+      &lt;!ATTLIST "TIFFIFD" "tagSets" #CDATA #REQUIRED&gt;
+        &lt;!-- Data type: String --&gt;
+      &lt;!ATTLIST "TIFFIFD" "parentTagNumber" #CDATA #IMPLIED&gt;
+        &lt;!-- The tag number of the field pointing to this IFD --&gt; 
+        &lt;!-- Data type: Integer --&gt;
+      &lt;!ATTLIST "TIFFIFD" "parentTagName" #CDATA #IMPLIED&gt;
+        &lt;!-- A mnemonic name for the field pointing to this IFD, if known 
+             --&gt; 
+        &lt;!-- Data type: String --&gt;
+
+      &lt;!ELEMENT "TIFFField" (TIFFBytes | TIFFAsciis |
+        TIFFShorts | TIFFSShorts | TIFFLongs | TIFFSLongs |
+        TIFFRationals | TIFFSRationals |
+        TIFFFloats | TIFFDoubles | TIFFUndefined)&gt;
+        &lt;!-- A field containing data --&gt; 
+        &lt;!ATTLIST "TIFFField" "number" #CDATA #REQUIRED&gt;
+          &lt;!-- The tag number asociated with the field --&gt; 
+          &lt;!-- Data type: String --&gt;
+        &lt;!ATTLIST "TIFFField" "name" #CDATA #IMPLIED&gt;
+          &lt;!-- A mnemonic name associated with the field, if known --&gt; 
+          &lt;!-- Data type: String --&gt;
+
+        &lt;!ELEMENT "TIFFBytes" (TIFFByte)*&gt;
+          &lt;!-- A sequence of TIFFByte nodes --&gt; 
+
+          &lt;!ELEMENT "TIFFByte" EMPTY&gt;
+            &lt;!-- An integral value between 0 and 255 --&gt; 
+            &lt;!ATTLIST "TIFFByte" "value" #CDATA #IMPLIED&gt;
+              &lt;!-- The value --&gt; 
+              &lt;!-- Data type: String --&gt;
+            &lt;!ATTLIST "TIFFByte" "description" #CDATA #IMPLIED&gt;
+              &lt;!-- A description, if available --&gt; 
+              &lt;!-- Data type: String --&gt;
+
+        &lt;!ELEMENT "TIFFAsciis" (TIFFAscii)*&gt;
+          &lt;!-- A sequence of TIFFAscii nodes --&gt; 
+
+          &lt;!ELEMENT "TIFFAscii" EMPTY&gt;
+            &lt;!-- A String value --&gt; 
+            &lt;!ATTLIST "TIFFAscii" "value" #CDATA #IMPLIED&gt;
+              &lt;!-- The value --&gt; 
+              &lt;!-- Data type: String --&gt;
+
+        &lt;!ELEMENT "TIFFShorts" (TIFFShort)*&gt;
+          &lt;!-- A sequence of TIFFShort nodes --&gt; 
+
+          &lt;!ELEMENT "TIFFShort" EMPTY&gt;
+            &lt;!-- An integral value between 0 and 65535 --&gt; 
+            &lt;!ATTLIST "TIFFShort" "value" #CDATA #IMPLIED&gt;
+              &lt;!-- The value --&gt; 
+              &lt;!-- Data type: String --&gt;
+            &lt;!ATTLIST "TIFFShort" "description" #CDATA #IMPLIED&gt;
+              &lt;!-- A description, if available --&gt; 
+              &lt;!-- Data type: String --&gt;
+
+        &lt;!ELEMENT "TIFFSShorts" (TIFFSShort)*&gt;
+          &lt;!-- A sequence of TIFFSShort nodes --&gt; 
+
+          &lt;!ELEMENT "TIFFSShort" EMPTY&gt;
+            &lt;!-- An integral value between -32768 and 32767 --&gt; 
+            &lt;!ATTLIST "TIFFSShort" "value" #CDATA #IMPLIED&gt;
+              &lt;!-- The value --&gt; 
+              &lt;!-- Data type: String --&gt;
+            &lt;!ATTLIST "TIFFSShort" "description" #CDATA #IMPLIED&gt;
+              &lt;!-- A description, if available --&gt; 
+              &lt;!-- Data type: String --&gt;
+
+        &lt;!ELEMENT "TIFFLongs" (TIFFLong)*&gt;
+          &lt;!-- A sequence of TIFFLong nodes --&gt; 
+
+          &lt;!ELEMENT "TIFFLong" EMPTY&gt;
+            &lt;!-- An integral value between 0 and 4294967295 --&gt; 
+            &lt;!ATTLIST "TIFFLong" "value" #CDATA #IMPLIED&gt;
+              &lt;!-- The value --&gt; 
+              &lt;!-- Data type: String --&gt;
+            &lt;!ATTLIST "TIFFLong" "description" #CDATA #IMPLIED&gt;
+              &lt;!-- A description, if available --&gt; 
+              &lt;!-- Data type: String --&gt;
+
+        &lt;!ELEMENT "TIFFSLongs" (TIFFSLong)*&gt;
+          &lt;!-- A sequence of TIFFSLong nodes --&gt; 
+
+          &lt;!ELEMENT "TIFFSLong" EMPTY&gt;
+            &lt;!-- An integral value between -2147483648 and 2147482647 --&gt; 
+            &lt;!ATTLIST "TIFFSLong" "value" #CDATA #IMPLIED&gt;
+              &lt;!-- The value --&gt; 
+              &lt;!-- Data type: String --&gt;
+            &lt;!ATTLIST "TIFFSLong" "description" #CDATA #IMPLIED&gt;
+              &lt;!-- A description, if available --&gt; 
+              &lt;!-- Data type: String --&gt;
+
+        &lt;!ELEMENT "TIFFRationals" (TIFFRational)*&gt;
+          &lt;!-- A sequence of TIFFRational nodes --&gt; 
+
+          &lt;!ELEMENT "TIFFRational" EMPTY&gt;
+            &lt;!-- A rational value consisting of an unsigned numerator and 
+                 denominator --&gt; 
+            &lt;!ATTLIST "TIFFRational" "value" #CDATA #IMPLIED&gt;
+              &lt;!-- The numerator and denominator, separated by a slash --&gt; 
+              &lt;!-- Data type: String --&gt;
+
+        &lt;!ELEMENT "TIFFSRationals" (TIFFSRational)*&gt;
+          &lt;!-- A sequence of TIFFSRational nodes --&gt; 
+
+          &lt;!ELEMENT "TIFFSRational" EMPTY&gt;
+            &lt;!-- A rational value consisting of a signed numerator and 
+                 denominator --&gt; 
+            &lt;!ATTLIST "TIFFSRational" "value" #CDATA #IMPLIED&gt;
+              &lt;!-- The numerator and denominator, separated by a slash --&gt; 
+              &lt;!-- Data type: String --&gt;
+
+        &lt;!ELEMENT "TIFFFloats" (TIFFFloat)*&gt;
+          &lt;!-- A sequence of TIFFFloat nodes --&gt; 
+
+          &lt;!ELEMENT "TIFFFloat" EMPTY&gt;
+            &lt;!-- A single-precision floating-point value --&gt; 
+            &lt;!ATTLIST "TIFFFloat" "value" #CDATA #IMPLIED&gt;
+              &lt;!-- The value --&gt; 
+              &lt;!-- Data type: String --&gt;
+
+        &lt;!ELEMENT "TIFFDoubles" (TIFFDouble)*&gt;
+          &lt;!-- A sequence of TIFFDouble nodes --&gt; 
+
+          &lt;!ELEMENT "TIFFDouble" EMPTY&gt;
+            &lt;!-- A double-precision floating-point value --&gt; 
+            &lt;!ATTLIST "TIFFDouble" "value" #CDATA #IMPLIED&gt;
+              &lt;!-- The value --&gt; 
+              &lt;!-- Data type: String --&gt;
+
+        &lt;!ELEMENT "TIFFUndefined" EMPTY&gt;
+          &lt;!-- Uninterpreted byte data --&gt; 
+          &lt;!ATTLIST "TIFFUndefined" "value" #CDATA #IMPLIED&gt;
+            &lt;!-- A list of comma-separated byte values --&gt; 
+            &lt;!-- Data type: String --&gt;
+]&gt;
+</pre>
+
+@since 1.9
+
+</body>
+</html>
--- a/jdk/src/java.desktop/share/classes/javax/imageio/metadata/package.html	Mon Nov 23 10:00:50 2015 -0800
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/metadata/package.html	Mon Nov 23 12:26:12 2015 -0800
@@ -2,7 +2,7 @@
 <html>
 <head>
 <!--
-Copyright (c) 2000, 2003, Oracle and/or its affiliates. All rights reserved.
+Copyright (c) 2000, 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
@@ -82,6 +82,11 @@
 
 <ul>
 <li>
+<A HREF="doc-files/bmp_metadata.html">
+BMP metadata
+</A>
+
+<li>
 <A HREF="doc-files/gif_metadata.html">
 GIF metadata
 </A>
@@ -97,8 +102,8 @@
 </A>
 
 <li>
-<A HREF="doc-files/bmp_metadata.html">
-BMP metadata
+<A HREF="doc-files/tiff_metadata.html#StreamMetadata">
+TIFF metadata
 </A>
 
 <li>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/BaselineTIFFTagSet.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,2190 @@
+/*
+ * 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 javax.imageio.plugins.tiff;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class representing the set of tags found in the baseline TIFF
+ * specification as well as some common additional tags.
+ *
+ * <p> The non-baseline tags included in this class are:
+ * <ul>
+ * <li> {@link #TAG_JPEG_TABLES JPEGTables}
+ * <li> {@link #TAG_ICC_PROFILE ICC&nbsp;Profile}
+ * </ul>
+ *
+ * <p> The non-baseline values of baseline tags included in this class are
+ * <ul>
+ * <li>{@link #TAG_COMPRESSION Compression} tag values:
+ * <ul>
+ * <li>{@link #COMPRESSION_JPEG JPEG-in-TIFF&nbsp;compression}</li>
+ * <li>{@link #COMPRESSION_ZLIB Zlib-in-TIFF&nbsp;compression}</li>
+ * <li>{@link #COMPRESSION_DEFLATE Deflate&nbsp;compression}</li>
+ * </ul>
+ * </li>
+ * <li>{@link #TAG_PHOTOMETRIC_INTERPRETATION PhotometricInterpretation}
+ * tag values:
+ * <ul>
+ * <li>{@link #PHOTOMETRIC_INTERPRETATION_ICCLAB ICCLAB&nbsp;
+ * photometric&nbsp;interpretation}</li>
+ * </ul>
+ * </li>
+ * </ul>
+ *
+ * @since 1.9
+ * @see   <a href="http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf">  TIFF 6.0 Specification</a>
+ */
+public class BaselineTIFFTagSet extends TIFFTagSet {
+
+    private static BaselineTIFFTagSet theInstance = null;
+
+    // Tags from TIFF 6.0 specification
+
+    /**
+     * Constant specifying the "NewSubfileType" tag.
+     *
+     * @see #NEW_SUBFILE_TYPE_REDUCED_RESOLUTION
+     * @see #NEW_SUBFILE_TYPE_SINGLE_PAGE
+     * @see #NEW_SUBFILE_TYPE_TRANSPARENCY
+     */
+    public static final int TAG_NEW_SUBFILE_TYPE = 254;
+
+    /**
+     * A mask to be used with the "NewSubfileType" tag.
+     *
+     * @see #TAG_NEW_SUBFILE_TYPE
+     */
+    public static final int NEW_SUBFILE_TYPE_REDUCED_RESOLUTION = 1;
+
+    /**
+     * A mask to be used with the "NewSubfileType" tag.
+     *
+     * @see #TAG_NEW_SUBFILE_TYPE
+     */
+    public static final int NEW_SUBFILE_TYPE_SINGLE_PAGE = 2;
+
+    /**
+     * A mask to be used with the "NewSubfileType" tag.
+     *
+     * @see #TAG_NEW_SUBFILE_TYPE
+     */
+    public static final int NEW_SUBFILE_TYPE_TRANSPARENCY = 4;
+
+    /**
+     * Constant specifying the "SubfileType" tag.
+     *
+     * @see #SUBFILE_TYPE_FULL_RESOLUTION
+     * @see #SUBFILE_TYPE_REDUCED_RESOLUTION
+     * @see #SUBFILE_TYPE_SINGLE_PAGE
+     */
+    public static final int TAG_SUBFILE_TYPE = 255;
+
+    /**
+     * A value to be used with the "SubfileType" tag.
+     *
+     * @see #TAG_SUBFILE_TYPE
+     */
+    public static final int SUBFILE_TYPE_FULL_RESOLUTION = 1;
+
+    /**
+     * A value to be used with the "SubfileType" tag.
+     *
+     * @see #TAG_SUBFILE_TYPE
+     */
+    public static final int SUBFILE_TYPE_REDUCED_RESOLUTION = 2;
+
+    /**
+     * A value to be used with the "SubfileType" tag.
+     *
+     * @see #TAG_SUBFILE_TYPE
+     */
+    public static final int SUBFILE_TYPE_SINGLE_PAGE = 3;
+
+    /**
+     * Constant specifying the "ImageWidth" tag.
+     */
+    public static final int TAG_IMAGE_WIDTH = 256;
+
+    /**
+     * Constant specifying the "ImageLength" tag.
+     */
+    public static final int TAG_IMAGE_LENGTH = 257;
+
+    /**
+     * Constant specifying the "BitsPerSample" tag.
+     */
+    public static final int TAG_BITS_PER_SAMPLE = 258;
+
+    /**
+     * Constant specifying the "Compression" tag.
+     *
+     * @see #COMPRESSION_NONE
+     * @see #COMPRESSION_CCITT_RLE
+     * @see #COMPRESSION_CCITT_T_4
+     * @see #COMPRESSION_CCITT_T_6
+     * @see #COMPRESSION_LZW
+     * @see #COMPRESSION_OLD_JPEG
+     * @see #COMPRESSION_JPEG
+     * @see #COMPRESSION_ZLIB
+     * @see #COMPRESSION_PACKBITS
+     * @see #COMPRESSION_DEFLATE
+     */
+    public static final int TAG_COMPRESSION = 259;
+
+    /**
+     * A value to be used with the "Compression" tag.
+     *
+     * @see #TAG_COMPRESSION
+     */
+    public static final int COMPRESSION_NONE = 1;
+
+    /**
+     * A value to be used with the "Compression" tag.
+     *
+     * @see #TAG_COMPRESSION
+     */
+    public static final int COMPRESSION_CCITT_RLE = 2;
+
+    /**
+     * A value to be used with the "Compression" tag.
+     *
+     * @see #TAG_COMPRESSION
+     */
+    public static final int COMPRESSION_CCITT_T_4 = 3;
+
+    /**
+     * A value to be used with the "Compression" tag.
+     *
+     * @see #TAG_COMPRESSION
+     */
+    public static final int COMPRESSION_CCITT_T_6 = 4;
+
+    /**
+     * A value to be used with the "Compression" tag.
+     *
+     * @see #TAG_COMPRESSION
+     */
+    public static final int COMPRESSION_LZW = 5;
+
+    /**
+     * A value to be used with the "Compression" tag.
+     *
+     * @see #TAG_COMPRESSION
+     */
+    public static final int COMPRESSION_OLD_JPEG = 6;
+
+    /**
+     * A value to be used with the "Compression" tag.
+     *
+     * @see #TAG_COMPRESSION
+     * @see <a href="http://partners.adobe.com/public/developer/en/tiff/TIFFphotoshop.pdf">TIFF Specification Supplement 2</a>
+     */
+    public static final int COMPRESSION_JPEG = 7;
+
+    /**
+     * A value to be used with the "Compression" tag.
+     *
+     * @see #TAG_COMPRESSION
+     * @see <a href="http://partners.adobe.com/public/developer/en/tiff/TIFFphotoshop.pdf"> TIFF Specification Supplement 2</a>
+     */
+    public static final int COMPRESSION_ZLIB = 8;
+
+    /**
+     * A value to be used with the "Compression" tag.
+     *
+     * @see #TAG_COMPRESSION
+     */
+    public static final int COMPRESSION_PACKBITS = 32773;
+
+    /**
+     * A value to be used with the "Compression" tag.
+     *
+     * @see #TAG_COMPRESSION
+     * @see <a href="http://www.isi.edu/in-notes/rfc1951.txt">DEFLATE specification</a>
+     * @see <a href="http://partners.adobe.com/public/developer/en/tiff/TIFFphotoshop.pdf"> TIFF Specification Supplement 2</a>
+     */
+    public static final int COMPRESSION_DEFLATE = 32946;
+
+    /**
+     * Constant specifying the "PhotometricInterpretation" tag.
+     *
+     * @see #PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO
+     * @see #PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO
+     * @see #PHOTOMETRIC_INTERPRETATION_RGB
+     * @see #PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR
+     * @see #PHOTOMETRIC_INTERPRETATION_TRANSPARENCY_MASK
+     * @see #PHOTOMETRIC_INTERPRETATION_Y_CB_CR
+     * @see #PHOTOMETRIC_INTERPRETATION_CIELAB
+     * @see #PHOTOMETRIC_INTERPRETATION_ICCLAB
+     */
+    public static final int TAG_PHOTOMETRIC_INTERPRETATION = 262;
+
+    /**
+     * A value to be used with the "PhotometricInterpretation" tag.
+     *
+     * @see #TAG_PHOTOMETRIC_INTERPRETATION
+     */
+    public static final int PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO = 0;
+
+    /**
+     * A value to be used with the "PhotometricInterpretation" tag.
+     *
+     * @see #TAG_PHOTOMETRIC_INTERPRETATION
+     */
+    public static final int PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO = 1;
+
+    /**
+     * A value to be used with the "PhotometricInterpretation" tag.
+     *
+     * @see #TAG_PHOTOMETRIC_INTERPRETATION
+     */
+    public static final int PHOTOMETRIC_INTERPRETATION_RGB = 2;
+
+    /**
+     * A value to be used with the "PhotometricInterpretation" tag.
+     *
+     * @see #TAG_PHOTOMETRIC_INTERPRETATION
+     */
+    public static final int PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR = 3;
+
+    /**
+     * A value to be used with the "PhotometricInterpretation" tag.
+     *
+     * @see #TAG_PHOTOMETRIC_INTERPRETATION
+     */
+    public static final int PHOTOMETRIC_INTERPRETATION_TRANSPARENCY_MASK = 4;
+
+    /**
+     * A value to be used with the "PhotometricInterpretation" tag.
+     *
+     * @see #TAG_PHOTOMETRIC_INTERPRETATION
+     */
+    public static final int PHOTOMETRIC_INTERPRETATION_CMYK = 5;
+
+    /**
+     * A value to be used with the "PhotometricInterpretation" tag.
+     *
+     * @see #TAG_PHOTOMETRIC_INTERPRETATION
+     */
+    public static final int PHOTOMETRIC_INTERPRETATION_Y_CB_CR = 6;
+
+    /**
+     * A value to be used with the "PhotometricInterpretation" tag.
+     *
+     * @see #TAG_PHOTOMETRIC_INTERPRETATION
+     */
+    public static final int PHOTOMETRIC_INTERPRETATION_CIELAB = 8;
+
+    /**
+     * A value to be used with the "PhotometricInterpretation" tag.
+     *
+     * @see #TAG_PHOTOMETRIC_INTERPRETATION
+     * @see <a href="http://partners.adobe.com/public/developer/en/tiff/TIFFPM6.pdf">TIFF Specification Supplement 1</a>
+     */
+    public static final int PHOTOMETRIC_INTERPRETATION_ICCLAB = 9;
+
+    /**
+     * Constant specifying the "Threshholding" tag.
+     *
+     * @see #THRESHHOLDING_NONE
+     * @see #THRESHHOLDING_ORDERED_DITHER
+     * @see #THRESHHOLDING_RANDOMIZED_DITHER
+     */
+    public static final int TAG_THRESHHOLDING = 263;
+
+    /**
+     * A value to be used with the "Thresholding" tag.
+     *
+     * @see #TAG_THRESHHOLDING
+     */
+    public static final int THRESHHOLDING_NONE = 1;
+
+    /**
+     * A value to be used with the "Thresholding" tag.
+     *
+     * @see #TAG_THRESHHOLDING
+     */
+    public static final int THRESHHOLDING_ORDERED_DITHER = 2;
+
+    /**
+     * A value to be used with the "Thresholding" tag.
+     *
+     * @see #TAG_THRESHHOLDING
+     */
+    public static final int THRESHHOLDING_RANDOMIZED_DITHER = 3;
+
+    /**
+     * Constant specifying the "Cell_Width" tag.
+     */
+    public static final int TAG_CELL_WIDTH = 264;
+
+    /**
+     * Constant specifying the "cell_length" tag.
+     */
+    public static final int TAG_CELL_LENGTH = 265;
+
+    /**
+     * Constant specifying the "fill_order" tag.
+     *
+     * @see #FILL_ORDER_LEFT_TO_RIGHT
+     * @see #FILL_ORDER_RIGHT_TO_LEFT
+     */
+    public static final int TAG_FILL_ORDER = 266;
+
+    /**
+     * A value to be used with the "FillOrder" tag.
+     *
+     * @see #TAG_FILL_ORDER
+     */
+    public static final int FILL_ORDER_LEFT_TO_RIGHT = 1;
+
+    /**
+     * A value to be used with the "FillOrder" tag.
+     *
+     * @see #TAG_FILL_ORDER
+     */
+    public static final int FILL_ORDER_RIGHT_TO_LEFT = 2;
+
+    /**
+     * Constant specifying the "document_name" tag.
+     */
+    public static final int TAG_DOCUMENT_NAME = 269;
+
+    /**
+     * Constant specifying the "Image_description" tag.
+     */
+    public static final int TAG_IMAGE_DESCRIPTION = 270;
+
+    /**
+     * Constant specifying the "Make" tag.
+     */
+    public static final int TAG_MAKE = 271;
+
+    /**
+     * Constant specifying the "Model" tag.
+     */
+    public static final int TAG_MODEL = 272;
+
+    /**
+     * Constant specifying the "Strip_offsets" tag.
+     */
+    public static final int TAG_STRIP_OFFSETS = 273;
+
+    /**
+     * Constant specifying the "Orientation" tag.
+     *
+     * @see #ORIENTATION_ROW_0_TOP_COLUMN_0_LEFT
+     * @see #ORIENTATION_ROW_0_TOP_COLUMN_0_RIGHT
+     * @see #ORIENTATION_ROW_0_BOTTOM_COLUMN_0_RIGHT
+     * @see #ORIENTATION_ROW_0_BOTTOM_COLUMN_0_LEFT
+     * @see #ORIENTATION_ROW_0_LEFT_COLUMN_0_TOP
+     * @see #ORIENTATION_ROW_0_RIGHT_COLUMN_0_TOP
+     * @see #ORIENTATION_ROW_0_RIGHT_COLUMN_0_BOTTOM
+     * @see #ORIENTATION_ROW_0_LEFT_COLUMN_0_BOTTOM
+     */
+    public static final int TAG_ORIENTATION = 274;
+
+    /**
+     * A value to be used with the "Orientation" tag.
+     *
+     * @see #TAG_ORIENTATION
+     */
+    public static final int ORIENTATION_ROW_0_TOP_COLUMN_0_LEFT = 1;
+
+    /**
+     * A value to be used with the "Orientation" tag.
+     *
+     * @see #TAG_ORIENTATION
+     */
+    public static final int ORIENTATION_ROW_0_TOP_COLUMN_0_RIGHT = 2;
+
+    /**
+     * A value to be used with the "Orientation" tag.
+     *
+     * @see #TAG_ORIENTATION
+     */
+    public static final int ORIENTATION_ROW_0_BOTTOM_COLUMN_0_RIGHT = 3;
+
+    /**
+     * A value to be used with the "Orientation" tag.
+     *
+     * @see #TAG_ORIENTATION
+     */
+    public static final int ORIENTATION_ROW_0_BOTTOM_COLUMN_0_LEFT = 4;
+
+    /**
+     * A value to be used with the "Orientation" tag.
+     *
+     * @see #TAG_ORIENTATION
+     */
+    public static final int ORIENTATION_ROW_0_LEFT_COLUMN_0_TOP = 5;
+
+    /**
+     * A value to be used with the "Orientation" tag.
+     *
+     * @see #TAG_ORIENTATION
+     */
+    public static final int ORIENTATION_ROW_0_RIGHT_COLUMN_0_TOP = 6;
+
+    /**
+     * A value to be used with the "Orientation" tag.
+     *
+     * @see #TAG_ORIENTATION
+     */
+    public static final int ORIENTATION_ROW_0_RIGHT_COLUMN_0_BOTTOM = 7;
+
+    /**
+     * A value to be used with the "Orientation" tag.
+     *
+     * @see #TAG_ORIENTATION
+     */
+    public static final int ORIENTATION_ROW_0_LEFT_COLUMN_0_BOTTOM = 8;
+
+    /**
+     * Constant specifying the "Samples_per_pixel" tag.
+     */
+    public static final int TAG_SAMPLES_PER_PIXEL = 277;
+
+    /**
+     * Constant specifying the "Rows_per_strip" tag.
+     */
+    public static final int TAG_ROWS_PER_STRIP = 278;
+
+    /**
+     * Constant specifying the "Strip_byte_counts" tag.
+     */
+    public static final int TAG_STRIP_BYTE_COUNTS = 279;
+
+    /**
+     * Constant specifying the "Min_sample_value" tag.
+     */
+    public static final int TAG_MIN_SAMPLE_VALUE = 280;
+
+    /**
+     * Constant specifying the "Max_sample_value" tag.
+     */
+    public static final int TAG_MAX_SAMPLE_VALUE = 281;
+
+    /**
+     * Constant specifying the "XResolution" tag.
+     */
+    public static final int TAG_X_RESOLUTION = 282;
+
+    /**
+     * Constant specifying the "YResolution" tag.
+     */
+    public static final int TAG_Y_RESOLUTION = 283;
+
+    /**
+     * Constant specifying the "PlanarConfiguration" tag.
+     *
+     * @see #PLANAR_CONFIGURATION_CHUNKY
+     * @see #PLANAR_CONFIGURATION_PLANAR
+     */
+    public static final int TAG_PLANAR_CONFIGURATION = 284;
+
+    /**
+     * A value to be used with the "PlanarConfiguration" tag.
+     *
+     * @see #TAG_PLANAR_CONFIGURATION
+     */
+    public static final int PLANAR_CONFIGURATION_CHUNKY = 1;
+
+    /**
+     * A value to be used with the "PlanarConfiguration" tag.
+     *
+     * @see #TAG_PLANAR_CONFIGURATION
+     */
+    public static final int PLANAR_CONFIGURATION_PLANAR = 2;
+
+    /**
+     * Constant specifying the "PageName" tag.
+     */
+    public static final int TAG_PAGE_NAME = 285;
+
+    /**
+     * Constant specifying the "XPosition" tag.
+     */
+    public static final int TAG_X_POSITION = 286;
+
+    /**
+     * Constant specifying the "YPosition" tag.
+     */
+    public static final int TAG_Y_POSITION = 287;
+
+    /**
+     * Constant specifying the "FreeOffsets" tag.
+     */
+    public static final int TAG_FREE_OFFSETS = 288;
+
+    /**
+     * Constant specifying the "FreeByteCounts" tag.
+     */
+    public static final int TAG_FREE_BYTE_COUNTS = 289;
+
+    /**
+     * Constant specifying the "GrayResponseUnit" tag.
+     *
+     * @see #GRAY_RESPONSE_UNIT_TENTHS
+     * @see #GRAY_RESPONSE_UNIT_HUNDREDTHS
+     * @see #GRAY_RESPONSE_UNIT_THOUSANDTHS
+     * @see #GRAY_RESPONSE_UNIT_TEN_THOUSANDTHS
+     * @see #GRAY_RESPONSE_UNIT_HUNDRED_THOUSANDTHS
+     */
+    public static final int TAG_GRAY_RESPONSE_UNIT = 290;
+
+    /**
+     * A value to be used with the "GrayResponseUnit" tag.
+     *
+     * @see #TAG_GRAY_RESPONSE_UNIT
+     */
+    public static final int GRAY_RESPONSE_UNIT_TENTHS = 1;
+
+    /**
+     * A value to be used with the "GrayResponseUnit" tag.
+     *
+     * @see #TAG_GRAY_RESPONSE_UNIT
+     */
+    public static final int GRAY_RESPONSE_UNIT_HUNDREDTHS = 2;
+
+    /**
+     * A value to be used with the "GrayResponseUnit" tag.
+     *
+     * @see #TAG_GRAY_RESPONSE_UNIT
+     */
+    public static final int GRAY_RESPONSE_UNIT_THOUSANDTHS = 3;
+
+    /**
+     * A value to be used with the "GrayResponseUnit" tag.
+     *
+     * @see #TAG_GRAY_RESPONSE_UNIT
+     */
+    public static final int GRAY_RESPONSE_UNIT_TEN_THOUSANDTHS = 4;
+
+    /**
+     * A value to be used with the "GrayResponseUnit" tag.
+     *
+     * @see #TAG_GRAY_RESPONSE_UNIT
+     */
+    public static final int GRAY_RESPONSE_UNIT_HUNDRED_THOUSANDTHS = 5;
+
+    /**
+     * Constant specifying the "GrayResponseCurve" tag.
+     */
+    public static final int TAG_GRAY_RESPONSE_CURVE = 291;
+
+    /**
+     * Constant specifying the "T4Options" tag.
+     *
+     * @see #T4_OPTIONS_2D_CODING
+     * @see #T4_OPTIONS_UNCOMPRESSED
+     * @see #T4_OPTIONS_EOL_BYTE_ALIGNED
+     */
+    public static final int TAG_T4_OPTIONS = 292;
+
+    /**
+     * A mask to be used with the "T4Options" tag.
+     *
+     * @see #TAG_T4_OPTIONS
+     */
+    public static final int T4_OPTIONS_2D_CODING = 1;
+
+    /**
+     * A mask to be used with the "T4Options" tag.
+     *
+     * @see #TAG_T4_OPTIONS
+     */
+    public static final int T4_OPTIONS_UNCOMPRESSED = 2;
+
+    /**
+     * A mask to be used with the "T4Options" tag.
+     *
+     * @see #TAG_T4_OPTIONS
+     */
+    public static final int T4_OPTIONS_EOL_BYTE_ALIGNED = 4;
+
+    /**
+     * Constant specifying the "T6Options" tag.
+     *
+     * @see #T6_OPTIONS_UNCOMPRESSED
+     */
+    public static final int TAG_T6_OPTIONS = 293;
+
+    /**
+     * A mask to be used with the "T6Options" tag.
+     *
+     * @see #TAG_T6_OPTIONS
+     */
+    public static final int T6_OPTIONS_UNCOMPRESSED = 2;
+
+    /**
+     * Constant specifying the "ResolutionUnit" tag.
+     *
+     * @see #RESOLUTION_UNIT_NONE
+     * @see #RESOLUTION_UNIT_INCH
+     * @see #RESOLUTION_UNIT_CENTIMETER
+     */
+    public static final int TAG_RESOLUTION_UNIT = 296;
+
+    /**
+     * A value to be used with the "ResolutionUnit" tag.
+     *
+     * @see #TAG_RESOLUTION_UNIT
+     */
+    public static final int RESOLUTION_UNIT_NONE = 1;
+
+    /**
+     * A value to be used with the "ResolutionUnit" tag.
+     *
+     * @see #TAG_RESOLUTION_UNIT
+     */
+    public static final int RESOLUTION_UNIT_INCH = 2;
+
+    /**
+     * A value to be used with the "ResolutionUnit" tag.
+     *
+     * @see #TAG_RESOLUTION_UNIT
+     */
+    public static final int RESOLUTION_UNIT_CENTIMETER = 3;
+
+
+    /**
+     * Constant specifying the "PageNumber" tag.
+     */
+    public static final int TAG_PAGE_NUMBER = 297;
+
+    /**
+     * Constant specifying the "TransferFunction" tag.
+     */
+    public static final int TAG_TRANSFER_FUNCTION = 301;
+
+    /**
+     * Constant specifying the "Software" tag.
+     */
+    public static final int TAG_SOFTWARE = 305;
+
+    /**
+     * Constant specifying the "DateTime" tag.
+     */
+    public static final int TAG_DATE_TIME = 306;
+
+    /**
+     * Constant specifying the "Artist" tag.
+     */
+    public static final int TAG_ARTIST = 315;
+
+    /**
+     * Constant specifying the "HostComputer" tag.
+     */
+    public static final int TAG_HOST_COMPUTER = 316;
+
+    /**
+     * Constant specifying the "Predictor" tag.
+     *
+     * @see #TAG_WHITE_POINT
+     * @see #TAG_PRIMARY_CHROMATICITES
+     * @see #TAG_COLOR_MAP
+     * @see #TAG_HALFTONE_HINTS
+     * @see #TAG_TILE_WIDTH
+     * @see #TAG_TILE_LENGTH
+     * @see #TAG_TILE_OFFSETS
+     * @see #TAG_TILE_BYTE_COUNTS
+     */
+    public static final int TAG_PREDICTOR = 317;
+
+    /**
+     * A value to be used with the "Predictor" tag.
+     *
+     * @see #TAG_PREDICTOR
+     */
+    public static final int PREDICTOR_NONE = 1;
+
+    /**
+     * A value to be used with the "Predictor" tag.
+     *
+     * @see #TAG_PREDICTOR
+     */
+    public static final int PREDICTOR_HORIZONTAL_DIFFERENCING = 2;
+
+    /**
+     * Constant specifying the "WhitePoint" tag.
+     */
+    public static final int TAG_WHITE_POINT = 318;
+
+    /**
+     * Constant specifying the "PrimaryChromaticites" tag.
+     */
+    public static final int TAG_PRIMARY_CHROMATICITES = 319;
+
+    /**
+     * Constant specifying the "ColorMap" tag.
+     */
+    public static final int TAG_COLOR_MAP = 320;
+
+    /**
+     * Constant specifying the "HalftoneHints" tag.
+     */
+    public static final int TAG_HALFTONE_HINTS = 321;
+
+    /**
+     * Constant specifying the "TileWidth" tag.
+     */
+    public static final int TAG_TILE_WIDTH = 322;
+
+    /**
+     * Constant specifying the "TileLength" tag.
+     */
+    public static final int TAG_TILE_LENGTH = 323;
+
+    /**
+     * Constant specifying the "TileOffsets" tag.
+     */
+    public static final int TAG_TILE_OFFSETS = 324;
+
+    /**
+     * Constant specifying the "TileByteCounts" tag.
+     */
+    public static final int TAG_TILE_BYTE_COUNTS = 325;
+
+    /**
+     * Constant specifying the "InkSet" tag.
+     *
+     * @see #INK_SET_CMYK
+     * @see #INK_SET_NOT_CMYK
+     */
+    public static final int TAG_INK_SET = 332;
+
+    /**
+     * A value to be used with the "InkSet" tag.
+     *
+     * @see #TAG_INK_SET
+     */
+    public static final int INK_SET_CMYK = 1;
+
+    /**
+     * A value to be used with the "InkSet" tag.
+     *
+     * @see #TAG_INK_SET
+     */
+    public static final int INK_SET_NOT_CMYK = 2;
+
+    /**
+     * Constant specifying the "InkNames" tag.
+     */
+    public static final int TAG_INK_NAMES = 333;
+
+    /**
+     * Constant specifying the "NumberOfInks" tag.
+     */
+    public static final int TAG_NUMBER_OF_INKS = 334;
+
+    /**
+     * Constant specifying the "DotRange" tag.
+     */
+    public static final int TAG_DOT_RANGE = 336;
+
+    /**
+     * Constant specifying the "TargetPrinter" tag.
+     */
+    public static final int TAG_TARGET_PRINTER = 337;
+
+    /**
+     * Constant specifying the "ExtraSamples" tag.
+     *
+     * @see #EXTRA_SAMPLES_UNSPECIFIED
+     * @see #EXTRA_SAMPLES_ASSOCIATED_ALPHA
+     * @see #EXTRA_SAMPLES_UNASSOCIATED_ALPHA
+     */
+    public static final int TAG_EXTRA_SAMPLES = 338;
+
+    /**
+     * A value to be used with the "ExtraSamples" tag.
+     *
+     * @see #TAG_EXTRA_SAMPLES
+     */
+    public static final int EXTRA_SAMPLES_UNSPECIFIED = 0;
+
+    /**
+     * A value to be used with the "ExtraSamples" tag.
+     *
+     * @see #TAG_EXTRA_SAMPLES
+     */
+    public static final int EXTRA_SAMPLES_ASSOCIATED_ALPHA = 1;
+
+    /**
+     * A value to be used with the "ExtraSamples" tag.
+     *
+     * @see #TAG_EXTRA_SAMPLES
+     */
+    public static final int EXTRA_SAMPLES_UNASSOCIATED_ALPHA = 2;
+
+    /**
+     * Constant specifying the "SampleFormat" tag.
+     *
+     * @see #SAMPLE_FORMAT_UNSIGNED_INTEGER
+     * @see #SAMPLE_FORMAT_SIGNED_INTEGER
+     * @see #SAMPLE_FORMAT_FLOATING_POINT
+     * @see #SAMPLE_FORMAT_UNDEFINED
+     */
+    public static final int TAG_SAMPLE_FORMAT = 339;
+
+    /**
+     * A value to be used with the "SampleFormat" tag.
+     *
+     * @see #TAG_SAMPLE_FORMAT
+     */
+    public static final int SAMPLE_FORMAT_UNSIGNED_INTEGER = 1;
+
+    /**
+     * A value to be used with the "SampleFormat" tag.
+     *
+     * @see #TAG_SAMPLE_FORMAT
+     */
+    public static final int SAMPLE_FORMAT_SIGNED_INTEGER = 2;
+
+    /**
+     * A value to be used with the "SampleFormat" tag.
+     *
+     * @see #TAG_SAMPLE_FORMAT
+     */
+    public static final int SAMPLE_FORMAT_FLOATING_POINT = 3;
+
+    /**
+     * A value to be used with the "SampleFormat" tag.
+     *
+     * @see #TAG_SAMPLE_FORMAT
+     */
+    public static final int SAMPLE_FORMAT_UNDEFINED = 4;
+
+    /**
+     * Constant specifying the "SMinSampleValue" tag.
+     */
+    public static final int TAG_S_MIN_SAMPLE_VALUE = 340;
+
+    /**
+     * Constant specifying the "SMaxSampleValue" tag.
+     */
+    public static final int TAG_S_MAX_SAMPLE_VALUE = 341;
+
+    /**
+     * Constant specifying the "TransferRange" tag.
+     */
+    public static final int TAG_TRANSFER_RANGE = 342;
+
+    /**
+     * Constant specifying the "JPEGTables" tag.
+     *
+     * @see <a href="http://partners.adobe.com/public/developer/en/tiff/TIFFphotoshop.pdf">TIFF Specification Supplement 2</a>
+     * @see <a href="ftp://ftp.sgi.com/graphics/tiff/TTN2.draft.txt">JPEG-in-TIFF compression</a>
+     */
+    public static final int TAG_JPEG_TABLES = 347;
+
+    /**
+     * Constant specifying the "JPEGProc" tag.
+     */
+    public static final int TAG_JPEG_PROC = 512;
+
+    /**
+     * A value to be used with the "JPEGProc" tag.
+     *
+     * @see #TAG_JPEG_PROC
+     */
+    public static final int JPEG_PROC_BASELINE = 1;
+
+    /**
+     * A value to be used with the "JPEGProc" tag.
+     *
+     * @see #TAG_JPEG_PROC
+     */
+    public static final int JPEG_PROC_LOSSLESS = 14;
+
+    /**
+     * Constant specifying the "JPEGInterchangeFormat" tag.
+     */
+    public static final int TAG_JPEG_INTERCHANGE_FORMAT = 513;
+
+    /**
+     * Constant specifying the "JPEGInterchangeFormatLength" tag.
+     */
+    public static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514;
+
+    /**
+     * Constant specifying the "JPEGRestartInterval" tag.
+     */
+    public static final int TAG_JPEG_RESTART_INTERVAL = 515;
+
+    /**
+     * Constant specifying the "JPEGLosslessPredictors" tag.
+     */
+    public static final int TAG_JPEG_LOSSLESS_PREDICTORS = 517;
+
+    /**
+     * Constant specifying the "JPEGPointTransforms" tag.
+     */
+    public static final int TAG_JPEG_POINT_TRANSFORMS = 518;
+
+    /**
+     * Constant specifying the "JPEGQTables" tag.
+     */
+    public static final int TAG_JPEG_Q_TABLES = 519;
+
+    /**
+     * Constant specifying the "JPEGDCTables" tag.
+     */
+    public static final int TAG_JPEG_DC_TABLES = 520;
+
+    /**
+     * Constant specifying the "JPEGACTables" tag.
+     */
+    public static final int TAG_JPEG_AC_TABLES = 521;
+
+    /**
+     * Constant specifying the "YCbCrCoefficients" tag.
+     */
+    public static final int TAG_Y_CB_CR_COEFFICIENTS = 529;
+
+    /**
+     * Constant specifying the "YCbCrSubsampling" tag.
+     */
+    public static final int TAG_Y_CB_CR_SUBSAMPLING = 530;
+
+    /**
+     * Constant specifying the "YCbCrPositioning" tag.
+     *
+     * @see #Y_CB_CR_POSITIONING_CENTERED
+     * @see #Y_CB_CR_POSITIONING_COSITED
+     */
+    public static final int TAG_Y_CB_CR_POSITIONING = 531;
+
+    /**
+     * A value to be used with the "YCbCrPositioning" tag.
+     *
+     * @see #TAG_Y_CB_CR_POSITIONING
+     */
+    public static final int Y_CB_CR_POSITIONING_CENTERED = 1;
+
+    /**
+     * A value to be used with the "YCbCrPositioning" tag.
+     *
+     * @see #TAG_Y_CB_CR_POSITIONING
+     */
+    public static final int Y_CB_CR_POSITIONING_COSITED = 2;
+
+    /**
+     * Constant specifying the "ReferenceBlackWhite" tag.
+     */
+    public static final int TAG_REFERENCE_BLACK_WHITE = 532;
+
+    /**
+     * Constant specifying the "Copyright" tag.
+     */
+    public static final int TAG_COPYRIGHT = 33432;
+
+    // Common non-baseline tags
+
+    // ICC profiles (Spec ICC 1:2001-04, Appendix B)
+
+    // 34675 - Embedded ICC Profile               (UNDEFINED/any)
+
+    /**
+     * Constant specifying the "ICC Profile" tag.
+     *
+     * @see <a href="http://www.color.org/ICC1V42.pdf">ICC Specification, section B.4: Embedding ICC profiles in TIFF files</a>
+     */
+    public static final int TAG_ICC_PROFILE = 34675;
+
+    // Artist
+
+    static class Artist extends TIFFTag {
+
+        public Artist() {
+            super("Artist",
+                  TAG_ARTIST,
+                  1 << TIFF_ASCII);
+        }
+    }
+
+    // BitsPerSample
+
+    static class BitsPerSample extends TIFFTag {
+
+        public BitsPerSample() {
+            super("BitsPerSample",
+                  TAG_BITS_PER_SAMPLE,
+                  1 << TIFF_SHORT);
+        }
+    }
+
+    // CellLength
+
+    static class CellLength extends TIFFTag {
+
+        public CellLength() {
+            super("CellLength",
+                  TAG_CELL_LENGTH,
+                  1 << TIFF_SHORT,
+                  1);
+        }
+    }
+
+    // CellWidth tag
+
+    static class CellWidth extends TIFFTag {
+
+        public CellWidth() {
+            super("CellWidth",
+                  TAG_CELL_WIDTH,
+                  1 << TIFF_SHORT,
+                  1);
+        }
+    }
+
+    // ColorMap
+
+    static class ColorMap extends TIFFTag {
+
+        public ColorMap() {
+            super("ColorMap",
+                  TAG_COLOR_MAP,
+                  1 << TIFF_SHORT);
+        }
+    }
+
+    // Compression
+
+    static class Compression extends TIFFTag {
+
+        public Compression() {
+            super("Compression",
+                  TAG_COMPRESSION,
+                  1 << TIFF_SHORT,
+                  1);
+
+            addValueName(COMPRESSION_NONE, "Uncompressed");
+            addValueName(COMPRESSION_CCITT_RLE, "CCITT RLE");
+            addValueName(COMPRESSION_CCITT_T_4, "CCITT T.4");
+            addValueName(COMPRESSION_CCITT_T_6, "CCITT T.6");
+            addValueName(COMPRESSION_LZW, "LZW");
+            addValueName(COMPRESSION_OLD_JPEG, "Old JPEG");
+            addValueName(COMPRESSION_JPEG, "JPEG");
+            addValueName(COMPRESSION_ZLIB, "ZLib");
+            addValueName(COMPRESSION_PACKBITS, "PackBits");
+            addValueName(COMPRESSION_DEFLATE, "Deflate"); // Non-baseline
+
+            // 32771 CCITT
+            // 32809 ThunderScan
+            // 32766 NeXT
+            // 32909 Pixar
+            // 34676 SGI
+            // 34677 SGI
+        }
+    }
+
+    // Copyright
+
+    static class Copyright extends TIFFTag {
+
+        public Copyright() {
+            super("Copyright",
+                  TAG_COPYRIGHT,
+                  1 << TIFF_ASCII);
+        }
+    }
+
+    // DateTime
+
+    static class DateTime extends TIFFTag {
+
+        public DateTime() {
+            super("DateTime",
+                  TAG_DATE_TIME,
+                  1 << TIFF_ASCII,
+                  20);
+        }
+    }
+
+    // DocumentName
+
+    static class DocumentName extends TIFFTag {
+
+        public DocumentName() {
+            super("DocumentName",
+                  TAG_DOCUMENT_NAME,
+                  1 << TIFF_ASCII);
+        }
+    }
+
+    // DotRange
+
+    static class DotRange extends TIFFTag {
+
+        public DotRange() {
+            super("DotRange",
+                  TAG_DOT_RANGE,
+                  (1 << TIFF_BYTE) |
+                  (1 << TIFF_SHORT));
+        }
+    }
+
+    // ExtraSamples
+
+    static class ExtraSamples extends TIFFTag {
+
+        public ExtraSamples() {
+            super("ExtraSamples",
+                  TAG_EXTRA_SAMPLES,
+                  1 << TIFF_SHORT);
+
+            addValueName(EXTRA_SAMPLES_UNSPECIFIED,
+                         "Unspecified");
+            addValueName(EXTRA_SAMPLES_ASSOCIATED_ALPHA,
+                         "Associated Alpha");
+            addValueName(EXTRA_SAMPLES_UNASSOCIATED_ALPHA,
+                         "Unassociated Alpha");
+        }
+    }
+
+    // FillOrder
+
+    static class FillOrder extends TIFFTag {
+
+        public FillOrder() {
+            super("FillOrder",
+                  TAG_FILL_ORDER,
+                  1 << TIFF_SHORT,
+                  1);
+
+            addValueName(FILL_ORDER_LEFT_TO_RIGHT, "LeftToRight");
+            addValueName(FILL_ORDER_RIGHT_TO_LEFT, "RightToLeft");
+        }
+    }
+
+    // FreeByteCounts
+
+    static class FreeByteCounts extends TIFFTag {
+
+        public FreeByteCounts() {
+            super("FreeByteCounts",
+                  TAG_FREE_BYTE_COUNTS,
+                  1 << TIFF_LONG);
+        }
+    }
+
+    // FreeOffsets
+
+    static class FreeOffsets extends TIFFTag {
+
+        public FreeOffsets() {
+            super("FreeOffsets",
+                  TAG_FREE_OFFSETS,
+                  1 << TIFF_LONG);
+        }
+    }
+
+    // GrayResponseCurve
+
+    static class GrayResponseCurve extends TIFFTag {
+
+        public GrayResponseCurve() {
+            super("GrayResponseCurve",
+                  TAG_GRAY_RESPONSE_CURVE,
+                  1 << TIFF_SHORT);
+        }
+    }
+
+    // GrayResponseUnit
+
+    static class GrayResponseUnit extends TIFFTag {
+
+        public GrayResponseUnit() {
+            super("GrayResponseUnit",
+                  TAG_GRAY_RESPONSE_UNIT,
+                  1 << TIFF_SHORT,
+                  1);
+
+            addValueName(GRAY_RESPONSE_UNIT_TENTHS,
+                         "Tenths");
+            addValueName(GRAY_RESPONSE_UNIT_HUNDREDTHS,
+                         "Hundredths");
+            addValueName(GRAY_RESPONSE_UNIT_THOUSANDTHS,
+                         "Thousandths");
+            addValueName(GRAY_RESPONSE_UNIT_TEN_THOUSANDTHS,
+                         "Ten-Thousandths");
+            addValueName(GRAY_RESPONSE_UNIT_HUNDRED_THOUSANDTHS,
+                         "Hundred-Thousandths");
+        }
+    }
+
+    // HalftoneHints
+
+    static class HalftoneHints extends TIFFTag {
+
+        public HalftoneHints() {
+            super("HalftoneHints",
+                  TAG_HALFTONE_HINTS,
+                  1 << TIFF_SHORT,
+                  2);
+        }
+    }
+
+    // HostComputer
+
+    static class HostComputer extends TIFFTag {
+
+        public HostComputer() {
+            super("HostComputer",
+                  TAG_HOST_COMPUTER,
+                  1 << TIFF_ASCII);
+        }
+    }
+
+    // ImageDescription
+
+    static class ImageDescription extends TIFFTag {
+
+        public ImageDescription() {
+            super("ImageDescription",
+                  TAG_IMAGE_DESCRIPTION,
+                  1 << TIFF_ASCII);
+        }
+    }
+
+    // ImageLength tag
+
+    static class ImageLength extends TIFFTag {
+
+        public ImageLength() {
+            super("ImageLength",
+                  TAG_IMAGE_LENGTH,
+                  (1 << TIFF_SHORT) |
+                  (1 << TIFF_LONG),
+                  1);
+        }
+    }
+
+    // ImageWidth tag
+
+    static class ImageWidth extends TIFFTag {
+
+        public ImageWidth() {
+            super("ImageWidth",
+                  TAG_IMAGE_WIDTH,
+                  (1 << TIFF_SHORT) |
+                  (1 << TIFF_LONG),
+                  1);
+        }
+    }
+
+    // InkNames
+
+    static class InkNames extends TIFFTag {
+
+        public InkNames() {
+            super("InkNames",
+                  TAG_INK_NAMES,
+                  1 << TIFF_ASCII);
+        }
+    }
+
+    // InkSet
+
+    static class InkSet extends TIFFTag {
+
+        public InkSet() {
+            super("InkSet",
+                  TAG_INK_SET,
+                  1 << TIFF_SHORT,
+                  1);
+
+            addValueName(INK_SET_CMYK, "CMYK");
+            addValueName(INK_SET_NOT_CMYK, "Not CMYK");
+        }
+    }
+
+    // JPEGTables (Tech note)
+
+    static class JPEGTables extends TIFFTag {
+
+        public JPEGTables() {
+            super("JPEGTables",
+                  TAG_JPEG_TABLES,
+                  1 << TIFF_UNDEFINED);
+        }
+    }
+
+    // JPEGACTables
+
+    static class JPEGACTables extends TIFFTag {
+
+        public JPEGACTables() {
+            super("JPEGACTables",
+                  TAG_JPEG_AC_TABLES,
+                  1 << TIFF_LONG);
+        }
+    }
+
+    // JPEGDCTables
+
+    static class JPEGDCTables extends TIFFTag {
+
+        public JPEGDCTables() {
+            super("JPEGDCTables",
+                  TAG_JPEG_DC_TABLES,
+                  1 << TIFF_LONG);
+        }
+    }
+
+    // JPEGInterchangeFormat
+
+    static class JPEGInterchangeFormat extends TIFFTag {
+
+        public JPEGInterchangeFormat() {
+            super("JPEGInterchangeFormat",
+                  TAG_JPEG_INTERCHANGE_FORMAT,
+                  1 << TIFF_LONG,
+                  1);
+        }
+    }
+
+    // JPEGInterchangeFormatLength
+
+    static class JPEGInterchangeFormatLength extends TIFFTag {
+
+        public JPEGInterchangeFormatLength() {
+            super("JPEGInterchangeFormatLength",
+                  TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
+                  1 << TIFF_LONG,
+                  1);
+        }
+    }
+
+    // JPEGLosslessPredictors
+
+    static class JPEGLosslessPredictors extends TIFFTag {
+
+        public JPEGLosslessPredictors() {
+            super("JPEGLosslessPredictors",
+                  TAG_JPEG_LOSSLESS_PREDICTORS,
+                  1 << TIFF_SHORT);
+
+            addValueName(1, "A");
+            addValueName(2, "B");
+            addValueName(3, "C");
+            addValueName(4, "A+B-C");
+            addValueName(5, "A+((B-C)/2)");
+            addValueName(6, "B+((A-C)/2)");
+            addValueName(7, "(A+B)/2");
+        }
+    }
+
+    // JPEGPointTransforms
+
+    static class JPEGPointTransforms extends TIFFTag {
+
+        public JPEGPointTransforms() {
+            super("JPEGPointTransforms",
+                  TAG_JPEG_POINT_TRANSFORMS,
+                  1 << TIFF_SHORT);
+        }
+    }
+
+    // JPEGProc
+
+    static class JPEGProc extends TIFFTag {
+
+        public JPEGProc() {
+            super("JPEGProc",
+                  TAG_JPEG_PROC,
+                  1 << TIFF_SHORT,
+                  1);
+
+            addValueName(JPEG_PROC_BASELINE, "Baseline sequential process");
+            addValueName(JPEG_PROC_LOSSLESS,
+                         "Lossless process with Huffman coding");
+        }
+    }
+
+    // JPEGQTables
+
+    static class JPEGQTables extends TIFFTag {
+
+        public JPEGQTables() {
+            super("JPEGQTables",
+                  TAG_JPEG_Q_TABLES,
+                  1 << TIFF_LONG);
+        }
+    }
+
+    // JPEGRestartInterval
+
+    static class JPEGRestartInterval extends TIFFTag {
+
+        public JPEGRestartInterval() {
+            super("JPEGRestartInterval",
+                  TAG_JPEG_RESTART_INTERVAL,
+                  1 << TIFF_SHORT,
+                  1);
+        }
+    }
+
+    // Make
+
+    static class Make extends TIFFTag {
+
+        public Make() {
+            super("Make",
+                  TAG_MAKE,
+                  1 << TIFF_ASCII);
+        }
+    }
+
+    // MaxSampleValue
+
+    static class MaxSampleValue extends TIFFTag {
+
+        public MaxSampleValue() {
+            super("MaxSampleValue",
+                  TAG_MAX_SAMPLE_VALUE,
+                  1 << TIFF_SHORT);
+        }
+    }
+
+    // MinSampleValue
+
+    static class MinSampleValue extends TIFFTag {
+
+        public MinSampleValue() {
+            super("MinSampleValue",
+                  TAG_MIN_SAMPLE_VALUE,
+                  1 << TIFF_SHORT);
+        }
+    }
+
+    // Model
+
+    static class Model extends TIFFTag {
+
+        public Model() {
+            super("Model",
+                  TAG_MODEL,
+                  1 << TIFF_ASCII);
+        }
+    }
+
+    // NewSubfileType
+
+    static class NewSubfileType extends TIFFTag {
+
+        public NewSubfileType() {
+            super("NewSubfileType",
+                  TAG_NEW_SUBFILE_TYPE,
+                  1 << TIFF_LONG,
+                  1);
+
+            addValueName(0,
+                         "Default");
+            addValueName(NEW_SUBFILE_TYPE_REDUCED_RESOLUTION,
+                         "ReducedResolution");
+            addValueName(NEW_SUBFILE_TYPE_SINGLE_PAGE,
+                         "SinglePage");
+            addValueName(NEW_SUBFILE_TYPE_SINGLE_PAGE |
+                         NEW_SUBFILE_TYPE_REDUCED_RESOLUTION,
+                         "SinglePage+ReducedResolution");
+            addValueName(NEW_SUBFILE_TYPE_TRANSPARENCY,
+                         "Transparency");
+            addValueName(NEW_SUBFILE_TYPE_TRANSPARENCY |
+                         NEW_SUBFILE_TYPE_REDUCED_RESOLUTION,
+                         "Transparency+ReducedResolution");
+            addValueName(NEW_SUBFILE_TYPE_TRANSPARENCY |
+                         NEW_SUBFILE_TYPE_SINGLE_PAGE,
+                         "Transparency+SinglePage");
+            addValueName(NEW_SUBFILE_TYPE_TRANSPARENCY |
+                         NEW_SUBFILE_TYPE_SINGLE_PAGE |
+                         NEW_SUBFILE_TYPE_REDUCED_RESOLUTION,
+                         "Transparency+SinglePage+ReducedResolution");
+        }
+    }
+
+    // NumberOfInks
+
+    static class NumberOfInks extends TIFFTag {
+
+        public NumberOfInks() {
+            super("NumberOfInks",
+                  TAG_NUMBER_OF_INKS,
+                  1 << TIFF_SHORT,
+                  1);
+        }
+    }
+
+    // Orientation
+
+    static class Orientation extends TIFFTag {
+
+        public Orientation() {
+            super("Orientation",
+                  TAG_ORIENTATION,
+                  1 << TIFF_SHORT,
+                  1);
+
+            addValueName(ORIENTATION_ROW_0_TOP_COLUMN_0_LEFT,
+                         "Row 0=Top, Column 0=Left");
+            addValueName(ORIENTATION_ROW_0_TOP_COLUMN_0_RIGHT,
+                         "Row 0=Top, Column 0=Right");
+            addValueName(ORIENTATION_ROW_0_BOTTOM_COLUMN_0_RIGHT,
+                         "Row 0=Bottom, Column 0=Right");
+            addValueName(ORIENTATION_ROW_0_BOTTOM_COLUMN_0_LEFT,
+                         "Row 0=Bottom, Column 0=Left");
+            addValueName(ORIENTATION_ROW_0_LEFT_COLUMN_0_TOP,
+                         "Row 0=Left, Column 0=Top");
+            addValueName(ORIENTATION_ROW_0_RIGHT_COLUMN_0_TOP,
+                         "Row 0=Right, Column 0=Top");
+            addValueName(ORIENTATION_ROW_0_RIGHT_COLUMN_0_BOTTOM,
+                         "Row 0=Right, Column 0=Bottom");
+        }
+    }
+
+    // PageName
+
+    static class PageName extends TIFFTag {
+
+        public PageName() {
+            super("PageName",
+                  TAG_PAGE_NAME,
+                  1 << TIFF_ASCII);
+        }
+    }
+
+    // PageNumber
+
+    static class PageNumber extends TIFFTag {
+
+        public PageNumber() {
+            super("PageNumber",
+                  TAG_PAGE_NUMBER,
+                  1 << TIFF_SHORT);
+        }
+    }
+
+    // PhotometricInterpretation
+
+    static class PhotometricInterpretation extends TIFFTag {
+
+        public PhotometricInterpretation() {
+            super("PhotometricInterpretation",
+                  TAG_PHOTOMETRIC_INTERPRETATION,
+                  1 << TIFF_SHORT,
+                  1);
+
+            addValueName(PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO,
+                         "WhiteIsZero");
+            addValueName(PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO,
+                         "BlackIsZero");
+            addValueName(PHOTOMETRIC_INTERPRETATION_RGB,
+                         "RGB");
+            addValueName(PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR,
+                         "Palette Color");
+            addValueName(PHOTOMETRIC_INTERPRETATION_TRANSPARENCY_MASK,
+                         "Transparency Mask");
+            addValueName(PHOTOMETRIC_INTERPRETATION_CMYK,
+                         "CMYK");
+            addValueName(PHOTOMETRIC_INTERPRETATION_Y_CB_CR,
+                         "YCbCr");
+            addValueName(PHOTOMETRIC_INTERPRETATION_CIELAB,
+                         "CIELAB");
+            addValueName(PHOTOMETRIC_INTERPRETATION_ICCLAB,
+                         "ICCLAB"); // Non-baseline
+        }
+    }
+
+    // PlanarConfiguration
+
+    static class PlanarConfiguration extends TIFFTag {
+
+        public PlanarConfiguration() {
+            super("PlanarConfiguration",
+                  TAG_PLANAR_CONFIGURATION,
+                  1 << TIFF_SHORT,
+                  1);
+
+            addValueName(PLANAR_CONFIGURATION_CHUNKY, "Chunky");
+            addValueName(PLANAR_CONFIGURATION_PLANAR, "Planar");
+        }
+    }
+
+    // Predictor
+
+    static class Predictor extends TIFFTag {
+
+        public Predictor() {
+            super("Predictor",
+                  TAG_PREDICTOR,
+                  1 << TIFF_SHORT,
+                  1);
+
+            addValueName(PREDICTOR_NONE,
+                         "None");
+            addValueName(PREDICTOR_HORIZONTAL_DIFFERENCING,
+                         "Horizontal Differencing");
+        }
+    }
+
+    // PrimaryChromaticities
+
+    static class PrimaryChromaticities extends TIFFTag {
+
+        public PrimaryChromaticities() {
+            super("PrimaryChromaticities",
+                  TAG_PRIMARY_CHROMATICITES,
+                  1 << TIFF_RATIONAL,
+                  6);
+        }
+    }
+
+    // ReferenceBlackWhite
+
+    static class ReferenceBlackWhite extends TIFFTag {
+
+        public ReferenceBlackWhite() {
+            super("ReferenceBlackWhite",
+                  TAG_REFERENCE_BLACK_WHITE,
+                  1 << TIFF_RATIONAL);
+        }
+    }
+
+    // ResolutionUnit
+
+    static class ResolutionUnit extends TIFFTag {
+
+        public ResolutionUnit() {
+            super("ResolutionUnit",
+                  TAG_RESOLUTION_UNIT,
+                  1 << TIFF_SHORT,
+                  1);
+
+            addValueName(RESOLUTION_UNIT_NONE, "None");
+            addValueName(RESOLUTION_UNIT_INCH, "Inch");
+            addValueName(RESOLUTION_UNIT_CENTIMETER, "Centimeter");
+        }
+    }
+
+    // RowsPerStrip
+
+    static class RowsPerStrip extends TIFFTag {
+
+        public RowsPerStrip() {
+            super("RowsPerStrip",
+                  TAG_ROWS_PER_STRIP,
+                  (1 << TIFF_SHORT) |
+                  (1 << TIFF_LONG),
+                  1);
+        }
+    }
+
+    // SampleFormat
+
+    static class SampleFormat extends TIFFTag {
+
+        public SampleFormat() {
+            super("SampleFormat",
+                  TAG_SAMPLE_FORMAT,
+                  1 << TIFF_SHORT);
+
+            addValueName(SAMPLE_FORMAT_UNSIGNED_INTEGER, "Unsigned Integer");
+            addValueName(SAMPLE_FORMAT_SIGNED_INTEGER, "Signed Integer");
+            addValueName(SAMPLE_FORMAT_FLOATING_POINT, "Floating Point");
+            addValueName(SAMPLE_FORMAT_UNDEFINED, "Undefined");
+        }
+    }
+
+    // SamplesPerPixel
+
+    static class SamplesPerPixel extends TIFFTag {
+
+        public SamplesPerPixel() {
+            super("SamplesPerPixel",
+                  TAG_SAMPLES_PER_PIXEL,
+                  1 << TIFF_SHORT,
+                  1);
+        }
+    }
+
+    // SMaxSampleValue
+
+    static class SMaxSampleValue extends TIFFTag {
+
+        public SMaxSampleValue() {
+            super("SMaxSampleValue",
+                  TAG_S_MAX_SAMPLE_VALUE,
+                  (1 << TIFF_BYTE) |
+                  (1 << TIFF_SHORT) |
+                  (1 << TIFF_LONG) |
+                  (1 << TIFF_RATIONAL) |
+                  (1 << TIFF_SBYTE) |
+                  (1 << TIFF_SSHORT) |
+                  (1 << TIFF_SLONG) |
+                  (1 << TIFF_SRATIONAL) |
+                  (1 << TIFF_FLOAT) |
+                  (1 << TIFF_DOUBLE));
+        }
+    }
+
+    // SMinSampleValue
+
+    static class SMinSampleValue extends TIFFTag {
+
+        public SMinSampleValue() {
+            super("SMinSampleValue",
+                  TAG_S_MIN_SAMPLE_VALUE,
+                  (1 << TIFF_BYTE) |
+                  (1 << TIFF_SHORT) |
+                  (1 << TIFF_LONG) |
+                  (1 << TIFF_RATIONAL) |
+                  (1 << TIFF_SBYTE) |
+                  (1 << TIFF_SSHORT) |
+                  (1 << TIFF_SLONG) |
+                  (1 << TIFF_SRATIONAL) |
+                  (1 << TIFF_FLOAT) |
+                  (1 << TIFF_DOUBLE));
+        }
+    }
+
+    // Software
+
+    static class Software extends TIFFTag {
+
+        public Software() {
+            super("Software",
+                  TAG_SOFTWARE,
+                  1 << TIFF_ASCII);
+        }
+    }
+
+    // StripByteCounts
+
+    static class StripByteCounts extends TIFFTag {
+
+        public StripByteCounts() {
+            super("StripByteCounts",
+                  TAG_STRIP_BYTE_COUNTS,
+                  (1 << TIFF_SHORT) |
+                  (1 << TIFF_LONG));
+        }
+    }
+
+    // StripOffsets
+
+    static class StripOffsets extends TIFFTag {
+
+        public StripOffsets() {
+            super("StripOffsets",
+                  TAG_STRIP_OFFSETS,
+                  (1 << TIFF_SHORT) |
+                  (1 << TIFF_LONG));
+        }
+    }
+
+    // SubfileType (deprecated by TIFF but retained for backward compatibility)
+
+    static class SubfileType extends TIFFTag {
+
+        public SubfileType() {
+            super("SubfileType",
+                  TAG_SUBFILE_TYPE,
+                  1 << TIFF_SHORT,
+                  1);
+
+            addValueName(SUBFILE_TYPE_FULL_RESOLUTION, "FullResolution");
+            addValueName(SUBFILE_TYPE_REDUCED_RESOLUTION, "ReducedResolution");
+            addValueName(SUBFILE_TYPE_SINGLE_PAGE, "SinglePage");
+        }
+    }
+
+    // T4Options
+
+    static class T4Options extends TIFFTag {
+
+        public T4Options() {
+            super("T4Options",
+                  TAG_T4_OPTIONS,
+                  1 << TIFF_LONG,
+                  1);
+
+            addValueName(0,
+                         "Default 1DCoding"); // 0x00
+            addValueName(T4_OPTIONS_2D_CODING,
+                         "2DCoding"); // 0x01
+            addValueName(T4_OPTIONS_UNCOMPRESSED,
+                         "Uncompressed"); // 0x02
+            addValueName(T4_OPTIONS_2D_CODING |
+                         T4_OPTIONS_UNCOMPRESSED,
+                         "2DCoding+Uncompressed"); // 0x03
+            addValueName(T4_OPTIONS_EOL_BYTE_ALIGNED,
+                         "EOLByteAligned"); // 0x04
+            addValueName(T4_OPTIONS_2D_CODING |
+                         T4_OPTIONS_EOL_BYTE_ALIGNED,
+                         "2DCoding+EOLByteAligned"); // 0x05
+            addValueName(T4_OPTIONS_UNCOMPRESSED |
+                         T4_OPTIONS_EOL_BYTE_ALIGNED,
+                         "Uncompressed+EOLByteAligned"); // 0x06
+            addValueName(T4_OPTIONS_2D_CODING |
+                         T4_OPTIONS_UNCOMPRESSED |
+                         T4_OPTIONS_EOL_BYTE_ALIGNED,
+                         "2DCoding+Uncompressed+EOLByteAligned"); // 0x07
+        }
+    }
+
+    // T6Options
+
+    static class T6Options extends TIFFTag {
+
+        public T6Options() {
+            super("T6Options",
+                  TAG_T6_OPTIONS,
+                  1 << TIFF_LONG,
+                  1);
+
+            addValueName(0,
+                         "Default"); // 0x00
+            // 0x01 is not possible as bit 0 is unused and always zero.
+            addValueName(T6_OPTIONS_UNCOMPRESSED,
+                         "Uncompressed"); // 0x02
+        }
+    }
+
+    // TargetPrinter
+
+    static class TargetPrinter extends TIFFTag {
+
+        public TargetPrinter() {
+            super("TargetPrinter",
+                  TAG_TARGET_PRINTER,
+                  1 << TIFF_ASCII);
+        }
+    }
+
+    // Threshholding
+
+    static class Threshholding extends TIFFTag {
+
+        public Threshholding() {
+            super("Threshholding",
+                  TAG_THRESHHOLDING,
+                  1 << TIFF_SHORT,
+                  1);
+
+            addValueName(1, "None");
+            addValueName(2, "OrderedDither");
+            addValueName(3, "RandomizedDither");
+        }
+    }
+
+    // TileByteCounts
+
+    static class TileByteCounts extends TIFFTag {
+
+        public TileByteCounts() {
+            super("TileByteCounts",
+                  TAG_TILE_BYTE_COUNTS,
+                  (1 << TIFF_SHORT) |
+                  (1 << TIFF_LONG));
+        }
+    }
+
+    // TileOffsets
+
+    static class TileOffsets extends TIFFTag {
+
+        public TileOffsets() {
+            super("TileOffsets",
+                  TAG_TILE_OFFSETS,
+                  1 << TIFF_LONG);
+        }
+    }
+
+    // TileLength tag
+
+    static class TileLength extends TIFFTag {
+
+        public TileLength() {
+            super("TileLength",
+                  TAG_TILE_LENGTH,
+                  (1 << TIFF_SHORT) |
+                  (1 << TIFF_LONG),
+                  1);
+        }
+    }
+
+    // TileWidth tag
+
+    static class TileWidth extends TIFFTag {
+
+        public TileWidth() {
+            super("TileWidth",
+                  TAG_TILE_WIDTH,
+                  (1 << TIFF_SHORT) |
+                  (1 << TIFF_LONG),
+                  1);
+        }
+    }
+
+    // TransferFunction
+
+    static class TransferFunction extends TIFFTag {
+
+        public TransferFunction() {
+            super("TransferFunction",
+                  TAG_TRANSFER_FUNCTION,
+                  1 << TIFF_SHORT);
+        }
+    }
+
+    // TransferRange
+
+    static class TransferRange extends TIFFTag {
+
+        public TransferRange() {
+            super("TransferRange",
+                  TAG_TRANSFER_RANGE,
+                  1 << TIFF_SHORT,
+                  6);
+        }
+    }
+
+    // WhitePoint
+
+    static class WhitePoint extends TIFFTag {
+
+        public WhitePoint() {
+            super("WhitePoint",
+                  TAG_WHITE_POINT,
+                  1 << TIFF_RATIONAL,
+                  2);
+        }
+    }
+
+    // XPosition
+
+    static class XPosition extends TIFFTag {
+
+        public XPosition() {
+            super("XPosition",
+                  TAG_X_POSITION,
+                  1 << TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    // XResolution
+
+    static class XResolution extends TIFFTag {
+
+        public XResolution() {
+            super("XResolution",
+                  TAG_X_RESOLUTION,
+                  1 << TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    // YCbCrCoefficients
+
+    static class YCbCrCoefficients extends TIFFTag {
+
+        public YCbCrCoefficients() {
+            super("YCbCrCoefficients",
+                  TAG_Y_CB_CR_COEFFICIENTS,
+                  1 << TIFF_RATIONAL,
+                  3);
+        }
+    }
+
+    // YCbCrPositioning
+
+    static class YCbCrPositioning extends TIFFTag {
+
+        public YCbCrPositioning() {
+            super("YCbCrPositioning",
+                  TAG_Y_CB_CR_POSITIONING,
+                  1 << TIFF_SHORT,
+                  1);
+
+            addValueName(Y_CB_CR_POSITIONING_CENTERED, "Centered");
+            addValueName(Y_CB_CR_POSITIONING_COSITED, "Cosited");
+        }
+    }
+
+    // YCbCrSubSampling
+
+    static class YCbCrSubSampling extends TIFFTag {
+
+        public YCbCrSubSampling() {
+            super("YCbCrSubSampling",
+                  TAG_Y_CB_CR_SUBSAMPLING,
+                  1 << TIFF_SHORT,
+                  2);
+        }
+    }
+
+    // YPosition
+
+    static class YPosition extends TIFFTag {
+
+        public YPosition() {
+            super("YPosition",
+                  TAG_Y_POSITION,
+                  1 << TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    // YResolution
+
+    static class YResolution extends TIFFTag {
+
+        public YResolution() {
+            super("YResolution",
+                  TAG_Y_RESOLUTION,
+                  1 << TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    // Non-6.0 tags
+
+    // ICC Profile (Spec. ICC.1:2001-12, File Format for Color Profiles)
+
+    static class ICCProfile extends TIFFTag {
+
+        public ICCProfile() {
+            super("ICC Profile",
+                  TAG_ICC_PROFILE,
+                  1 << TIFF_UNDEFINED);
+        }
+    }
+
+    private static List<TIFFTag> tags;
+
+    private static void initTags() {
+        tags = new ArrayList<TIFFTag>(76);
+
+        tags.add(new BaselineTIFFTagSet.Artist());
+        tags.add(new BaselineTIFFTagSet.BitsPerSample());
+        tags.add(new BaselineTIFFTagSet.CellLength());
+        tags.add(new BaselineTIFFTagSet.CellWidth());
+        tags.add(new BaselineTIFFTagSet.ColorMap());
+        tags.add(new BaselineTIFFTagSet.Compression());
+        tags.add(new BaselineTIFFTagSet.Copyright());
+        tags.add(new BaselineTIFFTagSet.DateTime());
+        tags.add(new BaselineTIFFTagSet.DocumentName());
+        tags.add(new BaselineTIFFTagSet.DotRange());
+        tags.add(new BaselineTIFFTagSet.ExtraSamples());
+        tags.add(new BaselineTIFFTagSet.FillOrder());
+        tags.add(new BaselineTIFFTagSet.FreeByteCounts());
+        tags.add(new BaselineTIFFTagSet.FreeOffsets());
+        tags.add(new BaselineTIFFTagSet.GrayResponseCurve());
+        tags.add(new BaselineTIFFTagSet.GrayResponseUnit());
+        tags.add(new BaselineTIFFTagSet.HalftoneHints());
+        tags.add(new BaselineTIFFTagSet.HostComputer());
+        tags.add(new BaselineTIFFTagSet.ImageDescription());
+        tags.add(new BaselineTIFFTagSet.ICCProfile());
+        tags.add(new BaselineTIFFTagSet.ImageLength());
+        tags.add(new BaselineTIFFTagSet.ImageWidth());
+        tags.add(new BaselineTIFFTagSet.InkNames());
+        tags.add(new BaselineTIFFTagSet.InkSet());
+        tags.add(new BaselineTIFFTagSet.JPEGACTables());
+        tags.add(new BaselineTIFFTagSet.JPEGDCTables());
+        tags.add(new BaselineTIFFTagSet.JPEGInterchangeFormat());
+        tags.add(new BaselineTIFFTagSet.JPEGInterchangeFormatLength());
+        tags.add(new BaselineTIFFTagSet.JPEGLosslessPredictors());
+        tags.add(new BaselineTIFFTagSet.JPEGPointTransforms());
+        tags.add(new BaselineTIFFTagSet.JPEGProc());
+        tags.add(new BaselineTIFFTagSet.JPEGQTables());
+        tags.add(new BaselineTIFFTagSet.JPEGRestartInterval());
+        tags.add(new BaselineTIFFTagSet.JPEGTables());
+        tags.add(new BaselineTIFFTagSet.Make());
+        tags.add(new BaselineTIFFTagSet.MaxSampleValue());
+        tags.add(new BaselineTIFFTagSet.MinSampleValue());
+        tags.add(new BaselineTIFFTagSet.Model());
+        tags.add(new BaselineTIFFTagSet.NewSubfileType());
+        tags.add(new BaselineTIFFTagSet.NumberOfInks());
+        tags.add(new BaselineTIFFTagSet.Orientation());
+        tags.add(new BaselineTIFFTagSet.PageName());
+        tags.add(new BaselineTIFFTagSet.PageNumber());
+        tags.add(new BaselineTIFFTagSet.PhotometricInterpretation());
+        tags.add(new BaselineTIFFTagSet.PlanarConfiguration());
+        tags.add(new BaselineTIFFTagSet.Predictor());
+        tags.add(new BaselineTIFFTagSet.PrimaryChromaticities());
+        tags.add(new BaselineTIFFTagSet.ReferenceBlackWhite());
+        tags.add(new BaselineTIFFTagSet.ResolutionUnit());
+        tags.add(new BaselineTIFFTagSet.RowsPerStrip());
+        tags.add(new BaselineTIFFTagSet.SampleFormat());
+        tags.add(new BaselineTIFFTagSet.SamplesPerPixel());
+        tags.add(new BaselineTIFFTagSet.SMaxSampleValue());
+        tags.add(new BaselineTIFFTagSet.SMinSampleValue());
+        tags.add(new BaselineTIFFTagSet.Software());
+        tags.add(new BaselineTIFFTagSet.StripByteCounts());
+        tags.add(new BaselineTIFFTagSet.StripOffsets());
+        tags.add(new BaselineTIFFTagSet.SubfileType());
+        tags.add(new BaselineTIFFTagSet.T4Options());
+        tags.add(new BaselineTIFFTagSet.T6Options());
+        tags.add(new BaselineTIFFTagSet.TargetPrinter());
+        tags.add(new BaselineTIFFTagSet.Threshholding());
+        tags.add(new BaselineTIFFTagSet.TileByteCounts());
+        tags.add(new BaselineTIFFTagSet.TileOffsets());
+        tags.add(new BaselineTIFFTagSet.TileLength());
+        tags.add(new BaselineTIFFTagSet.TileWidth());
+        tags.add(new BaselineTIFFTagSet.TransferFunction());
+        tags.add(new BaselineTIFFTagSet.TransferRange());
+        tags.add(new BaselineTIFFTagSet.WhitePoint());
+        tags.add(new BaselineTIFFTagSet.XPosition());
+        tags.add(new BaselineTIFFTagSet.XResolution());
+        tags.add(new BaselineTIFFTagSet.YCbCrCoefficients());
+        tags.add(new BaselineTIFFTagSet.YCbCrPositioning());
+        tags.add(new BaselineTIFFTagSet.YCbCrSubSampling());
+        tags.add(new BaselineTIFFTagSet.YPosition());
+        tags.add(new BaselineTIFFTagSet.YResolution());
+    }
+
+    private BaselineTIFFTagSet() {
+        super(tags);
+    }
+
+    /**
+     * Returns a shared instance of a <code>BaselineTIFFTagSet</code>.
+     *
+     * @return a <code>BaselineTIFFTagSet</code> instance.
+     */
+    public synchronized static BaselineTIFFTagSet getInstance() {
+        if (theInstance == null) {
+            initTags();
+            theInstance = new BaselineTIFFTagSet();
+            tags = null;
+        }
+        return theInstance;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/ExifGPSTagSet.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,724 @@
+/*
+ * 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 javax.imageio.plugins.tiff;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class representing the tags found in an Exif GPS Info IFD.
+ *
+ * <p> The definitions of the data types referenced by the field
+ * definitions may be found in the {@link TIFFTag TIFFTag} class.
+ *
+ * @since 1.9
+ * @see   ExifTIFFTagSet
+ */
+public class ExifGPSTagSet extends TIFFTagSet {
+    private static ExifGPSTagSet theInstance = null;
+
+    /**
+     * A tag indicating the GPS tag version (type BYTE, count = 4).
+     *
+     * @see #GPS_VERSION_2_2
+     */
+    public static final int TAG_GPS_VERSION_ID = 0;
+
+    /**
+     * A value to be used with the "GPSVersionID" tag to indicate GPS version
+     * 2.2.  The value equals the US-ASCII encoding of the byte array
+     * <code>{'2', '2', '0', '0'}</code>.
+     *
+     * @see #TAG_GPS_VERSION_ID
+     */
+    public static final String GPS_VERSION_2_2 =
+        new String(new byte[] { '2', '2', '0', '0' },
+        StandardCharsets.US_ASCII);
+
+    /**
+     * A tag indicating the North or South latitude (type ASCII, count = 2).
+     *
+     * @see #LATITUDE_REF_NORTH
+     * @see #LATITUDE_REF_SOUTH
+     */
+    public static final int TAG_GPS_LATITUDE_REF = 1;
+
+    /**
+     * A tag indicating the Latitude (type RATIONAL, count = 3).
+     */
+    public static final int TAG_GPS_LATITUDE = 2;
+
+    /**
+     * A tag indicating the East or West Longitude (type ASCII, count = 2).
+     *
+     * @see #LONGITUDE_REF_EAST
+     * @see #LONGITUDE_REF_WEST
+     */
+    public static final int TAG_GPS_LONGITUDE_REF = 3;
+
+    /**
+     * A tag indicating the Longitude (type RATIONAL, count = 3).
+     */
+    public static final int TAG_GPS_LONGITUDE = 4;
+
+    /**
+     * A tag indicating the Altitude reference (type BYTE, count = 1);
+     *
+     * @see #ALTITUDE_REF_SEA_LEVEL
+     * @see #ALTITUDE_REF_SEA_LEVEL_REFERENCE
+     */
+    public static final int TAG_GPS_ALTITUDE_REF = 5;
+
+    /**
+     * A tag indicating the Altitude (type RATIONAL, count = 1).
+     */
+    public static final int TAG_GPS_ALTITUDE = 6;
+
+    /**
+     * A tag indicating the GPS time (atomic clock) (type RATIONAL, count = 3).
+     */
+    public static final int TAG_GPS_TIME_STAMP = 7;
+
+    /**
+     * A tag indicating the GPS satellites used for measurement (type ASCII).
+     */
+    public static final int TAG_GPS_SATELLITES = 8;
+
+    /**
+     * A tag indicating the GPS receiver status (type ASCII, count = 2).
+     *
+     * @see #STATUS_MEASUREMENT_IN_PROGRESS
+     * @see #STATUS_MEASUREMENT_INTEROPERABILITY
+     */
+    public static final int TAG_GPS_STATUS = 9;
+
+    /**
+     * A tag indicating the GPS measurement mode (type ASCII, count = 2).
+     *
+     * @see #MEASURE_MODE_2D
+     * @see #MEASURE_MODE_3D
+     */
+    public static final int TAG_GPS_MEASURE_MODE = 10;
+
+    /**
+     * A tag indicating the Measurement precision (type RATIONAL, count = 1).
+     */
+    public static final int TAG_GPS_DOP = 11;
+
+    /**
+     * A tag indicating the Speed unit (type ASCII, count = 2).
+     *
+     * @see #SPEED_REF_KILOMETERS_PER_HOUR
+     * @see #SPEED_REF_MILES_PER_HOUR
+     * @see #SPEED_REF_KNOTS
+     */
+    public static final int TAG_GPS_SPEED_REF = 12;
+
+    /**
+     * A tag indicating the Speed of GPS receiver (type RATIONAL, count = 1).
+     */
+    public static final int TAG_GPS_SPEED = 13;
+
+    /**
+     * A tag indicating the Reference for direction of movement (type ASCII,
+     * count = 2).
+     *
+     * @see #DIRECTION_REF_TRUE
+     * @see #DIRECTION_REF_MAGNETIC
+     */
+    public static final int TAG_GPS_TRACK_REF = 14;
+
+    /**
+     * A tag indicating the Direction of movement (type RATIONAL, count = 1).
+     */
+    public static final int TAG_GPS_TRACK = 15;
+
+    /**
+     * A tag indicating the Reference for direction of image (type ASCII,
+     * count = 2).
+     *
+     * @see #DIRECTION_REF_TRUE
+     * @see #DIRECTION_REF_MAGNETIC
+     */
+    public static final int TAG_GPS_IMG_DIRECTION_REF = 16;
+
+    /**
+     * A tag indicating the Direction of image (type RATIONAL, count = 1).
+     */
+    public static final int TAG_GPS_IMG_DIRECTION = 17;
+
+    /**
+     * A tag indicating the Geodetic survey data used (type ASCII).
+     */
+    public static final int TAG_GPS_MAP_DATUM = 18;
+
+    /**
+     * A tag indicating the Reference for latitude of destination (type
+     * ASCII, count = 2).
+     *
+     * @see #LATITUDE_REF_NORTH
+     * @see #LATITUDE_REF_SOUTH
+     */
+    public static final int TAG_GPS_DEST_LATITUDE_REF = 19;
+
+    /**
+     * A tag indicating the Latitude of destination (type RATIONAL, count = 3).
+     */
+    public static final int TAG_GPS_DEST_LATITUDE = 20;
+
+    /**
+     * A tag indicating the Reference for longitude of destination (type
+     * ASCII, count = 2).
+     *
+     * @see #LONGITUDE_REF_EAST
+     * @see #LONGITUDE_REF_WEST
+     */
+    public static final int TAG_GPS_DEST_LONGITUDE_REF = 21;
+
+    /**
+     * A tag indicating the Longitude of destination (type RATIONAL,
+     * count = 3).
+     */
+    public static final int TAG_GPS_DEST_LONGITUDE = 22;
+
+    /**
+     * A tag indicating the Reference for bearing of destination (type ASCII,
+     * count = 2).
+     *
+     * @see #DIRECTION_REF_TRUE
+     * @see #DIRECTION_REF_MAGNETIC
+     */
+    public static final int TAG_GPS_DEST_BEARING_REF = 23;
+
+    /**
+     * A tag indicating the Bearing of destination (type RATIONAL, count = 1).
+     */
+    public static final int TAG_GPS_DEST_BEARING = 24;
+
+    /**
+     * A tag indicating the Reference for distance to destination (type ASCII,
+     * count = 2).
+     *
+     * @see #DEST_DISTANCE_REF_KILOMETERS
+     * @see #DEST_DISTANCE_REF_MILES
+     * @see #DEST_DISTANCE_REF_KNOTS
+     */
+    public static final int TAG_GPS_DEST_DISTANCE_REF = 25;
+
+    /**
+     * A tag indicating the Distance to destination (type RATIONAL, count = 1).
+     */
+    public static final int TAG_GPS_DEST_DISTANCE = 26;
+
+    /**
+     * A tag indicating the Name of GPS processing method (type UNDEFINED).
+     */
+    public static final int TAG_GPS_PROCESSING_METHOD = 27;
+
+    /**
+     * A tag indicating the Name of GPS area (type UNDEFINED).
+     */
+    public static final int TAG_GPS_AREA_INFORMATION = 28;
+
+    /**
+     * A tag indicating the GPS date (type ASCII, count 11).
+     */
+    public static final int TAG_GPS_DATE_STAMP = 29;
+
+    /**
+     * A tag indicating the GPS differential correction (type SHORT,
+     * count = 1).
+     *
+     * @see #DIFFERENTIAL_CORRECTION_NONE
+     * @see #DIFFERENTIAL_CORRECTION_APPLIED
+     */
+    public static final int TAG_GPS_DIFFERENTIAL = 30;
+
+    /**
+     * A value to be used with the "GPSLatitudeRef" and
+     * "GPSDestLatitudeRef" tags.
+     *
+     * @see #TAG_GPS_LATITUDE_REF
+     * @see #TAG_GPS_DEST_LATITUDE_REF
+     */
+    public static final String LATITUDE_REF_NORTH = "N";
+
+    /**
+     * A value to be used with the "GPSLatitudeRef" and
+     * "GPSDestLatitudeRef" tags.
+     *
+     * @see #TAG_GPS_LATITUDE_REF
+     * @see #TAG_GPS_DEST_LATITUDE_REF
+     */
+    public static final String LATITUDE_REF_SOUTH = "S";
+
+    /**
+     * A value to be used with the "GPSLongitudeRef" and
+     * "GPSDestLongitudeRef" tags.
+     *
+     * @see #TAG_GPS_LONGITUDE_REF
+     * @see #TAG_GPS_DEST_LONGITUDE_REF
+     */
+    public static final String LONGITUDE_REF_EAST = "E";
+
+    /**
+     * A value to be used with the "GPSLongitudeRef" and
+     * "GPSDestLongitudeRef" tags.
+     *
+     * @see #TAG_GPS_LONGITUDE_REF
+     * @see #TAG_GPS_DEST_LONGITUDE_REF
+     */
+    public static final String LONGITUDE_REF_WEST = "W";
+
+    /**
+     * A value to be used with the "GPSAltitudeRef" tag.
+     *
+     * @see #TAG_GPS_ALTITUDE_REF
+     */
+    public static final int ALTITUDE_REF_SEA_LEVEL = 0;
+
+    /**
+     * A value to be used with the "GPSAltitudeRef" tag.
+     *
+     * @see #TAG_GPS_ALTITUDE_REF
+     */
+    public static final int ALTITUDE_REF_SEA_LEVEL_REFERENCE = 1;
+
+    /**
+     * A value to be used with the "GPSStatus" tag.
+     *
+     * @see #TAG_GPS_STATUS
+     */
+    public static final String STATUS_MEASUREMENT_IN_PROGRESS = "A";
+
+    /**
+     * A value to be used with the "GPSStatus" tag.
+     *
+     * @see #TAG_GPS_STATUS
+     */
+    public static final String STATUS_MEASUREMENT_INTEROPERABILITY = "V";
+
+    /**
+     * A value to be used with the "GPSMeasureMode" tag.
+     *
+     * @see #TAG_GPS_MEASURE_MODE
+     */
+    public static final String MEASURE_MODE_2D = "2";
+
+    /**
+     * A value to be used with the "GPSMeasureMode" tag.
+     *
+     * @see #TAG_GPS_MEASURE_MODE
+     */
+    public static final String MEASURE_MODE_3D = "3";
+
+    /**
+     * A value to be used with the "GPSSpeedRef" tag.
+     *
+     * @see #TAG_GPS_SPEED_REF
+     */
+    public static final String SPEED_REF_KILOMETERS_PER_HOUR = "K";
+
+    /**
+     * A value to be used with the "GPSSpeedRef" tag.
+     *
+     * @see #TAG_GPS_SPEED_REF
+     */
+    public static final String SPEED_REF_MILES_PER_HOUR = "M";
+
+    /**
+     * A value to be used with the "GPSSpeedRef" tag.
+     *
+     * @see #TAG_GPS_SPEED_REF
+     */
+    public static final String SPEED_REF_KNOTS = "N";
+
+    /**
+     * A value to be used with the "GPSTrackRef", "GPSImgDirectionRef",
+     * and "GPSDestBearingRef" tags.
+     *
+     * @see #TAG_GPS_TRACK_REF
+     * @see #TAG_GPS_IMG_DIRECTION_REF
+     * @see #TAG_GPS_DEST_BEARING_REF
+     */
+    public static final String DIRECTION_REF_TRUE = "T";
+
+    /**
+     * A value to be used with the "GPSTrackRef", "GPSImgDirectionRef",
+     * and "GPSDestBearingRef" tags.
+     *
+     * @see #TAG_GPS_TRACK_REF
+     * @see #TAG_GPS_IMG_DIRECTION_REF
+     * @see #TAG_GPS_DEST_BEARING_REF
+     */
+    public static final String DIRECTION_REF_MAGNETIC = "M";
+
+    /**
+     * A value to be used with the "GPSDestDistanceRef" tag.
+     *
+     * @see #TAG_GPS_DEST_DISTANCE_REF
+     */
+    public static final String DEST_DISTANCE_REF_KILOMETERS = "K";
+
+    /**
+     * A value to be used with the "GPSDestDistanceRef" tag.
+     *
+     * @see #TAG_GPS_DEST_DISTANCE_REF
+     */
+    public static final String DEST_DISTANCE_REF_MILES = "M";
+
+    /**
+     * A value to be used with the "GPSDestDistanceRef" tag.
+     *
+     * @see #TAG_GPS_DEST_DISTANCE_REF
+     */
+    public static final String DEST_DISTANCE_REF_KNOTS = "N";
+
+    /**
+     * A value to be used with the "GPSDifferential" tag.
+     *
+     * @see #TAG_GPS_DIFFERENTIAL
+     */
+    public static final int DIFFERENTIAL_CORRECTION_NONE = 0;
+
+    /**
+     * A value to be used with the "GPSDifferential" tag.
+     *
+     * @see #TAG_GPS_DIFFERENTIAL
+     */
+    public static final int DIFFERENTIAL_CORRECTION_APPLIED = 1;
+
+    static class GPSVersionID extends TIFFTag {
+        public GPSVersionID() {
+            super("GPSVersionID",
+                  TAG_GPS_VERSION_ID,
+                  1 << TIFFTag.TIFF_BYTE);
+        }
+    }
+
+    static class GPSLatitudeRef extends TIFFTag {
+        public GPSLatitudeRef() {
+            super("GPSLatitudeRef",
+                  TAG_GPS_LATITUDE_REF,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class GPSLatitude extends TIFFTag {
+        public GPSLatitude() {
+            super("GPSLatitude",
+                  TAG_GPS_LATITUDE,
+                  1 << TIFFTag.TIFF_RATIONAL);
+        }
+    }
+
+    static class GPSLongitudeRef extends TIFFTag {
+        public GPSLongitudeRef() {
+            super("GPSLongitudeRef",
+                  TAG_GPS_LONGITUDE_REF,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class GPSLongitude extends TIFFTag {
+        public GPSLongitude() {
+            super("GPSLongitude",
+                  TAG_GPS_LONGITUDE,
+                  1 << TIFFTag.TIFF_RATIONAL);
+        }
+    }
+
+    static class GPSAltitudeRef extends TIFFTag {
+        public GPSAltitudeRef() {
+            super("GPSAltitudeRef",
+                  TAG_GPS_ALTITUDE_REF,
+                  1 << TIFFTag.TIFF_BYTE);
+
+            addValueName(ALTITUDE_REF_SEA_LEVEL, "Sea level");
+            addValueName(ALTITUDE_REF_SEA_LEVEL_REFERENCE,
+                         "Sea level reference (negative value)");
+        }
+    }
+
+    static class GPSAltitude extends TIFFTag {
+        public GPSAltitude() {
+            super("GPSAltitude",
+                  TAG_GPS_ALTITUDE,
+                  1 << TIFFTag.TIFF_RATIONAL);
+        }
+    }
+
+    static class GPSTimeStamp extends TIFFTag {
+        public GPSTimeStamp() {
+            super("GPSTimeStamp",
+                  TAG_GPS_TIME_STAMP,
+                  1 << TIFFTag.TIFF_RATIONAL);
+        }
+    }
+
+    static class GPSSatellites extends TIFFTag {
+        public GPSSatellites() {
+            super("GPSSatellites",
+                  TAG_GPS_SATELLITES,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class GPSStatus extends TIFFTag {
+        public GPSStatus() {
+            super("GPSStatus",
+                  TAG_GPS_STATUS,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class GPSMeasureMode extends TIFFTag {
+        public GPSMeasureMode() {
+            super("GPSMeasureMode",
+                  TAG_GPS_MEASURE_MODE,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class GPSDOP extends TIFFTag {
+        public GPSDOP() {
+            super("GPSDOP",
+                  TAG_GPS_DOP,
+                  1 << TIFFTag.TIFF_RATIONAL);
+        }
+    }
+
+    static class GPSSpeedRef extends TIFFTag {
+        public GPSSpeedRef() {
+            super("GPSSpeedRef",
+                  TAG_GPS_SPEED_REF,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class GPSSpeed extends TIFFTag {
+        public GPSSpeed() {
+            super("GPSSpeed",
+                  TAG_GPS_SPEED,
+                  1 << TIFFTag.TIFF_RATIONAL);
+        }
+    }
+
+    static class GPSTrackRef extends TIFFTag {
+        public GPSTrackRef() {
+            super("GPSTrackRef",
+                  TAG_GPS_TRACK_REF,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class GPSTrack extends TIFFTag {
+        public GPSTrack() {
+            super("GPSTrack",
+                  TAG_GPS_TRACK,
+                  1 << TIFFTag.TIFF_RATIONAL);
+        }
+    }
+
+    static class GPSImgDirectionRef extends TIFFTag {
+        public GPSImgDirectionRef() {
+            super("GPSImgDirectionRef",
+                  TAG_GPS_IMG_DIRECTION_REF,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class GPSImgDirection extends TIFFTag {
+        public GPSImgDirection() {
+            super("GPSImgDirection",
+                  TAG_GPS_IMG_DIRECTION,
+                  1 << TIFFTag.TIFF_RATIONAL);
+        }
+    }
+
+    static class GPSMapDatum extends TIFFTag {
+        public GPSMapDatum() {
+            super("GPSMapDatum",
+                  TAG_GPS_MAP_DATUM,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class GPSDestLatitudeRef extends TIFFTag {
+        public GPSDestLatitudeRef() {
+            super("GPSDestLatitudeRef",
+                  TAG_GPS_DEST_LATITUDE_REF,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class GPSDestLatitude extends TIFFTag {
+        public GPSDestLatitude() {
+            super("GPSDestLatitude",
+                  TAG_GPS_DEST_LATITUDE,
+                  1 << TIFFTag.TIFF_RATIONAL);
+        }
+    }
+
+    static class GPSDestLongitudeRef extends TIFFTag {
+        public GPSDestLongitudeRef() {
+            super("GPSDestLongitudeRef",
+                  TAG_GPS_DEST_LONGITUDE_REF,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class GPSDestLongitude extends TIFFTag {
+        public GPSDestLongitude() {
+            super("GPSDestLongitude",
+                  TAG_GPS_DEST_LONGITUDE,
+                  1 << TIFFTag.TIFF_RATIONAL);
+        }
+    }
+
+    static class GPSDestBearingRef extends TIFFTag {
+        public GPSDestBearingRef() {
+            super("GPSDestBearingRef",
+                  TAG_GPS_DEST_BEARING_REF,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class GPSDestBearing extends TIFFTag {
+        public GPSDestBearing() {
+            super("GPSDestBearing",
+                  TAG_GPS_DEST_BEARING,
+                  1 << TIFFTag.TIFF_RATIONAL);
+        }
+    }
+
+    static class GPSDestDistanceRef extends TIFFTag {
+        public GPSDestDistanceRef() {
+            super("GPSDestDistanceRef",
+                  TAG_GPS_DEST_DISTANCE_REF,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class GPSDestDistance extends TIFFTag {
+        public GPSDestDistance() {
+            super("GPSDestDistance",
+                  TAG_GPS_DEST_DISTANCE,
+                  1 << TIFFTag.TIFF_RATIONAL);
+        }
+    }
+
+    static class GPSProcessingMethod extends TIFFTag {
+        public GPSProcessingMethod() {
+            super("GPSProcessingMethod",
+                  TAG_GPS_PROCESSING_METHOD,
+                  1 << TIFFTag.TIFF_UNDEFINED);
+        }
+    }
+
+    static class GPSAreaInformation extends TIFFTag {
+        public GPSAreaInformation() {
+            super("GPSAreaInformation",
+                  TAG_GPS_AREA_INFORMATION,
+                  1 << TIFFTag.TIFF_UNDEFINED);
+        }
+    }
+
+    static class GPSDateStamp extends TIFFTag {
+        public GPSDateStamp() {
+            super("GPSDateStamp",
+                  TAG_GPS_DATE_STAMP,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class GPSDifferential extends TIFFTag {
+        public GPSDifferential() {
+            super("GPSDifferential",
+                  TAG_GPS_DIFFERENTIAL,
+                  1 << TIFFTag.TIFF_SHORT);
+            addValueName(DIFFERENTIAL_CORRECTION_NONE,
+                         "Measurement without differential correction");
+            addValueName(DIFFERENTIAL_CORRECTION_APPLIED,
+                         "Differential correction applied");
+
+        }
+    }
+
+    private static List<TIFFTag> initTags() {
+        ArrayList<TIFFTag> tags = new ArrayList<TIFFTag>(31);
+
+        tags.add(new GPSVersionID());
+        tags.add(new GPSLatitudeRef());
+        tags.add(new GPSLatitude());
+        tags.add(new GPSLongitudeRef());
+        tags.add(new GPSLongitude());
+        tags.add(new GPSAltitudeRef());
+        tags.add(new GPSAltitude());
+        tags.add(new GPSTimeStamp());
+        tags.add(new GPSSatellites());
+        tags.add(new GPSStatus());
+        tags.add(new GPSMeasureMode());
+        tags.add(new GPSDOP());
+        tags.add(new GPSSpeedRef());
+        tags.add(new GPSSpeed());
+        tags.add(new GPSTrackRef());
+        tags.add(new GPSTrack());
+        tags.add(new GPSImgDirectionRef());
+        tags.add(new GPSImgDirection());
+        tags.add(new GPSMapDatum());
+        tags.add(new GPSDestLatitudeRef());
+        tags.add(new GPSDestLatitude());
+        tags.add(new GPSDestLongitudeRef());
+        tags.add(new GPSDestLongitude());
+        tags.add(new GPSDestBearingRef());
+        tags.add(new GPSDestBearing());
+        tags.add(new GPSDestDistanceRef());
+        tags.add(new GPSDestDistance());
+        tags.add(new GPSProcessingMethod());
+        tags.add(new GPSAreaInformation());
+        tags.add(new GPSDateStamp());
+        tags.add(new GPSDifferential());
+        return tags;
+    }
+
+    private ExifGPSTagSet() {
+        super(initTags());
+    }
+
+    /**
+     * Returns a shared instance of an <code>ExifGPSTagSet</code>.
+     *
+     * @return an <code>ExifGPSTagSet</code> instance.
+     */
+    public synchronized static ExifGPSTagSet getInstance() {
+        if (theInstance == null) {
+            theInstance = new ExifGPSTagSet();
+        }
+        return theInstance;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/ExifInteroperabilityTagSet.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,103 @@
+/*
+ * 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 javax.imageio.plugins.tiff;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class representing the tags found in an Exif Interoperability IFD.
+ *
+ * @since 1.9
+ * @see   ExifTIFFTagSet
+ */
+public class ExifInteroperabilityTagSet extends TIFFTagSet {
+    /**
+     * A tag indicating the identification of the Interoperability rule
+     * (type ASCII).
+     *
+     * @see #INTEROPERABILITY_INDEX_R98
+     * @see #INTEROPERABILITY_INDEX_THM
+     */
+    public static final int TAG_INTEROPERABILITY_INDEX = 1;
+
+    /**
+     * A value to be used with the "InteroperabilityIndex" tag. Indicates
+     * a file conforming to the R98 file specification of Recommended Exif
+     * Interoperability Rules (ExifR98) or to the DCF basic file stipulated
+     * by the Design Rule for Camera File System (type ASCII).
+     *
+     * @see #TAG_INTEROPERABILITY_INDEX
+     */
+    public static final String INTEROPERABILITY_INDEX_R98 = "R98";
+
+    /**
+     * A value to be used with the "InteroperabilityIndex" tag. Indicates
+     * a file conforming to the DCF thumbnail file stipulated by the Design
+     * rule for Camera File System (type ASCII).
+     *
+     * @see #TAG_INTEROPERABILITY_INDEX
+     */
+    public static final String INTEROPERABILITY_INDEX_THM = "THM";
+
+    private static ExifInteroperabilityTagSet theInstance = null;
+
+    static class InteroperabilityIndex extends TIFFTag {
+
+        public InteroperabilityIndex() {
+            super("InteroperabilityIndex",
+                  TAG_INTEROPERABILITY_INDEX,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    private static List<TIFFTag> tags;
+
+    private static void initTags() {
+        tags = new ArrayList<TIFFTag>(42);
+
+        tags.add(new ExifInteroperabilityTagSet.InteroperabilityIndex());
+    }
+
+    private ExifInteroperabilityTagSet() {
+        super(tags);
+    }
+
+    /**
+     * Returns the shared instance of
+     * <code>ExifInteroperabilityTagSet</code>.
+     *
+     * @return the <code>ExifInteroperabilityTagSet</code> instance.
+     */
+    public synchronized static ExifInteroperabilityTagSet getInstance() {
+        if (theInstance == null) {
+            initTags();
+            theInstance = new ExifInteroperabilityTagSet();
+            tags = null;
+        }
+        return theInstance;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/ExifParentTIFFTagSet.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,95 @@
+/*
+ * 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 javax.imageio.plugins.tiff;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class containing the TIFF tags used to reference the Exif and GPS IFDs.
+ * This tag set should be added to the root tag set by means of the
+ * {@link TIFFImageReadParam#addAllowedTagSet(TIFFTagSet)
+ * TIFFImageReadParam.addAllowedTagSet} method if Exif
+ * support is desired.
+ *
+ * @since 1.9
+ */
+public class ExifParentTIFFTagSet extends TIFFTagSet {
+
+    private static ExifParentTIFFTagSet theInstance = null;
+
+    // 34665 - Exif IFD Pointer                   (LONG/1)
+    /** Tag pointing to the Exif IFD (type LONG). */
+    public static final int TAG_EXIF_IFD_POINTER = 34665;
+
+    /** Tag pointing to a GPS info IFD (type LONG). */
+    public static final int TAG_GPS_INFO_IFD_POINTER = 34853;
+
+    // To be inserted into parent (root) TIFFTagSet
+    static class ExifIFDPointer extends TIFFTag {
+
+        public ExifIFDPointer() {
+            super("ExifIFDPointer",
+                  TAG_EXIF_IFD_POINTER,
+                  ExifTIFFTagSet.getInstance());
+        }
+    }
+
+    // To be inserted into parent (root) TIFFTagSet
+    static class GPSInfoIFDPointer extends TIFFTag {
+
+        public GPSInfoIFDPointer() {
+            super("GPSInfoIFDPointer",
+                  TAG_GPS_INFO_IFD_POINTER,
+                  ExifGPSTagSet.getInstance());
+        }
+    }
+
+    private static List<TIFFTag> tags;
+
+    private static void initTags() {
+        tags = new ArrayList<TIFFTag>(1);
+        tags.add(new ExifParentTIFFTagSet.ExifIFDPointer());
+        tags.add(new ExifParentTIFFTagSet.GPSInfoIFDPointer());
+    }
+
+    private ExifParentTIFFTagSet() {
+        super(tags);
+    }
+
+    /**
+     * Returns a shared instance of an <code>ExifParentTIFFTagSet</code>.
+     *
+     * @return an <code>ExifParentTIFFTagSet</code> instance.
+     */
+    public synchronized static ExifParentTIFFTagSet getInstance() {
+        if (theInstance == null) {
+            initTags();
+            theInstance = new ExifParentTIFFTagSet();
+            tags = null;
+        }
+        return theInstance;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/ExifTIFFTagSet.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,2007 @@
+/*
+ * 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 javax.imageio.plugins.tiff;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class representing the tags found in an Exif IFD.  Exif is a
+ * standard for annotating images used by most digital camera
+ * manufacturers.  The Exif specification may be found at
+ * <a href="http://www.exif.org/Exif2-2.PDF">
+ * <code>http://www.exif.org/Exif2-2.PDF</code>
+ * </a>.
+ *
+ * <p> The definitions of the data types referenced by the field
+ * definitions may be found in the {@link TIFFTag TIFFTag} class.
+ *
+ * @since 1.9
+ */
+public class ExifTIFFTagSet extends TIFFTagSet {
+
+    private static ExifTIFFTagSet theInstance = null;
+
+    /**
+     * A tag pointing to a GPS info IFD (type LONG).  This tag has
+     * been superseded by {@link ExifParentTIFFTagSet#TAG_GPS_INFO_IFD_POINTER}.
+     */
+    public static final int TAG_GPS_INFO_IFD_POINTER = 34853;
+
+    /** A tag pointing to an interoperability IFD (type LONG). */
+    public static final int TAG_INTEROPERABILITY_IFD_POINTER = 40965;
+
+    /**
+     * A tag containing the Exif version number (type UNDEFINED, count =
+     * 4).  Conformance to the Exif 2.1 standard is indicated using
+     * the ASCII value "0210" (with no terminating NUL).
+     *
+     * @see #EXIF_VERSION_2_1
+     * @see #EXIF_VERSION_2_2
+     */
+    public static final int TAG_EXIF_VERSION = 36864;
+
+    /**
+     * A value to be used with the "ExifVersion" tag to indicate Exif version
+     * 2.1.  The value equals the US-ASCII encoding of the byte array
+     * <code>{'0', '2', '1', '0'}</code>.
+     *
+     * @see #TAG_EXIF_VERSION
+     */
+    public static final String EXIF_VERSION_2_1 =
+        new String(new byte[] { '0', '2', '1', '0' },
+        StandardCharsets.US_ASCII);
+
+    /**
+     * A value to be used with the "ExifVersion" tag to indicate Exif version
+     * 2.2.  The value equals the US-ASCII encoding of the byte array
+     * <code>{'0', '2', '2', '0'}</code>.
+     *
+     * @see #TAG_EXIF_VERSION
+     */
+    public static final String EXIF_VERSION_2_2 =
+        new String(new byte[] { '0', '2', '2', '0' },
+        StandardCharsets.US_ASCII);
+
+    /**
+     * A tag indicating the FlashPix version number (type UNDEFINED,
+     * count = 4).
+     */
+    public static final int TAG_FLASHPIX_VERSION = 40960;
+
+    /**
+     * A tag indicating the color space information (type SHORT).  The
+     * legal values are given by the <code>COLOR_SPACE_*</code>
+     * constants.
+     *
+     * @see #COLOR_SPACE_SRGB
+     * @see #COLOR_SPACE_UNCALIBRATED
+     */
+    public static final int TAG_COLOR_SPACE = 40961;
+
+    /**
+     * A value to be used with the "ColorSpace" tag.
+     *
+     * @see #TAG_COLOR_SPACE
+     */
+    public static final int COLOR_SPACE_SRGB = 1;
+
+    /**
+     * A value to be used with the "ColorSpace" tag.
+     *
+     * @see #TAG_COLOR_SPACE
+     */
+    public static final int COLOR_SPACE_UNCALIBRATED = 0xFFFF;
+
+    /**
+     * A tag containing the components configuration information (type
+     * UNDEFINED, count = 4).
+     *
+     * @see #COMPONENTS_CONFIGURATION_DOES_NOT_EXIST
+     * @see #COMPONENTS_CONFIGURATION_Y
+     * @see #COMPONENTS_CONFIGURATION_CB
+     * @see #COMPONENTS_CONFIGURATION_CR
+     * @see #COMPONENTS_CONFIGURATION_R
+     * @see #COMPONENTS_CONFIGURATION_G
+     * @see #COMPONENTS_CONFIGURATION_B
+     */
+    public static final int TAG_COMPONENTS_CONFIGURATION = 37121;
+
+    /**
+     * A value to be used with the "ComponentsConfiguration" tag.
+     *
+     * @see #TAG_COMPONENTS_CONFIGURATION
+     */
+    public static final int COMPONENTS_CONFIGURATION_DOES_NOT_EXIST = 0;
+
+    /**
+     * A value to be used with the "ComponentsConfiguration" tag.
+     *
+     * @see #TAG_COMPONENTS_CONFIGURATION
+     */
+    public static final int COMPONENTS_CONFIGURATION_Y = 1;
+
+    /**
+     * A value to be used with the "ComponentsConfiguration" tag.
+     *
+     * @see #TAG_COMPONENTS_CONFIGURATION
+     */
+    public static final int COMPONENTS_CONFIGURATION_CB = 2;
+
+    /**
+     * A value to be used with the "ComponentsConfiguration" tag.
+     *
+     * @see #TAG_COMPONENTS_CONFIGURATION
+     */
+    public static final int COMPONENTS_CONFIGURATION_CR = 3;
+
+    /**
+     * A value to be used with the "ComponentsConfiguration" tag.
+     *
+     * @see #TAG_COMPONENTS_CONFIGURATION
+     */
+    public static final int COMPONENTS_CONFIGURATION_R = 4;
+
+    /**
+     * A value to be used with the "ComponentsConfiguration" tag.
+     *
+     * @see #TAG_COMPONENTS_CONFIGURATION
+     */
+    public static final int COMPONENTS_CONFIGURATION_G = 5;
+
+    /**
+     * A value to be used with the "ComponentsConfiguration" tag.
+     *
+     * @see #TAG_COMPONENTS_CONFIGURATION
+     */
+    public static final int COMPONENTS_CONFIGURATION_B = 6;
+
+    /**
+     * A tag indicating the number of compressed bits per pixel
+     * (type RATIONAL).
+     */
+    public static final int TAG_COMPRESSED_BITS_PER_PIXEL = 37122;
+
+    /**
+     * A tag indicating the pixel X dimension (type SHORT or LONG).
+     * This value records the valid width of the meaningful image for
+     * a compressed file, whether or not there is padding or a restart
+     * marker.
+     */
+    public static final int TAG_PIXEL_X_DIMENSION = 40962;
+
+    /**
+     * A tag indicating the pixel Y dimension (type SHORT or LONG).
+     * This value records the valid height of the meaningful image for
+     * a compressed file, whether or not there is padding or a restart
+     * marker.
+     */
+    public static final int TAG_PIXEL_Y_DIMENSION = 40963;
+
+    /**
+     * A tag indicating a manufacturer-defined maker note (type
+     * UNDEFINED).
+     */
+    public static final int TAG_MAKER_NOTE = 37500;
+
+    /**
+     * A tag indicating a manufacturer-defined marker note (type UNDEFINED).
+     * This tag has been superseded by {@link #TAG_MAKER_NOTE}.
+     */
+    public static final int TAG_MARKER_NOTE = TAG_MAKER_NOTE;
+
+    /**
+     * A tag indicating a user comment (type UNDEFINED).  The first 8
+     * bytes are used to specify the character encoding.
+     */
+    public static final int TAG_USER_COMMENT = 37510;
+
+    /**
+     * A tag indicating the name of a related sound file (type ASCII).
+     */
+    public static final int TAG_RELATED_SOUND_FILE = 40964;
+
+    /**
+     * A tag indicating the date and time when the original image was
+     * generated (type ASCII).
+     */
+    public static final int TAG_DATE_TIME_ORIGINAL = 36867;
+
+    /**
+     * A tag indicating the date and time when the image was stored as
+     * digital data (type ASCII).
+     */
+    public static final int TAG_DATE_TIME_DIGITIZED = 36868;
+
+    /**
+     * A tag used to record fractions of seconds for the "DateTime" tag
+     * (type ASCII).
+     */
+    public static final int TAG_SUB_SEC_TIME = 37520;
+
+    /**
+     * A tag used to record fractions of seconds for the
+     * "DateTimeOriginal" tag (type ASCII).
+     */
+    public static final int TAG_SUB_SEC_TIME_ORIGINAL = 37521;
+
+    /**
+     * A tag used to record fractions of seconds for the
+     * "DateTimeDigitized" tag (type ASCII).
+     */
+    public static final int TAG_SUB_SEC_TIME_DIGITIZED = 37522;
+
+    /**
+     * A tag indicating the exposure time, in seconds (type RATIONAL).
+     */
+    public static final int TAG_EXPOSURE_TIME = 33434;
+
+    /**
+     * A tag indicating the F number (type RATIONAL).
+     */
+    public static final int TAG_F_NUMBER = 33437;
+
+    /**
+     * A tag indicating the class of the programs used to set exposure
+     * when the picture was taken (type SHORT).
+     *
+     * @see #EXPOSURE_PROGRAM_NOT_DEFINED
+     * @see #EXPOSURE_PROGRAM_MANUAL
+     * @see #EXPOSURE_PROGRAM_NORMAL_PROGRAM
+     * @see #EXPOSURE_PROGRAM_APERTURE_PRIORITY
+     * @see #EXPOSURE_PROGRAM_SHUTTER_PRIORITY
+     * @see #EXPOSURE_PROGRAM_CREATIVE_PROGRAM
+     * @see #EXPOSURE_PROGRAM_ACTION_PROGRAM
+     * @see #EXPOSURE_PROGRAM_PORTRAIT_MODE
+     * @see #EXPOSURE_PROGRAM_LANDSCAPE_MODE
+     * @see #EXPOSURE_PROGRAM_MAX_RESERVED
+     */
+    public static final int TAG_EXPOSURE_PROGRAM = 34850;
+
+    /**
+     * A value to be used with the "ExposureProgram" tag.
+     *
+     * @see #TAG_EXPOSURE_PROGRAM
+     */
+    public static final int EXPOSURE_PROGRAM_NOT_DEFINED = 0;
+
+    /**
+     * A value to be used with the "ExposureProgram" tag.
+     *
+     * @see #TAG_EXPOSURE_PROGRAM
+     */
+    public static final int EXPOSURE_PROGRAM_MANUAL = 1;
+
+    /**
+     * A value to be used with the "ExposureProgram" tag.
+     *
+     * @see #TAG_EXPOSURE_PROGRAM
+     */
+    public static final int EXPOSURE_PROGRAM_NORMAL_PROGRAM = 2;
+
+    /**
+     * A value to be used with the "ExposureProgram" tag.
+     *
+     * @see #TAG_EXPOSURE_PROGRAM
+     */
+    public static final int EXPOSURE_PROGRAM_APERTURE_PRIORITY = 3;
+
+    /**
+     * A value to be used with the "ExposureProgram" tag.
+     *
+     * @see #TAG_EXPOSURE_PROGRAM
+     */
+    public static final int EXPOSURE_PROGRAM_SHUTTER_PRIORITY = 4;
+
+    /**
+     * A value to be used with the "ExposureProgram" tag.
+     *
+     * @see #TAG_EXPOSURE_PROGRAM
+     */
+    public static final int EXPOSURE_PROGRAM_CREATIVE_PROGRAM = 5;
+
+    /**
+     * A value to be used with the "ExposureProgram" tag.
+     *
+     * @see #TAG_EXPOSURE_PROGRAM
+     */
+    public static final int EXPOSURE_PROGRAM_ACTION_PROGRAM = 6;
+
+    /**
+     * A value to be used with the "ExposureProgram" tag.
+     *
+     * @see #TAG_EXPOSURE_PROGRAM
+     */
+    public static final int EXPOSURE_PROGRAM_PORTRAIT_MODE = 7;
+
+    /**
+     * A value to be used with the "ExposureProgram" tag.
+     *
+     * @see #TAG_EXPOSURE_PROGRAM
+     */
+    public static final int EXPOSURE_PROGRAM_LANDSCAPE_MODE = 8;
+
+    /**
+     * A value to be used with the "ExposureProgram" tag.
+     *
+     * @see #TAG_EXPOSURE_PROGRAM
+     */
+    public static final int EXPOSURE_PROGRAM_MAX_RESERVED = 255;
+
+    /**
+     * A tag indicating the spectral sensitivity of each channel of
+     * the camera used (type ASCII).  The tag value is an ASCII string
+     * compatible with the ASTM standard.
+     */
+    public static final int TAG_SPECTRAL_SENSITIVITY = 34852;
+
+    /**
+     * A tag indicating the ISO speed and ISO latitude of the camera
+     * or input device, as specified in ISO 12232<sup>xiv</sup> (type
+     * SHORT).
+     */
+    public static final int TAG_ISO_SPEED_RATINGS= 34855;
+
+    /**
+     * A tag indicating the optoelectric conversion function,
+     * specified in ISO 14254<sup>xv</sup> (type UNDEFINED).  OECF is
+     * the relationship between the camera optical input and the image
+     * values.
+     */
+    public static final int TAG_OECF = 34856;
+
+    /**
+     * A tag indicating the shutter speed (type SRATIONAL).
+     */
+    public static final int TAG_SHUTTER_SPEED_VALUE = 37377;
+
+    /**
+     * A tag indicating the lens aperture (type RATIONAL).
+     */
+    public static final int TAG_APERTURE_VALUE = 37378;
+
+    /**
+     * A tag indicating the value of brightness (type SRATIONAL).
+     */
+    public static final int TAG_BRIGHTNESS_VALUE = 37379;
+
+    /**
+     * A tag indicating the exposure bias (type SRATIONAL).
+     */
+    public static final int TAG_EXPOSURE_BIAS_VALUE = 37380;
+
+    /**
+     * A tag indicating the smallest F number of the lens (type
+     * RATIONAL).
+     */
+    public static final int TAG_MAX_APERTURE_VALUE = 37381;
+
+    /**
+     * A tag indicating the distance to the subject, in meters (type
+     * RATIONAL).
+     */
+    public static final int TAG_SUBJECT_DISTANCE = 37382;
+
+    /**
+     * A tag indicating the metering mode (type SHORT).
+     *
+     * @see #METERING_MODE_UNKNOWN
+     * @see #METERING_MODE_AVERAGE
+     * @see #METERING_MODE_CENTER_WEIGHTED_AVERAGE
+     * @see #METERING_MODE_SPOT
+     * @see #METERING_MODE_MULTI_SPOT
+     * @see #METERING_MODE_PATTERN
+     * @see #METERING_MODE_PARTIAL
+     * @see #METERING_MODE_MIN_RESERVED
+     * @see #METERING_MODE_MAX_RESERVED
+     * @see #METERING_MODE_OTHER
+     */
+    public static final int TAG_METERING_MODE = 37383;
+
+    /**
+     * A value to be used with the "MeteringMode" tag.
+     *
+     * @see #TAG_METERING_MODE
+     */
+    public static final int METERING_MODE_UNKNOWN = 0;
+
+    /**
+     * A value to be used with the "MeteringMode" tag.
+     *
+     * @see #TAG_METERING_MODE
+     */
+    public static final int METERING_MODE_AVERAGE = 1;
+
+    /**
+     * A value to be used with the "MeteringMode" tag.
+     *
+     * @see #TAG_METERING_MODE
+     */
+    public static final int METERING_MODE_CENTER_WEIGHTED_AVERAGE = 2;
+
+    /**
+     * A value to be used with the "MeteringMode" tag.
+     *
+     * @see #TAG_METERING_MODE
+     */
+    public static final int METERING_MODE_SPOT = 3;
+
+    /**
+     * A value to be used with the "MeteringMode" tag.
+     *
+     * @see #TAG_METERING_MODE
+     */
+    public static final int METERING_MODE_MULTI_SPOT = 4;
+
+    /**
+     * A value to be used with the "MeteringMode" tag.
+     *
+     * @see #TAG_METERING_MODE
+     */
+    public static final int METERING_MODE_PATTERN = 5;
+
+    /**
+     * A value to be used with the "MeteringMode" tag.
+     *
+     * @see #TAG_METERING_MODE
+     */
+    public static final int METERING_MODE_PARTIAL = 6;
+
+    /**
+     * A value to be used with the "MeteringMode" tag.
+     *
+     * @see #TAG_METERING_MODE
+     */
+    public static final int METERING_MODE_MIN_RESERVED = 7;
+
+    /**
+     * A value to be used with the "MeteringMode" tag.
+     *
+     * @see #TAG_METERING_MODE
+     */
+    public static final int METERING_MODE_MAX_RESERVED = 254;
+
+    /**
+     * A value to be used with the "MeteringMode" tag.
+     *
+     * @see #TAG_METERING_MODE
+     */
+    public static final int METERING_MODE_OTHER = 255;
+
+    /**
+     * A tag indicatingthe kind of light source (type SHORT).
+     *
+     * @see #LIGHT_SOURCE_UNKNOWN
+     * @see #LIGHT_SOURCE_DAYLIGHT
+     * @see #LIGHT_SOURCE_FLUORESCENT
+     * @see #LIGHT_SOURCE_TUNGSTEN
+     * @see #LIGHT_SOURCE_STANDARD_LIGHT_A
+     * @see #LIGHT_SOURCE_STANDARD_LIGHT_B
+     * @see #LIGHT_SOURCE_STANDARD_LIGHT_C
+     * @see #LIGHT_SOURCE_D55
+     * @see #LIGHT_SOURCE_D65
+     * @see #LIGHT_SOURCE_D75
+     * @see #LIGHT_SOURCE_OTHER
+     */
+    public static final int TAG_LIGHT_SOURCE = 37384;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_UNKNOWN = 0;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_DAYLIGHT = 1;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_FLUORESCENT = 2;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_TUNGSTEN = 3;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_FLASH = 4;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_FINE_WEATHER = 9;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_CLOUDY_WEATHER = 10;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_SHADE = 11;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_DAYLIGHT_FLUORESCENT = 12;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_DAY_WHITE_FLUORESCENT = 13;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_COOL_WHITE_FLUORESCENT = 14;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_WHITE_FLUORESCENT = 15;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_STANDARD_LIGHT_A = 17;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_STANDARD_LIGHT_B = 18;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_STANDARD_LIGHT_C = 19;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_D55 = 20;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_D65 = 21;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_D75 = 22;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_D50 = 23;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_ISO_STUDIO_TUNGSTEN = 24;
+
+    /**
+     * A value to be used with the "LightSource" tag.
+     *
+     * @see #TAG_LIGHT_SOURCE
+     */
+    public static final int LIGHT_SOURCE_OTHER = 255;
+
+    /**
+     * A tag indicating the flash firing status and flash return
+     * status (type SHORT).
+     *
+     * @see #FLASH_DID_NOT_FIRE
+     * @see #FLASH_FIRED
+     * @see #FLASH_STROBE_RETURN_LIGHT_NOT_DETECTED
+     * @see #FLASH_STROBE_RETURN_LIGHT_DETECTED
+     */
+    public static final int TAG_FLASH = 37385;
+
+    /**
+     * A value to be used with the "Flash" tag, indicating that the
+     * flash did not fire.
+     *
+     * @see #TAG_FLASH
+     */
+    public static final int FLASH_DID_NOT_FIRE = 0x0;
+
+    /**
+     * A value to be used with the "Flash" tag, indicating that the
+     * flash fired, but the strobe return status is unknown.
+     *
+     * @see #TAG_FLASH
+     */
+    public static final int FLASH_FIRED = 0x1;
+
+    /**
+     * A value to be used with the "Flash" tag, indicating that the
+     * flash fired, but the strobe return light was not detected.
+     *
+     * @see #TAG_FLASH
+     */
+    public static final int FLASH_STROBE_RETURN_LIGHT_NOT_DETECTED = 0x5;
+
+    /**
+     * A value to be used with the "Flash" tag, indicating that the
+     * flash fired, and the strobe return light was detected.
+     *
+     * @see #TAG_FLASH
+     */
+    public static final int FLASH_STROBE_RETURN_LIGHT_DETECTED = 0x7;
+
+    /**
+     * A mask to be used with the "Flash" tag, indicating that the
+     * flash fired.
+     *
+     * @see #TAG_FLASH
+     */
+    public static final int FLASH_MASK_FIRED = 0x1;
+
+    /**
+     * A mask to be used with the "Flash" tag, indicating strobe return
+     * light not detected.
+     *
+     * @see #TAG_FLASH
+     */
+    public static final int FLASH_MASK_RETURN_NOT_DETECTED = 0x4;
+
+    /**
+     * A mask to be used with the "Flash" tag, indicating strobe return
+     * light detected.
+     *
+     * @see #TAG_FLASH
+     */
+    public static final int FLASH_MASK_RETURN_DETECTED = 0x6;
+
+    /**
+     * A mask to be used with the "Flash" tag, indicating compulsory flash
+     * firing mode.
+     *
+     * @see #TAG_FLASH
+     */
+    public static final int FLASH_MASK_MODE_FLASH_FIRING = 0x8;
+
+    /**
+     * A mask to be used with the "Flash" tag, indicating compulsory flash
+     * suppression mode.
+     *
+     * @see #TAG_FLASH
+     */
+    public static final int FLASH_MASK_MODE_FLASH_SUPPRESSION = 0x10;
+
+    /**
+     * A mask to be used with the "Flash" tag, indicating auto mode.
+     *
+     * @see #TAG_FLASH
+     */
+    public static final int FLASH_MASK_MODE_AUTO = 0x18;
+
+    /**
+     * A mask to be used with the "Flash" tag, indicating no flash function
+     * present.
+     *
+     * @see #TAG_FLASH
+     */
+    public static final int FLASH_MASK_FUNCTION_NOT_PRESENT = 0x20;
+
+    /**
+     * A mask to be used with the "Flash" tag, indicating red-eye reduction
+     * supported.
+     *
+     * @see #TAG_FLASH
+     */
+    public static final int FLASH_MASK_RED_EYE_REDUCTION = 0x40;
+
+    /**
+     * A tag indicating the actual focal length of the lens, in
+     * millimeters (type RATIONAL).
+     */
+    public static final int TAG_FOCAL_LENGTH = 37386;
+
+    /**
+     * A tag indicating the location and area of the main subject in
+     * the overall scene.
+     */
+    public static final int TAG_SUBJECT_AREA = 37396;
+
+    /**
+     * A tag indicating the strobe energy at the time the image was
+     * captured, as measured in Beam Candle Power Seconds (BCPS) (type
+     * RATIONAL).
+     */
+    public static final int TAG_FLASH_ENERGY = 41483;
+
+    /**
+     * A tag indicating the camera or input device spatial frequency
+     * table and SFR values in the direction of image width, image
+     * height, and diagonal direction, as specified in ISO
+     * 12233<sup>xvi</sup> (type UNDEFINED).
+     */
+    public static final int TAG_SPATIAL_FREQUENCY_RESPONSE = 41484;
+
+    /**
+     * Indicates the number of pixels in the image width (X) direction
+     * per FocalPlaneResolutionUnit on the camera focal plane (type
+     * RATIONAL).
+     */
+    public static final int TAG_FOCAL_PLANE_X_RESOLUTION = 41486;
+
+    /**
+     * Indicate the number of pixels in the image height (Y) direction
+     * per FocalPlaneResolutionUnit on the camera focal plane (type
+     * RATIONAL).
+     */
+    public static final int TAG_FOCAL_PLANE_Y_RESOLUTION = 41487;
+
+    /**
+     * Indicates the unit for measuring FocalPlaneXResolution and
+     * FocalPlaneYResolution (type SHORT).
+     *
+     * @see #FOCAL_PLANE_RESOLUTION_UNIT_NONE
+     * @see #FOCAL_PLANE_RESOLUTION_UNIT_INCH
+     * @see #FOCAL_PLANE_RESOLUTION_UNIT_CENTIMETER
+     */
+    public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT = 41488;
+
+    /**
+     * A value to be used with the "FocalPlaneResolutionUnit" tag.
+     *
+     * @see #TAG_FOCAL_PLANE_RESOLUTION_UNIT
+     */
+    public static final int FOCAL_PLANE_RESOLUTION_UNIT_NONE = 1;
+
+    /**
+     * A value to be used with the "FocalPlaneXResolution" tag.
+     *
+     * @see #TAG_FOCAL_PLANE_RESOLUTION_UNIT
+     */
+    public static final int FOCAL_PLANE_RESOLUTION_UNIT_INCH = 2;
+
+    /**
+     * A value to be used with the "FocalPlaneXResolution" tag.
+     *
+     * @see #TAG_FOCAL_PLANE_RESOLUTION_UNIT
+     */
+    public static final int FOCAL_PLANE_RESOLUTION_UNIT_CENTIMETER = 3;
+
+    /**
+     * A tag indicating the column and row of the center pixel of the
+     * main subject in the scene (type SHORT, count = 2).
+     */
+    public static final int TAG_SUBJECT_LOCATION = 41492;
+
+    /**
+     * A tag indicating the exposure index selected on the camera or
+     * input device at the time the image was captured (type
+     * RATIONAL).
+     */
+    public static final int TAG_EXPOSURE_INDEX = 41493;
+
+    /**
+     * A tag indicating the sensor type on the camera or input device
+     * (type SHORT).
+     *
+     * @see #SENSING_METHOD_NOT_DEFINED
+     * @see #SENSING_METHOD_ONE_CHIP_COLOR_AREA_SENSOR
+     * @see #SENSING_METHOD_TWO_CHIP_COLOR_AREA_SENSOR
+     * @see #SENSING_METHOD_THREE_CHIP_COLOR_AREA_SENSOR
+     * @see #SENSING_METHOD_COLOR_SEQUENTIAL_AREA_SENSOR
+     * @see #SENSING_METHOD_TRILINEAR_SENSOR
+     * @see #SENSING_METHOD_COLOR_SEQUENTIAL_LINEAR_SENSOR
+     */
+    public static final int TAG_SENSING_METHOD = 41495;
+
+    /**
+     * A value to be used with the "SensingMethod" tag.
+     *
+     * @see #TAG_SENSING_METHOD
+     */
+    public static final int SENSING_METHOD_NOT_DEFINED = 1;
+
+    /**
+     * A value to be used with the "SensingMethod" tag.
+     *
+     * @see #TAG_SENSING_METHOD
+     */
+    public static final int SENSING_METHOD_ONE_CHIP_COLOR_AREA_SENSOR = 2;
+
+    /**
+     * A value to be used with the "SensingMethod" tag.
+     *
+     * @see #TAG_SENSING_METHOD
+     */
+    public static final int SENSING_METHOD_TWO_CHIP_COLOR_AREA_SENSOR = 3;
+
+    /**
+     * A value to be used with the "SensingMethod" tag.
+     *
+     * @see #TAG_SENSING_METHOD
+     */
+    public static final int SENSING_METHOD_THREE_CHIP_COLOR_AREA_SENSOR = 4;
+
+    /**
+     * A value to be used with the "SensingMethod" tag.
+     *
+     * @see #TAG_SENSING_METHOD
+     */
+    public static final int SENSING_METHOD_COLOR_SEQUENTIAL_AREA_SENSOR = 5;
+
+    /**
+     * A value to be used with the "SensingMethod" tag.
+     *
+     * @see #TAG_SENSING_METHOD
+     */
+    public static final int SENSING_METHOD_TRILINEAR_SENSOR = 7;
+
+    /**
+     * A value to be used with the "SensingMethod" tag.
+     *
+     * @see #TAG_SENSING_METHOD
+     */
+    public static final int SENSING_METHOD_COLOR_SEQUENTIAL_LINEAR_SENSOR = 8;
+
+    /**
+     * A tag indicating the image source (type UNDEFINED).
+     *
+     * @see #FILE_SOURCE_DSC
+     */
+    public static final int TAG_FILE_SOURCE = 41728;
+
+    /**
+     * A value to be used with the "FileSource" tag.
+     *
+     * @see #TAG_FILE_SOURCE
+     */
+    public static final int FILE_SOURCE_DSC = 3;
+
+    /**
+     * A tag indicating the type of scene (type UNDEFINED).
+     *
+     * @see #SCENE_TYPE_DSC
+     */
+    public static final int TAG_SCENE_TYPE = 41729;
+
+    /**
+     * A value to be used with the "SceneType" tag.
+     *
+     * @see #TAG_SCENE_TYPE
+     */
+    public static final int SCENE_TYPE_DSC = 1;
+
+    /**
+     * A tag indicating the color filter array geometric pattern of
+     * the image sensor when a one-chip color area sensor if used
+     * (type UNDEFINED).
+     */
+    public static final int TAG_CFA_PATTERN = 41730;
+
+    /**
+     * A tag indicating the use of special processing on image data,
+     * such as rendering geared to output.
+     */
+    public static final int TAG_CUSTOM_RENDERED = 41985;
+
+    /**
+     * A value to be used with the "CustomRendered" tag.
+     *
+     * @see #TAG_CUSTOM_RENDERED
+     */
+    public static final int CUSTOM_RENDERED_NORMAL = 0;
+
+    /**
+     * A value to be used with the "CustomRendered" tag.
+     *
+     * @see #TAG_CUSTOM_RENDERED
+     */
+    public static final int CUSTOM_RENDERED_CUSTOM = 1;
+
+    /**
+     * A tag indicating the exposure mode set when the image was shot.
+     */
+    public static final int TAG_EXPOSURE_MODE = 41986;
+
+    /**
+     * A value to be used with the "ExposureMode" tag.
+     *
+     * @see #TAG_EXPOSURE_MODE
+     */
+    public static final int EXPOSURE_MODE_AUTO_EXPOSURE = 0;
+
+    /**
+     * A value to be used with the "ExposureMode" tag.
+     *
+     * @see #TAG_EXPOSURE_MODE
+     */
+    public static final int EXPOSURE_MODE_MANUAL_EXPOSURE = 1;
+
+    /**
+     * A value to be used with the "ExposureMode" tag.
+     *
+     * @see #TAG_EXPOSURE_MODE
+     */
+    public static final int EXPOSURE_MODE_AUTO_BRACKET = 2;
+
+    /**
+     * A tag indicating the white balance mode set when the image was shot.
+     */
+    public static final int TAG_WHITE_BALANCE = 41987;
+
+    /**
+     * A value to be used with the "WhiteBalance" tag.
+     *
+     * @see #TAG_WHITE_BALANCE
+     */
+    public static final int WHITE_BALANCE_AUTO = 0;
+
+    /**
+     * A value to be used with the "WhiteBalance" tag.
+     *
+     * @see #TAG_WHITE_BALANCE
+     */
+    public static final int WHITE_BALANCE_MANUAL = 1;
+
+    /**
+     * A tag indicating the digital zoom ratio when the image was shot.
+     */
+    public static final int TAG_DIGITAL_ZOOM_RATIO = 41988;
+
+    /**
+     * A tag indicating the equivalent focal length assuming a 35mm film
+     * camera, in millimeters.
+     */
+    public static final int TAG_FOCAL_LENGTH_IN_35MM_FILM = 41989;
+
+    /**
+     * A tag indicating the type of scene that was shot.
+     */
+    public static final int TAG_SCENE_CAPTURE_TYPE = 41990;
+
+    /**
+     * A value to be used with the "SceneCaptureType" tag.
+     *
+     * @see #TAG_SCENE_CAPTURE_TYPE
+     */
+    public static final int SCENE_CAPTURE_TYPE_STANDARD = 0;
+
+    /**
+     * A value to be used with the "SceneCaptureType" tag.
+     *
+     * @see #TAG_SCENE_CAPTURE_TYPE
+     */
+    public static final int SCENE_CAPTURE_TYPE_LANDSCAPE = 1;
+
+    /**
+     * A value to be used with the "SceneCaptureType" tag.
+     *
+     * @see #TAG_SCENE_CAPTURE_TYPE
+     */
+    public static final int SCENE_CAPTURE_TYPE_PORTRAIT = 2;
+
+    /**
+     * A value to be used with the "SceneCaptureType" tag.
+     *
+     * @see #TAG_SCENE_CAPTURE_TYPE
+     */
+    public static final int SCENE_CAPTURE_TYPE_NIGHT_SCENE = 3;
+
+    /**
+     * A tag indicating the degree of overall image gain adjustment.
+     */
+    public static final int TAG_GAIN_CONTROL = 41991;
+
+    /**
+     * A value to be used with the "GainControl" tag.
+     *
+     * @see #TAG_GAIN_CONTROL
+     */
+    public static final int GAIN_CONTROL_NONE = 0;
+
+    /**
+     * A value to be used with the "GainControl" tag.
+     *
+     * @see #TAG_GAIN_CONTROL
+     */
+    public static final int GAIN_CONTROL_LOW_GAIN_UP = 1;
+
+    /**
+     * A value to be used with the "GainControl" tag.
+     *
+     * @see #TAG_GAIN_CONTROL
+     */
+    public static final int GAIN_CONTROL_HIGH_GAIN_UP = 2;
+
+    /**
+     * A value to be used with the "GainControl" tag.
+     *
+     * @see #TAG_GAIN_CONTROL
+     */
+    public static final int GAIN_CONTROL_LOW_GAIN_DOWN = 3;
+
+    /**
+     * A value to be used with the "GainControl" tag.
+     *
+     * @see #TAG_GAIN_CONTROL
+     */
+    public static final int GAIN_CONTROL_HIGH_GAIN_DOWN = 4;
+
+    /**
+     * A tag indicating the direction of contrast processing applied
+     * by the camera when the image was shot.
+     */
+    public static final int TAG_CONTRAST = 41992;
+
+    /**
+     * A value to be used with the "Contrast" tag.
+     *
+     * @see #TAG_CONTRAST
+     */
+    public static final int CONTRAST_NORMAL = 0;
+
+    /**
+     * A value to be used with the "Contrast" tag.
+     *
+     * @see #TAG_CONTRAST
+     */
+    public static final int CONTRAST_SOFT = 1;
+
+    /**
+     * A value to be used with the "Contrast" tag.
+     *
+     * @see #TAG_CONTRAST
+     */
+    public static final int CONTRAST_HARD = 2;
+
+    /**
+     * A tag indicating the direction of saturation processing
+     * applied by the camera when the image was shot.
+     */
+    public static final int TAG_SATURATION = 41993;
+
+    /**
+     * A value to be used with the "Saturation" tag.
+     *
+     * @see #TAG_SATURATION
+     */
+    public static final int SATURATION_NORMAL = 0;
+
+    /**
+     * A value to be used with the "Saturation" tag.
+     *
+     * @see #TAG_SATURATION
+     */
+    public static final int SATURATION_LOW = 1;
+
+    /**
+     * A value to be used with the "Saturation" tag.
+     *
+     * @see #TAG_SATURATION
+     */
+    public static final int SATURATION_HIGH = 2;
+
+    /**
+     * A tag indicating the direction of sharpness processing
+     * applied by the camera when the image was shot.
+     */
+    public static final int TAG_SHARPNESS = 41994;
+
+    /**
+     * A value to be used with the "Sharpness" tag.
+     *
+     * @see #TAG_SHARPNESS
+     */
+    public static final int SHARPNESS_NORMAL = 0;
+
+    /**
+     * A value to be used with the "Sharpness" tag.
+     *
+     * @see #TAG_SHARPNESS
+     */
+    public static final int SHARPNESS_SOFT = 1;
+
+    /**
+     * A value to be used with the "Sharpness" tag.
+     *
+     * @see #TAG_SHARPNESS
+     */
+    public static final int SHARPNESS_HARD = 2;
+
+    /**
+     * A tag indicating information on the picture-taking conditions
+     * of a particular camera model.
+     */
+    public static final int TAG_DEVICE_SETTING_DESCRIPTION = 41995;
+
+    /**
+     * A tag indicating the distance to the subject.
+     */
+    public static final int TAG_SUBJECT_DISTANCE_RANGE = 41996;
+
+    /**
+     * A value to be used with the "SubjectDistanceRange" tag.
+     *
+     * @see #TAG_SUBJECT_DISTANCE_RANGE
+     */
+    public static final int SUBJECT_DISTANCE_RANGE_UNKNOWN = 0;
+
+    /**
+     * A value to be used with the "SubjectDistanceRange" tag.
+     *
+     * @see #TAG_SUBJECT_DISTANCE_RANGE
+     */
+    public static final int SUBJECT_DISTANCE_RANGE_MACRO = 1;
+
+    /**
+     * A value to be used with the "SubjectDistanceRange" tag.
+     *
+     * @see #TAG_SUBJECT_DISTANCE_RANGE
+     */
+    public static final int SUBJECT_DISTANCE_RANGE_CLOSE_VIEW = 2;
+
+    /**
+     * A value to be used with the "SubjectDistanceRange" tag.
+     *
+     * @see #TAG_SUBJECT_DISTANCE_RANGE
+     */
+    public static final int SUBJECT_DISTANCE_RANGE_DISTANT_VIEW = 3;
+
+    /**
+     * A tag indicating an identifier assigned uniquely to each image.
+     */
+    public static final int TAG_IMAGE_UNIQUE_ID = 42016;
+
+    // Exif 2.1 private
+
+    // GPS Attribute Information
+    //     0 - GPSVersionID                       (BYTE/4)
+    //     1 - GPSLatitudeRef                     (ASCII/2)
+    //     2 - GPSLatitude                        (RATIONAL/3)
+    //     3 - GPSLongitudeRef                    (ASCII/2)
+    //     4 - GPSLongitude                       (RATIONAL/3)
+    //     5 - GPSAltitudeRef                     (BYTE/1)
+    //     6 - GPSAltitude                        (RATIONAL/1)
+    //     7 - GPSTimeStamp                       (RATIONAL/3)
+    //     8 - GPSSatellites                      (ASCII/any)
+    //     9 - GPSStatus                          (ASCII/2)
+    //    10 - GPSMeasureMode                     (ASCII/2)
+    //    11 - GPSDOP                             (RATIONAL/1)
+    //    12 - GPSSpeedRef                        (ASCII/2)
+    //    13 - GPSSpeed                           (RATIONAL/1)
+    //    14 - GPSTrackRef                        (ASCII/2)
+    //    15 - GPSTrack                           (RATIONAL/1)
+    //    16 - GPSImgDirectionRef                 (ASCII/2)
+    //    17 - GPSImgDirection                    (RATIONAL/1)
+    //    18 - GPSMapDatum                        (ASCII/any)
+    //    19 - GPSDestLatitudeRef                 (ASCII/2)
+    //    20 - GPSDestLatitude                    (RATIONAL/3)
+    //    21 - GPSDestLongitudeRef                (ASCII/2)
+    //    22 - GPSDestLongitude                   (RATIONAL/3)
+    //    23 - GPSDestBearingRef                  (ASCII/2)
+    //    24 - GPSDestBearing                     (RATIONAL/1)
+    //    25 - GPSDestDistanceRef                 (ASCII/2)
+    //    26 - GPSDestDistance                    (RATIONAL/1)
+
+    //     0 - Interoperability Index             (ASCII/any)
+
+    // Exif tags
+
+    static class ExifVersion extends TIFFTag {
+
+        public ExifVersion() {
+            super("Exifversion",
+                  TAG_EXIF_VERSION,
+                  1 << TIFFTag.TIFF_UNDEFINED,
+                  4);
+        }
+    }
+
+    static class FlashPixVersion extends TIFFTag {
+
+        public FlashPixVersion() {
+            super("FlashPixVersion",
+                  TAG_FLASHPIX_VERSION,
+                  1 << TIFFTag.TIFF_UNDEFINED,
+                  4);
+        }
+    }
+
+    static class ColorSpace extends TIFFTag {
+
+        public ColorSpace() {
+            super("ColorSpace",
+                  TAG_COLOR_SPACE,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(COLOR_SPACE_SRGB, "sRGB");
+            addValueName(COLOR_SPACE_UNCALIBRATED, "Uncalibrated");
+        }
+    }
+
+    static class ComponentsConfiguration extends TIFFTag {
+
+        public ComponentsConfiguration() {
+            super("ComponentsConfiguration",
+                  TAG_COMPONENTS_CONFIGURATION,
+                  1 << TIFFTag.TIFF_UNDEFINED,
+                  4);
+
+            addValueName(COMPONENTS_CONFIGURATION_DOES_NOT_EXIST,
+                         "DoesNotExist");
+            addValueName(COMPONENTS_CONFIGURATION_Y, "Y");
+            addValueName(COMPONENTS_CONFIGURATION_CB, "Cb");
+            addValueName(COMPONENTS_CONFIGURATION_CR, "Cr");
+            addValueName(COMPONENTS_CONFIGURATION_R, "R");
+            addValueName(COMPONENTS_CONFIGURATION_G, "G");
+            addValueName(COMPONENTS_CONFIGURATION_B, "B");
+        }
+    }
+
+    static class CompressedBitsPerPixel extends TIFFTag {
+
+        public CompressedBitsPerPixel() {
+            super("CompressedBitsPerPixel",
+                  TAG_COMPRESSED_BITS_PER_PIXEL,
+                  1 << TIFFTag.TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    static class PixelXDimension extends TIFFTag {
+
+        public PixelXDimension() {
+            super("PixelXDimension",
+                  TAG_PIXEL_X_DIMENSION,
+                  (1 << TIFFTag.TIFF_SHORT) |
+                  (1 << TIFFTag.TIFF_LONG),
+                  1);
+        }
+    }
+
+    static class PixelYDimension extends TIFFTag {
+
+        public PixelYDimension() {
+            super("PixelYDimension",
+                  TAG_PIXEL_Y_DIMENSION,
+                  (1 << TIFFTag.TIFF_SHORT) |
+                  (1 << TIFFTag.TIFF_LONG),
+                  1);
+        }
+    }
+
+    static class MakerNote extends TIFFTag {
+
+        public MakerNote() {
+            super("MakerNote",
+                  TAG_MAKER_NOTE,
+                  1 << TIFFTag.TIFF_UNDEFINED);
+        }
+    }
+
+    static class UserComment extends TIFFTag {
+
+        public UserComment() {
+            super("UserComment",
+                  TAG_USER_COMMENT,
+                  1 << TIFFTag.TIFF_UNDEFINED);
+        }
+    }
+
+    static class RelatedSoundFile extends TIFFTag {
+
+        public RelatedSoundFile() {
+            super("RelatedSoundFile",
+                  TAG_RELATED_SOUND_FILE,
+                  1 << TIFFTag.TIFF_ASCII,
+                  13);
+        }
+    }
+
+    static class DateTimeOriginal extends TIFFTag {
+
+        public DateTimeOriginal() {
+            super("DateTimeOriginal",
+                  TAG_DATE_TIME_ORIGINAL,
+                  1 << TIFFTag.TIFF_ASCII,
+                  20);
+        }
+    }
+
+    static class DateTimeDigitized extends TIFFTag {
+
+        public DateTimeDigitized() {
+            super("DateTimeDigitized",
+                  TAG_DATE_TIME_DIGITIZED,
+                  1 << TIFFTag.TIFF_ASCII,
+                  20);
+        }
+    }
+
+    static class SubSecTime extends TIFFTag {
+
+        public SubSecTime() {
+            super("SubSecTime",
+                  TAG_SUB_SEC_TIME,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class SubSecTimeOriginal extends TIFFTag {
+
+        public SubSecTimeOriginal() {
+            super("SubSecTimeOriginal",
+                  TAG_SUB_SEC_TIME_ORIGINAL,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class SubSecTimeDigitized extends TIFFTag {
+
+        public SubSecTimeDigitized() {
+            super("SubSecTimeDigitized",
+                  TAG_SUB_SEC_TIME_DIGITIZED,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class ExposureTime extends TIFFTag {
+
+        public ExposureTime() {
+            super("ExposureTime",
+                  TAG_EXPOSURE_TIME,
+                  1 << TIFFTag.TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    static class FNumber extends TIFFTag {
+
+        public FNumber() {
+            super("FNumber",
+                  TAG_F_NUMBER,
+                  1 << TIFFTag.TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    static class ExposureProgram extends TIFFTag {
+
+        public ExposureProgram() {
+            super("ExposureProgram",
+                  TAG_EXPOSURE_PROGRAM,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(EXPOSURE_PROGRAM_NOT_DEFINED, "Not Defined");
+            addValueName(EXPOSURE_PROGRAM_MANUAL, "Manual");
+            addValueName(EXPOSURE_PROGRAM_NORMAL_PROGRAM, "Normal Program");
+            addValueName(EXPOSURE_PROGRAM_APERTURE_PRIORITY,
+                         "Aperture Priority");
+            addValueName(EXPOSURE_PROGRAM_SHUTTER_PRIORITY,
+                         "Shutter Priority");
+            addValueName(EXPOSURE_PROGRAM_CREATIVE_PROGRAM,
+                         "Creative Program");
+            addValueName(EXPOSURE_PROGRAM_ACTION_PROGRAM, "Action Program");
+            addValueName(EXPOSURE_PROGRAM_PORTRAIT_MODE, "Portrait Mode");
+            addValueName(EXPOSURE_PROGRAM_LANDSCAPE_MODE, "Landscape Mode");
+        }
+    }
+
+    static class SpectralSensitivity extends TIFFTag {
+        public SpectralSensitivity() {
+            super("SpectralSensitivity",
+                  TAG_SPECTRAL_SENSITIVITY,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    static class ISOSpeedRatings extends TIFFTag {
+
+        public ISOSpeedRatings() {
+            super("ISOSpeedRatings",
+                  TAG_ISO_SPEED_RATINGS,
+                  1 << TIFFTag.TIFF_SHORT);
+        }
+    }
+
+    static class OECF extends TIFFTag {
+
+        public OECF() {
+            super("OECF",
+                  TAG_OECF,
+                  1 << TIFFTag.TIFF_UNDEFINED);
+        }
+    }
+
+    static class ShutterSpeedValue extends TIFFTag {
+
+        public ShutterSpeedValue() {
+            super("ShutterSpeedValue",
+                  TAG_SHUTTER_SPEED_VALUE,
+                  1 << TIFFTag.TIFF_SRATIONAL,
+                  1);
+        }
+    }
+
+    static class ApertureValue extends TIFFTag {
+
+        public ApertureValue() {
+            super("ApertureValue",
+                  TAG_APERTURE_VALUE,
+                  1 << TIFFTag.TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    static class BrightnessValue extends TIFFTag {
+
+        public BrightnessValue() {
+            super("BrightnessValue",
+                  TAG_BRIGHTNESS_VALUE,
+                  1 << TIFFTag.TIFF_SRATIONAL,
+                  1);
+        }
+    }
+
+    static class ExposureBiasValue extends TIFFTag {
+
+        public ExposureBiasValue() {
+            super("ExposureBiasValue",
+                  TAG_EXPOSURE_BIAS_VALUE,
+                  1 << TIFFTag.TIFF_SRATIONAL,
+                  1);
+        }
+    }
+
+    static class MaxApertureValue extends TIFFTag {
+
+        public MaxApertureValue() {
+            super("MaxApertureValue",
+                  TAG_MAX_APERTURE_VALUE,
+                  1 << TIFFTag.TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    static class SubjectDistance extends TIFFTag {
+
+        public SubjectDistance() {
+            super("SubjectDistance",
+                  TAG_SUBJECT_DISTANCE,
+                  1 << TIFFTag.TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    static class MeteringMode extends TIFFTag {
+
+        public MeteringMode() {
+            super("MeteringMode",
+                  TAG_METERING_MODE,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(METERING_MODE_UNKNOWN, "Unknown");
+            addValueName(METERING_MODE_AVERAGE, "Average");
+            addValueName(METERING_MODE_CENTER_WEIGHTED_AVERAGE,
+                         "CenterWeightedAverage");
+            addValueName(METERING_MODE_SPOT, "Spot");
+            addValueName(METERING_MODE_MULTI_SPOT, "MultiSpot");
+            addValueName(METERING_MODE_PATTERN, "Pattern");
+            addValueName(METERING_MODE_PARTIAL, "Partial");
+            addValueName(METERING_MODE_OTHER, "Other");
+        }
+    }
+
+    static class LightSource extends TIFFTag {
+
+        public LightSource() {
+            super("LightSource",
+                  TAG_LIGHT_SOURCE,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(LIGHT_SOURCE_UNKNOWN, "Unknown");
+            addValueName(LIGHT_SOURCE_DAYLIGHT, "Daylight");
+            addValueName(LIGHT_SOURCE_FLUORESCENT, "Fluorescent");
+            addValueName(LIGHT_SOURCE_TUNGSTEN, "Tungsten");
+            addValueName(LIGHT_SOURCE_STANDARD_LIGHT_A, "Standard Light A");
+            addValueName(LIGHT_SOURCE_STANDARD_LIGHT_B, "Standard Light B");
+            addValueName(LIGHT_SOURCE_STANDARD_LIGHT_C, "Standard Light C");
+            addValueName(LIGHT_SOURCE_D55, "D55");
+            addValueName(LIGHT_SOURCE_D65, "D65");
+            addValueName(LIGHT_SOURCE_D75, "D75");
+            addValueName(LIGHT_SOURCE_OTHER, "Other");
+        }
+    }
+
+    static class Flash extends TIFFTag {
+
+        public Flash() {
+            super("Flash",
+                  TAG_FLASH,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(FLASH_DID_NOT_FIRE, "Flash Did Not Fire");
+            addValueName(FLASH_FIRED, "Flash Fired");
+            addValueName(FLASH_STROBE_RETURN_LIGHT_NOT_DETECTED,
+                         "Strobe Return Light Not Detected");
+            addValueName(FLASH_STROBE_RETURN_LIGHT_DETECTED,
+                         "Strobe Return Light Detected");
+        }
+    }
+
+    static class FocalLength extends TIFFTag {
+
+        public FocalLength() {
+            super("FocalLength",
+                  TAG_FOCAL_LENGTH,
+                  1 << TIFFTag.TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    static class SubjectArea extends TIFFTag {
+
+        public SubjectArea() {
+            super("SubjectArea",
+                  TAG_SUBJECT_AREA,
+                  1 << TIFFTag.TIFF_SHORT);
+        }
+    }
+
+    static class FlashEnergy extends TIFFTag {
+
+        public FlashEnergy() {
+            super("FlashEnergy",
+                  TAG_FLASH_ENERGY,
+                  1 << TIFFTag.TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    static class SpatialFrequencyResponse extends TIFFTag {
+
+        public SpatialFrequencyResponse() {
+            super("SpatialFrequencyResponse",
+                  TAG_SPATIAL_FREQUENCY_RESPONSE,
+                  1 << TIFFTag.TIFF_UNDEFINED);
+        }
+    }
+
+    static class FocalPlaneXResolution extends TIFFTag {
+
+        public FocalPlaneXResolution() {
+            super("FocalPlaneXResolution",
+                  TAG_FOCAL_PLANE_X_RESOLUTION,
+                  1 << TIFFTag.TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    static class FocalPlaneYResolution extends TIFFTag {
+
+        public FocalPlaneYResolution() {
+            super("FocalPlaneYResolution",
+                  TAG_FOCAL_PLANE_Y_RESOLUTION,
+                  1 << TIFFTag.TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    static class FocalPlaneResolutionUnit extends TIFFTag {
+
+        public FocalPlaneResolutionUnit() {
+            super("FocalPlaneResolutionUnit",
+                  TAG_FOCAL_PLANE_RESOLUTION_UNIT,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(FOCAL_PLANE_RESOLUTION_UNIT_NONE, "None");
+            addValueName(FOCAL_PLANE_RESOLUTION_UNIT_INCH, "Inch");
+            addValueName(FOCAL_PLANE_RESOLUTION_UNIT_CENTIMETER, "Centimeter");
+        }
+    }
+
+    static class SubjectLocation extends TIFFTag {
+
+        public SubjectLocation() {
+            super("SubjectLocation",
+                  TAG_SUBJECT_LOCATION,
+                  1 << TIFFTag.TIFF_SHORT,
+                  2);
+        }
+    }
+
+    static class ExposureIndex extends TIFFTag {
+
+        public ExposureIndex() {
+            super("ExposureIndex",
+                  TAG_EXPOSURE_INDEX,
+                  1 << TIFFTag.TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    static class SensingMethod extends TIFFTag {
+
+        public SensingMethod() {
+            super("SensingMethod",
+                  TAG_SENSING_METHOD,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(SENSING_METHOD_NOT_DEFINED, "Not Defined");
+            addValueName(SENSING_METHOD_ONE_CHIP_COLOR_AREA_SENSOR,
+                         "One-chip color area sensor");
+            addValueName(SENSING_METHOD_TWO_CHIP_COLOR_AREA_SENSOR,
+                         "Two-chip color area sensor");
+            addValueName(SENSING_METHOD_THREE_CHIP_COLOR_AREA_SENSOR,
+                         "Three-chip color area sensor");
+            addValueName(SENSING_METHOD_COLOR_SEQUENTIAL_AREA_SENSOR,
+                         "Color sequential area sensor");
+            addValueName(SENSING_METHOD_TRILINEAR_SENSOR, "Trilinear sensor");
+            addValueName(SENSING_METHOD_COLOR_SEQUENTIAL_LINEAR_SENSOR,
+                         "Color sequential linear sensor");
+        }
+    }
+
+    static class FileSource extends TIFFTag {
+
+        public FileSource() {
+            super("FileSource",
+                  TAG_FILE_SOURCE,
+                  1 << TIFFTag.TIFF_UNDEFINED,
+                  1);
+
+            addValueName(FILE_SOURCE_DSC, "DSC");
+        }
+    }
+
+    static class SceneType extends TIFFTag {
+
+        public SceneType() {
+            super("SceneType",
+                  TAG_SCENE_TYPE,
+                  1 << TIFFTag.TIFF_UNDEFINED,
+                  1);
+
+            addValueName(SCENE_TYPE_DSC, "A directly photographed image");
+        }
+    }
+
+    static class CFAPattern extends TIFFTag {
+
+        public CFAPattern() {
+            super("CFAPattern",
+                  TAG_CFA_PATTERN,
+                  1 << TIFFTag.TIFF_UNDEFINED);
+        }
+    }
+
+    static class CustomRendered extends TIFFTag {
+
+        public CustomRendered() {
+            super("CustomRendered",
+                  TAG_CUSTOM_RENDERED,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(CUSTOM_RENDERED_NORMAL, "Normal process");
+            addValueName(CUSTOM_RENDERED_CUSTOM, "Custom process");
+        }
+    }
+
+    static class ExposureMode extends TIFFTag {
+
+        public ExposureMode() {
+            super("ExposureMode",
+                  TAG_EXPOSURE_MODE,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(EXPOSURE_MODE_AUTO_EXPOSURE, "Auto exposure");
+            addValueName(EXPOSURE_MODE_MANUAL_EXPOSURE, "Manual exposure");
+            addValueName(EXPOSURE_MODE_AUTO_BRACKET, "Auto bracket");
+        }
+    }
+
+    static class WhiteBalance extends TIFFTag {
+
+        public WhiteBalance() {
+            super("WhiteBalance",
+                  TAG_WHITE_BALANCE,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(WHITE_BALANCE_AUTO, "Auto white balance");
+            addValueName(WHITE_BALANCE_MANUAL, "Manual white balance");
+        }
+    }
+
+    static class DigitalZoomRatio extends TIFFTag {
+
+        public DigitalZoomRatio() {
+            super("DigitalZoomRatio",
+                  TAG_DIGITAL_ZOOM_RATIO,
+                  1 << TIFFTag.TIFF_RATIONAL,
+                  1);
+        }
+    }
+
+    static class FocalLengthIn35mmFilm extends TIFFTag {
+
+        public FocalLengthIn35mmFilm() {
+            super("FocalLengthIn35mmFilm",
+                  TAG_FOCAL_LENGTH_IN_35MM_FILM,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+        }
+    }
+
+    static class SceneCaptureType extends TIFFTag {
+
+        public SceneCaptureType() {
+            super("SceneCaptureType",
+                  TAG_SCENE_CAPTURE_TYPE,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(SCENE_CAPTURE_TYPE_STANDARD, "Standard");
+            addValueName(SCENE_CAPTURE_TYPE_LANDSCAPE, "Landscape");
+            addValueName(SCENE_CAPTURE_TYPE_PORTRAIT, "Portrait");
+            addValueName(SCENE_CAPTURE_TYPE_NIGHT_SCENE, "Night scene");
+        }
+    }
+
+    static class GainControl extends TIFFTag {
+
+        public GainControl() {
+            super("GainControl",
+                  TAG_GAIN_CONTROL,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(GAIN_CONTROL_NONE, "None");
+            addValueName(GAIN_CONTROL_LOW_GAIN_UP, "Low gain up");
+            addValueName(GAIN_CONTROL_HIGH_GAIN_UP, "High gain up");
+            addValueName(GAIN_CONTROL_LOW_GAIN_DOWN, "Low gain down");
+            addValueName(GAIN_CONTROL_HIGH_GAIN_DOWN, "High gain down");
+        }
+    }
+
+    static class Contrast extends TIFFTag {
+
+        public Contrast() {
+            super("Contrast",
+                  TAG_CONTRAST,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(CONTRAST_NORMAL, "Normal");
+            addValueName(CONTRAST_SOFT, "Soft");
+            addValueName(CONTRAST_HARD, "Hard");
+        }
+    }
+
+    static class Saturation extends TIFFTag {
+
+        public Saturation() {
+            super("Saturation",
+                  TAG_SATURATION,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(SATURATION_NORMAL, "Normal");
+            addValueName(SATURATION_LOW, "Low saturation");
+            addValueName(SATURATION_HIGH, "High saturation");
+        }
+    }
+
+    static class Sharpness extends TIFFTag {
+
+        public Sharpness() {
+            super("Sharpness",
+                  TAG_SHARPNESS,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(SHARPNESS_NORMAL, "Normal");
+            addValueName(SHARPNESS_SOFT, "Soft");
+            addValueName(SHARPNESS_HARD, "Hard");
+        }
+    }
+
+    static class DeviceSettingDescription extends TIFFTag {
+
+        public DeviceSettingDescription() {
+            super("DeviceSettingDescription",
+                  TAG_DEVICE_SETTING_DESCRIPTION,
+                  1 << TIFFTag.TIFF_UNDEFINED);
+        }
+    }
+
+    static class SubjectDistanceRange extends TIFFTag {
+
+        public SubjectDistanceRange() {
+            super("SubjectDistanceRange",
+                  TAG_SUBJECT_DISTANCE_RANGE,
+                  1 << TIFFTag.TIFF_SHORT,
+                  1);
+
+            addValueName(SUBJECT_DISTANCE_RANGE_UNKNOWN, "unknown");
+            addValueName(SUBJECT_DISTANCE_RANGE_MACRO, "Macro");
+            addValueName(SUBJECT_DISTANCE_RANGE_CLOSE_VIEW, "Close view");
+            addValueName(SUBJECT_DISTANCE_RANGE_DISTANT_VIEW, "Distant view");
+        }
+    }
+
+    static class ImageUniqueID extends TIFFTag {
+
+        public ImageUniqueID() {
+            super("ImageUniqueID",
+                  TAG_IMAGE_UNIQUE_ID,
+                  1 << TIFFTag.TIFF_ASCII,
+                  33);
+        }
+    }
+
+    static class InteroperabilityIFD extends TIFFTag {
+        public InteroperabilityIFD() {
+            super("InteroperabilityIFD",
+                  TAG_INTEROPERABILITY_IFD_POINTER,
+                  ExifInteroperabilityTagSet.getInstance());
+        }
+    }
+
+    private static List<TIFFTag> tags;
+
+    private static void initTags() {
+        tags = new ArrayList<TIFFTag>(42);
+
+        tags.add(new ExifTIFFTagSet.ExifVersion());
+        tags.add(new ExifTIFFTagSet.FlashPixVersion());
+        tags.add(new ExifTIFFTagSet.ColorSpace());
+        tags.add(new ExifTIFFTagSet.ComponentsConfiguration());
+        tags.add(new ExifTIFFTagSet.CompressedBitsPerPixel());
+        tags.add(new ExifTIFFTagSet.PixelXDimension());
+        tags.add(new ExifTIFFTagSet.PixelYDimension());
+        tags.add(new ExifTIFFTagSet.MakerNote());
+        tags.add(new ExifTIFFTagSet.UserComment());
+        tags.add(new ExifTIFFTagSet.RelatedSoundFile());
+        tags.add(new ExifTIFFTagSet.DateTimeOriginal());
+        tags.add(new ExifTIFFTagSet.DateTimeDigitized());
+        tags.add(new ExifTIFFTagSet.SubSecTime());
+        tags.add(new ExifTIFFTagSet.SubSecTimeOriginal());
+        tags.add(new ExifTIFFTagSet.SubSecTimeDigitized());
+        tags.add(new ExifTIFFTagSet.ExposureTime());
+        tags.add(new ExifTIFFTagSet.FNumber());
+        tags.add(new ExifTIFFTagSet.ExposureProgram());
+        tags.add(new ExifTIFFTagSet.SpectralSensitivity());
+        tags.add(new ExifTIFFTagSet.ISOSpeedRatings());
+        tags.add(new ExifTIFFTagSet.OECF());
+        tags.add(new ExifTIFFTagSet.ShutterSpeedValue());
+        tags.add(new ExifTIFFTagSet.ApertureValue());
+        tags.add(new ExifTIFFTagSet.BrightnessValue());
+        tags.add(new ExifTIFFTagSet.ExposureBiasValue());
+        tags.add(new ExifTIFFTagSet.MaxApertureValue());
+        tags.add(new ExifTIFFTagSet.SubjectDistance());
+        tags.add(new ExifTIFFTagSet.MeteringMode());
+        tags.add(new ExifTIFFTagSet.LightSource());
+        tags.add(new ExifTIFFTagSet.Flash());
+        tags.add(new ExifTIFFTagSet.FocalLength());
+        tags.add(new ExifTIFFTagSet.SubjectArea());
+        tags.add(new ExifTIFFTagSet.FlashEnergy());
+        tags.add(new ExifTIFFTagSet.SpatialFrequencyResponse());
+        tags.add(new ExifTIFFTagSet.FocalPlaneXResolution());
+        tags.add(new ExifTIFFTagSet.FocalPlaneYResolution());
+        tags.add(new ExifTIFFTagSet.FocalPlaneResolutionUnit());
+        tags.add(new ExifTIFFTagSet.SubjectLocation());
+        tags.add(new ExifTIFFTagSet.ExposureIndex());
+        tags.add(new ExifTIFFTagSet.SensingMethod());
+        tags.add(new ExifTIFFTagSet.FileSource());
+        tags.add(new ExifTIFFTagSet.SceneType());
+        tags.add(new ExifTIFFTagSet.CFAPattern());
+        tags.add(new ExifTIFFTagSet.CustomRendered());
+        tags.add(new ExifTIFFTagSet.ExposureMode());
+        tags.add(new ExifTIFFTagSet.WhiteBalance());
+        tags.add(new ExifTIFFTagSet.DigitalZoomRatio());
+        tags.add(new ExifTIFFTagSet.FocalLengthIn35mmFilm());
+        tags.add(new ExifTIFFTagSet.SceneCaptureType());
+        tags.add(new ExifTIFFTagSet.GainControl());
+        tags.add(new ExifTIFFTagSet.Contrast());
+        tags.add(new ExifTIFFTagSet.Saturation());
+        tags.add(new ExifTIFFTagSet.Sharpness());
+        tags.add(new ExifTIFFTagSet.DeviceSettingDescription());
+        tags.add(new ExifTIFFTagSet.SubjectDistanceRange());
+        tags.add(new ExifTIFFTagSet.ImageUniqueID());
+        tags.add(new ExifTIFFTagSet.InteroperabilityIFD());
+    }
+
+    private ExifTIFFTagSet() {
+        super(tags);
+    }
+
+    /**
+     * Returns a shared instance of an <code>ExifTIFFTagSet</code>.
+     *
+     * @return an <code>ExifTIFFTagSet</code> instance.
+     */
+    public synchronized static ExifTIFFTagSet getInstance() {
+        if (theInstance == null) {
+            initTags();
+            theInstance = new ExifTIFFTagSet();
+            tags = null;
+        }
+        return theInstance;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/FaxTIFFTagSet.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package javax.imageio.plugins.tiff;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class representing the extra tags found in a
+ * <a href="http://tools.ietf.org/html/rfc2306"> TIFF-F</a> (RFC 2036) file.
+ *
+ * @since 1.9
+ */
+public class FaxTIFFTagSet extends TIFFTagSet {
+
+    private static FaxTIFFTagSet theInstance = null;
+
+    /** Tag indicating the number of bad fax lines (type SHORT or LONG). */
+    public static final int TAG_BAD_FAX_LINES = 326;
+
+    /**
+     * Tag indicating the number of lines of clean fax data (type
+     * SHORT).
+     *
+     * @see #CLEAN_FAX_DATA_NO_ERRORS
+     * @see #CLEAN_FAX_DATA_ERRORS_CORRECTED
+     * @see #CLEAN_FAX_DATA_ERRORS_UNCORRECTED
+     */
+    public static final int TAG_CLEAN_FAX_DATA = 327;
+
+    /**
+     * A value to be used with the "CleanFaxData" tag.
+     *
+     * @see #TAG_CLEAN_FAX_DATA
+     */
+    public static final int CLEAN_FAX_DATA_NO_ERRORS = 0;
+
+    /**
+     * A value to be used with the "CleanFaxData" tag.
+     *
+     * @see #TAG_CLEAN_FAX_DATA
+     */
+    public static final int CLEAN_FAX_DATA_ERRORS_CORRECTED = 1;
+
+    /**
+     * A value to be used with the "CleanFaxData" tag.
+     *
+     * @see #TAG_CLEAN_FAX_DATA
+     */
+    public static final int CLEAN_FAX_DATA_ERRORS_UNCORRECTED = 2;
+
+    /**
+     * Tag indicating the number of consecutive bad lines (type
+     * SHORT or LONG).
+     */
+    public static final int TAG_CONSECUTIVE_BAD_LINES = 328;
+
+    static class BadFaxLines extends TIFFTag {
+
+        public BadFaxLines() {
+            super("BadFaxLines",
+                  TAG_BAD_FAX_LINES,
+                  1 << TIFF_SHORT |
+                  1 << TIFF_LONG,
+                  1);
+        }
+    }
+
+    static class CleanFaxData extends TIFFTag {
+
+        public CleanFaxData() {
+            super("CleanFaxData",
+                  TAG_CLEAN_FAX_DATA,
+                  1 << TIFF_SHORT,
+                  1);
+
+            addValueName(CLEAN_FAX_DATA_NO_ERRORS,
+                         "No errors");
+            addValueName(CLEAN_FAX_DATA_ERRORS_CORRECTED,
+                         "Errors corrected");
+            addValueName(CLEAN_FAX_DATA_ERRORS_UNCORRECTED,
+                         "Errors uncorrected");
+        }
+    }
+
+    static class ConsecutiveBadFaxLines extends TIFFTag {
+
+        public ConsecutiveBadFaxLines() {
+            super("ConsecutiveBadFaxLines",
+                  TAG_CONSECUTIVE_BAD_LINES,
+                  1 << TIFF_SHORT |
+                  1 << TIFF_LONG,
+                  1);
+        }
+    }
+
+    private static List<TIFFTag> tags;
+
+    private static void initTags() {
+        tags = new ArrayList<TIFFTag>(42);
+
+        tags.add(new FaxTIFFTagSet.BadFaxLines());
+        tags.add(new FaxTIFFTagSet.CleanFaxData());
+        tags.add(new FaxTIFFTagSet.ConsecutiveBadFaxLines());
+    }
+
+    private FaxTIFFTagSet() {
+        super(tags);
+    }
+
+    /**
+     * Returns a shared instance of a <code>FaxTIFFTagSet</code>.
+     *
+     * @return a <code>FaxTIFFTagSet</code> instance.
+     */
+    public synchronized static FaxTIFFTagSet getInstance() {
+        if (theInstance == null) {
+            initTags();
+            theInstance = new FaxTIFFTagSet();
+            tags = null;
+        }
+        return theInstance;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/GeoTIFFTagSet.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,152 @@
+/*
+ * 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 javax.imageio.plugins.tiff;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class representing the tags found in a GeoTIFF IFD.  GeoTIFF is a
+ * standard for annotating georeferenced or geocoded raster imagery.
+ * The GeoTIFF specification may be found at <a
+ * href="http://www.remotesensing.org/geotiff/spec/geotiffhome.html">
+ * <code>http://www.remotesensing.org/geotiff/spec/geotiffhome.html</code>
+ * </a>. This class does <i>not</i> handle the <i>GeoKey</i>s referenced
+ * from a <i>GeoKeyDirectoryTag</i> as those are not TIFF tags per se.
+ *
+ * <p>The definitions of the data types referenced by the field
+ * definitions may be found in the {@link TIFFTag TIFFTag} class.</p>
+ *
+ * @since 1.9
+ */
+public class GeoTIFFTagSet extends TIFFTagSet {
+
+    private static GeoTIFFTagSet theInstance = null;
+
+    /**
+     * A tag used to specify the size of raster pixel spacing in
+     * model space units.
+     */
+    public static final int TAG_MODEL_PIXEL_SCALE = 33550;
+
+    /**
+     * A tag used to specify the transformation matrix between the raster
+     * space and the model space.
+     */
+    public static final int TAG_MODEL_TRANSFORMATION = 34264;
+
+    /** A tag used to store raster-to-model tiepoint pairs. */
+    public static final int TAG_MODEL_TIE_POINT = 33922;
+
+    /** A tag used to store the <i>GeoKey</i> directory. */
+    public static final int TAG_GEO_KEY_DIRECTORY = 34735;
+
+    /** A tag used to store all <code>double</code>-values <i>GeoKey</i>s. */
+    public static final int TAG_GEO_DOUBLE_PARAMS = 34736;
+
+    /** A tag used to store all ASCII-values <i>GeoKey</i>s. */
+    public static final int TAG_GEO_ASCII_PARAMS = 34737;
+
+    // GeoTIFF tags
+
+    static class ModelPixelScale extends TIFFTag {
+        public ModelPixelScale() {
+            super("ModelPixelScaleTag",
+                  TAG_MODEL_PIXEL_SCALE,
+                  1 << TIFFTag.TIFF_DOUBLE);
+        }
+    }
+
+    static class ModelTransformation extends TIFFTag {
+        public ModelTransformation() {
+            super("ModelTransformationTag",
+                  TAG_MODEL_TRANSFORMATION,
+                  1 << TIFFTag.TIFF_DOUBLE);
+        }
+    }
+
+    static class ModelTiePoint extends TIFFTag {
+        public ModelTiePoint() {
+            super("ModelTiePointTag",
+                  TAG_MODEL_TIE_POINT,
+                  1 << TIFFTag.TIFF_DOUBLE);
+        }
+    }
+
+    static class GeoKeyDirectory extends TIFFTag {
+        public GeoKeyDirectory() {
+            super("GeoKeyDirectory",
+                  TAG_GEO_KEY_DIRECTORY,
+                  1 << TIFFTag.TIFF_SHORT);
+        }
+    }
+
+    static class GeoDoubleParams extends TIFFTag {
+        public GeoDoubleParams() {
+            super("GeoDoubleParams",
+                  TAG_GEO_DOUBLE_PARAMS,
+                  1 << TIFFTag.TIFF_DOUBLE);
+        }
+    }
+
+    static class GeoAsciiParams extends TIFFTag {
+        public GeoAsciiParams() {
+            super("GeoAsciiParams",
+                  TAG_GEO_ASCII_PARAMS,
+                  1 << TIFFTag.TIFF_ASCII);
+        }
+    }
+
+    private static List<TIFFTag> tags;
+
+    private static void initTags() {
+        tags = new ArrayList<TIFFTag>(42);
+
+        tags.add(new GeoTIFFTagSet.ModelPixelScale());
+        tags.add(new GeoTIFFTagSet.ModelTransformation());
+        tags.add(new GeoTIFFTagSet.ModelTiePoint());
+        tags.add(new GeoTIFFTagSet.GeoKeyDirectory());
+        tags.add(new GeoTIFFTagSet.GeoDoubleParams());
+        tags.add(new GeoTIFFTagSet.GeoAsciiParams());
+    }
+
+    private GeoTIFFTagSet() {
+        super(tags);
+    }
+
+    /**
+     * Returns a shared instance of a <code>GeoTIFFTagSet</code>.
+     *
+     * @return a <code>GeoTIFFTagSet</code> instance.
+     */
+    public synchronized static GeoTIFFTagSet getInstance() {
+        if (theInstance == null) {
+            initTags();
+            theInstance = new GeoTIFFTagSet();
+            tags = null;
+        }
+        return theInstance;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFDirectory.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,471 @@
+/*
+ * 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 javax.imageio.plugins.tiff;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import javax.imageio.metadata.IIOInvalidTreeException;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+import com.sun.imageio.plugins.tiff.TIFFIFD;
+import com.sun.imageio.plugins.tiff.TIFFImageMetadata;
+
+/**
+ * A convenience class for simplifying interaction with TIFF native
+ * image metadata. A TIFF image metadata tree represents an Image File
+ * Directory (IFD) from a TIFF 6.0 stream. An IFD consists of a number of
+ * IFD Entries each of which associates an identifying tag number with
+ * a compatible value. A <code>TIFFDirectory</code> instance corresponds
+ * to an IFD and contains a set of {@link TIFFField}s each of which
+ * corresponds to an IFD Entry in the IFD.
+ *
+ * <p>When reading, a <code>TIFFDirectory</code> may be created by passing
+ * the value returned by {@link javax.imageio.ImageReader#getImageMetadata
+ * ImageReader.getImageMetadata()} to {@link #createFromMetadata
+ * createFromMetadata()}. The {@link TIFFField}s in the directory may then
+ * be obtained using the accessor methods provided in this class.</p>
+ *
+ * <p>When writing, an {@link IIOMetadata} object for use by one of the
+ * <code>write()</code> methods of {@link javax.imageio.ImageWriter} may be
+ * created from a <code>TIFFDirectory</code> by {@link #getAsMetadata()}.
+ * The <code>TIFFDirectory</code> itself may be created by construction or
+ * from the <code>IIOMetadata</code> object returned by
+ * {@link javax.imageio.ImageWriter#getDefaultImageMetadata
+ * ImageWriter.getDefaultImageMetadata()}. The <code>TIFFField</code>s in the
+ * directory may be set using the mutator methods provided in this class.</p>
+ *
+ * <p>A <code>TIFFDirectory</code> is aware of the tag numbers in the
+ * group of {@link TIFFTagSet}s associated with it. When
+ * a <code>TIFFDirectory</code> is created from a native image metadata
+ * object, these tag sets are derived from the <tt>tagSets</tt> attribute
+ * of the <tt>TIFFIFD</tt> node.</p>
+ *
+ * <p>A <code>TIFFDirectory</code> might also have a parent {@link TIFFTag}.
+ * This will occur if the directory represents an IFD other than the root
+ * IFD of the image. The parent tag is the tag of the IFD Entry which is a
+ * pointer to the IFD represented by this <code>TIFFDirectory</code>. The
+ * {@link TIFFTag#isIFDPointer} method of this parent <code>TIFFTag</code>
+ * must return <code>true</code>.  When a <code>TIFFDirectory</code> is
+ * created from a native image metadata object, the parent tag set is set
+ * from the <tt>parentTagName</tt> attribute of the corresponding
+ * <tt>TIFFIFD</tt> node. Note that a <code>TIFFDirectory</code> instance
+ * which has a non-<code>null</code> parent tag will be contained in the
+ * data field of a <code>TIFFField</code> instance which has a tag field
+ * equal to the contained directory's parent tag.</p>
+ *
+ * <p>As an example consider an Exif image. The <code>TIFFDirectory</code>
+ * instance corresponding to the Exif IFD in the Exif stream would have parent
+ * tag {@link ExifParentTIFFTagSet#TAG_EXIF_IFD_POINTER TAG_EXIF_IFD_POINTER}
+ * and would include {@link ExifTIFFTagSet} in its group of known tag sets.
+ * The <code>TIFFDirectory</code> corresponding to this Exif IFD will be
+ * contained in the data field of a <code>TIFFField</code> which will in turn
+ * be contained in the <code>TIFFDirectory</code> corresponding to the primary
+ * IFD of the Exif image which will itself have a <code>null</code>-valued
+ * parent tag.</p>
+ *
+ * <p><b>Note that this implementation is not synchronized. </b>If multiple
+ * threads use a <code>TIFFDirectory</code> instance concurrently, and at
+ * least one of the threads modifies the directory, for example, by adding
+ * or removing <code>TIFFField</code>s or <code>TIFFTagSet</code>s, it
+ * <i>must</i> be synchronized externally.</p>
+ *
+ * @since 1.9
+ * @see   IIOMetadata
+ * @see   TIFFField
+ * @see   TIFFTag
+ * @see   TIFFTagSet
+ */
+public class TIFFDirectory implements Cloneable {
+
+    /** The largest low-valued tag number in the TIFF 6.0 specification. */
+    private static final int MAX_LOW_FIELD_TAG_NUM =
+        BaselineTIFFTagSet.TAG_REFERENCE_BLACK_WHITE;
+
+    /** The <code>TIFFTagSets</code> associated with this directory. */
+    private List<TIFFTagSet> tagSets;
+
+    /** The parent <code>TIFFTag</code> of this directory. */
+    private TIFFTag parentTag;
+
+    /**
+     * The fields in this directory which have a low tag number. These are
+     * managed as an array for efficiency as they are the most common fields.
+     */
+    private TIFFField[] lowFields = new TIFFField[MAX_LOW_FIELD_TAG_NUM + 1];
+
+    /** The number of low tag numbered fields in the directory. */
+    private int numLowFields = 0;
+
+    /**
+     * A mapping of <code>Integer</code> tag numbers to <code>TIFFField</code>s
+     * for fields which are not low tag numbered.
+     */
+    private Map<Integer,TIFFField> highFields = new TreeMap<Integer,TIFFField>();
+
+    /**
+     * Creates a <code>TIFFDirectory</code> instance from the contents of
+     * an image metadata object. The supplied object must support an image
+     * metadata format supported by the TIFF {@link javax.imageio.ImageWriter}
+     * plug-in. This will usually be either the TIFF native image metadata
+     * format <tt>javax_imageio_tiff_image_1.0</tt> or the Java
+     * Image I/O standard metadata format <tt>javax_imageio_1.0</tt>.
+     *
+     * @param tiffImageMetadata A metadata object which supports a compatible
+     * image metadata format.
+     *
+     * @return A <code>TIFFDirectory</code> populated from the contents of
+     * the supplied metadata object.
+     *
+     * @throws NullPointerException if <code>tiffImageMetadata</code>
+     * is <code>null</code>.
+     * @throws IllegalArgumentException if <code>tiffImageMetadata</code>
+     * does not support a compatible image metadata format.
+     * @throws IIOInvalidTreeException if the supplied metadata object
+     * cannot be parsed.
+     */
+    public static TIFFDirectory
+        createFromMetadata(IIOMetadata tiffImageMetadata)
+        throws IIOInvalidTreeException {
+
+        if(tiffImageMetadata == null) {
+            throw new NullPointerException("tiffImageMetadata == null");
+        }
+
+        TIFFImageMetadata tim;
+        if(tiffImageMetadata instanceof TIFFImageMetadata) {
+            tim = (TIFFImageMetadata)tiffImageMetadata;
+        } else {
+            // Create a native metadata object.
+            ArrayList<TIFFTagSet> l = new ArrayList<TIFFTagSet>(1);
+            l.add(BaselineTIFFTagSet.getInstance());
+            tim = new TIFFImageMetadata(l);
+
+            // Determine the format name to use.
+            String formatName = null;
+            if(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME.equals
+               (tiffImageMetadata.getNativeMetadataFormatName())) {
+                formatName = TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME;
+            } else {
+                String[] extraNames =
+                    tiffImageMetadata.getExtraMetadataFormatNames();
+                if(extraNames != null) {
+                    for(int i = 0; i < extraNames.length; i++) {
+                        if(TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME.equals
+                           (extraNames[i])) {
+                            formatName = extraNames[i];
+                            break;
+                        }
+                    }
+                }
+
+                if(formatName == null) {
+                    if(tiffImageMetadata.isStandardMetadataFormatSupported()) {
+                        formatName =
+                            IIOMetadataFormatImpl.standardMetadataFormatName;
+                    } else {
+                        throw new IllegalArgumentException
+                            ("Parameter does not support required metadata format!");
+                    }
+                }
+            }
+
+            // Set the native metadata object from the tree.
+            tim.setFromTree(formatName,
+                            tiffImageMetadata.getAsTree(formatName));
+        }
+
+        return tim.getRootIFD();
+    }
+
+    /**
+     * Converts a <code>TIFFDirectory</code> to a <code>TIFFIFD</code>.
+     */
+    private static TIFFIFD getDirectoryAsIFD(TIFFDirectory dir) {
+        if(dir instanceof TIFFIFD) {
+            return (TIFFIFD)dir;
+        }
+
+        TIFFIFD ifd = new TIFFIFD(Arrays.asList(dir.getTagSets()),
+                                  dir.getParentTag());
+        TIFFField[] fields = dir.getTIFFFields();
+        int numFields = fields.length;
+        for(int i = 0; i < numFields; i++) {
+            TIFFField f = fields[i];
+            TIFFTag tag = f.getTag();
+            if(tag.isIFDPointer()) {
+                TIFFDirectory subIFD =
+                    getDirectoryAsIFD((TIFFDirectory)f.getData());
+                f = new TIFFField(tag, f.getType(), (long)f.getCount(), subIFD);
+            }
+            ifd.addTIFFField(f);
+        }
+
+        return ifd;
+    }
+
+    /**
+     * Constructs a <code>TIFFDirectory</code> which is aware of a given
+     * group of {@link TIFFTagSet}s. An optional parent {@link TIFFTag}
+     * may also be specified.
+     *
+     * @param tagSets The <code>TIFFTagSets</code> associated with this
+     * directory.
+     * @param parentTag The parent <code>TIFFTag</code> of this directory;
+     * may be <code>null</code>.
+     * @throws NullPointerException if <code>tagSets</code> is
+     * <code>null</code>.
+     */
+    public TIFFDirectory(TIFFTagSet[] tagSets, TIFFTag parentTag) {
+        if(tagSets == null) {
+            throw new NullPointerException("tagSets == null!");
+        }
+        this.tagSets = new ArrayList<TIFFTagSet>(tagSets.length);
+        int numTagSets = tagSets.length;
+        for(int i = 0; i < numTagSets; i++) {
+            this.tagSets.add(tagSets[i]);
+        }
+        this.parentTag = parentTag;
+    }
+
+    /**
+     * Returns the {@link TIFFTagSet}s of which this directory is aware.
+     *
+     * @return The <code>TIFFTagSet</code>s associated with this
+     * <code>TIFFDirectory</code>.
+     */
+    public TIFFTagSet[] getTagSets() {
+        return tagSets.toArray(new TIFFTagSet[tagSets.size()]);
+    }
+
+    /**
+     * Adds an element to the group of {@link TIFFTagSet}s of which this
+     * directory is aware.
+     *
+     * @param tagSet The <code>TIFFTagSet</code> to add.
+     * @throws NullPointerException if <code>tagSet</code> is
+     * <code>null</code>.
+     */
+    public void addTagSet(TIFFTagSet tagSet) {
+        if(tagSet == null) {
+            throw new NullPointerException("tagSet == null");
+        }
+
+        if(!tagSets.contains(tagSet)) {
+            tagSets.add(tagSet);
+        }
+    }
+
+    /**
+     * Removes an element from the group of {@link TIFFTagSet}s of which this
+     * directory is aware.
+     *
+     * @param tagSet The <code>TIFFTagSet</code> to remove.
+     * @throws NullPointerException if <code>tagSet</code> is
+     * <code>null</code>.
+     */
+    public void removeTagSet(TIFFTagSet tagSet) {
+        if(tagSet == null) {
+            throw new NullPointerException("tagSet == null");
+        }
+
+        if(tagSets.contains(tagSet)) {
+            tagSets.remove(tagSet);
+        }
+    }
+
+    /**
+     * Returns the parent {@link TIFFTag} of this directory if one
+     * has been defined or <code>null</code> otherwise.
+     *
+     * @return The parent <code>TIFFTag</code> of this
+     * <code>TIFFDiectory</code> or <code>null</code>.
+     */
+    public TIFFTag getParentTag() {
+        return parentTag;
+    }
+
+    /**
+     * Returns the {@link TIFFTag} which has tag number equal to
+     * <code>tagNumber</code> or <code>null</code> if no such tag
+     * exists in the {@link TIFFTagSet}s associated with this
+     * directory.
+     *
+     * @param tagNumber The tag number of interest.
+     * @return The corresponding <code>TIFFTag</code> or <code>null</code>.
+     */
+    public TIFFTag getTag(int tagNumber) {
+        return TIFFIFD.getTag(tagNumber, tagSets);
+    }
+
+    /**
+     * Returns the number of {@link TIFFField}s in this directory.
+     *
+     * @return The number of <code>TIFFField</code>s in this
+     * <code>TIFFDirectory</code>.
+     */
+    public int getNumTIFFFields() {
+        return numLowFields + highFields.size();
+    }
+
+    /**
+     * Determines whether a TIFF field with the given tag number is
+     * contained in this directory.
+     *
+     * @param tagNumber The tag number.
+     * @return Whether a {@link TIFFTag} with tag number equal to
+     * <code>tagNumber</code> is present in this <code>TIFFDirectory</code>.
+     */
+    public boolean containsTIFFField(int tagNumber) {
+        return (tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM &&
+                lowFields[tagNumber] != null) ||
+            highFields.containsKey(Integer.valueOf(tagNumber));
+    }
+
+    /**
+     * Adds a TIFF field to the directory.
+     *
+     * @param f The field to add.
+     * @throws NullPointerException if <code>f</code> is <code>null</code>.
+     */
+    public void addTIFFField(TIFFField f) {
+        if(f == null) {
+            throw new NullPointerException("f == null");
+        }
+        int tagNumber = f.getTagNumber();
+        if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
+            if(lowFields[tagNumber] == null) {
+                numLowFields++;
+            }
+            lowFields[tagNumber] = f;
+        } else {
+            highFields.put(Integer.valueOf(tagNumber), f);
+        }
+    }
+
+    /**
+     * Retrieves a TIFF field from the directory.
+     *
+     * @param tagNumber The tag number of the tag associated with the field.
+     * @return A <code>TIFFField</code> with the requested tag number of
+     * <code>null</code> if no such field is present.
+     */
+    public TIFFField getTIFFField(int tagNumber) {
+        TIFFField f;
+        if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
+            f = lowFields[tagNumber];
+        } else {
+            f = highFields.get(Integer.valueOf(tagNumber));
+        }
+        return f;
+    }
+
+    /**
+     * Removes a TIFF field from the directory.
+     *
+     * @param tagNumber The tag number of the tag associated with the field.
+     */
+    public void removeTIFFField(int tagNumber) {
+        if(tagNumber >= 0 && tagNumber <= MAX_LOW_FIELD_TAG_NUM) {
+            if(lowFields[tagNumber] != null) {
+                numLowFields--;
+                lowFields[tagNumber] = null;
+            }
+        } else {
+            highFields.remove(Integer.valueOf(tagNumber));
+        }
+    }
+
+    /**
+     * Retrieves all TIFF fields from the directory.
+     *
+     * @return An array of all TIFF fields in order of numerically increasing
+     * tag number.
+     */
+    public TIFFField[] getTIFFFields() {
+        // Allocate return value.
+        TIFFField[] fields = new TIFFField[numLowFields + highFields.size()];
+
+        // Copy any low-index fields.
+        int nextIndex = 0;
+        for(int i = 0; i <= MAX_LOW_FIELD_TAG_NUM; i++) {
+            if(lowFields[i] != null) {
+                fields[nextIndex++] = lowFields[i];
+                if(nextIndex == numLowFields) break;
+            }
+        }
+
+        // Copy any high-index fields.
+        if(!highFields.isEmpty()) {
+            Iterator<Integer> keys = highFields.keySet().iterator();
+            while(keys.hasNext()) {
+                fields[nextIndex++] = highFields.get(keys.next());
+            }
+        }
+
+        return fields;
+    }
+
+    /**
+     * Removes all TIFF fields from the directory.
+     */
+    public void removeTIFFFields() {
+        Arrays.fill(lowFields, (Object)null);
+        numLowFields = 0;
+        highFields.clear();
+    }
+
+    /**
+     * Converts the directory to a metadata object.
+     *
+     * @return A metadata instance initialized from the contents of this
+     * <code>TIFFDirectory</code>.
+     */
+    public IIOMetadata getAsMetadata() {
+        return new TIFFImageMetadata(getDirectoryAsIFD(this));
+    }
+
+    /**
+     * Clones the directory and all the fields contained therein.
+     *
+     * @return A clone of this <code>TIFFDirectory</code>.
+     * @throws CloneNotSupportedException if the instance cannot be cloned.
+     */
+    @Override
+    public TIFFDirectory clone() throws CloneNotSupportedException {
+        TIFFDirectory dir = (TIFFDirectory) super.clone();
+        dir.tagSets = new ArrayList<TIFFTagSet>(tagSets);
+        dir.parentTag = getParentTag();
+        TIFFField[] fields = getTIFFFields();
+        for(TIFFField field : fields) {
+            dir.addTIFFField(field.clone());
+        }
+
+        return dir;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFField.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,1421 @@
+/*
+ * 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 javax.imageio.plugins.tiff;
+
+import java.util.StringTokenizer;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import com.sun.imageio.plugins.tiff.TIFFFieldNode;
+import com.sun.imageio.plugins.tiff.TIFFIFD;
+
+/**
+ * A class representing a field in a TIFF 6.0 Image File Directory.
+ *
+ * <p> A field in a TIFF Image File Directory (IFD) is defined as a
+ * tag number accompanied by a sequence of values of identical data type.
+ * TIFF 6.0 defines 12 data types; a 13th type <code>IFD</code> is
+ * defined in TIFF Tech Note 1 of TIFF Specification Supplement 1. These
+ * TIFF data types are referred to by Java constants and mapped internally
+ * onto Java language data types and type names as follows:
+ *
+ * <br>
+ * <br>
+ * <table border="1">
+ * <caption>TIFF Data Type to Java Data Type Mapping</caption>
+ *
+ * <tr>
+ * <th>
+ * <b>TIFF Data Type</b>
+ * </th>
+ * <th>
+ * <b>Java Constant</b>
+ * </th>
+ * <th>
+ * <b>Java Data Type</b>
+ * </th>
+ * <th>
+ * <b>Java Type Name</b>
+ * </th>
+ * </tr>
+ *
+ * <tr>
+ * <td>
+ * <tt>BYTE</tt>
+ * </td>
+ * <td>
+ * {@link TIFFTag#TIFF_BYTE}
+ * </td>
+ * <td>
+ * <code>byte</code>
+ * </td>
+ * <td>
+ * <code>"Byte"</code>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>
+ * <tt>ASCII</tt>
+ * </td>
+ * <td>
+ * {@link TIFFTag#TIFF_ASCII}
+ * </td>
+ * <td>
+ * <code>String</code>
+ * </td>
+ * <td>
+ * <code>"Ascii"</code>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>
+ * <tt>SHORT</tt>
+ * </td>
+ * <td>
+ * {@link TIFFTag#TIFF_SHORT}
+ * </td>
+ * <td>
+ * <code>char</code>
+ * </td>
+ * <td>
+ * <code>"Short"</code>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>
+ * <tt>LONG</tt>
+ * </td>
+ * <td>
+ * {@link TIFFTag#TIFF_LONG}
+ * </td>
+ * <td>
+ * <code>long</code>
+ * </td>
+ * <td>
+ * <code>"Long"</code>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>
+ * <tt>RATIONAL</tt>
+ * </td>
+ * <td>
+ * {@link TIFFTag#TIFF_RATIONAL}
+ * </td>
+ * <td>
+ * <code>long[2]</code> {numerator, denominator}
+ * </td>
+ * <td>
+ * <code>"Rational"</code>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>
+ * <tt>SBYTE</tt>
+ * </td>
+ * <td>
+ * {@link TIFFTag#TIFF_SBYTE}
+ * </td>
+ * <td>
+ * <code>byte</code>
+ * </td>
+ * <td>
+ * <code>"SByte"</code>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>
+ * <tt>UNDEFINED</tt>
+ * </td>
+ * <td>
+ * {@link TIFFTag#TIFF_UNDEFINED}
+ * </td>
+ * <td>
+ * <code>byte</code>
+ * </td>
+ * <td>
+ * <code>"Undefined"</code>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>
+ * <tt>SSHORT</tt>
+ * </td>
+ * <td>
+ * {@link TIFFTag#TIFF_SSHORT}
+ * </td>
+ * <td>
+ * <code>short</code>
+ * </td>
+ * <td>
+ * <code>"SShort"</code>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>
+ * <tt>SLONG</tt>
+ * </td>
+ * <td>
+ * {@link TIFFTag#TIFF_SLONG}
+ * </td>
+ * <td>
+ * <code>int</code>
+ * </td>
+ * <td>
+ * <code>"SLong"</code>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>
+ * <tt>SRATIONAL</tt>
+ * </td>
+ * <td>
+ * {@link TIFFTag#TIFF_SRATIONAL}
+ * </td>
+ * <td>
+ * <code>int[2]</code> {numerator, denominator}
+ * </td>
+ * <td>
+ * <code>"SRational"</code>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>
+ * <tt>FLOAT</tt>
+ * </td>
+ * <td>
+ * {@link TIFFTag#TIFF_FLOAT}
+ * </td>
+ * <td>
+ * <code>float</code>
+ * </td>
+ * <td>
+ * <code>"Float"</code>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>
+ * <tt>DOUBLE</tt>
+ * </td>
+ * <td>
+ * {@link TIFFTag#TIFF_DOUBLE}
+ * </td>
+ * <td>
+ * <code>double</code>
+ * </td>
+ * <td>
+ * <code>"Double"</code>
+ * </td>
+ * </tr>
+ *
+ * <tr>
+ * <td>
+ * <tt>IFD</tt>
+ * </td>
+ * <td>
+ * {@link TIFFTag#TIFF_IFD_POINTER}
+ * </td>
+ * <td>
+ * <code>long</code>
+ * </td>
+ * <td>
+ * <code>"IFDPointer"</code>
+ * </td>
+ * </tr>
+ *
+ * </table>
+ *
+ * @since 1.9
+ * @see   TIFFDirectory
+ * @see   TIFFTag
+ */
+public class TIFFField implements Cloneable {
+
+    private static final String[] typeNames = {
+        null,
+        "Byte", "Ascii", "Short", "Long", "Rational",
+        "SByte", "Undefined", "SShort", "SLong", "SRational",
+        "Float", "Double", "IFDPointer"
+    };
+
+    private static final boolean[] isIntegral = {
+        false,
+        true, false, true, true, false,
+        true, true, true, true, false,
+        false, false, false
+    };
+
+    /** The tag. */
+    private TIFFTag tag;
+
+    /** The tag number. */
+    private int tagNumber;
+
+    /** The tag type. */
+    private int type;
+
+    /** The number of data items present in the field. */
+    private int count;
+
+    /** The field data. */
+    private Object data;
+
+    /** The IFD contents if available. This will usually be a TIFFIFD. */
+    private TIFFDirectory dir;
+
+    /** The default constructor. */
+    private TIFFField() {}
+
+    private static String getAttribute(Node node, String attrName) {
+        NamedNodeMap attrs = node.getAttributes();
+        return attrs.getNamedItem(attrName).getNodeValue();
+    }
+
+    private static void initData(Node node,
+                                 int[] otype, int[] ocount, Object[] odata) {
+        int type;
+        int count;
+        Object data = null;
+
+        String typeName = node.getNodeName();
+        typeName = typeName.substring(4);
+        typeName = typeName.substring(0, typeName.length() - 1);
+        type = TIFFField.getTypeByName(typeName);
+        if (type == -1) {
+            throw new IllegalArgumentException("typeName = " + typeName);
+        }
+
+        Node child = node.getFirstChild();
+
+        count = 0;
+        while (child != null) {
+            String childTypeName = child.getNodeName().substring(4);
+            if (!typeName.equals(childTypeName)) {
+                // warning
+            }
+
+            ++count;
+            child = child.getNextSibling();
+        }
+
+        if (count > 0) {
+            data = createArrayForType(type, count);
+            child = node.getFirstChild();
+            int idx = 0;
+            while (child != null) {
+                String value = getAttribute(child, "value");
+
+                String numerator, denominator;
+                int slashPos;
+
+                switch (type) {
+                case TIFFTag.TIFF_ASCII:
+                    ((String[])data)[idx] = value;
+                    break;
+                case TIFFTag.TIFF_BYTE:
+                case TIFFTag.TIFF_SBYTE:
+                    ((byte[])data)[idx] =
+                        (byte)Integer.parseInt(value);
+                    break;
+                case TIFFTag.TIFF_SHORT:
+                    ((char[])data)[idx] =
+                        (char)Integer.parseInt(value);
+                    break;
+                case TIFFTag.TIFF_SSHORT:
+                    ((short[])data)[idx] =
+                        (short)Integer.parseInt(value);
+                    break;
+                case TIFFTag.TIFF_SLONG:
+                    ((int[])data)[idx] =
+                        Integer.parseInt(value);
+                    break;
+                case TIFFTag.TIFF_LONG:
+                case TIFFTag.TIFF_IFD_POINTER:
+                    ((long[])data)[idx] =
+                        Long.parseLong(value);
+                    break;
+                case TIFFTag.TIFF_FLOAT:
+                    ((float[])data)[idx] =
+                        Float.parseFloat(value);
+                    break;
+                case TIFFTag.TIFF_DOUBLE:
+                    ((double[])data)[idx] =
+                        Double.parseDouble(value);
+                    break;
+                case TIFFTag.TIFF_SRATIONAL:
+                    slashPos = value.indexOf("/");
+                    numerator = value.substring(0, slashPos);
+                    denominator = value.substring(slashPos + 1);
+
+                    ((int[][])data)[idx] = new int[2];
+                    ((int[][])data)[idx][0] =
+                        Integer.parseInt(numerator);
+                    ((int[][])data)[idx][1] =
+                        Integer.parseInt(denominator);
+                    break;
+                case TIFFTag.TIFF_RATIONAL:
+                    slashPos = value.indexOf("/");
+                    numerator = value.substring(0, slashPos);
+                    denominator = value.substring(slashPos + 1);
+
+                    ((long[][])data)[idx] = new long[2];
+                    ((long[][])data)[idx][0] =
+                        Long.parseLong(numerator);
+                    ((long[][])data)[idx][1] =
+                        Long.parseLong(denominator);
+                    break;
+                default:
+                    // error
+                }
+
+                idx++;
+                child = child.getNextSibling();
+            }
+        }
+
+        otype[0] = type;
+        ocount[0] = count;
+        odata[0] = data;
+    }
+
+    /**
+     * Creates a <code>TIFFField</code> from a TIFF native image
+     * metadata node. If the value of the <tt>"tagNumber"</tt> attribute
+     * of the node is not found in <code>tagSet</code> then a new
+     * <code>TIFFTag</code> with name <code>TIFFTag.UNKNOWN_TAG_NAME</code>
+     * will be created and assigned to the field.
+     *
+     * @param tagSet The <code>TIFFTagSet</code> to which the
+     * <code>TIFFTag</code> of the field belongs.
+     * @param node A native TIFF image metadata <code>TIFFField</code> node.
+     * @throws NullPointerException if <code>node</code> is
+     * <code>null</code>.
+     * @throws IllegalArgumentException if the name of the node is not
+     * <code>"TIFFField"</code>.
+     * @return A new {@code TIFFField}.
+     */
+    public static TIFFField createFromMetadataNode(TIFFTagSet tagSet,
+                                                   Node node) {
+        if (node == null) {
+            throw new NullPointerException("node == null!");
+        }
+        String name = node.getNodeName();
+        if (!name.equals("TIFFField")) {
+            throw new IllegalArgumentException("!name.equals(\"TIFFField\")");
+        }
+
+        int tagNumber = Integer.parseInt(getAttribute(node, "number"));
+        TIFFTag tag = null;
+        if (tagSet != null) {
+            tag = tagSet.getTag(tagNumber);
+        }
+
+        int type = TIFFTag.TIFF_UNDEFINED;
+        int count = 0;
+        Object data = null;
+
+        Node child = node.getFirstChild();
+        if (child != null) {
+            String typeName = child.getNodeName();
+            if (typeName.equals("TIFFUndefined")) {
+                String values = getAttribute(child, "value");
+                StringTokenizer st = new StringTokenizer(values, ",");
+                count = st.countTokens();
+
+                byte[] bdata = new byte[count];
+                for (int i = 0; i < count; i++) {
+                    bdata[i] = (byte)Integer.parseInt(st.nextToken());
+                }
+
+                type = TIFFTag.TIFF_UNDEFINED;
+                data = bdata;
+            } else {
+                int[] otype = new int[1];
+                int[] ocount = new int[1];
+                Object[] odata = new Object[1];
+
+                initData(node.getFirstChild(), otype, ocount, odata);
+                type = otype[0];
+                count = ocount[0];
+                data = odata[0];
+            }
+        } else if (tag != null) {
+            int t = TIFFTag.MAX_DATATYPE;
+            while(t >= TIFFTag.MIN_DATATYPE && !tag.isDataTypeOK(t)) {
+                t--;
+            }
+            type = t;
+        }
+
+        if (tag == null) {
+            tag = new TIFFTag(TIFFTag.UNKNOWN_TAG_NAME, tagNumber, 1 << type);
+        }
+
+        return new TIFFField(tag, type, count, data);
+    }
+
+    /**
+     * Constructs a <code>TIFFField</code> with arbitrary data. The
+     * <code>type</code> parameter must be a value for which
+     * {@link TIFFTag#isDataTypeOK tag.isDataTypeOK()}
+     * returns <code>true</code>. The <code>data</code> parameter must
+     * be an array of a Java type appropriate for the type of the TIFF
+     * field.
+     *
+     * <p>Note that the value (data) of the <code>TIFFField</code>
+     * will always be the actual field value regardless of the number of
+     * bytes required for that value. This is the case despite the fact
+     * that the TIFF <i>IFD Entry</i> corresponding to the field may
+     * actually contain the offset to the value of the field rather than
+     * the value itself (the latter occurring if and only if the
+     * value fits into 4 bytes). In other words, the value of the
+     * field will already have been read from the TIFF stream. (An exception
+     * to this case may occur when the field represents the contents of a
+     * non-baseline IFD. In that case the data will be a <code>long[]</code>
+     * containing the offset to the IFD and the <code>TIFFDirectory</code>
+     * returned by {@link #getDirectory()} will be its contents.)
+     *
+     * @param tag The tag to associated with this field.
+     * @param type One of the <code>TIFFTag.TIFF_*</code> constants
+     * indicating the data type of the field as written to the TIFF stream.
+     * @param count The number of data values.
+     * @param data The actual data content of the field.
+     *
+     * @throws NullPointerException if <code>tag&nbsp;==&nbsp;null</code>.
+     * @throws IllegalArgumentException if <code>type</code> is not
+     * one of the <code>TIFFTag.TIFF_*</code> data type constants.
+     * @throws IllegalArgumentException if <code>type</code> is an unacceptable
+     * data type for the supplied <code>TIFFTag</code>.
+     * @throws IllegalArgumentException if <code>count&nbsp;&lt;&nbsp;0</code>.
+     * @throws NullPointerException if <code>data&nbsp;==&nbsp;null</code>.
+     * @throws IllegalArgumentException if <code>data</code> is an instance of
+     * a class incompatible with the specified type.
+     * @throws IllegalArgumentException if the size of the data array is wrong.
+     */
+    public TIFFField(TIFFTag tag, int type, int count, Object data) {
+        if(tag == null) {
+            throw new NullPointerException("tag == null!");
+        } else if(type < TIFFTag.MIN_DATATYPE || type > TIFFTag.MAX_DATATYPE) {
+            throw new IllegalArgumentException("Unknown data type "+type);
+        } else if(!tag.isDataTypeOK(type)) {
+            throw new IllegalArgumentException("Illegal data type " + type
+                + " for " + tag.getName() + " tag");
+        } else if(count < 0) {
+            throw new IllegalArgumentException("count < 0!");
+        } else if(data == null) {
+            throw new NullPointerException("data == null!");
+        }
+
+        boolean isDataArrayCorrect = false;
+
+        switch (type) {
+        case TIFFTag.TIFF_BYTE:
+        case TIFFTag.TIFF_SBYTE:
+        case TIFFTag.TIFF_UNDEFINED:
+            isDataArrayCorrect = data instanceof byte[]
+                && ((byte[])data).length == count;
+            break;
+        case TIFFTag.TIFF_ASCII:
+            isDataArrayCorrect = data instanceof String[]
+                && ((String[])data).length == count;
+            break;
+        case TIFFTag.TIFF_SHORT:
+            isDataArrayCorrect = data instanceof char[]
+                && ((char[])data).length == count;
+            break;
+        case TIFFTag.TIFF_LONG:
+            isDataArrayCorrect = data instanceof long[]
+                && ((long[])data).length == count;
+            break;
+        case TIFFTag.TIFF_IFD_POINTER:
+            isDataArrayCorrect = data instanceof long[]
+                && ((long[])data).length == 1;
+            break;
+        case TIFFTag.TIFF_RATIONAL:
+            isDataArrayCorrect = data instanceof long[][]
+                && ((long[][])data).length == count
+                && ((long[][])data)[0].length == 2;
+            break;
+        case TIFFTag.TIFF_SSHORT:
+            isDataArrayCorrect = data instanceof short[]
+                && ((short[])data).length == count;
+            break;
+        case TIFFTag.TIFF_SLONG:
+            isDataArrayCorrect = data instanceof int[]
+                && ((int[])data).length == count;
+            break;
+        case TIFFTag.TIFF_SRATIONAL:
+            isDataArrayCorrect = data instanceof int[][]
+                && ((int[][])data).length == count
+                && ((int[][])data)[0].length == 2;
+            break;
+        case TIFFTag.TIFF_FLOAT:
+            isDataArrayCorrect = data instanceof float[]
+                && ((float[])data).length == count;
+            break;
+        case TIFFTag.TIFF_DOUBLE:
+            isDataArrayCorrect = data instanceof double[]
+                && ((double[])data).length == count;
+            break;
+        default:
+            throw new IllegalArgumentException("Unknown data type "+type);
+        }
+
+        if (!isDataArrayCorrect) {
+            throw new IllegalArgumentException
+                ("Illegal class or length for data array");
+        }
+
+        this.tag = tag;
+        this.tagNumber = tag.getNumber();
+        this.type = type;
+        this.count = count;
+        this.data = data;
+    }
+
+    /**
+     * Constructs a data array using {@link #createArrayForType
+     * createArrayForType()} and invokes
+     * {@link #TIFFField(TIFFTag,int,int,Object)} with the supplied
+     * parameters and the created array.
+     *
+     * @param tag The tag to associated with this field.
+     * @param type One of the <code>TIFFTag.TIFF_*</code> constants
+     * indicating the data type of the field as written to the TIFF stream.
+     * @param count The number of data values.
+     * @throws NullPointerException if <code>tag&nbsp;==&nbsp;null</code>.
+     * @throws IllegalArgumentException if <code>type</code> is not
+     * one of the <code>TIFFTag.TIFF_*</code> data type constants.
+     * @throws IllegalArgumentException if <code>type</code> is an unacceptable
+     * data type for the supplied <code>TIFFTag</code>.
+     * @throws IllegalArgumentException if <code>count&nbsp;&lt;&nbsp;0</code>.
+     * @see #TIFFField(TIFFTag,int,int,Object)
+     */
+    public TIFFField(TIFFTag tag, int type, int count) {
+        this(tag, type, count, createArrayForType(type, count));
+    }
+
+    /**
+     * Constructs a <code>TIFFField</code> with a single non-negative integral
+     * value.
+     * The field will have type
+     * {@link TIFFTag#TIFF_SHORT  TIFF_SHORT} if
+     * <code>val&nbsp;&lt;&nbsp;65536</code> and type
+     * {@link TIFFTag#TIFF_LONG TIFF_LONG} otherwise.  The count
+     * of the field will be unity.
+     *
+     * @param tag The tag to associate with this field.
+     * @param value The value to associate with this field.
+     * @throws NullPointerException if <code>tag&nbsp;==&nbsp;null</code>.
+     * @throws IllegalArgumentException if the derived type is unacceptable
+     * for the supplied <code>TIFFTag</code>.
+     * @throws IllegalArgumentException if <code>value&nbsp;&lt;&nbsp;0</code>.
+     */
+    public TIFFField(TIFFTag tag, int value) {
+        if(tag == null) {
+            throw new NullPointerException("tag == null!");
+        }
+        if (value < 0) {
+            throw new IllegalArgumentException("value < 0!");
+        }
+
+        this.tag = tag;
+        this.tagNumber = tag.getNumber();
+        this.count = 1;
+
+        if (value < 65536) {
+            if (!tag.isDataTypeOK(TIFFTag.TIFF_SHORT)) {
+                throw new IllegalArgumentException("Illegal data type "
+                    + TIFFTag.TIFF_SHORT + " for " + tag.getName() + " tag");
+            }
+            this.type = TIFFTag.TIFF_SHORT;
+            char[] cdata = new char[1];
+            cdata[0] = (char)value;
+            this.data = cdata;
+        } else {
+            if (!tag.isDataTypeOK(TIFFTag.TIFF_LONG)) {
+                throw new IllegalArgumentException("Illegal data type "
+                    + TIFFTag.TIFF_LONG + " for " + tag.getName() + " tag");
+            }
+            this.type = TIFFTag.TIFF_LONG;
+            long[] ldata = new long[1];
+            ldata[0] = value;
+            this.data = ldata;
+        }
+    }
+
+    /**
+     * Constructs a <code>TIFFField</code> with an IFD offset and contents.
+     * The offset will be stored as the data of this field as
+     * <code>long[] {offset}</code>. The directory will not be cloned. The count
+     * of the field will be unity.
+     *
+     * @param tag The tag to associated with this field.
+     * @param type One of the constants <code>TIFFTag.TIFF_LONG</code> or
+     * <code>TIFFTag.TIFF_IFD_POINTER</code>.
+     * @param offset The IFD offset.
+     * @param dir The directory.
+     *
+     * @throws NullPointerException if <code>tag&nbsp;==&nbsp;null</code>.
+     * @throws IllegalArgumentException if <code>type</code> is neither
+     * <code>TIFFTag.TIFF_LONG</code> nor <code>TIFFTag.TIFF_IFD_POINTER</code>.
+     * @throws IllegalArgumentException if <code>type</code> is an unacceptable
+     * data type for the supplied <code>TIFFTag</code>.
+     * @throws IllegalArgumentException if <code>offset</code> is non-positive.
+     * @throws NullPointerException if <code>dir&nbsp;==&nbsp;null</code>.
+     *
+     * @see #TIFFField(TIFFTag,int,int,Object)
+     */
+    public TIFFField(TIFFTag tag, int type, long offset, TIFFDirectory dir) {
+        this(tag, type, 1, new long[] {offset});
+        if (type != TIFFTag.TIFF_LONG && type != TIFFTag.TIFF_IFD_POINTER) {
+            throw new IllegalArgumentException("type " + type
+                + " is neither TIFFTag.TIFF_LONG nor TIFFTag.TIFF_IFD_POINTER");
+        } else if (offset <= 0) {
+            throw new IllegalArgumentException("offset " + offset
+                + " is non-positive");
+        } else if (dir == null) {
+            throw new NullPointerException("dir == null");
+        }
+        this.dir = dir;
+    }
+
+    /**
+     * Retrieves the tag associated with this field.
+     *
+     * @return The associated <code>TIFFTag</code>.
+     */
+    public TIFFTag getTag() {
+        return tag;
+    }
+
+    /**
+     * Retrieves the tag number in the range <code>[0,&nbsp;65535]</code>.
+     *
+     * @return The tag number.
+     */
+    public int getTagNumber() {
+        return tagNumber;
+    }
+
+    /**
+     * Returns the type of the data stored in the field.  For a TIFF 6.0
+     * stream, the value will equal one of the <code>TIFFTag.TIFF_*</code>
+     * constants. For future revisions of TIFF, higher values are possible.
+     *
+     * @return The data type of the field value.
+     */
+    public int getType() {
+        return type;
+    }
+
+    /**
+     * Returns the name of the supplied data type constant.
+     *
+     * @param dataType One of the <code>TIFFTag.TIFF_*</code> constants
+     * indicating the data type of the field as written to the TIFF stream.
+     * @return The type name corresponding to the supplied type constant.
+     * @throws IllegalArgumentException if <code>dataType</code> is not
+     * one of the <code>TIFFTag.TIFF_*</code> data type constants.
+     */
+    public static String getTypeName(int dataType) {
+        if (dataType < TIFFTag.MIN_DATATYPE ||
+            dataType > TIFFTag.MAX_DATATYPE) {
+            throw new IllegalArgumentException("Unknown data type "+dataType);
+        }
+
+        return typeNames[dataType];
+    }
+
+    /**
+     * Returns the data type constant corresponding to the supplied data
+     * type name. If the name is unknown <code>-1</code> will be returned.
+     *
+     * @param typeName The type name.
+     * @return One of the <code>TIFFTag.TIFF_*</code> constants or
+     * <code>-1</code> if the name is not recognized.
+     */
+    public static int getTypeByName(String typeName) {
+        for (int i = TIFFTag.MIN_DATATYPE; i <= TIFFTag.MAX_DATATYPE; i++) {
+            if (typeName.equals(typeNames[i])) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Creates an array appropriate for the indicated data type.
+     *
+     * @param dataType One of the <code>TIFFTag.TIFF_*</code> data type
+     * constants.
+     * @param count The number of values in the array.
+     * @return An array appropriate for the specified data type.
+     *
+     * @throws IllegalArgumentException if <code>dataType</code> is not
+     * one of the <code>TIFFTag.TIFF_*</code> data type constants.
+     * @throws IllegalArgumentException if <code>count&nbsp;&lt;&nbsp;0</code>.
+     */
+    public static Object createArrayForType(int dataType, int count) {
+        if(count < 0) {
+            throw new IllegalArgumentException("count < 0!");
+        }
+        switch (dataType) {
+        case TIFFTag.TIFF_BYTE:
+        case TIFFTag.TIFF_SBYTE:
+        case TIFFTag.TIFF_UNDEFINED:
+            return new byte[count];
+        case TIFFTag.TIFF_ASCII:
+            return new String[count];
+        case TIFFTag.TIFF_SHORT:
+            return new char[count];
+        case TIFFTag.TIFF_LONG:
+        case TIFFTag.TIFF_IFD_POINTER:
+            return new long[count];
+        case TIFFTag.TIFF_RATIONAL:
+            return new long[count][2];
+        case TIFFTag.TIFF_SSHORT:
+            return new short[count];
+        case TIFFTag.TIFF_SLONG:
+            return new int[count];
+        case TIFFTag.TIFF_SRATIONAL:
+            return new int[count][2];
+        case TIFFTag.TIFF_FLOAT:
+            return new float[count];
+        case TIFFTag.TIFF_DOUBLE:
+            return new double[count];
+        default:
+            throw new IllegalArgumentException("Unknown data type "+dataType);
+        }
+    }
+
+    /**
+     * Returns the <code>TIFFField</code> as a node named either
+     * <tt>"TIFFField"</tt> or <tt>"TIFFIFD"</tt> as described in the
+     * TIFF native image metadata specification. The node will be named
+     * <tt>"TIFFIFD"</tt> if and only if the field's data object is an
+     * instance of {@link TIFFDirectory} or equivalently
+     * {@link TIFFTag#isIFDPointer getTag.isIFDPointer()} returns
+     * <code>true</code>.
+     *
+     * @return a <code>Node</code> named <tt>"TIFFField"</tt> or
+     * <tt>"TIFFIFD"</tt>.
+     */
+    public Node getAsNativeNode() {
+        return new TIFFFieldNode(this);
+    }
+
+    /**
+     * Indicates whether the value associated with the field is of
+     * integral data type.
+     *
+     * @return Whether the field type is integral.
+     */
+    public boolean isIntegral() {
+        return isIntegral[type];
+    }
+
+    /**
+     * Returns the number of data items present in the field.  For
+     * <code>TIFFTag.TIFF_ASCII</code> fields, the value returned is the
+     * number of <code>String</code>s, not the total length of the
+     * data as in the file representation.
+     *
+     * @return The number of data items present in the field.
+     */
+    public int getCount() {
+        return count;
+    }
+
+    /**
+     * Returns a reference to the data object associated with the field.
+     *
+     * @return The data object of the field.
+     */
+    public Object getData() {
+        return data;
+    }
+
+    /**
+     * Returns the data as an uninterpreted array of
+     * <code>byte</code>s.  The type of the field must be one of
+     * <code>TIFFTag.TIFF_BYTE</code>, <code>TIFF_SBYTE</code>, or
+     * <code>TIFF_UNDEFINED</code>.
+     *
+     * <p> For data in <code>TIFFTag.TIFF_BYTE</code> format, the application
+     * must take care when promoting the data to longer integral types
+     * to avoid sign extension.
+     *
+     * @throws ClassCastException if the field is not of type
+     * <code>TIFF_BYTE</code>, <code>TIFF_SBYTE</code>, or
+     * <code>TIFF_UNDEFINED</code>.
+     * @return The data as an uninterpreted array of bytes.
+     */
+    public byte[] getAsBytes() {
+        return (byte[])data;
+    }
+
+    /**
+     * Returns <code>TIFFTag.TIFF_SHORT</code> data as an array of
+     * <code>char</code>s (unsigned 16-bit integers).
+     *
+     * @throws ClassCastException if the field is not of type
+     * <code>TIFF_SHORT</code>.
+     * @return The data as an array of {@code char}s.
+     */
+    public char[] getAsChars() {
+        return (char[])data;
+    }
+
+    /**
+     * Returns <code>TIFFTag.TIFF_SSHORT</code> data as an array of
+     * <code>short</code>s (signed 16-bit integers).
+     *
+     * @throws ClassCastException if the field is not of type
+     * <code>TIFF_SSHORT</code>.
+     * @return The data as an array of {@code short}s.
+     */
+    public short[] getAsShorts() {
+        return (short[])data;
+    }
+
+    /**
+     * Returns <code>TIFFTag.TIFF_SLONG</code> data as an array of
+     * <code>int</code>s (signed 32-bit integers).
+     *
+     * @throws ClassCastException if the field is not of type
+     * <code>TIFF_SHORT</code>, <code>TIFF_SSHORT</code>, or
+     * <code>TIFF_SLONG</code>.
+     * @return The data as an array of {@code int}s.
+     */
+    public int[] getAsInts() {
+        if (data instanceof int[]) {
+            return (int[])data;
+        } else if (data instanceof char[]){
+            char[] cdata = (char[])data;
+            int[] idata = new int[cdata.length];
+            for (int i = 0; i < cdata.length; i++) {
+                idata[i] = cdata[i] & 0xffff;
+            }
+            return idata;
+        } else if (data instanceof short[]){
+            short[] sdata = (short[])data;
+            int[] idata = new int[sdata.length];
+            for (int i = 0; i < sdata.length; i++) {
+                idata[i] = (int)sdata[i];
+            }
+            return idata;
+        } else {
+            throw new ClassCastException("Data not char[], short[], or int[]!");
+        }
+    }
+
+    /**
+     * Returns <code>TIFFTag.TIFF_LONG</code> or
+     * <code>TIFF_IFD_POINTER</code> data as an array of
+     * <code>long</code>s (signed 64-bit integers).
+     *
+     * @throws ClassCastException if the field is not of type
+     * <code>TIFF_LONG</code> or <code>TIFF_IFD_POINTER</code>.
+     * @return The data as an array of {@code long}s.
+     */
+    public long[] getAsLongs() {
+        return (long[])data;
+    }
+
+    /**
+     * Returns <code>TIFFTag.TIFF_FLOAT</code> data as an array of
+     * <code>float</code>s (32-bit floating-point values).
+     *
+     * @throws ClassCastException if the field is not of type
+     * <code>TIFF_FLOAT</code>.
+     * @return The data as an array of {@code float}s.
+     */
+    public float[] getAsFloats() {
+        return (float[])data;
+    }
+
+    /**
+     * Returns <code>TIFFTag.TIFF_DOUBLE</code> data as an array of
+     * <code>double</code>s (64-bit floating-point values).
+     *
+     * @throws ClassCastException if the field is not of type
+     * <code>TIFF_DOUBLE</code>.
+     * @return The data as an array of {@code double}s.
+     */
+    public double[] getAsDoubles() {
+        return (double[])data;
+    }
+
+    /**
+     * Returns <code>TIFFTag.TIFF_SRATIONAL</code> data as an array of
+     * 2-element arrays of <code>int</code>s.
+     *
+     * @throws ClassCastException if the field is not of type
+     * <code>TIFF_SRATIONAL</code>.
+     * @return The data as an array of signed rationals.
+     */
+    public int[][] getAsSRationals() {
+        return (int[][])data;
+    }
+
+    /**
+     * Returns <code>TIFFTag.TIFF_RATIONAL</code> data as an array of
+     * 2-element arrays of <code>long</code>s.
+     *
+     * @throws ClassCastException if the field is not of type
+     * <code>TIFF_RATIONAL</code>.
+     * @return The data as an array of unsigned rationals.
+     */
+    public long[][] getAsRationals() {
+        return (long[][])data;
+    }
+
+    /**
+     * Returns data in any format as an <code>int</code>.
+     *
+     * <p> <code>TIFFTag.TIFF_BYTE</code> values are treated as unsigned; that
+     * is, no sign extension will take place and the returned value
+     * will be in the range [0, 255].  <code>TIFF_SBYTE</code> data
+     * will be returned in the range [-128, 127].
+     *
+     * <p> A <code>TIFF_UNDEFINED</code> value is treated as though
+     * it were a <code>TIFF_BYTE</code>.
+     *
+     * <p> Data in <code>TIFF_SLONG</code>, <code>TIFF_LONG</code>,
+     * <code>TIFF_FLOAT</code>, <code>TIFF_DOUBLE</code> or
+     * <code>TIFF_IFD_POINTER</code> format are simply cast to
+     * <code>int</code> and may suffer from truncation.
+     *
+     * <p> Data in <code>TIFF_SRATIONAL</code> or
+     * <code>TIFF_RATIONAL</code> format are evaluated by dividing the
+     * numerator into the denominator using double-precision
+     * arithmetic and then casting to <code>int</code>.  Loss of
+     * precision and truncation may occur.
+     *
+     * <p> Data in <code>TIFF_ASCII</code> format will be parsed as by
+     * the <code>Double.parseDouble</code> method, with the result
+     * case to <code>int</code>.
+     *
+     * @param index The index of the data.
+     * @return The data at the given index as an {@code int}.
+     */
+    public int getAsInt(int index) {
+        switch (type) {
+        case TIFFTag.TIFF_BYTE:
+        case TIFFTag.TIFF_UNDEFINED:
+            return ((byte[])data)[index] & 0xff;
+        case TIFFTag.TIFF_SBYTE:
+            return ((byte[])data)[index];
+        case TIFFTag.TIFF_SHORT:
+            return ((char[])data)[index] & 0xffff;
+        case TIFFTag.TIFF_SSHORT:
+            return ((short[])data)[index];
+        case TIFFTag.TIFF_SLONG:
+            return ((int[])data)[index];
+        case TIFFTag.TIFF_LONG:
+        case TIFFTag.TIFF_IFD_POINTER:
+            return (int)((long[])data)[index];
+        case TIFFTag.TIFF_FLOAT:
+            return (int)((float[])data)[index];
+        case TIFFTag.TIFF_DOUBLE:
+            return (int)((double[])data)[index];
+        case TIFFTag.TIFF_SRATIONAL:
+            int[] ivalue = getAsSRational(index);
+            return (int)((double)ivalue[0]/ivalue[1]);
+        case TIFFTag.TIFF_RATIONAL:
+            long[] lvalue = getAsRational(index);
+            return (int)((double)lvalue[0]/lvalue[1]);
+        case TIFFTag.TIFF_ASCII:
+             String s = ((String[])data)[index];
+             return (int)Double.parseDouble(s);
+        default:
+            throw new ClassCastException(); // should never happen
+        }
+    }
+
+    /**
+     * Returns data in any format as a <code>long</code>.
+     *
+     * <p> <code>TIFFTag.TIFF_BYTE</code> and <code>TIFF_UNDEFINED</code> data
+     * are treated as unsigned; that is, no sign extension will take
+     * place and the returned value will be in the range [0, 255].
+     * <code>TIFF_SBYTE</code> data will be returned in the range
+     * [-128, 127].
+     *
+     * <p> Data in <code>TIFF_ASCII</code> format will be parsed as by
+     * the <code>Double.parseDouble</code> method, with the result
+     * cast to <code>long</code>.
+     *
+     * @param index The index of the data.
+     * @return The data at the given index as a {@code long}.
+     */
+    public long getAsLong(int index) {
+        switch (type) {
+        case TIFFTag.TIFF_BYTE:
+        case TIFFTag.TIFF_UNDEFINED:
+            return ((byte[])data)[index] & 0xff;
+        case TIFFTag.TIFF_SBYTE:
+            return ((byte[])data)[index];
+        case TIFFTag.TIFF_SHORT:
+            return ((char[])data)[index] & 0xffff;
+        case TIFFTag.TIFF_SSHORT:
+            return ((short[])data)[index];
+        case TIFFTag.TIFF_SLONG:
+            return ((int[])data)[index];
+        case TIFFTag.TIFF_LONG:
+        case TIFFTag.TIFF_IFD_POINTER:
+            return ((long[])data)[index];
+        case TIFFTag.TIFF_SRATIONAL:
+            int[] ivalue = getAsSRational(index);
+            return (long)((double)ivalue[0]/ivalue[1]);
+        case TIFFTag.TIFF_RATIONAL:
+            long[] lvalue = getAsRational(index);
+            return (long)((double)lvalue[0]/lvalue[1]);
+        case TIFFTag.TIFF_ASCII:
+             String s = ((String[])data)[index];
+             return (long)Double.parseDouble(s);
+        default:
+            throw new ClassCastException(); // should never happen
+        }
+    }
+
+    /**
+     * Returns data in any format as a <code>float</code>.
+     *
+     * <p> <code>TIFFTag.TIFF_BYTE</code> and <code>TIFF_UNDEFINED</code> data
+     * are treated as unsigned; that is, no sign extension will take
+     * place and the returned value will be in the range [0, 255].
+     * <code>TIFF_SBYTE</code> data will be returned in the range
+     * [-128, 127].
+     *
+     * <p> Data in <code>TIFF_SLONG</code>, <code>TIFF_LONG</code>,
+     * <code>TIFF_DOUBLE</code>, or <code>TIFF_IFD_POINTER</code> format are
+     * simply cast to <code>float</code> and may suffer from
+     * truncation.
+     *
+     * <p> Data in <code>TIFF_SRATIONAL</code> or
+     * <code>TIFF_RATIONAL</code> format are evaluated by dividing the
+     * numerator into the denominator using double-precision
+     * arithmetic and then casting to <code>float</code>.
+     *
+     * <p> Data in <code>TIFF_ASCII</code> format will be parsed as by
+     * the <code>Double.parseDouble</code> method, with the result
+     * cast to <code>float</code>.
+     *
+     * @param index The index of the data.
+     * @return The data at the given index as a {@code float}.
+     */
+    public float getAsFloat(int index) {
+        switch (type) {
+        case TIFFTag.TIFF_BYTE:
+        case TIFFTag.TIFF_UNDEFINED:
+            return ((byte[])data)[index] & 0xff;
+        case TIFFTag.TIFF_SBYTE:
+            return ((byte[])data)[index];
+        case TIFFTag.TIFF_SHORT:
+            return ((char[])data)[index] & 0xffff;
+        case TIFFTag.TIFF_SSHORT:
+            return ((short[])data)[index];
+        case TIFFTag.TIFF_SLONG:
+            return ((int[])data)[index];
+        case TIFFTag.TIFF_LONG:
+        case TIFFTag.TIFF_IFD_POINTER:
+            return ((long[])data)[index];
+        case TIFFTag.TIFF_FLOAT:
+            return ((float[])data)[index];
+        case TIFFTag.TIFF_DOUBLE:
+            return (float)((double[])data)[index];
+        case TIFFTag.TIFF_SRATIONAL:
+            int[] ivalue = getAsSRational(index);
+            return (float)((double)ivalue[0]/ivalue[1]);
+        case TIFFTag.TIFF_RATIONAL:
+            long[] lvalue = getAsRational(index);
+            return (float)((double)lvalue[0]/lvalue[1]);
+        case TIFFTag.TIFF_ASCII:
+             String s = ((String[])data)[index];
+             return (float)Double.parseDouble(s);
+        default:
+            throw new ClassCastException(); // should never happen
+        }
+    }
+
+    /**
+     * Returns data in any format as a <code>double</code>.
+     *
+     * <p> <code>TIFFTag.TIFF_BYTE</code> and <code>TIFF_UNDEFINED</code> data
+     * are treated as unsigned; that is, no sign extension will take
+     * place and the returned value will be in the range [0, 255].
+     * <code>TIFF_SBYTE</code> data will be returned in the range
+     * [-128, 127].
+     *
+     * <p> Data in <code>TIFF_SRATIONAL</code> or
+     * <code>TIFF_RATIONAL</code> format are evaluated by dividing the
+     * numerator into the denominator using double-precision
+     * arithmetic.
+     *
+     * <p> Data in <code>TIFF_ASCII</code> format will be parsed as by
+     * the <code>Double.parseDouble</code> method.
+     *
+     * @param index The index of the data.
+     * @return The data at the given index as a {@code double}.
+     */
+    public double getAsDouble(int index) {
+        switch (type) {
+        case TIFFTag.TIFF_BYTE:
+        case TIFFTag.TIFF_UNDEFINED:
+            return ((byte[])data)[index] & 0xff;
+        case TIFFTag.TIFF_SBYTE:
+            return ((byte[])data)[index];
+        case TIFFTag.TIFF_SHORT:
+            return ((char[])data)[index] & 0xffff;
+        case TIFFTag.TIFF_SSHORT:
+            return ((short[])data)[index];
+        case TIFFTag.TIFF_SLONG:
+            return ((int[])data)[index];
+        case TIFFTag.TIFF_LONG:
+        case TIFFTag.TIFF_IFD_POINTER:
+            return ((long[])data)[index];
+        case TIFFTag.TIFF_FLOAT:
+            return ((float[])data)[index];
+        case TIFFTag.TIFF_DOUBLE:
+            return ((double[])data)[index];
+        case TIFFTag.TIFF_SRATIONAL:
+            int[] ivalue = getAsSRational(index);
+            return (double)ivalue[0]/ivalue[1];
+        case TIFFTag.TIFF_RATIONAL:
+            long[] lvalue = getAsRational(index);
+            return (double)lvalue[0]/lvalue[1];
+        case TIFFTag.TIFF_ASCII:
+             String s = ((String[])data)[index];
+             return Double.parseDouble(s);
+        default:
+            throw new ClassCastException(); // should never happen
+        }
+    }
+
+    /**
+     * Returns a <code>TIFFTag.TIFF_ASCII</code> value as a
+     * <code>String</code>.
+     *
+     * @throws ClassCastException if the field is not of type
+     * <code>TIFF_ASCII</code>.
+     *
+     * @param index The index of the data.
+     * @return The data at the given index as a {@code String}.
+     */
+    public String getAsString(int index) {
+        return ((String[])data)[index];
+    }
+
+    /**
+     * Returns a <code>TIFFTag.TIFF_SRATIONAL</code> data item as a
+     * two-element array of <code>int</code>s.
+     *
+     * @param index The index of the data.
+     * @return The data at the given index as a signed rational.
+     * @throws ClassCastException if the field is not of type
+     * <code>TIFF_SRATIONAL</code>.
+     */
+    public int[] getAsSRational(int index) {
+        return ((int[][])data)[index];
+    }
+
+    /**
+     * Returns a TIFFTag.TIFF_RATIONAL data item as a two-element array
+     * of ints.
+     *
+     * @param index The index of the data.
+     * @return The data at the given index as an unsigned rational.
+     * @throws ClassCastException if the field is not of type
+     * <code>TIFF_RATIONAL</code>.
+     */
+    public long[] getAsRational(int index) {
+        return ((long[][])data)[index];
+    }
+
+
+    /**
+     * Returns a <code>String</code> containing a human-readable
+     * version of the data item.  Data of type
+     * <code>TIFFTag.TIFF_RATIONAL</code> or <code>TIFF_SRATIONAL</code> are
+     * represented as a pair of integers separated by a
+     * <code>'/'</code> character.
+     *
+     * @param index The index of the data.
+     * @return The data at the given index as a {@code String}.
+     * @throws ClassCastException if the field is not of one of the
+     * legal field types.
+     */
+    public String getValueAsString(int index) {
+        switch (type) {
+        case TIFFTag.TIFF_ASCII:
+            return ((String[])data)[index];
+        case TIFFTag.TIFF_BYTE:
+        case TIFFTag.TIFF_UNDEFINED:
+            return Integer.toString(((byte[])data)[index] & 0xff);
+        case TIFFTag.TIFF_SBYTE:
+            return Integer.toString(((byte[])data)[index]);
+        case TIFFTag.TIFF_SHORT:
+            return Integer.toString(((char[])data)[index] & 0xffff);
+        case TIFFTag.TIFF_SSHORT:
+            return Integer.toString(((short[])data)[index]);
+        case TIFFTag.TIFF_SLONG:
+            return Integer.toString(((int[])data)[index]);
+        case TIFFTag.TIFF_LONG:
+        case TIFFTag.TIFF_IFD_POINTER:
+            return Long.toString(((long[])data)[index]);
+        case TIFFTag.TIFF_FLOAT:
+            return Float.toString(((float[])data)[index]);
+        case TIFFTag.TIFF_DOUBLE:
+            return Double.toString(((double[])data)[index]);
+        case TIFFTag.TIFF_SRATIONAL:
+            int[] ivalue = getAsSRational(index);
+            String srationalString;
+            if(ivalue[1] != 0 && ivalue[0] % ivalue[1] == 0) {
+                // If the denominator is a non-zero integral divisor
+                // of the numerator then convert the fraction to be
+                // with respect to a unity denominator.
+                srationalString =
+                    Integer.toString(ivalue[0] / ivalue[1]) + "/1";
+            } else {
+                // Use the values directly.
+                srationalString =
+                    Integer.toString(ivalue[0]) +
+                    "/" +
+                    Integer.toString(ivalue[1]);
+            }
+            return srationalString;
+        case TIFFTag.TIFF_RATIONAL:
+            long[] lvalue = getAsRational(index);
+            String rationalString;
+            if(lvalue[1] != 0L && lvalue[0] % lvalue[1] == 0) {
+                // If the denominator is a non-zero integral divisor
+                // of the numerator then convert the fraction to be
+                // with respect to a unity denominator.
+                rationalString =
+                    Long.toString(lvalue[0] / lvalue[1]) + "/1";
+            } else {
+                // Use the values directly.
+                rationalString =
+                    Long.toString(lvalue[0]) +
+                    "/" +
+                    Long.toString(lvalue[1]);
+            }
+            return rationalString;
+        default:
+            throw new ClassCastException(); // should never happen
+        }
+    }
+
+    /**
+     * Returns whether the field has a <code>TIFFDirectory</code>.
+     *
+     * @return true if and only if getDirectory() returns non-null.
+     */
+    public boolean hasDirectory() {
+        return getDirectory() != null;
+    }
+
+    /**
+     * Returns the associated <code>TIFFDirectory</code>, if available. If no
+     * directory is set, then <code>null</code> will be returned.
+     *
+     * @return the TIFFDirectory instance or null.
+     */
+    public TIFFDirectory getDirectory() {
+        return dir;
+    }
+
+    /**
+     * Clones the field and all the information contained therein.
+     *
+     * @return A clone of this <code>TIFFField</code>.
+     * @throws CloneNotSupportedException if the instance cannot be cloned.
+     */
+    @Override
+    public TIFFField clone() throws CloneNotSupportedException {
+        TIFFField field = (TIFFField)super.clone();
+
+        Object fieldData;
+        switch (type) {
+        case TIFFTag.TIFF_BYTE:
+        case TIFFTag.TIFF_UNDEFINED:
+        case TIFFTag.TIFF_SBYTE:
+            fieldData = ((byte[])data).clone();
+            break;
+        case TIFFTag.TIFF_SHORT:
+            fieldData = ((char[])data).clone();
+            break;
+        case TIFFTag.TIFF_SSHORT:
+            fieldData = ((short[])data).clone();
+            break;
+        case TIFFTag.TIFF_SLONG:
+            fieldData = ((int[])data).clone();
+            break;
+        case TIFFTag.TIFF_LONG:
+        case TIFFTag.TIFF_IFD_POINTER:
+            fieldData = ((long[])data).clone();
+            break;
+        case TIFFTag.TIFF_FLOAT:
+            fieldData = ((float[])data).clone();
+            break;
+        case TIFFTag.TIFF_DOUBLE:
+            fieldData = ((double[])data).clone();
+            break;
+        case TIFFTag.TIFF_SRATIONAL:
+            fieldData = ((int[][])data).clone();
+            break;
+        case TIFFTag.TIFF_RATIONAL:
+            fieldData = ((long[][])data).clone();
+            break;
+        case TIFFTag.TIFF_ASCII:
+            fieldData = ((String[])data).clone();
+            break;
+        default:
+            throw new ClassCastException(); // should never happen
+        }
+
+        field.tag = tag;
+        field.tagNumber = tagNumber;
+        field.type = type;
+        field.count = count;
+        field.data = fieldData;
+        field.dir = dir != null ? dir.clone() : null;
+
+        return field;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFImageReadParam.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,116 @@
+/*
+ * 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 javax.imageio.plugins.tiff;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.imageio.ImageReadParam;
+
+/**
+ * A subclass of {@link ImageReadParam} allowing control over
+ * the TIFF reading process.
+ *
+ * <p> Because TIFF is an extensible format, the reader requires
+ * information about any tags used by TIFF extensions in order to emit
+ * meaningful metadata.  Also, TIFF extensions may define new
+ * compression types.  Both types of information about extensions may
+ * be provided by this interface.
+ *
+ * <p> Additional TIFF tags must be organized into
+ * <code>TIFFTagSet</code>s.  A <code>TIFFTagSet</code> may be
+ * provided to the reader by means of the
+ * <code>addAllowedTagSet</code> method.  By default, the tag sets
+ * <code>BaselineTIFFTagSet</code>, <code>FaxTIFFTagSet</code>,
+ * <code>ExifParentTIFFTagSet</code>, and <code>GeoTIFFTagSet</code>
+ * are included.
+ *
+ * @since 1.9
+ */
+public class TIFFImageReadParam extends ImageReadParam {
+
+    private List<TIFFTagSet> allowedTagSets = new ArrayList<TIFFTagSet>(4);
+
+    /**
+     * Constructs a <code>TIFFImageReadParam</code>.  Tags defined by
+     * the <code>TIFFTagSet</code>s <code>BaselineTIFFTagSet</code>,
+     * <code>FaxTIFFTagSet</code>, <code>ExifParentTIFFTagSet</code>, and
+     * <code>GeoTIFFTagSet</code> will be supported.
+     *
+     * @see BaselineTIFFTagSet
+     * @see FaxTIFFTagSet
+     * @see ExifParentTIFFTagSet
+     * @see GeoTIFFTagSet
+     */
+    public TIFFImageReadParam() {
+        addAllowedTagSet(BaselineTIFFTagSet.getInstance());
+        addAllowedTagSet(FaxTIFFTagSet.getInstance());
+        addAllowedTagSet(ExifParentTIFFTagSet.getInstance());
+        addAllowedTagSet(GeoTIFFTagSet.getInstance());
+    }
+
+    /**
+     * Adds a <code>TIFFTagSet</code> object to the list of allowed
+     * tag sets.
+     *
+     * @param tagSet a <code>TIFFTagSet</code>.
+     *
+     * @throws IllegalArgumentException if <code>tagSet</code> is
+     * <code>null</code>.
+     */
+    public void addAllowedTagSet(TIFFTagSet tagSet) {
+        if (tagSet == null) {
+            throw new IllegalArgumentException("tagSet == null!");
+        }
+        allowedTagSets.add(tagSet);
+    }
+
+    /**
+     * Removes a <code>TIFFTagSet</code> object from the list of
+     * allowed tag sets.  Removal is based on the <code>equals</code>
+     * method of the <code>TIFFTagSet</code>, which is normally
+     * defined as reference equality.
+     *
+     * @param tagSet a <code>TIFFTagSet</code>.
+     *
+     * @throws IllegalArgumentException if <code>tagSet</code> is
+     * <code>null</code>.
+     */
+    public void removeAllowedTagSet(TIFFTagSet tagSet) {
+        if (tagSet == null) {
+            throw new IllegalArgumentException("tagSet == null!");
+        }
+        allowedTagSets.remove(tagSet);
+    }
+
+    /**
+     * Returns a <code>List</code> containing the allowed
+     * <code>TIFFTagSet</code> objects.
+     *
+     * @return a <code>List</code> of <code>TIFFTagSet</code>s.
+     */
+    public List<TIFFTagSet> getAllowedTagSets() {
+        return allowedTagSets;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFTag.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,413 @@
+/*
+ * 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 javax.imageio.plugins.tiff;
+
+import java.util.Iterator;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * A class defining the notion of a TIFF tag.  A TIFF tag is a key
+ * that may appear in an Image File Directory (IFD).  In the IFD
+ * each tag has some data associated with it, which may consist of zero
+ * or more values of a given data type. The combination of a tag and a
+ * value is known as an IFD Entry or TIFF Field.
+ *
+ * <p> The actual tag values used in the root IFD of a standard ("baseline")
+ * tiff stream are defined in the {@link BaselineTIFFTagSet
+ * BaselineTIFFTagSet} class.
+ *
+ * @since 1.9
+ * @see   BaselineTIFFTagSet
+ * @see   TIFFField
+ * @see   TIFFTagSet
+ */
+public class TIFFTag {
+
+    // TIFF 6.0 + Adobe PageMaker(R) 6.0 TIFF Technical Notes 1 IFD data type
+
+    /** Flag for 8 bit unsigned integers. */
+    public static final int TIFF_BYTE        =  1;
+
+    /** Flag for null-terminated ASCII strings. */
+    public static final int TIFF_ASCII       =  2;
+
+    /** Flag for 16 bit unsigned integers. */
+    public static final int TIFF_SHORT       =  3;
+
+    /** Flag for 32 bit unsigned integers. */
+    public static final int TIFF_LONG        =  4;
+
+    /** Flag for pairs of 32 bit unsigned integers. */
+    public static final int TIFF_RATIONAL    =  5;
+
+    /** Flag for 8 bit signed integers. */
+    public static final int TIFF_SBYTE       =  6;
+
+    /** Flag for 8 bit uninterpreted bytes. */
+    public static final int TIFF_UNDEFINED   =  7;
+
+    /** Flag for 16 bit signed integers. */
+    public static final int TIFF_SSHORT      =  8;
+
+    /** Flag for 32 bit signed integers. */
+    public static final int TIFF_SLONG       =  9;
+
+    /** Flag for pairs of 32 bit signed integers. */
+    public static final int TIFF_SRATIONAL   = 10;
+
+    /** Flag for 32 bit IEEE floats. */
+    public static final int TIFF_FLOAT       = 11;
+
+    /** Flag for 64 bit IEEE doubles. */
+    public static final int TIFF_DOUBLE      = 12;
+
+    /**
+     * Flag for IFD pointer defined in TIFF Tech Note 1 in
+     * TIFF Specification Supplement 1.
+     */
+    public static final int TIFF_IFD_POINTER = 13;
+
+    /**
+     * The numerically smallest constant representing a TIFF data type.
+     */
+    public static final int MIN_DATATYPE = TIFF_BYTE;
+
+    /**
+     * The numerically largest constant representing a TIFF data type.
+     */
+    public static final int MAX_DATATYPE = TIFF_IFD_POINTER;
+
+    /**
+     * The name assigned to a tag with an unknown tag number. Such
+     * a tag may be created for example when reading an IFD and a
+     * tag number is encountered which is not in any of the
+     * <code>TIFFTagSet</code>s known to the reader.
+     */
+    public static final String UNKNOWN_TAG_NAME = "UnknownTag";
+
+    /**
+     * Disallowed data type mask.
+     */
+    private static final int DISALLOWED_DATATYPES_MASK = ~0x3fff;
+
+    private static final int[] SIZE_OF_TYPE = {
+        0, //  0 = n/a
+        1, //  1 = byte
+        1, //  2 = ascii
+        2, //  3 = short
+        4, //  4 = long
+        8, //  5 = rational
+        1, //  6 = sbyte
+        1, //  7 = undefined
+        2, //  8 = sshort
+        4, //  9 = slong
+        8, // 10 = srational
+        4, // 11 = float
+        8, // 12 = double
+        4, // 13 = IFD_POINTER
+    };
+
+    private int number;
+    private String name;
+    private int dataTypes;
+    private int count;
+    private TIFFTagSet tagSet = null;
+
+    // Mnemonic names for integral enumerated constants
+    private SortedMap<Integer,String> valueNames = null;
+
+    /**
+     * Constructs a <code>TIFFTag</code> with a given name, tag number, set
+     * of legal data types, and value count. A negative value count signifies
+     * that either an arbitrary number of values is legal or the required count
+     * is determined by the values of other fields in the IFD. A non-negative
+     * count specifies the number of values which an associated field must
+     * contain. The tag will have no associated <code>TIFFTagSet</code>.
+     *
+     * <p> If there are mnemonic names to be associated with the legal
+     * data values for the tag, {@link #addValueName(int, String)
+     * addValueName()} should be called on the new instance for each name.
+     * Mnemonic names apply only to tags which have integral data type.</p>
+     *
+     * <p> See the documentation for {@link #getDataTypes()
+     * getDataTypes()} for an explanation of how the set
+     * of data types is to be converted into a bit mask.</p>
+     *
+     * @param name the name of the tag.
+     * @param number the number used to represent the tag.
+     * @param dataTypes a bit mask indicating the set of legal data
+     * types for this tag.
+     * @param count the value count for this tag.
+     * @throws NullPointerException if name is null.
+     * @throws IllegalArgumentException if number is negative or dataTypes
+     * is negative or specifies an out of range type.
+     */
+    public TIFFTag(String name, int number, int dataTypes, int count) {
+        if (name == null) {
+            throw new NullPointerException("name == null");
+        } else if (number < 0) {
+            throw new IllegalArgumentException("number (" + number + ") < 0");
+        } else if (dataTypes < 0
+            || (dataTypes & DISALLOWED_DATATYPES_MASK) != 0) {
+            throw new IllegalArgumentException("dataTypes out of range");
+        }
+
+        this.name = name;
+        this.number = number;
+        this.dataTypes = dataTypes;
+        this.count = count;
+    }
+
+    /**
+     * Constructs a <code>TIFFTag</code> with a given name, tag number and
+     * <code>TIFFTagSet</code> to which it refers. The legal data types are
+     * set to include {@link #TIFF_LONG} and {@link #TIFF_IFD_POINTER} and the
+     * value count is unity. The <code>TIFFTagSet</code> will
+     * represent the set of <code>TIFFTag</code>s which appear in the IFD
+     * pointed to. A <code>TIFFTag</code> represents an IFD pointer if and
+     * only if <code>tagSet</code> is non-<code>null</code> or the data
+     * type <code>TIFF_IFD_POINTER</code> is legal.
+     *
+     * @param name the name of the tag.
+     * @param number the number used to represent the tag.
+     * @param tagSet the <code>TIFFTagSet</code> to which this tag belongs.
+     * @throws NullPointerException if name or tagSet is null.
+     * @throws IllegalArgumentException if number is negative.
+     *
+     * @see #TIFFTag(String, int, int, int)
+     */
+    public TIFFTag(String name, int number, TIFFTagSet tagSet) {
+        this(name, number,
+            1 << TIFFTag.TIFF_LONG | 1 << TIFFTag.TIFF_IFD_POINTER, 1);
+        if (tagSet == null) {
+            throw new NullPointerException("tagSet == null");
+        }
+        this.tagSet = tagSet;
+    }
+
+    /**
+     * Constructs  a  <code>TIFFTag</code>  with  a  given  name,  tag number,
+     * and set  of  legal  data  types.  The value count of the tag will be
+     * undefined and it will  have  no associated <code>TIFFTagSet</code>.
+     *
+     * @param name the name of the tag.
+     * @param number the number used to represent the tag.
+     * @param dataTypes a bit mask indicating the set of legal data
+     * types for this tag.
+     * @throws NullPointerException if name is null.
+     * @throws IllegalArgumentException if number is negative or dataTypes
+     * is negative or specifies an out of range type.
+     *
+     * @see #TIFFTag(String, int, int, int)
+     */
+    public TIFFTag(String name, int number, int dataTypes) {
+        this(name, number, dataTypes, -1);
+    }
+
+    /**
+     * Returns the number of bytes used to store a value of the given
+     * data type.
+     *
+     * @param dataType the data type to be queried.
+     *
+     * @return the number of bytes used to store the given data type.
+     *
+     * @throws IllegalArgumentException if <code>datatype</code> is
+     * less than <code>MIN_DATATYPE</code> or greater than
+     * <code>MAX_DATATYPE</code>.
+     */
+    public static int getSizeOfType(int dataType) {
+        if (dataType < MIN_DATATYPE ||dataType > MAX_DATATYPE) {
+            throw new IllegalArgumentException("dataType out of range!");
+        }
+
+        return SIZE_OF_TYPE[dataType];
+    }
+
+    /**
+     * Returns the name of the tag, as it will appear in image metadata.
+     *
+     * @return the tag name, as a <code>String</code>.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns the integer used to represent the tag.
+     *
+     * @return the tag number, as an <code>int</code>.
+     */
+    public int getNumber() {
+        return number;
+    }
+
+    /**
+     * Returns a bit mask indicating the set of data types that may
+     * be used to store the data associated with the tag.
+     * For example, a tag that can store both SHORT and LONG values
+     * would return a value of:
+     *
+     * <pre>
+     * (1 &lt;&lt; TIFFTag.TIFF_SHORT) | (1 &lt;&lt; TIFFTag.TIFF_LONG)
+     * </pre>
+     *
+     * @return an <code>int</code> containing a bitmask encoding the
+     * set of valid data types.
+     */
+    public int getDataTypes() {
+        return dataTypes;
+    }
+
+    /**
+     * Returns the value count of this tag. If this value is positive, it
+     * represents the required number of values for a <code>TIFFField</code>
+     * which has this tag. If the value is negative, the count is undefined.
+     * In the latter case the count may be derived, e.g., the number of values
+     * of the <code>BitsPerSample</code> field is <code>SamplesPerPixel</code>,
+     * or it may be variable as in the case of most <code>US-ASCII</code>
+     * fields.
+     *
+     * @return the value count of this tag.
+     */
+    public int getCount() {
+        return count;
+    }
+
+    /**
+     * Returns <code>true</code> if the given data type
+     * may be used for the data associated with this tag.
+     *
+     * @param dataType the data type to be queried, one of
+     * <code>TIFF_BYTE</code>, <code>TIFF_SHORT</code>, etc.
+     *
+     * @return a <code>boolean</code> indicating whether the given
+     * data type may be used with this tag.
+     *
+     * @throws IllegalArgumentException if <code>datatype</code> is
+     * less than <code>MIN_DATATYPE</code> or greater than
+     * <code>MAX_DATATYPE</code>.
+     */
+    public boolean isDataTypeOK(int dataType) {
+        if (dataType < MIN_DATATYPE || dataType > MAX_DATATYPE) {
+            throw new IllegalArgumentException("datatype not in range!");
+        }
+        return (dataTypes & (1 << dataType)) != 0;
+    }
+
+    /**
+     * Returns the <code>TIFFTagSet</code> of which this tag is a part.
+     *
+     * @return the containing <code>TIFFTagSet</code>.
+     */
+    public TIFFTagSet getTagSet() {
+        return tagSet;
+    }
+
+    /**
+     * Returns <code>true</code> if this tag is used to point to an IFD
+     * structure containing additional tags. A <code>TIFFTag</code> represents
+     * an IFD pointer if and only if its <code>TIFFTagSet</code> is
+     * non-<code>null</code> or the data type <code>TIFF_IFD_POINTER</code> is
+     * legal. This condition will be satisfied if and only if either
+     * <code>getTagSet()&nbsp;!=&nbsp;null</code> or
+     * <code>isDataTypeOK(TIFF_IFD_POINTER)&nbsp;==&nbsp;true</code>.
+     *
+     * <p>Many TIFF extensions use the IFD mechanism in order to limit the
+     * number of new tags that may appear in the root IFD.</p>
+     *
+     * @return <code>true</code> if this tag points to an IFD.
+     */
+    public boolean isIFDPointer() {
+        return tagSet != null || isDataTypeOK(TIFF_IFD_POINTER);
+    }
+
+    /**
+     * Returns <code>true</code> if there are mnemonic names associated with
+     * the set of legal values for the data associated with this tag.  Mnemonic
+     * names apply only to tags which have integral data type.
+     *
+     * @return <code>true</code> if mnemonic value names are available.
+     */
+    public boolean hasValueNames() {
+        return valueNames != null;
+    }
+
+    /**
+     * Adds a mnemonic name for a particular value that this tag's data may take
+     * on.  Mnemonic names apply only to tags which have integral data type.
+     *
+     * @param value the data value.
+     * @param name the name to associate with the value.
+     */
+    protected void addValueName(int value, String name) {
+        if (valueNames == null) {
+            valueNames = new TreeMap<Integer,String>();
+        }
+        valueNames.put(Integer.valueOf(value), name);
+    }
+
+    /**
+     * Returns the mnemonic name associated with a particular value
+     * that this tag's data may take on, or <code>null</code> if
+     * no name is present.  Mnemonic names apply only to tags which have
+     * integral data type.
+     *
+     * @param value the data value.
+     *
+     * @return the mnemonic name associated with the value, as a
+     * <code>String</code>.
+     */
+    public String getValueName(int value) {
+        if (valueNames == null) {
+            return null;
+        }
+        return valueNames.get(Integer.valueOf(value));
+    }
+
+    /**
+     * Returns an array of values for which mnemonic names are defined.  The
+     * method {@link #getValueName(int) getValueName()} will return
+     * non-{@code null} only for values contained in the returned array.
+     * Mnemonic names apply only to tags which have integral data type.
+     *
+     * @return the values for which there is a mnemonic name.
+     */
+    public int[] getNamedValues() {
+        int[] intValues = null;
+        if (valueNames != null) {
+            Set<Integer> values = valueNames.keySet();
+            Iterator<Integer> iter = values.iterator();
+            intValues = new int[values.size()];
+            int i = 0;
+            while (iter.hasNext()) {
+                intValues[i++] = iter.next();
+            }
+        }
+        return intValues;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/TIFFTagSet.java	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,165 @@
+/*
+ * 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 javax.imageio.plugins.tiff;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * A class representing a set of TIFF tags.  Each tag in the set must
+ * have a unique number (this is a limitation of the TIFF
+ * specification itself).
+ *
+ * <p> This class and its subclasses are responsible for mapping
+ * between raw tag numbers and <code>TIFFTag</code> objects, which
+ * contain additional information about each tag, such as the tag's
+ * name, legal data types, and mnemonic names for some or all of ts
+ * data values.
+ *
+ * @since 1.9
+ * @see   TIFFTag
+ */
+public class TIFFTagSet {
+
+    private SortedMap<Integer,TIFFTag> allowedTagsByNumber = new TreeMap<Integer,TIFFTag>();
+
+    private SortedMap<String,TIFFTag> allowedTagsByName = new TreeMap<String,TIFFTag>();
+
+    /**
+     * Constructs a TIFFTagSet.
+     */
+    private TIFFTagSet() {}
+
+    /**
+     * Constructs a <code>TIFFTagSet</code>, given a <code>List</code>
+     * of <code>TIFFTag</code> objects.
+     *
+     * @param tags a <code>List</code> object containing
+     * <code>TIFFTag</code> objects to be added to this tag set.
+     *
+     * @throws IllegalArgumentException if <code>tags</code> is
+     * <code>null</code>, or contains objects that are not instances
+     * of the <code>TIFFTag</code> class.
+     */
+    public TIFFTagSet(List<TIFFTag> tags) {
+        if (tags == null) {
+            throw new IllegalArgumentException("tags == null!");
+        }
+        Iterator<TIFFTag> iter = tags.iterator();
+        while (iter.hasNext()) {
+            Object o = iter.next();
+            if (!(o instanceof TIFFTag)) {
+                throw new IllegalArgumentException(
+                                               "tags contains a non-TIFFTag!");
+            }
+            TIFFTag tag = (TIFFTag)o;
+
+            allowedTagsByNumber.put(Integer.valueOf(tag.getNumber()), tag);
+            allowedTagsByName.put(tag.getName(), tag);
+        }
+    }
+
+    /**
+     * Returns the <code>TIFFTag</code> from this set that is
+     * associated with the given tag number, or <code>null</code> if
+     * no tag exists for that number.
+     *
+     * @param tagNumber the number of the tag to be retrieved.
+     *
+     * @return the numbered <code>TIFFTag</code>, or <code>null</code>.
+     */
+    public TIFFTag getTag(int tagNumber) {
+        return allowedTagsByNumber.get(Integer.valueOf(tagNumber));
+    }
+
+    /**
+     * Returns the <code>TIFFTag</code> having the given tag name, or
+     * <code>null</code> if the named tag does not belong to this tag set.
+     *
+     * @param tagName the name of the tag to be retrieved, as a
+     * <code>String</code>.
+     *
+     * @return the named <code>TIFFTag</code>, or <code>null</code>.
+     *
+     * @throws IllegalArgumentException if <code>tagName</code> is
+     * <code>null</code>.
+     */
+    public TIFFTag getTag(String tagName) {
+        if (tagName == null) {
+            throw new IllegalArgumentException("tagName == null!");
+        }
+        return allowedTagsByName.get(tagName);
+    }
+
+    /**
+     * Retrieves an unmodifiable numerically increasing set of tag numbers.
+     *
+     * <p>The returned object is unmodifiable and contains the tag
+     * numbers of all <code>TIFFTag</code>s in this <code>TIFFTagSet</code>
+     * sorted into ascending order according to
+     * {@link Integer#compareTo(Object)}.</p>
+     *
+     * @return All tag numbers in this set.
+     */
+    public SortedSet<Integer> getTagNumbers() {
+        Set<Integer> tagNumbers = allowedTagsByNumber.keySet();
+        SortedSet<Integer> sortedTagNumbers;
+        if(tagNumbers instanceof SortedSet) {
+            sortedTagNumbers = (SortedSet<Integer>)tagNumbers;
+        } else {
+            sortedTagNumbers = new TreeSet<Integer>(tagNumbers);
+        }
+
+        return Collections.unmodifiableSortedSet(sortedTagNumbers);
+    }
+
+    /**
+     * Retrieves an unmodifiable lexicographically increasing set of tag names.
+     *
+     * <p>The returned object is unmodifiable and contains the tag
+     * names of all <code>TIFFTag</code>s in this <code>TIFFTagSet</code>
+     * sorted into ascending order according to
+     * {@link String#compareTo(Object)}.</p>
+     *
+     * @return All tag names in this set.
+     */
+    public SortedSet<String> getTagNames() {
+        Set<String> tagNames = allowedTagsByName.keySet();
+        SortedSet<String> sortedTagNames;
+        if(tagNames instanceof SortedSet) {
+            sortedTagNames = (SortedSet<String>)tagNames;
+        } else {
+            sortedTagNames = new TreeSet<String>(tagNames);
+        }
+
+        return Collections.unmodifiableSortedSet(sortedTagNames);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/plugins/tiff/package.html	Mon Nov 23 12:26:12 2015 -0800
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<!--
+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.
+-->
+</head>
+
+<body bgcolor="white">
+
+<body>
+Public classes used by the built-in TIFF plug-ins.
+
+<p>
+This package contains classes supporting the built-in TIFF reader and writer
+plug-ins. Classes are provided for simplifying interaction with metadata,
+including Exif metadata common in digital photography, and an extension of
+{@link javax.imageio.ImageReadParam} which permits specifying which metadata
+tags are allowed to be read. For more information about the operation of the
+built-in TIFF plug-ins, see the
+<a HREF="../../metadata/doc-files/tiff_metadata.html">TIFF metadata format
+specification and usage notes</a>.
+
+<br>
+<br>
+<br>
+
+@since 1.9
+</body>
+</html>
--- a/jdk/src/java.desktop/share/classes/javax/imageio/spi/IIORegistry.java	Mon Nov 23 10:00:50 2015 -0800
+++ b/jdk/src/java.desktop/share/classes/javax/imageio/spi/IIORegistry.java	Mon Nov 23 12:26:12 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -49,6 +49,8 @@
 import com.sun.imageio.plugins.bmp.BMPImageWriterSpi;
 import com.sun.imageio.plugins.wbmp.WBMPImageReaderSpi;
 import com.sun.imageio.plugins.wbmp.WBMPImageWriterSpi;
+import com.sun.imageio.plugins.tiff.TIFFImageReaderSpi;
+import com.sun.imageio.plugins.tiff.TIFFImageWriterSpi;
 import sun.awt.AppContext;
 import java.util.ServiceLoader;
 import java.util.ServiceConfigurationError;
@@ -168,6 +170,8 @@
         registerServiceProvider(new BMPImageWriterSpi());
         registerServiceProvider(new WBMPImageReaderSpi());
         registerServiceProvider(new WBMPImageWriterSpi());
+        registerServiceProvider(new TIFFImageReaderSpi());
+        registerServiceProvider(new TIFFImageWriterSpi());
         registerServiceProvider(new PNGImageReaderSpi());
         registerServiceProvider(new PNGImageWriterSpi());
         registerServiceProvider(new JPEGImageReaderSpi());