Started breaking up TIFFImageEncoder into constituent classes


git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/commons/branches/Temp_URI_Resolution@1387604 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/java/org/apache/xmlgraphics/image/codec/tiff/CompressionValue.java b/src/java/org/apache/xmlgraphics/image/codec/tiff/CompressionValue.java
new file mode 100644
index 0000000..b1fe2ed
--- /dev/null
+++ b/src/java/org/apache/xmlgraphics/image/codec/tiff/CompressionValue.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.xmlgraphics.image.codec.tiff;
+
+
+/** Enumerates the possible compression values for TIFF images. */
+public enum CompressionValue {
+    /** No compression. */
+    NONE(1),
+    /**
+     * Modified Huffman Compression (CCITT Group 3 1D facsimile compression).
+     * <p><b>Not currently supported.</b>
+     */
+    GROUP3_1D(2),
+    /**
+     * CCITT T.4 bilevel compression (CCITT Group 3 2D facsimile compression).
+     * <p><b>Not currently supported.</b>
+     */
+    GROUP3_2D(3),
+    /**
+     * CCITT T.6 bilevel compression (CCITT Group 4 facsimile compression).
+     * <p><b>Not currently supported.</b>
+     */
+    GROUP4(4),
+    /** LZW compression. <p><b>Not supported.</b> */
+    LZW(5),
+    /**
+     * Code for original JPEG-in-TIFF compression which has been depricated (for many good reasons)
+     * in favor of Tech Note 2 JPEG compression (compression scheme 7).
+     * <p><b>Not supported.</b>
+     */
+    JPEG_BROKEN(6),
+    /** <a href="ftp://ftp.sgi.com/graphics/tiff/TTN2.draft.txt"> JPEG-in-TIFF</a> compression. */
+    JPEG_TTN2(7),
+    /** Byte-oriented run-length encoding "PackBits" compression. */
+    PACKBITS(32773),
+    /**
+     * <a href="http://info.internet.isi.edu:80/in-notes/rfc/files/rfc1951.txt">
+     * DEFLATE</a> lossless compression (also known as "Zip-in-TIFF").
+     */
+    DEFLATE(32946);
+
+    private final int compressionValue;
+
+    private CompressionValue(int compressionValue) {
+        this.compressionValue = compressionValue;
+    }
+
+    int getValue() {
+        return compressionValue;
+    }
+
+    /**
+     * Gets the compression value given the name of the compression type.
+     * @param name the compression name
+     * @return the compression value
+     */
+    public static CompressionValue getValue(String name) {
+        if (name == null) {
+            return PACKBITS;
+        }
+        for (CompressionValue cv : CompressionValue.values()) {
+            if (cv.toString().equalsIgnoreCase(name)) {
+                return cv;
+            }
+        }
+        throw new IllegalArgumentException("Unknown compression value: " + name);
+    }
+}
diff --git a/src/java/org/apache/xmlgraphics/image/codec/tiff/ExtraSamplesType.java b/src/java/org/apache/xmlgraphics/image/codec/tiff/ExtraSamplesType.java
new file mode 100644
index 0000000..5600092
--- /dev/null
+++ b/src/java/org/apache/xmlgraphics/image/codec/tiff/ExtraSamplesType.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.xmlgraphics.image.codec.tiff;
+
+import java.awt.image.ColorModel;
+
+enum ExtraSamplesType {
+    UNSPECIFIED(0),
+    ASSOCIATED_ALPHA(1),
+    UNASSOCIATED_ALPHA(2);
+
+    private final int typeValue;
+
+    private ExtraSamplesType(int value) {
+        this.typeValue = value;
+    }
+
+    static ExtraSamplesType getValue(ColorModel colorModel, int numExtraSamples) {
+        if (numExtraSamples == 1 && colorModel.hasAlpha()) {
+            return colorModel.isAlphaPremultiplied() ? ASSOCIATED_ALPHA : UNASSOCIATED_ALPHA;
+        }
+        return UNSPECIFIED;
+    }
+
+    int getValue() {
+        return typeValue;
+    }
+}
\ No newline at end of file
diff --git a/src/java/org/apache/xmlgraphics/image/codec/tiff/ImageInfo.java b/src/java/org/apache/xmlgraphics/image/codec/tiff/ImageInfo.java
new file mode 100644
index 0000000..f30286a
--- /dev/null
+++ b/src/java/org/apache/xmlgraphics/image/codec/tiff/ImageInfo.java
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.xmlgraphics.image.codec.tiff;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+import java.awt.image.IndexColorModel;
+import java.awt.image.RenderedImage;
+
+final class ImageInfo {
+
+    // Default values
+    private static final int DEFAULT_ROWS_PER_STRIP = 8;
+
+    private final int numExtraSamples;
+    private final ExtraSamplesType extraSampleType;
+    private final ImageType imageType;
+    private final int colormapSize;
+    private final char[] colormap;
+    private final int tileWidth;
+    private final int tileHeight;
+    private final int numTiles;
+    private final long bytesPerRow;
+    private final long bytesPerTile;
+
+    private ImageInfo(ImageInfoBuilder builder) {
+        this.numExtraSamples = builder.numExtraSamples;
+        this.extraSampleType = builder.extraSampleType;
+        this.imageType = builder.imageType;
+        this.colormapSize = builder.colormapSize;
+        this.colormap = copyColormap(builder.colormap);
+        this.tileWidth = builder.tileWidth;
+        this.tileHeight = builder.tileHeight;
+        this.numTiles = builder.numTiles;
+        this.bytesPerRow = builder.bytesPerRow;
+        this.bytesPerTile = builder.bytesPerTile;
+    }
+
+    private static char[] copyColormap(char[] colorMap) {
+        if (colorMap == null) {
+            return null;
+        }
+        char[] copy = new char[colorMap.length];
+        System.arraycopy(colorMap, 0, copy, 0, colorMap.length);
+        return copy;
+    }
+
+    private static int getNumberOfExtraSamplesForColorSpace(ColorSpace colorSpace,
+            ImageType imageType, int numBands) {
+        if (imageType == ImageType.GENERIC) {
+            return numBands - 1;
+        } else if (numBands > 1) {
+            return numBands - colorSpace.getNumComponents();
+        } else {
+            return 0;
+        }
+    }
+
+    private static char[] createColormap(final int sizeOfColormap, byte[] r, byte[] g, byte[] b) {
+        int redIndex = 0;
+        int greenIndex = sizeOfColormap;
+        int blueIndex = 2 * sizeOfColormap;
+        char[] colormap = new char[sizeOfColormap * 3];
+        for (int i = 0; i < sizeOfColormap; i++) {
+            // beware of sign extended bytes
+            colormap[redIndex++] = convertColorToColormapChar(0xff & r[i]);
+            colormap[greenIndex++] = convertColorToColormapChar(0xff & g[i]);
+            colormap[blueIndex++] = convertColorToColormapChar(0xff & b[i]);
+        }
+        return colormap;
+    }
+
+    private static char convertColorToColormapChar(int color) {
+        return (char) (color << 8 | color);
+    }
+
+    int getNumberOfExtraSamples() {
+        return numExtraSamples;
+    }
+
+    ExtraSamplesType getExtraSamplesType() {
+        return extraSampleType;
+    }
+
+    ImageType getType() {
+        return imageType;
+    }
+
+    int getColormapSize() {
+        return colormapSize;
+    }
+
+    char[] getColormap() {
+        return copyColormap(colormap);
+    }
+
+    int getTileWidth() {
+        return tileWidth;
+    }
+
+    int getTileHeight() {
+        return tileHeight;
+    }
+
+    int getNumTiles() {
+        return numTiles;
+    }
+
+    long getBytesPerRow() {
+        return bytesPerRow;
+    }
+
+    long getBytesPerTile() {
+        return bytesPerTile;
+    }
+
+    static ImageInfo newInstance(RenderedImage im, int dataTypeSize, int numBands,
+            ColorModel colorModel, TIFFEncodeParam params) {
+        ImageInfoBuilder builder = new ImageInfoBuilder();
+        if (colorModel instanceof IndexColorModel) { // Bilevel or palette
+            IndexColorModel indexColorModel = (IndexColorModel) colorModel;
+            int colormapSize = indexColorModel.getMapSize();
+            byte[] r = new byte[colormapSize];
+            indexColorModel.getReds(r);
+            byte[] g = new byte[colormapSize];
+            indexColorModel.getGreens(g);
+            byte[] b = new byte[colormapSize];
+            indexColorModel.getBlues(b);
+
+            builder.imageType = ImageType.getTypeFromRGB(colormapSize, r, g, b, dataTypeSize,
+                    numBands);
+            if (builder.imageType == ImageType.PALETTE) {
+                builder.colormap = createColormap(colormapSize, r, g, b);
+                builder.colormapSize = colormapSize * 3;
+            }
+        } else if (colorModel == null) {
+            if (dataTypeSize == 1 && numBands == 1) { // bilevel
+                builder.imageType = ImageType.BILEVEL_BLACK_IS_ZERO;
+            } else {
+                builder.imageType = ImageType.GENERIC;
+                builder.numExtraSamples = numBands > 1 ? numBands - 1 : 0;
+            }
+        } else {
+            ColorSpace colorSpace = colorModel.getColorSpace();
+            builder.imageType = ImageType.getTypeFromColorSpace(colorSpace, params);
+            builder.numExtraSamples = getNumberOfExtraSamplesForColorSpace(colorSpace,
+                    builder.imageType, numBands);
+            builder.extraSampleType = ExtraSamplesType.getValue(colorModel,
+                    builder.numExtraSamples);
+        }
+
+        // Initialize tile dimensions.
+        final int width = im.getWidth();
+        final int height = im.getHeight();
+        if (params.getWriteTiled()) {
+            builder.tileWidth = params.getTileWidth() > 0 ? params.getTileWidth() : width;
+            builder.tileHeight = params.getTileHeight() > 0 ? params.getTileHeight() : height;
+            // NB: Parentheses are used in this statement for correct rounding.
+            builder.numTiles = ((width + builder.tileWidth - 1) / builder.tileWidth)
+                    * ((height + builder.tileHeight - 1) / builder.tileHeight);
+        } else {
+            builder.tileWidth = width;
+            builder.tileHeight = params.getTileHeight() > 0 ? params.getTileHeight()
+                    : DEFAULT_ROWS_PER_STRIP;
+            builder.numTiles = (int) Math.ceil(height / (double) builder.tileHeight);
+        }
+        builder.setBytesPerRow(dataTypeSize, numBands)
+        .setBytesPerTile();
+        return builder.build();
+    }
+
+    private static final class ImageInfoBuilder {
+        private ImageType imageType = ImageType.UNSUPPORTED;
+        private int numExtraSamples;
+        private char[] colormap;
+        private int colormapSize;
+        private ExtraSamplesType extraSampleType = ExtraSamplesType.UNSPECIFIED;
+        private int tileWidth;
+        private int tileHeight;
+        private int numTiles;
+        private long bytesPerRow;
+        private long bytesPerTile;
+
+        private ImageInfoBuilder setBytesPerRow(int dataTypeSize, int numBands) {
+            bytesPerRow = (long) Math.ceil((dataTypeSize / 8.0) * tileWidth * numBands);
+            return this;
+        }
+
+        private ImageInfoBuilder setBytesPerTile() {
+            bytesPerTile = bytesPerRow * tileHeight;
+            return this;
+        }
+
+        private ImageInfo build() {
+            return new ImageInfo(this);
+        }
+    }
+}
diff --git a/src/java/org/apache/xmlgraphics/image/codec/tiff/ImageType.java b/src/java/org/apache/xmlgraphics/image/codec/tiff/ImageType.java
new file mode 100644
index 0000000..188ea1f
--- /dev/null
+++ b/src/java/org/apache/xmlgraphics/image/codec/tiff/ImageType.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.xmlgraphics.image.codec.tiff;
+
+import java.awt.color.ColorSpace;
+
+import org.apache.xmlgraphics.image.codec.util.PropertyUtil;
+
+enum ImageType {
+    UNSUPPORTED(-1),
+    BILEVEL_WHITE_IS_ZERO(0),
+    BILEVEL_BLACK_IS_ZERO(1),
+    GRAY(1),
+    PALETTE(3),
+    RGB(2),
+    CMYK(5),
+    YCBCR(6),
+    CIELAB(8),
+    GENERIC(1);
+
+    private final int photometricInterpretation;
+
+    private ImageType(int photometricInterpretation) {
+        this.photometricInterpretation = photometricInterpretation;
+    }
+
+    int getPhotometricInterpretation() {
+        return photometricInterpretation;
+    }
+
+    static ImageType getTypeFromRGB(int mapSize, byte[] r, byte[] g, byte[] b,
+            int dataTypeSize, int numBands) {
+        if (numBands == 1) {
+            if (dataTypeSize == 1) { // Bilevel image
+                if (mapSize != 2) {
+                    throw new IllegalArgumentException(PropertyUtil.getString("TIFFImageEncoder7"));
+                }
+
+                if (isBlackZero(r, g, b)) {
+                    return BILEVEL_BLACK_IS_ZERO;
+                } else if (isWhiteZero(r, g, b)) {
+                    return BILEVEL_WHITE_IS_ZERO;
+                }
+            }
+            return PALETTE;
+        }
+        return UNSUPPORTED;
+    }
+
+    private static boolean rgbIsValueAt(byte[] r, byte[] g, byte[] b, byte value, int i) {
+        return r[i] == value && g[i] == value && b[i] == value;
+    }
+
+    private static boolean bilevelColorValue(byte[] r, byte[] g, byte[] b, int blackValue,
+            int whiteValue) {
+        return rgbIsValueAt(r, g, b, (byte) blackValue, 0)
+                && rgbIsValueAt(r, g, b, (byte) whiteValue, 1);
+    }
+
+    private static boolean isBlackZero(byte[] r, byte[] g, byte[] b) {
+        return bilevelColorValue(r, g, b, 0, 0xff);
+    }
+
+    private static boolean isWhiteZero(byte[] r, byte[] g, byte[] b) {
+        return bilevelColorValue(r, g, b, 0xff, 0);
+    }
+
+    static ImageType getTypeFromColorSpace(ColorSpace colorSpace, TIFFEncodeParam params) {
+        switch (colorSpace.getType()) {
+        case ColorSpace.TYPE_CMYK:
+            return CMYK;
+        case ColorSpace.TYPE_GRAY:
+            return GRAY;
+        case ColorSpace.TYPE_Lab:
+            return CIELAB;
+        case ColorSpace.TYPE_RGB:
+            if (params.getJPEGCompressRGBToYCbCr()) {
+                return ImageType.YCBCR;
+            } else {
+                return ImageType.RGB;
+            }
+        case ColorSpace.TYPE_YCbCr:
+            return YCBCR;
+        default:
+            return GENERIC;
+        }
+    }
+}
diff --git a/src/java/org/apache/xmlgraphics/image/codec/tiff/TIFFEncodeParam.java b/src/java/org/apache/xmlgraphics/image/codec/tiff/TIFFEncodeParam.java
index 5afe0d2..d70e76c 100644
--- a/src/java/org/apache/xmlgraphics/image/codec/tiff/TIFFEncodeParam.java
+++ b/src/java/org/apache/xmlgraphics/image/codec/tiff/TIFFEncodeParam.java
@@ -45,59 +45,9 @@
  */
 public class TIFFEncodeParam implements ImageEncodeParam {
 
-    /** No compression. */
-    public static final int COMPRESSION_NONE          = 1;
+    private CompressionValue compression = CompressionValue.NONE;
 
-    /**
-     * Modified Huffman Compression (CCITT Group 3 1D facsimile compression).
-     * <p><b>Not currently supported.</b>
-     */
-    public static final int COMPRESSION_GROUP3_1D     = 2;
-
-    /**
-     * CCITT T.4 bilevel compression (CCITT Group 3 2D facsimile compression).
-     * <p><b>Not currently supported.</b>
-     */
-    public static final int COMPRESSION_GROUP3_2D     = 3;
-
-    /**
-     * CCITT T.6 bilevel compression (CCITT Group 4 facsimile compression).
-     * <p><b>Not currently supported.</b>
-     */
-    public static final int COMPRESSION_GROUP4        = 4;
-
-    /**
-     * LZW compression.
-     * <p><b>Not supported.</b>
-     */
-    public static final int COMPRESSION_LZW           = 5;
-
-    /**
-     * Code for original JPEG-in-TIFF compression which has been
-     * depricated (for many good reasons) in favor of Tech Note 2
-     * JPEG compression (compression scheme 7).
-     * <p><b>Not supported.</b>
-     */
-    public static final int COMPRESSION_JPEG_BROKEN   = 6;
-
-    /**
-     * <a href="ftp://ftp.sgi.com/graphics/tiff/TTN2.draft.txt">
-     * JPEG-in-TIFF</a> compression.
-     */
-    public static final int COMPRESSION_JPEG_TTN2     = 7;
-
-    /** Byte-oriented run-length encoding "PackBits" compression. */
-    public static final int COMPRESSION_PACKBITS      = 32773;
-
-    /**
-     * <a href="http://info.internet.isi.edu:80/in-notes/rfc/files/rfc1951.txt">
-     * DEFLATE</a> lossless compression (also known as "Zip-in-TIFF").
-     */
-    public static final int COMPRESSION_DEFLATE       = 32946;
-
-    private int compression = COMPRESSION_NONE;
-
-    private boolean writeTiled = false;
+    private boolean writeTiled;
     private int tileWidth;
     private int tileHeight;
 
@@ -117,10 +67,8 @@
         //nop
     }
 
