diff -r 098d54b4051d -r 68c0d866db5d jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriter.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/src/java.desktop/share/classes/com/sun/imageio/plugins/tiff/TIFFImageWriter.java Mon Nov 23 12:26:12 2015 -0800 @@ -0,0 +1,3610 @@ +/* + * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.sun.imageio.plugins.tiff; + +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.color.ColorSpace; +import java.awt.color.ICC_ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.IndexColorModel; +import java.awt.image.RenderedImage; +import java.awt.image.Raster; +import java.awt.image.SampleModel; +import java.awt.image.WritableRaster; +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.imageio.IIOException; +import javax.imageio.IIOImage; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.spi.ImageWriterSpi; +import javax.imageio.stream.ImageOutputStream; +import org.w3c.dom.Node; +import com.sun.imageio.plugins.common.ImageUtil; +import javax.imageio.plugins.tiff.BaselineTIFFTagSet; +import javax.imageio.plugins.tiff.ExifParentTIFFTagSet; +import javax.imageio.plugins.tiff.ExifTIFFTagSet; +import javax.imageio.plugins.tiff.TIFFField; +import javax.imageio.plugins.tiff.TIFFTag; +import javax.imageio.plugins.tiff.TIFFTagSet; +import com.sun.imageio.plugins.common.SimpleRenderedImage; +import com.sun.imageio.plugins.common.SingleTileRenderedImage; +import java.nio.charset.StandardCharsets; + +public class TIFFImageWriter extends ImageWriter { + + static final String EXIF_JPEG_COMPRESSION_TYPE = "Exif JPEG"; + + private static final int DEFAULT_BYTES_PER_STRIP = 8192; + + /** + * Supported TIFF compression types. + */ + static final String[] TIFFCompressionTypes = { + "CCITT RLE", + "CCITT T.4", + "CCITT T.6", + "LZW", + // "Old JPEG", + "JPEG", + "ZLib", + "PackBits", + "Deflate", + EXIF_JPEG_COMPRESSION_TYPE + }; + + // + // The lengths of the arrays 'compressionTypes', + // 'isCompressionLossless', and 'compressionNumbers' + // must be equal. + // + + /** + * Known TIFF compression types. + */ + static final String[] compressionTypes = { + "CCITT RLE", + "CCITT T.4", + "CCITT T.6", + "LZW", + "Old JPEG", + "JPEG", + "ZLib", + "PackBits", + "Deflate", + EXIF_JPEG_COMPRESSION_TYPE + }; + + /** + * Lossless flag for known compression types. + */ + static final boolean[] isCompressionLossless = { + true, // RLE + true, // T.4 + true, // T.6 + true, // LZW + false, // Old JPEG + false, // JPEG + true, // ZLib + true, // PackBits + true, // DEFLATE + false // Exif JPEG + }; + + /** + * Compression tag values for known compression types. + */ + static final int[] compressionNumbers = { + BaselineTIFFTagSet.COMPRESSION_CCITT_RLE, + BaselineTIFFTagSet.COMPRESSION_CCITT_T_4, + BaselineTIFFTagSet.COMPRESSION_CCITT_T_6, + BaselineTIFFTagSet.COMPRESSION_LZW, + BaselineTIFFTagSet.COMPRESSION_OLD_JPEG, + BaselineTIFFTagSet.COMPRESSION_JPEG, + BaselineTIFFTagSet.COMPRESSION_ZLIB, + BaselineTIFFTagSet.COMPRESSION_PACKBITS, + BaselineTIFFTagSet.COMPRESSION_DEFLATE, + BaselineTIFFTagSet.COMPRESSION_OLD_JPEG, // Exif JPEG + }; + + private ImageOutputStream stream; + private long headerPosition; + private RenderedImage image; + private ImageTypeSpecifier imageType; + private ByteOrder byteOrder; + private ImageWriteParam param; + private TIFFCompressor compressor; + private TIFFColorConverter colorConverter; + + private TIFFStreamMetadata streamMetadata; + private TIFFImageMetadata imageMetadata; + + private int sourceXOffset; + private int sourceYOffset; + private int sourceWidth; + private int sourceHeight; + private int[] sourceBands; + private int periodX; + private int periodY; + + private int bitDepth; // bits per channel + private int numBands; + private int tileWidth; + private int tileLength; + private int tilesAcross; + private int tilesDown; + + private int[] sampleSize = null; // Input sample size per band, in bits + private int scalingBitDepth = -1; // Output bit depth of the scaling tables + private boolean isRescaling = false; // Whether rescaling is needed. + + private boolean isBilevel; // Whether image is bilevel + private boolean isImageSimple; // Whether image can be copied into directly + private boolean isInverted; // Whether photometric inversion is required + + private boolean isTiled; // Whether the image is tiled (true) or stipped (false). + + private int nativePhotometricInterpretation; + private int photometricInterpretation; + + private char[] bitsPerSample; // Output sample size per band + private int sampleFormat = + BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED; // Output sample format + + // Tables for 1, 2, 4, or 8 bit output + private byte[][] scale = null; // 8 bit table + private byte[] scale0 = null; // equivalent to scale[0] + + // Tables for 16 bit output + private byte[][] scaleh = null; // High bytes of output + private byte[][] scalel = null; // Low bytes of output + + private int compression; + private int predictor; + + private int totalPixels; + private int pixelsDone; + + private long nextIFDPointerPos; + + // Next available space. + private long nextSpace = 0L; + + // Whether a sequence is being written. + private boolean isWritingSequence = false; + private boolean isInsertingEmpty = false; + private boolean isWritingEmpty = false; + + private int currentImage = 0; + + /** + * Converts a pixel's X coordinate into a horizontal tile index + * relative to a given tile grid layout specified by its X offset + * and tile width. + * + *
If tileWidth < 0
, the results of this method
+ * are undefined. If tileWidth == 0
, an
+ * ArithmeticException
will be thrown.
+ *
+ * @throws ArithmeticException If tileWidth == 0
.
+ */
+ public static int XToTileX(int x, int tileGridXOffset, int tileWidth) {
+ x -= tileGridXOffset;
+ if (x < 0) {
+ x += 1 - tileWidth; // force round to -infinity (ceiling)
+ }
+ return x/tileWidth;
+ }
+
+ /**
+ * Converts a pixel's Y coordinate into a vertical tile index
+ * relative to a given tile grid layout specified by its Y offset
+ * and tile height.
+ *
+ *
If tileHeight < 0
, the results of this method
+ * are undefined. If tileHeight == 0
, an
+ * ArithmeticException
will be thrown.
+ *
+ * @throws ArithmeticException If tileHeight == 0
.
+ */
+ public static int YToTileY(int y, int tileGridYOffset, int tileHeight) {
+ y -= tileGridYOffset;
+ if (y < 0) {
+ y += 1 - tileHeight; // force round to -infinity (ceiling)
+ }
+ return y/tileHeight;
+ }
+
+ public TIFFImageWriter(ImageWriterSpi originatingProvider) {
+ super(originatingProvider);
+ }
+
+ public ImageWriteParam getDefaultWriteParam() {
+ return new TIFFImageWriteParam(getLocale());
+ }
+
+ public void setOutput(Object output) {
+ super.setOutput(output);
+
+ if (output != null) {
+ if (!(output instanceof ImageOutputStream)) {
+ throw new IllegalArgumentException
+ ("output not an ImageOutputStream!");
+ }
+ this.stream = (ImageOutputStream)output;
+
+ //
+ // The output is expected to be positioned at a TIFF header
+ // or at some arbitrary location which may or may not be
+ // the EOF. In the former case the writer should be able
+ // either to overwrite the existing sequence or append to it.
+ //
+
+ // Set the position of the header and the next available space.
+ try {
+ headerPosition = this.stream.getStreamPosition();
+ try {
+ // Read byte order and magic number.
+ byte[] b = new byte[4];
+ stream.readFully(b);
+
+ // Check bytes for TIFF header.
+ if((b[0] == (byte)0x49 && b[1] == (byte)0x49 &&
+ b[2] == (byte)0x2a && b[3] == (byte)0x00) ||
+ (b[0] == (byte)0x4d && b[1] == (byte)0x4d &&
+ b[2] == (byte)0x00 && b[3] == (byte)0x2a)) {
+ // TIFF header.
+ this.nextSpace = stream.length();
+ } else {
+ // Neither TIFF header nor EOF: overwrite.
+ this.nextSpace = headerPosition;
+ }
+ } catch(IOException io) { // thrown by readFully()
+ // At EOF or not at a TIFF header.
+ this.nextSpace = headerPosition;
+ }
+ stream.seek(headerPosition);
+ } catch(IOException ioe) { // thrown by getStreamPosition()
+ // Assume it's at zero.
+ this.nextSpace = headerPosition = 0L;
+ }
+ } else {
+ this.stream = null;
+ }
+ }
+
+ public IIOMetadata
+ getDefaultStreamMetadata(ImageWriteParam param) {
+ return new TIFFStreamMetadata();
+ }
+
+ public IIOMetadata
+ getDefaultImageMetadata(ImageTypeSpecifier imageType,
+ ImageWriteParam param) {
+
+ Listjavax_imageio_1.0
tree to a
+ * TIFFImageMetadata
object.
+ *
+ * @param inData The metadata object.
+ * @return a TIFFImageMetadata
or null
if
+ * the standard tree derived from the input object is null
.
+ * @throws IllegalArgumentException if inData
is
+ * null
.
+ * @throws IllegalArgumentException if inData
does not support
+ * the standard metadata format.
+ * @throws IIOInvalidTreeException if inData
generates an
+ * invalid standard metadata tree.
+ */
+ private TIFFImageMetadata convertStandardImageMetadata(IIOMetadata inData)
+ throws IIOInvalidTreeException {
+
+ if(inData == null) {
+ throw new NullPointerException("inData == null!");
+ } else if(!inData.isStandardMetadataFormatSupported()) {
+ throw new IllegalArgumentException
+ ("inData does not support standard metadata format!");
+ }
+
+ TIFFImageMetadata outData = null;
+
+ String formatName = IIOMetadataFormatImpl.standardMetadataFormatName;
+ Node tree = inData.getAsTree(formatName);
+ if (tree != null) {
+ Listjavax_imageio_tiff_image_1.0
tree to a
+ * TIFFImageMetadata
object.
+ *
+ * @param inData The metadata object.
+ * @return a TIFFImageMetadata
or null
if
+ * the native tree derived from the input object is null
.
+ * @throws IllegalArgumentException if inData
is
+ * null
or does not support the native metadata format.
+ * @throws IIOInvalidTreeException if inData
generates an
+ * invalid native metadata tree.
+ */
+ private TIFFImageMetadata convertNativeImageMetadata(IIOMetadata inData)
+ throws IIOInvalidTreeException {
+
+ if(inData == null) {
+ throw new NullPointerException("inData == null!");
+ } else if(!Arrays.asList(inData.getMetadataFormatNames()).contains(
+ TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME)) {
+ throw new IllegalArgumentException
+ ("inData does not support native metadata format!");
+ }
+
+ TIFFImageMetadata outData = null;
+
+ String formatName = TIFFImageMetadata.NATIVE_METADATA_FORMAT_NAME;
+ Node tree = inData.getAsTree(formatName);
+ if (tree != null) {
+ ListColorModel
of the image being written.
+ * @param sm The SampleModel
of the image being written.
+ * @param destWidth The width of the written image after subsampling.
+ * @param destHeight The height of the written image after subsampling.
+ */
+ void setupMetadata(ColorModel cm, SampleModel sm,
+ int destWidth, int destHeight)
+ throws IIOException {
+ // Get initial IFD from metadata
+
+ // Always emit these fields:
+ //
+ // Override values from metadata:
+ //
+ // planarConfiguration -> chunky (planar not supported on output)
+ //
+ // Override values from metadata with image-derived values:
+ //
+ // bitsPerSample (if not bilivel)
+ // colorMap (if palette color)
+ // photometricInterpretation (derive from image)
+ // imageLength
+ // imageWidth
+ //
+ // rowsPerStrip \ / tileLength
+ // stripOffsets | OR | tileOffsets
+ // stripByteCounts / | tileByteCounts
+ // \ tileWidth
+ //
+ //
+ // Override values from metadata with write param values:
+ //
+ // compression
+
+ // Use values from metadata if present for these fields,
+ // otherwise use defaults:
+ //
+ // resolutionUnit
+ // XResolution (take from metadata if present)
+ // YResolution
+ // rowsPerStrip
+ // sampleFormat
+
+ TIFFIFD rootIFD = imageMetadata.getRootIFD();
+
+ BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();
+
+ // If PlanarConfiguration field present, set value to chunky.
+
+ TIFFField f =
+ rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
+ if(f != null &&
+ f.getAsInt(0) != BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY) {
+ TIFFField planarConfigurationField =
+ new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION),
+ BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY);
+ rootIFD.addTIFFField(planarConfigurationField);
+ }
+
+ char[] extraSamples = null;
+
+ this.photometricInterpretation = -1;
+ boolean forcePhotometricInterpretation = false;
+
+ f =
+ rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
+ if (f != null) {
+ photometricInterpretation = f.getAsInt(0);
+ if(photometricInterpretation ==
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR &&
+ !(cm instanceof IndexColorModel)) {
+ photometricInterpretation = -1;
+ } else {
+ forcePhotometricInterpretation = true;
+ }
+ }
+
+ int[] sampleSize = sm.getSampleSize();
+
+ int numBands = sm.getNumBands();
+ int numExtraSamples = 0;
+
+ // Check that numBands > 1 here because TIFF requires that
+ // SamplesPerPixel = numBands + numExtraSamples and numBands
+ // cannot be zero.
+ if (numBands > 1 && cm != null && cm.hasAlpha()) {
+ --numBands;
+ numExtraSamples = 1;
+ extraSamples = new char[1];
+ if (cm.isAlphaPremultiplied()) {
+ extraSamples[0] =
+ BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA;
+ } else {
+ extraSamples[0] =
+ BaselineTIFFTagSet.EXTRA_SAMPLES_UNASSOCIATED_ALPHA;
+ }
+ }
+
+ if (numBands == 3) {
+ this.nativePhotometricInterpretation =
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
+ if (photometricInterpretation == -1) {
+ photometricInterpretation =
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
+ }
+ } else if (sm.getNumBands() == 1 && cm instanceof IndexColorModel) {
+ IndexColorModel icm = (IndexColorModel)cm;
+ int r0 = icm.getRed(0);
+ int r1 = icm.getRed(1);
+ if (icm.getMapSize() == 2 &&
+ (r0 == icm.getGreen(0)) && (r0 == icm.getBlue(0)) &&
+ (r1 == icm.getGreen(1)) && (r1 == icm.getBlue(1)) &&
+ (r0 == 0 || r0 == 255) &&
+ (r1 == 0 || r1 == 255) &&
+ (r0 != r1)) {
+ // Black/white image
+
+ if (r0 == 0) {
+ nativePhotometricInterpretation =
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
+ } else {
+ nativePhotometricInterpretation =
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
+ }
+
+
+ // If photometricInterpretation is already set to
+ // WhiteIsZero or BlackIsZero, leave it alone
+ if (photometricInterpretation !=
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO &&
+ photometricInterpretation !=
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) {
+ photometricInterpretation =
+ r0 == 0 ?
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO :
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
+ }
+ } else {
+ nativePhotometricInterpretation =
+ photometricInterpretation =
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR;
+ }
+ } else {
+ if(cm != null) {
+ switch(cm.getColorSpace().getType()) {
+ case ColorSpace.TYPE_Lab:
+ nativePhotometricInterpretation =
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB;
+ break;
+ case ColorSpace.TYPE_YCbCr:
+ nativePhotometricInterpretation =
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR;
+ break;
+ case ColorSpace.TYPE_CMYK:
+ nativePhotometricInterpretation =
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK;
+ break;
+ default:
+ nativePhotometricInterpretation =
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
+ }
+ } else {
+ nativePhotometricInterpretation =
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
+ }
+ if (photometricInterpretation == -1) {
+ photometricInterpretation = nativePhotometricInterpretation;
+ }
+ }
+
+ // Emit compression tag
+
+ int compressionMode = param.getCompressionMode();
+ switch(compressionMode) {
+ case ImageWriteParam.MODE_EXPLICIT:
+ {
+ String compressionType = param.getCompressionType();
+ if (compressionType == null) {
+ this.compression = BaselineTIFFTagSet.COMPRESSION_NONE;
+ } else {
+ // Determine corresponding compression tag value.
+ int len = compressionTypes.length;
+ for (int i = 0; i < len; i++) {
+ if (compressionType.equals(compressionTypes[i])) {
+ this.compression = compressionNumbers[i];
+ }
+ }
+ }
+ }
+ break;
+ case ImageWriteParam.MODE_COPY_FROM_METADATA:
+ {
+ TIFFField compField =
+ rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
+ if(compField != null) {
+ this.compression = compField.getAsInt(0);
+ } else {
+ this.compression = BaselineTIFFTagSet.COMPRESSION_NONE;
+ }
+ }
+ break;
+ default:
+ this.compression = BaselineTIFFTagSet.COMPRESSION_NONE;
+ }
+
+ TIFFField predictorField =
+ rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
+ if (predictorField != null) {
+ this.predictor = predictorField.getAsInt(0);
+
+ // We only support Horizontal Predictor for a bitDepth of 8
+ if (sampleSize[0] != 8 ||
+ // Check the value of the tag for validity
+ (predictor != BaselineTIFFTagSet.PREDICTOR_NONE &&
+ predictor !=
+ BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING)) {
+ // Set to default
+ predictor = BaselineTIFFTagSet.PREDICTOR_NONE;
+
+ // Emit this changed predictor value to metadata
+ TIFFField newPredictorField =
+ new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_PREDICTOR),
+ predictor);
+ rootIFD.addTIFFField(newPredictorField);
+ }
+ }
+
+ TIFFField compressionField =
+ new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_COMPRESSION),
+ compression);
+ rootIFD.addTIFFField(compressionField);
+
+ // Set Exif flag. Note that there is no way to determine definitively
+ // when an uncompressed thumbnail is being written as the Exif IFD
+ // pointer field is optional for thumbnails.
+ boolean isExif = false;
+ if(numBands == 3 &&
+ sampleSize[0] == 8 && sampleSize[1] == 8 && sampleSize[2] == 8) {
+ // Three bands with 8 bits per sample.
+ if(rootIFD.getTIFFField(ExifParentTIFFTagSet.TAG_EXIF_IFD_POINTER)
+ != null) {
+ // Exif IFD pointer present.
+ if(compression == BaselineTIFFTagSet.COMPRESSION_NONE &&
+ (photometricInterpretation ==
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB ||
+ photometricInterpretation ==
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR)) {
+ // Uncompressed RGB or YCbCr.
+ isExif = true;
+ } else if(compression ==
+ BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
+ // Compressed.
+ isExif = true;
+ }
+ } else if(compressionMode == ImageWriteParam.MODE_EXPLICIT &&
+ EXIF_JPEG_COMPRESSION_TYPE.equals
+ (param.getCompressionType())) {
+ // Exif IFD pointer absent but Exif JPEG compression set.
+ isExif = true;
+ }
+ }
+
+ // Initialize JPEG interchange format flag which is used to
+ // indicate that the image is stored as a single JPEG stream.
+ // This flag is separated from the 'isExif' flag in case JPEG
+ // interchange format is eventually supported for non-Exif images.
+ boolean isJPEGInterchange =
+ isExif && compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG;
+
+ this.compressor = null;
+ if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) {
+ compressor = new TIFFRLECompressor();
+
+ if (!forcePhotometricInterpretation) {
+ photometricInterpretation
+ = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
+ }
+ } else if (compression
+ == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) {
+ compressor = new TIFFT4Compressor();
+
+ if (!forcePhotometricInterpretation) {
+ photometricInterpretation
+ = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
+ }
+ } else if (compression
+ == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
+ compressor = new TIFFT6Compressor();
+
+ if (!forcePhotometricInterpretation) {
+ photometricInterpretation
+ = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
+ }
+ } else if (compression
+ == BaselineTIFFTagSet.COMPRESSION_LZW) {
+ compressor = new TIFFLZWCompressor(predictor);
+ } else if (compression
+ == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
+ if (isExif) {
+ compressor = new TIFFExifJPEGCompressor(param);
+ } else {
+ throw new IIOException("Old JPEG compression not supported!");
+ }
+ } else if (compression
+ == BaselineTIFFTagSet.COMPRESSION_JPEG) {
+ if (numBands == 3 && sampleSize[0] == 8
+ && sampleSize[1] == 8 && sampleSize[2] == 8) {
+ photometricInterpretation
+ = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR;
+ } else if (numBands == 1 && sampleSize[0] == 8) {
+ photometricInterpretation
+ = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
+ } else {
+ throw new IIOException("JPEG compression supported for 1- and 3-band byte images only!");
+ }
+ compressor = new TIFFJPEGCompressor(param);
+ } else if (compression
+ == BaselineTIFFTagSet.COMPRESSION_ZLIB) {
+ compressor = new TIFFZLibCompressor(param, predictor);
+ } else if (compression
+ == BaselineTIFFTagSet.COMPRESSION_PACKBITS) {
+ compressor = new TIFFPackBitsCompressor();
+ } else if (compression
+ == BaselineTIFFTagSet.COMPRESSION_DEFLATE) {
+ compressor = new TIFFDeflateCompressor(param, predictor);
+ } else {
+ // Determine inverse fill setting.
+ f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER);
+ boolean inverseFill = (f != null && f.getAsInt(0) == 2);
+
+ if (inverseFill) {
+ compressor = new TIFFLSBCompressor();
+ } else {
+ compressor = new TIFFNullCompressor();
+ }
+ }
+
+
+ this.colorConverter = null;
+ if (cm != null
+ && cm.getColorSpace().getType() == ColorSpace.TYPE_RGB) {
+ //
+ // Perform color conversion only if image has RGB color space.
+ //
+ if (photometricInterpretation
+ == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR
+ && compression
+ != BaselineTIFFTagSet.COMPRESSION_JPEG) {
+ //
+ // Convert RGB to YCbCr only if compression type is not
+ // JPEG in which case this is handled implicitly by the
+ // compressor.
+ //
+ colorConverter = new TIFFYCbCrColorConverter(imageMetadata);
+ } else if (photometricInterpretation
+ == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB) {
+ colorConverter = new TIFFCIELabColorConverter();
+ }
+ }
+
+ //
+ // Cannot at this time do YCbCr subsampling so set the
+ // YCbCrSubsampling field value to [1, 1] and the YCbCrPositioning
+ // field value to "cosited".
+ //
+ if(photometricInterpretation ==
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR &&
+ compression !=
+ BaselineTIFFTagSet.COMPRESSION_JPEG) {
+ // Remove old subsampling and positioning fields.
+ rootIFD.removeTIFFField
+ (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
+ rootIFD.removeTIFFField
+ (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING);
+
+ // Add unity chrominance subsampling factors.
+ rootIFD.addTIFFField
+ (new TIFFField
+ (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING),
+ TIFFTag.TIFF_SHORT,
+ 2,
+ new char[] {(char)1, (char)1}));
+
+ // Add cosited positioning.
+ rootIFD.addTIFFField
+ (new TIFFField
+ (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING),
+ TIFFTag.TIFF_SHORT,
+ 1,
+ new char[] {
+ (char)BaselineTIFFTagSet.Y_CB_CR_POSITIONING_COSITED
+ }));
+ }
+
+ TIFFField photometricInterpretationField =
+ new TIFFField(
+ base.getTag(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION),
+ photometricInterpretation);
+ rootIFD.addTIFFField(photometricInterpretationField);
+
+ this.bitsPerSample = new char[numBands + numExtraSamples];
+ this.bitDepth = 0;
+ for (int i = 0; i < numBands; i++) {
+ this.bitDepth = Math.max(bitDepth, sampleSize[i]);
+ }
+ if (bitDepth == 3) {
+ bitDepth = 4;
+ } else if (bitDepth > 4 && bitDepth < 8) {
+ bitDepth = 8;
+ } else if (bitDepth > 8 && bitDepth < 16) {
+ bitDepth = 16;
+ } else if (bitDepth > 16 && bitDepth < 32) {
+ bitDepth = 32;
+ } else if (bitDepth > 32) {
+ bitDepth = 64;
+ }
+
+ for (int i = 0; i < bitsPerSample.length; i++) {
+ bitsPerSample[i] = (char)bitDepth;
+ }
+
+ // Emit BitsPerSample. If the image is bilevel, emit if and only
+ // if already in the metadata and correct (count and value == 1).
+ if (bitsPerSample.length != 1 || bitsPerSample[0] != 1) {
+ TIFFField bitsPerSampleField =
+ new TIFFField(
+ base.getTag(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE),
+ TIFFTag.TIFF_SHORT,
+ bitsPerSample.length,
+ bitsPerSample);
+ rootIFD.addTIFFField(bitsPerSampleField);
+ } else { // bitsPerSample.length == 1 && bitsPerSample[0] == 1
+ TIFFField bitsPerSampleField =
+ rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
+ if(bitsPerSampleField != null) {
+ int[] bps = bitsPerSampleField.getAsInts();
+ if(bps == null || bps.length != 1 || bps[0] != 1) {
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
+ }
+ }
+ }
+
+ // Prepare SampleFormat field.
+ f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
+ if(f == null && (bitDepth == 16 || bitDepth == 32 || bitDepth == 64)) {
+ // Set up default content for 16-, 32-, and 64-bit cases.
+ char sampleFormatValue;
+ int dataType = sm.getDataType();
+ if(bitDepth == 16 && dataType == DataBuffer.TYPE_USHORT) {
+ sampleFormatValue =
+ (char)BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER;
+ } else if((bitDepth == 32 && dataType == DataBuffer.TYPE_FLOAT) ||
+ (bitDepth == 64 && dataType == DataBuffer.TYPE_DOUBLE)) {
+ sampleFormatValue =
+ (char)BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT;
+ } else {
+ sampleFormatValue =
+ BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER;
+ }
+ this.sampleFormat = (int)sampleFormatValue;
+ char[] sampleFormatArray = new char[bitsPerSample.length];
+ Arrays.fill(sampleFormatArray, sampleFormatValue);
+
+ // Update the metadata.
+ TIFFTag sampleFormatTag =
+ base.getTag(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
+
+ TIFFField sampleFormatField =
+ new TIFFField(sampleFormatTag, TIFFTag.TIFF_SHORT,
+ sampleFormatArray.length, sampleFormatArray);
+
+ rootIFD.addTIFFField(sampleFormatField);
+ } else if(f != null) {
+ // Get whatever was provided.
+ sampleFormat = f.getAsInt(0);
+ } else {
+ // Set default value for internal use only.
+ sampleFormat = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
+ }
+
+ if (extraSamples != null) {
+ TIFFField extraSamplesField =
+ new TIFFField(
+ base.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES),
+ TIFFTag.TIFF_SHORT,
+ extraSamples.length,
+ extraSamples);
+ rootIFD.addTIFFField(extraSamplesField);
+ } else {
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES);
+ }
+
+ TIFFField samplesPerPixelField =
+ new TIFFField(
+ base.getTag(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL),
+ bitsPerSample.length);
+ rootIFD.addTIFFField(samplesPerPixelField);
+
+ // Emit ColorMap if image is of palette color type
+ if (photometricInterpretation ==
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR &&
+ cm instanceof IndexColorModel) {
+ char[] colorMap = new char[3*(1 << bitsPerSample[0])];
+
+ IndexColorModel icm = (IndexColorModel)cm;
+
+ // mapSize is determined by BitsPerSample, not by incoming ICM.
+ int mapSize = 1 << bitsPerSample[0];
+ int indexBound = Math.min(mapSize, icm.getMapSize());
+ for (int i = 0; i < indexBound; i++) {
+ colorMap[i] = (char)((icm.getRed(i)*65535)/255);
+ colorMap[mapSize + i] = (char)((icm.getGreen(i)*65535)/255);
+ colorMap[2*mapSize + i] = (char)((icm.getBlue(i)*65535)/255);
+ }
+
+ TIFFField colorMapField =
+ new TIFFField(
+ base.getTag(BaselineTIFFTagSet.TAG_COLOR_MAP),
+ TIFFTag.TIFF_SHORT,
+ colorMap.length,
+ colorMap);
+ rootIFD.addTIFFField(colorMapField);
+ } else {
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP);
+ }
+
+ // Emit ICCProfile if there is no ICCProfile field already in the
+ // metadata and the ColorSpace is non-standard ICC.
+ if(cm != null &&
+ rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE) == null &&
+ ImageUtil.isNonStandardICCColorSpace(cm.getColorSpace())) {
+ ICC_ColorSpace iccColorSpace = (ICC_ColorSpace)cm.getColorSpace();
+ byte[] iccProfileData = iccColorSpace.getProfile().getData();
+ TIFFField iccProfileField =
+ new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_ICC_PROFILE),
+ TIFFTag.TIFF_UNDEFINED,
+ iccProfileData.length,
+ iccProfileData);
+ rootIFD.addTIFFField(iccProfileField);
+ }
+
+ // Always emit XResolution and YResolution.
+
+ TIFFField XResolutionField =
+ rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_X_RESOLUTION);
+ TIFFField YResolutionField =
+ rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_Y_RESOLUTION);
+
+ if(XResolutionField == null && YResolutionField == null) {
+ long[][] resRational = new long[1][2];
+ resRational[0] = new long[2];
+
+ TIFFField ResolutionUnitField =
+ rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT);
+
+ // Don't force dimensionless if one of the other dimensional
+ // quantities is present.
+ if(ResolutionUnitField == null &&
+ rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_X_POSITION) == null &&
+ rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_Y_POSITION) == null) {
+ // Set resolution to unit and units to dimensionless.
+ resRational[0][0] = 1;
+ resRational[0][1] = 1;
+
+ ResolutionUnitField =
+ new TIFFField(rootIFD.getTag
+ (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
+ BaselineTIFFTagSet.RESOLUTION_UNIT_NONE);
+ rootIFD.addTIFFField(ResolutionUnitField);
+ } else {
+ // Set resolution to a value which would make the maximum
+ // image dimension equal to 4 inches as arbitrarily stated
+ // in the description of ResolutionUnit in the TIFF 6.0
+ // specification. If the ResolutionUnit field specifies
+ // "none" then set the resolution to unity (1/1).
+ int resolutionUnit = ResolutionUnitField != null ?
+ ResolutionUnitField.getAsInt(0) :
+ BaselineTIFFTagSet.RESOLUTION_UNIT_INCH;
+ int maxDimension = Math.max(destWidth, destHeight);
+ switch(resolutionUnit) {
+ case BaselineTIFFTagSet.RESOLUTION_UNIT_INCH:
+ resRational[0][0] = maxDimension;
+ resRational[0][1] = 4;
+ break;
+ case BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER:
+ resRational[0][0] = 100L*maxDimension; // divide out 100
+ resRational[0][1] = 4*254; // 2.54 cm/inch * 100
+ break;
+ default:
+ resRational[0][0] = 1;
+ resRational[0][1] = 1;
+ }
+ }
+
+ XResolutionField =
+ new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION),
+ TIFFTag.TIFF_RATIONAL,
+ 1,
+ resRational);
+ rootIFD.addTIFFField(XResolutionField);
+
+ YResolutionField =
+ new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION),
+ TIFFTag.TIFF_RATIONAL,
+ 1,
+ resRational);
+ rootIFD.addTIFFField(YResolutionField);
+ } else if(XResolutionField == null && YResolutionField != null) {
+ // Set XResolution to YResolution.
+ long[] yResolution =
+ YResolutionField.getAsRational(0).clone();
+ XResolutionField =
+ new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION),
+ TIFFTag.TIFF_RATIONAL,
+ 1,
+ yResolution);
+ rootIFD.addTIFFField(XResolutionField);
+ } else if(XResolutionField != null && YResolutionField == null) {
+ // Set YResolution to XResolution.
+ long[] xResolution =
+ XResolutionField.getAsRational(0).clone();
+ YResolutionField =
+ new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION),
+ TIFFTag.TIFF_RATIONAL,
+ 1,
+ xResolution);
+ rootIFD.addTIFFField(YResolutionField);
+ }
+
+ // Set mandatory fields, overriding metadata passed in
+
+ int width = destWidth;
+ TIFFField imageWidthField =
+ new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_WIDTH),
+ width);
+ rootIFD.addTIFFField(imageWidthField);
+
+ int height = destHeight;
+ TIFFField imageLengthField =
+ new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_LENGTH),
+ height);
+ rootIFD.addTIFFField(imageLengthField);
+
+ // Determine rowsPerStrip
+
+ int rowsPerStrip;
+
+ TIFFField rowsPerStripField =
+ rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
+ if (rowsPerStripField != null) {
+ rowsPerStrip = rowsPerStripField.getAsInt(0);
+ if(rowsPerStrip < 0) {
+ rowsPerStrip = height;
+ }
+ } else {
+ int bitsPerPixel = bitDepth*(numBands + numExtraSamples);
+ int bytesPerRow = (bitsPerPixel*width + 7)/8;
+ rowsPerStrip =
+ Math.max(Math.max(DEFAULT_BYTES_PER_STRIP/bytesPerRow, 1), 8);
+ }
+ rowsPerStrip = Math.min(rowsPerStrip, height);
+
+ // Tiling flag.
+ boolean useTiling = false;
+
+ // Analyze tiling parameters
+ int tilingMode = param.getTilingMode();
+ if (tilingMode == ImageWriteParam.MODE_DISABLED ||
+ tilingMode == ImageWriteParam.MODE_DEFAULT) {
+ this.tileWidth = width;
+ this.tileLength = rowsPerStrip;
+ useTiling = false;
+ } else if (tilingMode == ImageWriteParam.MODE_EXPLICIT) {
+ tileWidth = param.getTileWidth();
+ tileLength = param.getTileHeight();
+ useTiling = true;
+ } else if (tilingMode == ImageWriteParam.MODE_COPY_FROM_METADATA) {
+ f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
+ if (f == null) {
+ tileWidth = width;
+ useTiling = false;
+ } else {
+ tileWidth = f.getAsInt(0);
+ useTiling = true;
+ }
+
+ f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH);
+ if (f == null) {
+ tileLength = rowsPerStrip;
+ } else {
+ tileLength = f.getAsInt(0);
+ useTiling = true;
+ }
+ } else {
+ throw new IIOException("Illegal value of tilingMode!");
+ }
+
+ if(compression == BaselineTIFFTagSet.COMPRESSION_JPEG) {
+ // Reset tile size per TTN2 spec for JPEG compression.
+ int subX;
+ int subY;
+ if(numBands == 1) {
+ subX = subY = 1;
+ } else {
+ subX = subY = TIFFJPEGCompressor.CHROMA_SUBSAMPLING;
+ }
+ if(useTiling) {
+ int MCUMultipleX = 8*subX;
+ int MCUMultipleY = 8*subY;
+ tileWidth =
+ Math.max(MCUMultipleX*((tileWidth +
+ MCUMultipleX/2)/MCUMultipleX),
+ MCUMultipleX);
+ tileLength =
+ Math.max(MCUMultipleY*((tileLength +
+ MCUMultipleY/2)/MCUMultipleY),
+ MCUMultipleY);
+ } else if(rowsPerStrip < height) {
+ int MCUMultiple = 8*Math.max(subX, subY);
+ rowsPerStrip = tileLength =
+ Math.max(MCUMultiple*((tileLength +
+ MCUMultiple/2)/MCUMultiple),
+ MCUMultiple);
+ }
+
+ // The written image may be unreadable if these fields are present.
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+
+ // Also remove fields related to the old JPEG encoding scheme
+ // which may be misleading when the compression type is JPEG.
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_PROC);
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_RESTART_INTERVAL);
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_LOSSLESS_PREDICTORS);
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_POINT_TRANSFORMS);
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_Q_TABLES);
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_DC_TABLES);
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_AC_TABLES);
+ } else if(isJPEGInterchange) {
+ // Force tile size to equal image size.
+ tileWidth = width;
+ tileLength = height;
+ } else if(useTiling) {
+ // Round tile size to multiple of 16 per TIFF 6.0 specification
+ // (see pages 67-68 of version 6.0.1 from Adobe).
+ int tileWidthRemainder = tileWidth % 16;
+ if(tileWidthRemainder != 0) {
+ // Round to nearest multiple of 16 not less than 16.
+ tileWidth = Math.max(16*((tileWidth + 8)/16), 16);
+ processWarningOccurred(currentImage,
+ "Tile width rounded to multiple of 16.");
+ }
+
+ int tileLengthRemainder = tileLength % 16;
+ if(tileLengthRemainder != 0) {
+ // Round to nearest multiple of 16 not less than 16.
+ tileLength = Math.max(16*((tileLength + 8)/16), 16);
+ processWarningOccurred(currentImage,
+ "Tile height rounded to multiple of 16.");
+ }
+ }
+
+ this.tilesAcross = (width + tileWidth - 1)/tileWidth;
+ this.tilesDown = (height + tileLength - 1)/tileLength;
+
+ if (!useTiling) {
+ this.isTiled = false;
+
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH);
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS);
+
+ rowsPerStripField =
+ new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP),
+ rowsPerStrip);
+ rootIFD.addTIFFField(rowsPerStripField);
+
+ TIFFField stripOffsetsField =
+ new TIFFField(
+ base.getTag(BaselineTIFFTagSet.TAG_STRIP_OFFSETS),
+ TIFFTag.TIFF_LONG,
+ tilesDown);
+ rootIFD.addTIFFField(stripOffsetsField);
+
+ TIFFField stripByteCountsField =
+ new TIFFField(
+ base.getTag(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS),
+ TIFFTag.TIFF_LONG,
+ tilesDown);
+ rootIFD.addTIFFField(stripByteCountsField);
+ } else {
+ this.isTiled = true;
+
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
+
+ TIFFField tileWidthField =
+ new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_TILE_WIDTH),
+ tileWidth);
+ rootIFD.addTIFFField(tileWidthField);
+
+ TIFFField tileLengthField =
+ new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_TILE_LENGTH),
+ tileLength);
+ rootIFD.addTIFFField(tileLengthField);
+
+ TIFFField tileOffsetsField =
+ new TIFFField(
+ base.getTag(BaselineTIFFTagSet.TAG_TILE_OFFSETS),
+ TIFFTag.TIFF_LONG,
+ tilesDown*tilesAcross);
+ rootIFD.addTIFFField(tileOffsetsField);
+
+ TIFFField tileByteCountsField =
+ new TIFFField(
+ base.getTag(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS),
+ TIFFTag.TIFF_LONG,
+ tilesDown*tilesAcross);
+ rootIFD.addTIFFField(tileByteCountsField);
+ }
+
+ if(isExif) {
+ //
+ // Ensure presence of mandatory fields and absence of prohibited
+ // fields and those that duplicate information in JPEG marker
+ // segments per tables 14-18 of the Exif 2.2 specification.
+ //
+
+ // If an empty image is being written or inserted then infer
+ // that the primary IFD is being set up.
+ boolean isPrimaryIFD = isEncodingEmpty();
+
+ // Handle TIFF fields in order of increasing tag number.
+ if(compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
+ // ImageWidth
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
+
+ // ImageLength
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
+
+ // BitsPerSample
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
+ // Compression
+ if(isPrimaryIFD) {
+ rootIFD.removeTIFFField
+ (BaselineTIFFTagSet.TAG_COMPRESSION);
+ }
+
+ // PhotometricInterpretation
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
+
+ // StripOffsets
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
+
+ // SamplesPerPixel
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
+
+ // RowsPerStrip
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
+
+ // StripByteCounts
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
+ // XResolution and YResolution are handled above for all TIFFs.
+
+ // PlanarConfiguration
+ rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
+
+ // ResolutionUnit
+ if(rootIFD.getTIFFField
+ (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT) == null) {
+ f = new TIFFField(base.getTag
+ (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
+ BaselineTIFFTagSet.RESOLUTION_UNIT_INCH);
+ rootIFD.addTIFFField(f);
+ }
+
+ if(isPrimaryIFD) {
+ // JPEGInterchangeFormat
+ rootIFD.removeTIFFField
+ (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
+
+ // JPEGInterchangeFormatLength
+ rootIFD.removeTIFFField
+ (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+
+ // YCbCrSubsampling
+ rootIFD.removeTIFFField
+ (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
+
+ // YCbCrPositioning
+ if(rootIFD.getTIFFField
+ (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING) == null) {
+ f = new TIFFField
+ (base.getTag
+ (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING),
+ TIFFTag.TIFF_SHORT,
+ 1,
+ new char[] {
+ (char)BaselineTIFFTagSet.Y_CB_CR_POSITIONING_CENTERED
+ });
+ rootIFD.addTIFFField(f);
+ }
+ } else { // Thumbnail IFD
+ // JPEGInterchangeFormat
+ f = new TIFFField
+ (base.getTag
+ (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT),
+ TIFFTag.TIFF_LONG,
+ 1);
+ rootIFD.addTIFFField(f);
+
+ // JPEGInterchangeFormatLength
+ f = new TIFFField
+ (base.getTag
+ (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH),
+ TIFFTag.TIFF_LONG,
+ 1);
+ rootIFD.addTIFFField(f);
+
+ // YCbCrSubsampling
+ rootIFD.removeTIFFField
+ (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
+ }
+ } else { // Uncompressed
+ // ImageWidth through PlanarConfiguration are set above.
+ // XResolution and YResolution are handled above for all TIFFs.
+
+ // ResolutionUnit
+ if(rootIFD.getTIFFField
+ (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT) == null) {
+ f = new TIFFField(base.getTag
+ (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
+ BaselineTIFFTagSet.RESOLUTION_UNIT_INCH);
+ rootIFD.addTIFFField(f);
+ }
+
+
+ // JPEGInterchangeFormat
+ rootIFD.removeTIFFField
+ (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
+
+ // JPEGInterchangeFormatLength
+ rootIFD.removeTIFFField
+ (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+
+ if(photometricInterpretation ==
+ BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB) {
+ // YCbCrCoefficients
+ rootIFD.removeTIFFField
+ (BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS);
+
+ // YCbCrSubsampling
+ rootIFD.removeTIFFField
+ (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
+
+ // YCbCrPositioning
+ rootIFD.removeTIFFField
+ (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING);
+ }
+ }
+
+ // Get Exif tags.
+ TIFFTagSet exifTags = ExifTIFFTagSet.getInstance();
+
+ // Retrieve or create the Exif IFD.
+ TIFFIFD exifIFD = null;
+ f = rootIFD.getTIFFField
+ (ExifParentTIFFTagSet.TAG_EXIF_IFD_POINTER);
+ if(f != null && f.hasDirectory()) {
+ // Retrieve the Exif IFD.
+ exifIFD = (TIFFIFD)f.getDirectory();
+ } else if(isPrimaryIFD) {
+ // Create the Exif IFD.
+ List