-    /**
-     * Returns the value of the compression parameter.
-     */
-    public int getCompression() {
+    /** Returns the value of the compression parameter. */
+    public CompressionValue getCompression() {
         return compression;
     }
 
@@ -141,12 +89,12 @@
      *
      * @param compression    The compression type.
      */
-    public void setCompression(int compression) {
+    public void setCompression(CompressionValue compression) {
 
         switch(compression) {
-        case COMPRESSION_NONE:
-        case COMPRESSION_PACKBITS:
-        case COMPRESSION_DEFLATE:
+        case NONE:
+        case PACKBITS:
+        case DEFLATE:
             // Do nothing.
             break;
         default:
diff --git a/src/java/org/apache/xmlgraphics/image/codec/tiff/TIFFImageDecoder.java b/src/java/org/apache/xmlgraphics/image/codec/tiff/TIFFImageDecoder.java
index 30375cb..0ddaeb2 100644
--- a/src/java/org/apache/xmlgraphics/image/codec/tiff/TIFFImageDecoder.java
+++ b/src/java/org/apache/xmlgraphics/image/codec/tiff/TIFFImageDecoder.java
@@ -22,7 +22,6 @@
 import java.awt.image.RenderedImage;
 import java.io.IOException;
 
-import org.apache.xmlgraphics.image.codec.util.ImageDecodeParam;
 import org.apache.xmlgraphics.image.codec.util.ImageDecoderImpl;
 import org.apache.xmlgraphics.image.codec.util.PropertyUtil;
 import org.apache.xmlgraphics.image.codec.util.SeekableStream;
@@ -74,8 +73,7 @@
 
     public static final int TIFF_ICC_PROFILE                = 34675;
 
-    public TIFFImageDecoder(SeekableStream input,
-                            ImageDecodeParam param) {
+    public TIFFImageDecoder(SeekableStream input, TIFFDecodeParam param) {
         super(input, param);
     }
 
@@ -87,6 +85,6 @@
         if  ((page < 0) || (page >= getNumPages())) {
             throw new IOException(PropertyUtil.getString("TIFFImageDecoder0"));
         }
-        return new TIFFImage(input, (TIFFDecodeParam)param, page);
+        return new TIFFImage(input, (TIFFDecodeParam) param, page);
     }
 }
diff --git a/src/java/org/apache/xmlgraphics/image/codec/tiff/TIFFImageEncoder.java b/src/java/org/apache/xmlgraphics/image/codec/tiff/TIFFImageEncoder.java
index 8b70ce2..5a29ef1 100644
--- a/src/java/org/apache/xmlgraphics/image/codec/tiff/TIFFImageEncoder.java
+++ b/src/java/org/apache/xmlgraphics/image/codec/tiff/TIFFImageEncoder.java
@@ -20,7 +20,6 @@
 package org.apache.xmlgraphics.image.codec.tiff;
 
 import java.awt.Rectangle;
-import java.awt.color.ColorSpace;
 import java.awt.image.ColorModel;
 import java.awt.image.ComponentSampleModel;
 import java.awt.image.DataBuffer;
@@ -61,37 +60,13 @@
  */
 public class TIFFImageEncoder extends ImageEncoderImpl {
 
-    // Image Types
-    private static final int TIFF_UNSUPPORTED           = -1;
-    private static final int TIFF_BILEVEL_WHITE_IS_ZERO = 0;
-    private static final int TIFF_BILEVEL_BLACK_IS_ZERO = 1;
-    private static final int TIFF_GRAY                  = 2;
-    private static final int TIFF_PALETTE               = 3;
-    private static final int TIFF_RGB                   = 4;
-    private static final int TIFF_CMYK                  = 5;
-    private static final int TIFF_YCBCR                 = 6;
-    private static final int TIFF_CIELAB                = 7;
-    private static final int TIFF_GENERIC               = 8;
-
-    // Compression types
-    private static final int COMP_NONE      = 1;
-    private static final int COMP_JPEG_TTN2 = 7;
-    private static final int COMP_PACKBITS  = 32773;
-    private static final int COMP_DEFLATE   = 32946;
-
     // Incidental tags
     private static final int TIFF_JPEG_TABLES       = 347;
     private static final int TIFF_YCBCR_SUBSAMPLING = 530;
     private static final int TIFF_YCBCR_POSITIONING = 531;
     private static final int TIFF_REF_BLACK_WHITE   = 532;
 
-    // ExtraSamples types
-    private static final int EXTRA_SAMPLE_UNSPECIFIED        = 0;
-    private static final int EXTRA_SAMPLE_ASSOCIATED_ALPHA   = 1;
-    private static final int EXTRA_SAMPLE_UNASSOCIATED_ALPHA = 2;
 
-    // Default values
-    private static final int DEFAULT_ROWS_PER_STRIP = 8;
 
     public TIFFImageEncoder(OutputStream output, ImageEncodeParam param) {
         super(output, param);
@@ -197,9 +172,9 @@
     private int encode(RenderedImage im, TIFFEncodeParam encodeParam,
                        int ifdOffset, boolean isLast) throws IOException {
         // Currently all images are stored uncompressed.
-        int compression = encodeParam.getCompression();
+        CompressionValue compression = encodeParam.getCompression();
 
-        if (compression == COMP_JPEG_TTN2) {
+        if (compression == CompressionValue.JPEG_TTN2) {
             throw new IllegalArgumentException(PropertyUtil.getString("TIFFImageEncoder12"));
         }
 
@@ -214,263 +189,31 @@
 
         // Get SampleModel.
         SampleModel sampleModel = im.getSampleModel();
-
-        // Retrieve and verify sample size.
-        int[] sampleSize = sampleModel.getSampleSize();
-        for (int i = 1; i < sampleSize.length; i++) {
-            if (sampleSize[i] != sampleSize[0]) {
-                throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder0"));
-            }
-        }
-
-        // Check low bit limits.
-        int numBands = sampleModel.getNumBands();
-        if ((sampleSize[0] == 1 || sampleSize[0] == 4) && numBands != 1) {
-            throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder1"));
-        }
-
-        // Retrieve and verify data type.
-        int dataType = sampleModel.getDataType();
-        switch(dataType) {
-        case DataBuffer.TYPE_BYTE:
-            if (sampleSize[0] != 1 && sampleSize[0] == 4 &&    // todo does this make sense??
-               sampleSize[0] != 8) {                          // we get error only for 4
-                throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder2"));
-            }
-            break;
-        case DataBuffer.TYPE_SHORT:
-        case DataBuffer.TYPE_USHORT:
-            if (sampleSize[0] != 16) {
-                throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder3"));
-            }
-            break;
-        case DataBuffer.TYPE_INT:
-        case DataBuffer.TYPE_FLOAT:
-            if (sampleSize[0] != 32) {
-                throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder4"));
-            }
-            break;
-        default:
-            throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder5"));
-        }
-
-        boolean dataTypeIsShort =
-            dataType == DataBuffer.TYPE_SHORT ||
-            dataType == DataBuffer.TYPE_USHORT;
-
         ColorModel colorModel = im.getColorModel();
-        if (colorModel != null &&
-            colorModel instanceof IndexColorModel &&
-            dataType != DataBuffer.TYPE_BYTE) {
-            // Don't support (unsigned) short palette-color images.
-            throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder6"));
-        }
-        IndexColorModel icm = null;
-        int sizeOfColormap = 0;
-        char[] colormap = null;
+        int[] sampleSize = sampleModel.getSampleSize();
+        int dataTypeSize = sampleSize[0];
+        int numBands = sampleModel.getNumBands();
+        int dataType = sampleModel.getDataType();
+        validateImage(dataTypeSize, sampleSize, numBands, dataType, colorModel);
+
+        boolean dataTypeIsShort = dataType == DataBuffer.TYPE_SHORT
+                || dataType == DataBuffer.TYPE_USHORT;
 
         // Set image type.
-        int imageType = TIFF_UNSUPPORTED;
-        int numExtraSamples = 0;
-        int extraSampleType = EXTRA_SAMPLE_UNSPECIFIED;
-        if (colorModel instanceof IndexColorModel) { // Bilevel or palette
-            icm = (IndexColorModel)colorModel;
-            int mapSize = icm.getMapSize();
+        ImageInfo imageInfo = ImageInfo.newInstance(im, dataTypeSize, numBands, colorModel,
+                encodeParam);
 
-            if (sampleSize[0] == 1 && numBands == 1) { // Bilevel image
-
-                if (mapSize != 2) {
-                    throw new IllegalArgumentException(PropertyUtil.getString("TIFFImageEncoder7"));
-                }
-
-                byte[] r = new byte[mapSize];
-                icm.getReds(r);
-                byte[] g = new byte[mapSize];
-                icm.getGreens(g);
-                byte[] b = new byte[mapSize];
-                icm.getBlues(b);
-
-                if ((r[0] & 0xff) == 0 &&
-                    (r[1] & 0xff) == 255 &&
-                    (g[0] & 0xff) == 0 &&
-                    (g[1] & 0xff) == 255 &&
-                    (b[0] & 0xff) == 0 &&
-                    (b[1] & 0xff) == 255) {
-
-                    imageType = TIFF_BILEVEL_BLACK_IS_ZERO;
-
-                } else if ((r[0] & 0xff) == 255 &&
-                           (r[1] & 0xff) == 0 &&
-                           (g[0] & 0xff) == 255 &&
-                           (g[1] & 0xff) == 0 &&
-                           (b[0] & 0xff) == 255 &&
-                           (b[1] & 0xff) == 0) {
-
-                    imageType = TIFF_BILEVEL_WHITE_IS_ZERO;
-
-                } else {
-                    imageType = TIFF_PALETTE;
-                }
-
-            } else if (numBands == 1) { // Non-bilevel image.
-                // Palette color image.
-                imageType = TIFF_PALETTE;
-            }
-        } else if (colorModel == null) {
-
-            if (sampleSize[0] == 1 && numBands == 1) { // bilevel
-                imageType = TIFF_BILEVEL_BLACK_IS_ZERO;
-            } else { // generic image
-                imageType = TIFF_GENERIC;
-                if (numBands > 1) {
-                    numExtraSamples = numBands - 1;
-                }
-            }
-
-        } else { // colorModel is non-null but not an IndexColorModel
-            ColorSpace colorSpace = colorModel.getColorSpace();
-
-            switch(colorSpace.getType()) {
-            case ColorSpace.TYPE_CMYK:
-                imageType = TIFF_CMYK;
-                break;
-            case ColorSpace.TYPE_GRAY:
-                imageType = TIFF_GRAY;
-                break;
-            case ColorSpace.TYPE_Lab:
-                imageType = TIFF_CIELAB;
-                break;
-            case ColorSpace.TYPE_RGB:
-                if (compression == COMP_JPEG_TTN2
-                        && encodeParam.getJPEGCompressRGBToYCbCr()) {
-                    imageType = TIFF_YCBCR;
-                } else {
-                    imageType = TIFF_RGB;
-                }
-                break;
-            case ColorSpace.TYPE_YCbCr:
-                imageType = TIFF_YCBCR;
-                break;
-            default:
-                imageType = TIFF_GENERIC; // generic
-                break;
-            }
-
-            if (imageType == TIFF_GENERIC) {
-                numExtraSamples = numBands - 1;
-            } else if (numBands > 1) {
-                numExtraSamples = numBands - colorSpace.getNumComponents();
-            }
-
-            if (numExtraSamples == 1 && colorModel.hasAlpha()) {
-                extraSampleType = colorModel.isAlphaPremultiplied() ?
-                    EXTRA_SAMPLE_ASSOCIATED_ALPHA :
-                    EXTRA_SAMPLE_UNASSOCIATED_ALPHA;
-            }
-        }
-
-        if (imageType == TIFF_UNSUPPORTED) {
+        if (imageInfo.getType() == ImageType.UNSUPPORTED) {
             throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder8"));
         }
 
-        int photometricInterpretation = -1;
-        switch (imageType) {
-
-        case TIFF_BILEVEL_WHITE_IS_ZERO:
-            photometricInterpretation = 0;
-            break;
-
-        case TIFF_BILEVEL_BLACK_IS_ZERO:
-            photometricInterpretation = 1;
-            break;
-
-        case TIFF_GRAY:
-        case TIFF_GENERIC:
-            // Since the CS_GRAY colorspace is always of type black_is_zero
-            photometricInterpretation = 1;
-            break;
-
-        case TIFF_PALETTE:
-            photometricInterpretation = 3;
-
-            icm = (IndexColorModel)colorModel;
-            sizeOfColormap = icm.getMapSize();
-
-            byte[] r = new byte[sizeOfColormap];
-            icm.getReds(r);
-            byte[] g = new byte[sizeOfColormap];
-            icm.getGreens(g);
-            byte[] b = new byte[sizeOfColormap];
-            icm.getBlues(b);
-
-            int redIndex = 0, greenIndex = sizeOfColormap;
-            int blueIndex = 2 * sizeOfColormap;
-            colormap = new char[sizeOfColormap * 3];
-            for (int i = 0; i < sizeOfColormap; i++) {
-                int tmp = 0xff & r[i];   // beware of sign extended bytes
-                colormap[redIndex++]   = (char)((tmp << 8) | tmp);
-                tmp = 0xff & g[i];
-                colormap[greenIndex++] = (char)((tmp << 8) | tmp);
-                tmp = 0xff & b[i];
-                colormap[blueIndex++]  = (char)((tmp << 8) | tmp);
-            }
-
-            sizeOfColormap *= 3;
-
-            break;
-
-        case TIFF_RGB:
-            photometricInterpretation = 2;
-            break;
-
-        case TIFF_CMYK:
-            photometricInterpretation = 5;
-            break;
-
-        case TIFF_YCBCR:
-            photometricInterpretation = 6;
-            break;
-
-        case TIFF_CIELAB:
-            photometricInterpretation = 8;
-            break;
-
-        default:
-            throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder8"));
-        }
-
-        // Initialize tile dimensions.
-        int tileWidth;
-        int tileHeight;
-        if (isTiled) {
-            tileWidth = encodeParam.getTileWidth() > 0 ?
-                encodeParam.getTileWidth() : im.getTileWidth();
-            tileHeight = encodeParam.getTileHeight() > 0 ?
-                encodeParam.getTileHeight() : im.getTileHeight();
-        } else {
-            tileWidth = width;
-
-            tileHeight = encodeParam.getTileHeight() > 0 ?
-                encodeParam.getTileHeight() : DEFAULT_ROWS_PER_STRIP;
-        }
-
-        int numTiles;
-        if (isTiled) {
-            // NB: Parentheses are used in this statement for correct rounding.
-            numTiles =
-                ((width + tileWidth - 1) / tileWidth) *
-                ((height + tileHeight - 1) / tileHeight);
-        } else {
-            numTiles = (int)Math.ceil((double)height / (double)tileHeight);
-        }
+        final int numTiles = imageInfo.getNumTiles();
+        final long bytesPerTile = imageInfo.getBytesPerTile();
+        final long bytesPerRow = imageInfo.getBytesPerRow();
+        final int tileHeight = imageInfo.getTileHeight();
+        final int tileWidth = imageInfo.getTileWidth();
 
         long[] tileByteCounts = new long[numTiles];
-
-        long bytesPerRow =
-            (long)Math.ceil((sampleSize[0] / 8.0) * tileWidth * numBands);
-
-        long bytesPerTile = bytesPerRow * tileHeight;
-
         for (int i = 0; i < numTiles; i++) {
             tileByteCounts[i] = bytesPerTile;
         }
@@ -480,12 +223,7 @@
             long lastStripRows = height - (tileHeight * (numTiles - 1));
             tileByteCounts[numTiles - 1] = lastStripRows * bytesPerRow;
         }
-
-        long totalBytesOfData = bytesPerTile * (numTiles - 1) +
-            tileByteCounts[numTiles - 1];
-
-        // The data will be written after the IFD: create the array here
-        // but fill it in later.
+        long totalBytesOfData = bytesPerTile * (numTiles - 1) + tileByteCounts[numTiles - 1];
         long[] tileOffsets = new long[numTiles];
 
         // Basic fields - have to be in increasing numerical order.
@@ -502,7 +240,7 @@
         // ResolutionUnit                 296
 
         // Create Directory
-        SortedSet fields = new TreeSet();
+        SortedSet<TIFFField> fields = new TreeSet<TIFFField>();
 
         // Image Width
         fields.add(new TIFFField(TIFFImageDecoder.TIFF_IMAGE_WIDTH,
@@ -516,7 +254,7 @@
 
         char [] shortSampleSize = new char[numBands];
         for (int i = 0; i < numBands; i++) {
-            shortSampleSize[i] = (char)sampleSize[i];
+            shortSampleSize[i] = (char) dataTypeSize;
         }
         fields.add(new TIFFField(TIFFImageDecoder.TIFF_BITS_PER_SAMPLE,
                                  TIFFField.TIFF_SHORT, numBands,
@@ -524,12 +262,12 @@
 
         fields.add(new TIFFField(TIFFImageDecoder.TIFF_COMPRESSION,
                                  TIFFField.TIFF_SHORT, 1,
-                                 new char[] {(char)compression}));
+                                 new char[] {(char)compression.getValue()}));
 
         fields.add(
             new TIFFField(TIFFImageDecoder.TIFF_PHOTOMETRIC_INTERPRETATION,
                           TIFFField.TIFF_SHORT, 1,
-                                 new char[] {(char)photometricInterpretation}));
+                      new char[] {(char) imageInfo.getType().getPhotometricInterpretation()}));
 
         if (!isTiled) {
             fields.add(new TIFFField(TIFFImageDecoder.TIFF_STRIP_OFFSETS,
@@ -551,10 +289,10 @@
                                      tileByteCounts));
         }
 
-        if (colormap != null) {
+        if (imageInfo.getColormap() != null) {
             fields.add(new TIFFField(TIFFImageDecoder.TIFF_COLORMAP,
-                                     TIFFField.TIFF_SHORT, sizeOfColormap,
-                                     colormap));
+                    TIFFField.TIFF_SHORT, imageInfo.getColormapSize(),
+                    imageInfo.getColormap()));
         }
 
         if (isTiled) {
@@ -575,13 +313,13 @@
                                      tileByteCounts));
         }
 
-        if (numExtraSamples > 0) {
-            char[] extraSamples = new char[numExtraSamples];
-            for (int i = 0; i < numExtraSamples; i++) {
-                extraSamples[i] = (char)extraSampleType;
+        if (imageInfo.getNumberOfExtraSamples() > 0) {
+            char[] extraSamples = new char[imageInfo.getNumberOfExtraSamples()];
+            for (int i = 0; i < imageInfo.getNumberOfExtraSamples(); i++) {
+                extraSamples[i] = (char) imageInfo.getExtraSamplesType().getValue();
             }
             fields.add(new TIFFField(TIFFImageDecoder.TIFF_EXTRA_SAMPLES,
-                                     TIFFField.TIFF_SHORT, numExtraSamples,
+                    TIFFField.TIFF_SHORT, imageInfo.getNumberOfExtraSamples(),
                                      extraSamples));
         }
 
@@ -610,7 +348,7 @@
             // use it if available.
         }
 
-        if (imageType == TIFF_YCBCR) {
+        if (imageInfo.getType() == ImageType.YCBCR) {
             // YCbCrSubSampling: 2 is the default so we must write 1 as
             // we do not (yet) do any subsampling.
             char subsampleH = 1;
@@ -625,7 +363,7 @@
             fields.add(new TIFFField(TIFF_YCBCR_POSITIONING,
                                      TIFFField.TIFF_SHORT, 1,
                                      new char[]
-                {(char)((compression == COMP_JPEG_TTN2) ? 1 : 2)}));
+                    {(char) ((compression == CompressionValue.JPEG_TTN2) ? 1 : 2)}));
 
             // Reference black/white.
             long[][] refbw;
@@ -691,16 +429,16 @@
         Deflater deflater = null;
         boolean jpegRGBToYCbCr = false;
 
-        if (compression == COMP_NONE) {
+        if (compression == CompressionValue.NONE) {
             // Determine the number of bytes of padding necessary between
             // the end of the IFD and the first data segment such that the
             // alignment of the data conforms to the specification (required
             // for uncompressed data only).
             int numBytesPadding = 0;
-            if (sampleSize[0] == 16 && tileOffsets[0] % 2 != 0) {
+            if (dataTypeSize == 16 && tileOffsets[0] % 2 != 0) {
                 numBytesPadding = 1;
                 tileOffsets[0]++;
-            } else if (sampleSize[0] == 32 && tileOffsets[0] % 4 != 0) {
+            } else if (dataTypeSize == 32 && tileOffsets[0] % 4 != 0) {
                 numBytesPadding = (int)(4 - tileOffsets[0] % 4);
                 tileOffsets[0] += numBytesPadding;
             }
@@ -758,11 +496,11 @@
 
             int bufSize = 0;
             switch(compression) {
-            case COMP_PACKBITS:
-                bufSize = (int)(bytesPerTile + ((bytesPerRow + 127) / 128) * tileHeight);
+            case PACKBITS:
+                bufSize = (int) (bytesPerTile + ((bytesPerRow + 127) / 128) * tileHeight);
                 break;
-            case COMP_DEFLATE:
-                bufSize = (int)bytesPerTile;
+            case DEFLATE:
+                bufSize = (int) bytesPerTile;
                 deflater = new Deflater(encodeParam.getDeflateLevel());
                 break;
             default:
@@ -781,16 +519,16 @@
 
         // Whether to test for contiguous data.
         boolean checkContiguous =
-            ((sampleSize[0] == 1 &&
+            ((dataTypeSize == 1 &&
               sampleModel instanceof MultiPixelPackedSampleModel &&
               dataType == DataBuffer.TYPE_BYTE) ||
-             (sampleSize[0] == 8 &&
+             (dataTypeSize == 8 &&
               sampleModel instanceof ComponentSampleModel));
 
         // Also create a buffer to hold tileHeight lines of the
         // data to be written to the file, so we can use array writes.
         byte[] bpixels = null;
-        if (compression != COMP_JPEG_TTN2) {
+        if (compression != CompressionValue.JPEG_TTN2) {
             if (dataType == DataBuffer.TYPE_BYTE) {
                 bpixels = new byte[tileHeight * tileWidth * numBands];
             } else if (dataTypeIsShort) {
@@ -816,9 +554,9 @@
                     im.getData(new Rectangle(col, row, tileWidth, rows));
 
                 boolean useDataBuffer = false;
-                if (compression != COMP_JPEG_TTN2) { // JPEG access Raster
+                if (compression != CompressionValue.JPEG_TTN2) { // JPEG access Raster
                     if (checkContiguous) {
-                        if (sampleSize[0] == 8) { // 8-bit
+                        if (dataTypeSize == 8) { // 8-bit
                             ComponentSampleModel csm =
                                 (ComponentSampleModel)src.getSampleModel();
                             int[] bankIndices = csm.getBankIndices();
@@ -866,7 +604,7 @@
 
                 int pixel = 0;
                 int k = 0;
-                switch (sampleSize[0]) {
+                switch (dataTypeSize) {
 
                 case 1:
 
@@ -881,18 +619,18 @@
                                           src.getSampleModelTranslateX(),
                                           row -
                                           src.getSampleModelTranslateY());
-                        if (lineStride == (int)bytesPerRow) {
+                        if (lineStride == bytesPerRow) {
                             System.arraycopy(btmp, inOffset,
                                              bpixels, 0,
-                                             (int)bytesPerRow * rows);
+                                             (int) bytesPerRow * rows);
                         } else {
                             int outOffset = 0;
                             for (int j = 0; j < rows; j++) {
                                 System.arraycopy(btmp, inOffset,
                                                  bpixels, outOffset,
-                                                 (int)bytesPerRow);
+                                                 (int) bytesPerRow);
                                 inOffset += lineStride;
-                                outOffset += (int)bytesPerRow;
+                                outOffset += bytesPerRow;
                             }
                         }
                     } else {
@@ -927,16 +665,16 @@
                         }
                     }
 
-                    if (compression == COMP_NONE) {
+                    if (compression == CompressionValue.NONE) {
                         output.write(bpixels, 0, rows * ((tileWidth + 7) / 8));
-                    } else if (compression == COMP_PACKBITS) {
+                    } else if (compression == CompressionValue.PACKBITS) {
                         int numCompressedBytes =
                             compressPackBits(bpixels, rows,
-                                             (int)bytesPerRow,
+                                             bytesPerRow,
                                              compressBuf);
                         tileByteCounts[tileNum++] = numCompressedBytes;
                         output.write(compressBuf, 0, numCompressedBytes);
-                    } else if (compression == COMP_DEFLATE) {
+                    } else if (compression == CompressionValue.DEFLATE) {
                         int numCompressedBytes =
                             deflate(deflater, bpixels, compressBuf);
                         tileByteCounts[tileNum++] = numCompressedBytes;
@@ -966,16 +704,16 @@
                         }
                     }
 
-                    if (compression == COMP_NONE) {
+                    if (compression == CompressionValue.NONE) {
                         output.write(bpixels, 0, rows * ((tileWidth + 1) / 2));
-                    } else if (compression == COMP_PACKBITS) {
+                    } else if (compression == CompressionValue.PACKBITS) {
                         int numCompressedBytes =
                             compressPackBits(bpixels, rows,
-                                             (int)bytesPerRow,
+                                             bytesPerRow,
                                              compressBuf);
                         tileByteCounts[tileNum++] = numCompressedBytes;
                         output.write(compressBuf, 0, numCompressedBytes);
-                    } else if (compression == COMP_DEFLATE) {
+                    } else if (compression == CompressionValue.DEFLATE) {
                         int numCompressedBytes =
                             deflate(deflater, bpixels, compressBuf);
                         tileByteCounts[tileNum++] = numCompressedBytes;
@@ -985,7 +723,7 @@
 
                 case 8:
 
-                    if (compression != COMP_JPEG_TTN2) {
+                    if (compression != CompressionValue.JPEG_TTN2) {
                         if (useDataBuffer) {
                             byte[] btmp =
                                 ((DataBufferByte)src.getDataBuffer()).getData();
@@ -997,19 +735,19 @@
                                               row -
                                               src.getSampleModelTranslateY());
                             int lineStride = csm.getScanlineStride();
-                            if (lineStride == (int)bytesPerRow) {
+                            if (lineStride == bytesPerRow) {
                                 System.arraycopy(btmp,
                                                  inOffset,
                                                  bpixels, 0,
-                                                 (int)bytesPerRow * rows);
+                                                 (int) bytesPerRow * rows);
                             } else {
                                 int outOffset = 0;
                                 for (int j = 0; j < rows; j++) {
                                     System.arraycopy(btmp, inOffset,
                                                      bpixels, outOffset,
-                                                     (int)bytesPerRow);
+                                                     (int) bytesPerRow);
                                     inOffset += lineStride;
-                                    outOffset += (int)bytesPerRow;
+                                    outOffset += bytesPerRow;
                                 }
                             }
                         } else {
@@ -1019,16 +757,16 @@
                         }
                     }
 
-                    if (compression == COMP_NONE) {
+                    if (compression == CompressionValue.NONE) {
                         output.write(bpixels, 0, size);
-                    } else if (compression == COMP_PACKBITS) {
+                    } else if (compression == CompressionValue.PACKBITS) {
                         int numCompressedBytes =
                             compressPackBits(bpixels, rows,
-                                             (int)bytesPerRow,
+                                             bytesPerRow,
                                              compressBuf);
                         tileByteCounts[tileNum++] = numCompressedBytes;
                         output.write(compressBuf, 0, numCompressedBytes);
-                    } else if (compression == COMP_DEFLATE) {
+                    } else if (compression == CompressionValue.DEFLATE) {
                         int numCompressedBytes =
                             deflate(deflater, bpixels, compressBuf);
                         tileByteCounts[tileNum++] = numCompressedBytes;
@@ -1045,16 +783,16 @@
                         bpixels[ls++] = (byte) (value & 0x00ff);
                     }
 
-                    if (compression == COMP_NONE) {
+                    if (compression == CompressionValue.NONE) {
                         output.write(bpixels, 0, size*2);
-                    } else if (compression == COMP_PACKBITS) {
+                    } else if (compression == CompressionValue.PACKBITS) {
                         int numCompressedBytes =
                             compressPackBits(bpixels, rows,
-                                             (int)bytesPerRow,
+                                             bytesPerRow,
                                              compressBuf);
                         tileByteCounts[tileNum++] = numCompressedBytes;
                         output.write(compressBuf, 0, numCompressedBytes);
-                    } else if (compression == COMP_DEFLATE) {
+                    } else if (compression == CompressionValue.DEFLATE) {
                         int numCompressedBytes =
                             deflate(deflater, bpixels, compressBuf);
                         tileByteCounts[tileNum++] = numCompressedBytes;
@@ -1082,16 +820,16 @@
                             bpixels[lf++] = (byte)( value & 0x000000ff);
                         }
                     }
-                    if (compression == COMP_NONE) {
+                    if (compression == CompressionValue.NONE) {
                         output.write(bpixels, 0, size*4);
-                    } else if (compression == COMP_PACKBITS) {
+                    } else if (compression == CompressionValue.PACKBITS) {
                         int numCompressedBytes =
                             compressPackBits(bpixels, rows,
-                                             (int)bytesPerRow,
+                                             bytesPerRow,
                                              compressBuf);
                         tileByteCounts[tileNum++] = numCompressedBytes;
                         output.write(compressBuf, 0, numCompressedBytes);
-                    } else if (compression == COMP_DEFLATE) {
+                    } else if (compression == CompressionValue.DEFLATE) {
                         int numCompressedBytes =
                             deflate(deflater, bpixels, compressBuf);
                         tileByteCounts[tileNum++] = numCompressedBytes;
@@ -1103,7 +841,7 @@
             }
         }
 
-        if (compression == COMP_NONE) {
+        if (compression == CompressionValue.NONE) {
             // Write an extra byte for IFD word alignment if needed.
             if (skipByte) {
                 output.write((byte)0);
@@ -1209,6 +947,50 @@
         return nextIFDOffset;
     }
 
+    private void validateImage(int dataTypeSize, int[] sampleSize, int numBands, int dataType,
+            ColorModel colorModel) {
+        // Retrieve and verify sample size.
+        for (int i = 1; i < sampleSize.length; i++) {
+            if (sampleSize[i] != dataTypeSize) {
+                throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder0"));
+            }
+        }
+
+        // Check low bit limits.
+        if ((dataTypeSize == 1 || dataTypeSize == 4) && numBands != 1) {
+            throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder1"));
+        }
+
+        // Retrieve and verify data type.
+        switch (dataType) {
+        case DataBuffer.TYPE_BYTE:
+            if (dataTypeSize != 1 && dataTypeSize == 4 && // todo does this make sense??
+                    dataTypeSize != 8) { // we get error only for 4
+                throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder2"));
+            }
+            break;
+        case DataBuffer.TYPE_SHORT:
+        case DataBuffer.TYPE_USHORT:
+            if (dataTypeSize != 16) {
+                throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder3"));
+            }
+            break;
+        case DataBuffer.TYPE_INT:
+        case DataBuffer.TYPE_FLOAT:
+            if (dataTypeSize != 32) {
+                throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder4"));
+            }
+            break;
+        default:
+            throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder5"));
+        }
+
+        if (colorModel instanceof IndexColorModel && dataType != DataBuffer.TYPE_BYTE) {
+            // Don't support (unsigned) short palette-color images.
+            throw new RuntimeException(PropertyUtil.getString("TIFFImageEncoder6"));
+        }
+    }
+
     /**
      * Calculates the size of the IFD.
      */
@@ -1515,12 +1297,12 @@
      * Performs PackBits compression on a tile of data.
      */
     private static int compressPackBits(byte[] data, int numRows,
-                                        int bytesPerRow, byte[] compData) {
+                                        long bytesPerRow, byte[] compData) {
         int inOffset = 0;
         int outOffset = 0;
 
         for (int i = 0; i < numRows; i++) {
-            outOffset = packBits(data, inOffset, bytesPerRow,
+            outOffset = packBits(data, inOffset, (int) bytesPerRow,
                                  compData, outOffset);
             inOffset += bytesPerRow;
         }
diff --git a/src/java/org/apache/xmlgraphics/image/writer/internal/TIFFImageWriter.java b/src/java/org/apache/xmlgraphics/image/writer/internal/TIFFImageWriter.java
index 9af7a0e..0907e0c 100644
--- a/src/java/org/apache/xmlgraphics/image/writer/internal/TIFFImageWriter.java
+++ b/src/java/org/apache/xmlgraphics/image/writer/internal/TIFFImageWriter.java
@@ -23,6 +23,7 @@
 import java.io.IOException;
 import java.io.OutputStream;
 
+import org.apache.xmlgraphics.image.codec.tiff.CompressionValue;
 import org.apache.xmlgraphics.image.codec.tiff.TIFFEncodeParam;
 import org.apache.xmlgraphics.image.codec.tiff.TIFFField;
 import org.apache.xmlgraphics.image.codec.tiff.TIFFImageDecoder;
@@ -57,21 +58,9 @@
     private TIFFEncodeParam createTIFFEncodeParams(ImageWriterParams params) {
         TIFFEncodeParam encodeParams = new TIFFEncodeParam();
         if (params == null) {
-            encodeParams.setCompression(TIFFEncodeParam.COMPRESSION_NONE);
+            encodeParams.setCompression(CompressionValue.NONE);
         } else {
-            if (params.getCompressionMethod() == null) {
-                //PackBits as default
-                encodeParams.setCompression(TIFFEncodeParam.COMPRESSION_PACKBITS);
-            } else if ("PackBits".equalsIgnoreCase(params.getCompressionMethod())) {
-                encodeParams.setCompression(TIFFEncodeParam.COMPRESSION_PACKBITS);
-            } else if ("NONE".equalsIgnoreCase(params.getCompressionMethod())) {
-                encodeParams.setCompression(TIFFEncodeParam.COMPRESSION_NONE);
-            } else if ("Deflate".equalsIgnoreCase(params.getCompressionMethod())) {
-                encodeParams.setCompression(TIFFEncodeParam.COMPRESSION_DEFLATE);
-            } else {
-                throw new UnsupportedOperationException("Compression method not supported: "
-                        + params.getCompressionMethod());
-            }
+            encodeParams.setCompression(CompressionValue.getValue(params.getCompressionMethod()));
 
             if (params.getResolution() != null) {
                 int numPixX;
diff --git a/test/java/org/apache/xmlgraphics/image/codec/tiff/ImageInfoTestCase.java b/test/java/org/apache/xmlgraphics/image/codec/tiff/ImageInfoTestCase.java
new file mode 100644
index 0000000..9885fac
--- /dev/null
+++ b/test/java/org/apache/xmlgraphics/image/codec/tiff/ImageInfoTestCase.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.xmlgraphics.image.codec.tiff;
+
+import java.awt.color.ColorSpace;
+import java.awt.image.ColorModel;
+import java.awt.image.IndexColorModel;
+import java.awt.image.RenderedImage;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import static org.apache.xmlgraphics.image.codec.tiff.ExtraSamplesType.UNSPECIFIED;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.BILEVEL_BLACK_IS_ZERO;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.BILEVEL_WHITE_IS_ZERO;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.CIELAB;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.CMYK;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.GENERIC;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.GRAY;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.RGB;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.YCBCR;
+
+public class ImageInfoTestCase {
+
+    private ColorSpace colorSpace;
+    private ColorModel colorModel;
+    private RenderedImage image;
+    private TIFFEncodeParam params;
+
+    @Before
+    public void setUp() {
+        colorSpace = mock(ColorSpace.class);
+        colorModel = new TestColorModel(colorSpace, true);
+        image = mock(RenderedImage.class);
+        params = mock(TIFFEncodeParam.class);
+    }
+
+    @Test
+    public void testNullColorModel() {
+        testImageInfo(ImageInfo.newInstance(image, 1, 1, null, params),
+                BILEVEL_BLACK_IS_ZERO, 0, null, 0, UNSPECIFIED);
+
+        for (int i = 2; i < 10; i += 2) {
+            testImageInfo(ImageInfo.newInstance(image, 1, i, null, params),
+                    GENERIC, i - 1, null, 0, UNSPECIFIED);
+        }
+    }
+
+    @Test
+    public void testNonIndexColorModel() {
+        testTheColorSpaceType(ColorSpace.TYPE_CMYK, false, CMYK);
+        testTheColorSpaceType(ColorSpace.TYPE_GRAY, false, GRAY);
+        testTheColorSpaceType(ColorSpace.TYPE_RGB, true, YCBCR);
+        testTheColorSpaceType(ColorSpace.TYPE_RGB, false, RGB);
+    }
+
+    private void testTheColorSpaceType(int colorSpaceType, boolean getJpegCompress, ImageType expectedType) {
+        when(colorSpace.getType()).thenReturn(colorSpaceType);
+        TIFFEncodeParam params = mock(TIFFEncodeParam.class);
+        when(params.getJPEGCompressRGBToYCbCr()).thenReturn(getJpegCompress);
+
+        testImageInfo(ImageInfo.newInstance(image, 1, 1, colorModel, params),
+                expectedType, 0, null, 0, UNSPECIFIED);
+    }
+
+    @Test
+    public void testNonIndexColorModelWithNumBandsGreaterThan1() {
+        testWithNumOfBandsGreaterThan1(ColorSpace.TYPE_GRAY, GRAY, 3, 1);
+        testWithNumOfBandsGreaterThan1(ColorSpace.TYPE_Lab, CIELAB, 6, 3);
+        testWithNumOfBandsGreaterThan1(ColorSpace.TYPE_CMYK, CMYK, 5, 2);
+    }
+
+    private void testWithNumOfBandsGreaterThan1(int colorSpaceType, ImageType type, int numBands,
+            int numComponents) {
+        when(colorSpace.getType()).thenReturn(colorSpaceType);
+        when(colorSpace.getNumComponents()).thenReturn(numComponents);
+        testImageInfo(ImageInfo.newInstance(image, 2, numBands, colorModel, params),
+                type, numBands - numComponents, null, 0, UNSPECIFIED);
+    }
+
+    private void testImageInfo(ImageInfo imageInfo, ImageType imageType, int numExtraSamples,
+            char[] colormap, int colormapSize, ExtraSamplesType extraSamplesType) {
+        assertEquals(imageType, imageInfo.getType());
+        assertEquals(numExtraSamples, imageInfo.getNumberOfExtraSamples());
+        assertArrayEquals(colormap, imageInfo.getColormap());
+        assertEquals(colormapSize, imageInfo.getColormapSize());
+        assertEquals(extraSamplesType, imageInfo.getExtraSamplesType());
+    }
+
+    @Test
+    public void testIndexColorModel() {
+        byte[] blackIsZero = new byte[] {0, (byte) 0xff};
+        IndexColorModel icm = new IndexColorModel(1, 2, blackIsZero, blackIsZero, blackIsZero);
+        testImageInfo(ImageInfo.newInstance(image, 1, 1, icm, params),
+                BILEVEL_BLACK_IS_ZERO, 0, null, 0, UNSPECIFIED);
+
+        byte[] whiteIsZero = new byte[] {(byte) 0xff, 0};
+        icm = new IndexColorModel(1, 2, whiteIsZero, whiteIsZero, whiteIsZero);
+        testImageInfo(ImageInfo.newInstance(image, 1, 1, icm, params),
+                BILEVEL_WHITE_IS_ZERO, 0, null, 0, UNSPECIFIED);
+    }
+
+    @Test
+    public void testTileWidthHeight() {
+        when(params.getWriteTiled()).thenReturn(true);
+
+        when(image.getWidth()).thenReturn(10);
+        when(image.getHeight()).thenReturn(10);
+
+        for (int i = 1; i < 10000; i += 200) {
+            when(params.getTileWidth()).thenReturn(i);
+            when(params.getTileHeight()).thenReturn(i);
+            int numTiles = ((10 + i - 1) / i) * ((10 + i - 1) / i);
+            long bytesPerRow = (long) Math.ceil((1 / 8.0) * i * 1);
+            long bytesPerTile = bytesPerRow * i;
+
+            testTileOnImageInfo(ImageInfo.newInstance(image, 1, 1, colorModel, params),
+                    i, i, numTiles, bytesPerRow, bytesPerTile);
+        }
+    }
+
+    private void testTileOnImageInfo(ImageInfo imageInfo, int tileWidth, int tileHeight,
+            int numTiles, long bytesPerRow, long bytesPerTile) {
+        assertEquals(tileWidth, imageInfo.getTileWidth());
+        assertEquals(tileHeight, imageInfo.getTileHeight());
+        assertEquals(numTiles, imageInfo.getNumTiles());
+        assertEquals(bytesPerRow, imageInfo.getBytesPerRow());
+        assertEquals(bytesPerTile, imageInfo.getBytesPerTile());
+    }
+
+    @Test
+    public void testGetColormap() {
+        ImageInfo sut = ImageInfo.newInstance(image, 1, 1,
+                new IndexColorModel(1, 2, new byte[2], new byte[2], new byte[2], new byte[2]), params);
+        char[] colormap = sut.getColormap();
+        assertEquals(0, colormap[0]);
+        colormap[0] = 1;
+        //  Assert that getColormap() returns a defensive copy
+        assertEquals(0, sut.getColormap()[0]);
+    }
+
+    private static final class TestColorModel extends ColorModel {
+
+        protected TestColorModel(ColorSpace cspace, boolean isAlphaPremultiplied) {
+            super(1, new int[] {1, 1}, cspace, isAlphaPremultiplied, isAlphaPremultiplied, 1, 1);
+        }
+
+        @Override
+        public int getRed(int pixel) {
+            return 0;
+        }
+
+        @Override
+        public int getGreen(int pixel) {
+            return 0;
+        }
+
+        @Override
+        public int getBlue(int pixel) {
+            return 0;
+        }
+
+        @Override
+        public int getAlpha(int pixel) {
+            return 0;
+        }
+    }
+}
diff --git a/test/java/org/apache/xmlgraphics/image/codec/tiff/ImageTypeTestCase.java b/test/java/org/apache/xmlgraphics/image/codec/tiff/ImageTypeTestCase.java
new file mode 100644
index 0000000..6761617
--- /dev/null
+++ b/test/java/org/apache/xmlgraphics/image/codec/tiff/ImageTypeTestCase.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.xmlgraphics.image.codec.tiff;
+
+import java.awt.color.ColorSpace;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.BILEVEL_BLACK_IS_ZERO;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.BILEVEL_WHITE_IS_ZERO;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.CIELAB;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.CMYK;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.GENERIC;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.GRAY;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.PALETTE;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.RGB;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.UNSUPPORTED;
+import static org.apache.xmlgraphics.image.codec.tiff.ImageType.YCBCR;
+
+public class ImageTypeTestCase {
+    private static final class ColorContainer {
+        private final byte[] r;
+        private final byte[] g;
+        private final byte[] b;
+
+        private ColorContainer(byte[] r, byte[] g, byte[] b) {
+            this.r = r;
+            this.b = b;
+            this.g = g;
+        }
+    }
+
+    private ColorContainer blackIsZero;
+    private ColorContainer whiteIsZero;
+
+    @Before
+    public void setUp() {
+        byte[] blackSetToZero = {0, (byte) 0xff};
+        blackIsZero = new ColorContainer(blackSetToZero, blackSetToZero, blackSetToZero);
+        byte[] whiteSetToZero = {(byte) 0xff, 0};
+        whiteIsZero = new ColorContainer(whiteSetToZero, whiteSetToZero, whiteSetToZero);
+    }
+
+    @Test
+    public void testPhotometricInterpretationValue() {
+        assertEquals(0, BILEVEL_WHITE_IS_ZERO.getPhotometricInterpretation());
+        assertEquals(1, BILEVEL_BLACK_IS_ZERO.getPhotometricInterpretation());
+        assertEquals(1, GRAY.getPhotometricInterpretation());
+        assertEquals(3, PALETTE.getPhotometricInterpretation());
+        assertEquals(2, RGB.getPhotometricInterpretation());
+        assertEquals(5, CMYK.getPhotometricInterpretation());
+        assertEquals(6, YCBCR.getPhotometricInterpretation());
+        assertEquals(8, CIELAB.getPhotometricInterpretation());
+        assertEquals(1, GENERIC.getPhotometricInterpretation());
+    }
+
+    @Test
+    public void testGetTypeFromRGB() {
+        assertEquals(BILEVEL_BLACK_IS_ZERO, ImageType.getTypeFromRGB(2,
+                blackIsZero.r, blackIsZero.g, blackIsZero.b, 1, 1));
+        assertEquals(BILEVEL_WHITE_IS_ZERO, ImageType.getTypeFromRGB(2,
+                whiteIsZero.r, whiteIsZero.g, whiteIsZero.b, 1, 1));
+        // Test all other values (i.e. not including 0xff)
+        for (int b = 0; b < 255; b++) {
+            assertEquals(PALETTE, ImageType.getTypeFromRGB(2,
+                    make2ByteArray(0, b), make2ByteArray(0, b), make2ByteArray(0, b), 1, 1));
+            assertEquals(PALETTE, ImageType.getTypeFromRGB(2,
+                    make2ByteArray(b, 0), make2ByteArray(b, 0), make2ByteArray(b, 0), 1, 1));
+            if (b != 1) {
+                assertEquals(UNSUPPORTED, ImageType.getTypeFromRGB(2, null, null, null, 1, b));
+            }
+        }
+    }
+
+    private byte[] make2ByteArray(int b1, int b2) {
+        return new byte[] {(byte) b1, (byte) b2};
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testException() {
+        assertEquals(UNSUPPORTED, ImageType.getTypeFromRGB(1, null, null, null, 1, 1));
+    }
+
+    @Test
+    public void testGetTypeFromColorSpace() {
+        testIndividualColorSpaceType(CMYK, ColorSpace.TYPE_CMYK, false);
+        testIndividualColorSpaceType(GRAY, ColorSpace.TYPE_GRAY, false);
+        testIndividualColorSpaceType(CIELAB, ColorSpace.TYPE_Lab, false);
+        testIndividualColorSpaceType(YCBCR, ColorSpace.TYPE_YCbCr, false);
+        testIndividualColorSpaceType(YCBCR, ColorSpace.TYPE_RGB, true);
+        testIndividualColorSpaceType(RGB, ColorSpace.TYPE_RGB, false);
+    }
+
+    private void testIndividualColorSpaceType(ImageType expected, int type, boolean getJpegCompress) {
+        ColorSpace colorSpace = mock(ColorSpace.class);
+        when(colorSpace.getType()).thenReturn(type);
+        TIFFEncodeParam params = mock(TIFFEncodeParam.class);
+        when(params.getJPEGCompressRGBToYCbCr()).thenReturn(getJpegCompress);
+
+        assertEquals(expected, ImageType.getTypeFromColorSpace(colorSpace, params));
+    }
+}
diff --git a/test/java/org/apache/xmlgraphics/image/codec/tiff/TIFFImageEncoderTestCase.java b/test/java/org/apache/xmlgraphics/image/codec/tiff/TIFFImageEncoderTestCase.java
new file mode 100644
index 0000000..1603679
--- /dev/null
+++ b/test/java/org/apache/xmlgraphics/image/codec/tiff/TIFFImageEncoderTestCase.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.xmlgraphics.image.codec.tiff;
+
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import org.apache.commons.io.IOUtils;
+
+public class TIFFImageEncoderTestCase {
+
+    @Test
+    public void testGrayImage() throws IOException {
+        testImage(BufferedImage.TYPE_BYTE_GRAY, "gray.tiff");
+    }
+
+    @Test
+    public void testBilevelImage() throws IOException {
+        testImage(BufferedImage.TYPE_BYTE_BINARY, "bilevel.tiff");
+    }
+
+    private void testImage(int imageType, String imageFileName) throws IOException {
+        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+        TIFFImageEncoder encoder = new TIFFImageEncoder(byteStream, null);
+        encoder.encode(getImage(imageType));
+        byte[] actualArray = IOUtils.toByteArray(getClass().getResource(imageFileName).openStream());
+        assertArrayEquals(byteStream.toByteArray(), actualArray);
+    }
+
+    private RenderedImage getImage(int imageType) {
+        BufferedImage img = new BufferedImage(400, 400, imageType);
+        img.getSampleModel();
+        Graphics gfx = img.getGraphics();
+        gfx.setColor(Color.RED);
+        gfx.fillRect(10, 10, 100, 100);
+        gfx.setColor(Color.WHITE);
+        gfx.fillRect(50, 50, 100, 100);
+        return img;
+    }
+}
diff --git a/test/java/org/apache/xmlgraphics/image/codec/tiff/bilevel.tiff b/test/java/org/apache/xmlgraphics/image/codec/tiff/bilevel.tiff
new file mode 100644
index 0000000..23ec9da
--- /dev/null
+++ b/test/java/org/apache/xmlgraphics/image/codec/tiff/bilevel.tiff
Binary files differ
diff --git a/test/java/org/apache/xmlgraphics/image/codec/tiff/gray.tiff b/test/java/org/apache/xmlgraphics/image/codec/tiff/gray.tiff
new file mode 100644
index 0000000..3f66bd5
--- /dev/null
+++ b/test/java/org/apache/xmlgraphics/image/codec/tiff/gray.tiff
Binary files differ