0001: /*
0002: * $RCSfile: TIFFImageWriter.java,v $
0003: *
0004: *
0005: * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
0006: *
0007: * Redistribution and use in source and binary forms, with or without
0008: * modification, are permitted provided that the following conditions
0009: * are met:
0010: *
0011: * - Redistribution of source code must retain the above copyright
0012: * notice, this list of conditions and the following disclaimer.
0013: *
0014: * - Redistribution in binary form must reproduce the above copyright
0015: * notice, this list of conditions and the following disclaimer in
0016: * the documentation and/or other materials provided with the
0017: * distribution.
0018: *
0019: * Neither the name of Sun Microsystems, Inc. or the names of
0020: * contributors may be used to endorse or promote products derived
0021: * from this software without specific prior written permission.
0022: *
0023: * This software is provided "AS IS," without a warranty of any
0024: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
0025: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
0026: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
0027: * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
0028: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
0029: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
0030: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
0031: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
0032: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
0033: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
0034: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
0035: * POSSIBILITY OF SUCH DAMAGES.
0036: *
0037: * You acknowledge that this software is not designed or intended for
0038: * use in the design, construction, operation or maintenance of any
0039: * nuclear facility.
0040: *
0041: * $Revision: 1.24 $
0042: * $Date: 2007/09/01 00:27:20 $
0043: * $State: Exp $
0044: */
0045: package com.sun.media.imageioimpl.plugins.tiff;
0046:
0047: import java.awt.Point;
0048: import java.awt.Rectangle;
0049: import java.awt.color.ColorSpace;
0050: import java.awt.color.ICC_ColorSpace;
0051: import java.awt.image.BufferedImage;
0052: import java.awt.image.ColorModel;
0053: import java.awt.image.ComponentSampleModel;
0054: import java.awt.image.DataBuffer;
0055: import java.awt.image.DataBufferByte;
0056: import java.awt.image.IndexColorModel;
0057: import java.awt.image.MultiPixelPackedSampleModel;
0058: import java.awt.image.RenderedImage;
0059: import java.awt.image.Raster;
0060: import java.awt.image.SampleModel;
0061: import java.awt.image.WritableRaster;
0062: import java.io.EOFException;
0063: import java.io.IOException;
0064: import java.nio.ByteOrder;
0065: import java.util.ArrayList;
0066: import java.util.Arrays;
0067: import java.util.List;
0068: import javax.imageio.IIOException;
0069: import javax.imageio.IIOImage;
0070: import javax.imageio.ImageWriteParam;
0071: import javax.imageio.ImageWriter;
0072: import javax.imageio.ImageTypeSpecifier;
0073: import javax.imageio.metadata.IIOInvalidTreeException;
0074: import javax.imageio.metadata.IIOMetadata;
0075: import javax.imageio.metadata.IIOMetadataFormatImpl;
0076: import javax.imageio.spi.ImageWriterSpi;
0077: import javax.imageio.stream.ImageOutputStream;
0078: import org.w3c.dom.Node;
0079: import com.sun.media.imageio.plugins.tiff.BaselineTIFFTagSet;
0080: import com.sun.media.imageio.plugins.tiff.EXIFParentTIFFTagSet;
0081: import com.sun.media.imageio.plugins.tiff.EXIFTIFFTagSet;
0082: import com.sun.media.imageio.plugins.tiff.TIFFColorConverter;
0083: import com.sun.media.imageio.plugins.tiff.TIFFCompressor;
0084: import com.sun.media.imageio.plugins.tiff.TIFFField;
0085: import com.sun.media.imageio.plugins.tiff.TIFFImageWriteParam;
0086: import com.sun.media.imageio.plugins.tiff.TIFFTag;
0087: import com.sun.media.imageio.plugins.tiff.TIFFTagSet;
0088: import com.sun.media.imageioimpl.common.ImageUtil;
0089: import com.sun.media.imageioimpl.common.PackageUtil;
0090: import com.sun.media.imageioimpl.common.SimpleRenderedImage;
0091: import com.sun.media.imageioimpl.common.SingleTileRenderedImage;
0092:
0093: public class TIFFImageWriter extends ImageWriter {
0094:
0095: private static final boolean DEBUG = false; // XXX false for release!
0096:
0097: static final String EXIF_JPEG_COMPRESSION_TYPE = "EXIF JPEG";
0098:
0099: public static final int DEFAULT_BYTES_PER_STRIP = 8192;
0100:
0101: /**
0102: * Supported TIFF compression types.
0103: */
0104: public static final String[] TIFFCompressionTypes = { "CCITT RLE",
0105: "CCITT T.4", "CCITT T.6", "LZW",
0106: // "Old JPEG",
0107: "JPEG", "ZLib", "PackBits", "Deflate",
0108: EXIF_JPEG_COMPRESSION_TYPE };
0109:
0110: //
0111: // !!! The lengths of the arrays 'compressionTypes',
0112: // !!! 'isCompressionLossless', and 'compressionNumbers'
0113: // !!! must be equal.
0114: //
0115:
0116: /**
0117: * Known TIFF compression types.
0118: */
0119: public static final String[] compressionTypes = { "CCITT RLE",
0120: "CCITT T.4", "CCITT T.6", "LZW", "Old JPEG", "JPEG",
0121: "ZLib", "PackBits", "Deflate", EXIF_JPEG_COMPRESSION_TYPE };
0122:
0123: /**
0124: * Lossless flag for known compression types.
0125: */
0126: public static final boolean[] isCompressionLossless = { true, // RLE
0127: true, // T.4
0128: true, // T.6
0129: true, // LZW
0130: false, // Old JPEG
0131: false, // JPEG
0132: true, // ZLib
0133: true, // PackBits
0134: true, // DEFLATE
0135: false // EXIF JPEG
0136: };
0137:
0138: /**
0139: * Compression tag values for known compression types.
0140: */
0141: public static final int[] compressionNumbers = {
0142: BaselineTIFFTagSet.COMPRESSION_CCITT_RLE,
0143: BaselineTIFFTagSet.COMPRESSION_CCITT_T_4,
0144: BaselineTIFFTagSet.COMPRESSION_CCITT_T_6,
0145: BaselineTIFFTagSet.COMPRESSION_LZW,
0146: BaselineTIFFTagSet.COMPRESSION_OLD_JPEG,
0147: BaselineTIFFTagSet.COMPRESSION_JPEG,
0148: BaselineTIFFTagSet.COMPRESSION_ZLIB,
0149: BaselineTIFFTagSet.COMPRESSION_PACKBITS,
0150: BaselineTIFFTagSet.COMPRESSION_DEFLATE,
0151: BaselineTIFFTagSet.COMPRESSION_OLD_JPEG, // EXIF JPEG
0152: };
0153:
0154: ImageOutputStream stream;
0155: long headerPosition;
0156: RenderedImage image;
0157: ImageTypeSpecifier imageType;
0158: ByteOrder byteOrder;
0159: ImageWriteParam param;
0160: TIFFCompressor compressor;
0161: TIFFColorConverter colorConverter;
0162:
0163: TIFFStreamMetadata streamMetadata;
0164: TIFFImageMetadata imageMetadata;
0165:
0166: int sourceXOffset;
0167: int sourceYOffset;
0168: int sourceWidth;
0169: int sourceHeight;
0170: int[] sourceBands;
0171: int periodX;
0172: int periodY;
0173:
0174: int bitDepth; // bits per channel
0175: int numBands;
0176: int tileWidth;
0177: int tileLength;
0178: int tilesAcross;
0179: int tilesDown;
0180:
0181: int[] sampleSize = null; // Input sample size per band, in bits
0182: int scalingBitDepth = -1; // Output bit depth of the scaling tables
0183: boolean isRescaling = false; // Whether rescaling is needed.
0184:
0185: boolean isBilevel; // Whether image is bilevel
0186: boolean isImageSimple; // Whether image can be copied into directly
0187: boolean isInverted; // Whether photometric inversion is required
0188:
0189: boolean isTiled; // Whether the image is tiled (true) or stipped (false).
0190:
0191: int nativePhotometricInterpretation;
0192: int photometricInterpretation;
0193:
0194: char[] bitsPerSample; // Output sample size per band
0195: int sampleFormat = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED; // Output sample format
0196:
0197: // Tables for 1, 2, 4, or 8 bit output
0198: byte[][] scale = null; // 8 bit table
0199: byte[] scale0 = null; // equivalent to scale[0]
0200:
0201: // Tables for 16 bit output
0202: byte[][] scaleh = null; // High bytes of output
0203: byte[][] scalel = null; // Low bytes of output
0204:
0205: int compression;
0206: int predictor;
0207:
0208: int totalPixels;
0209: int pixelsDone;
0210:
0211: long nextIFDPointerPos;
0212:
0213: // Next available space.
0214: long nextSpace = 0L;
0215:
0216: // Whether a sequence is being written.
0217: boolean isWritingSequence = false;
0218:
0219: /**
0220: * Converts a pixel's X coordinate into a horizontal tile index
0221: * relative to a given tile grid layout specified by its X offset
0222: * and tile width.
0223: *
0224: * <p> If <code>tileWidth < 0</code>, the results of this method
0225: * are undefined. If <code>tileWidth == 0</code>, an
0226: * <code>ArithmeticException</code> will be thrown.
0227: *
0228: * @throws ArithmeticException If <code>tileWidth == 0</code>.
0229: */
0230: public static int XToTileX(int x, int tileGridXOffset, int tileWidth) {
0231: x -= tileGridXOffset;
0232: if (x < 0) {
0233: x += 1 - tileWidth; // force round to -infinity (ceiling)
0234: }
0235: return x / tileWidth;
0236: }
0237:
0238: /**
0239: * Converts a pixel's Y coordinate into a vertical tile index
0240: * relative to a given tile grid layout specified by its Y offset
0241: * and tile height.
0242: *
0243: * <p> If <code>tileHeight < 0</code>, the results of this method
0244: * are undefined. If <code>tileHeight == 0</code>, an
0245: * <code>ArithmeticException</code> will be thrown.
0246: *
0247: * @throws ArithmeticException If <code>tileHeight == 0</code>.
0248: */
0249: public static int YToTileY(int y, int tileGridYOffset,
0250: int tileHeight) {
0251: y -= tileGridYOffset;
0252: if (y < 0) {
0253: y += 1 - tileHeight; // force round to -infinity (ceiling)
0254: }
0255: return y / tileHeight;
0256: }
0257:
0258: public TIFFImageWriter(ImageWriterSpi originatingProvider) {
0259: super (originatingProvider);
0260: }
0261:
0262: public ImageWriteParam getDefaultWriteParam() {
0263: return new TIFFImageWriteParam(getLocale());
0264: }
0265:
0266: public void setOutput(Object output) {
0267: super .setOutput(output);
0268:
0269: if (output != null) {
0270: if (!(output instanceof ImageOutputStream)) {
0271: throw new IllegalArgumentException(
0272: "output not an ImageOutputStream!");
0273: }
0274: this .stream = (ImageOutputStream) output;
0275:
0276: //
0277: // The output is expected to be positioned at a TIFF header
0278: // or at some arbitrary location which may or may not be
0279: // the EOF. In the former case the writer should be able
0280: // either to overwrite the existing sequence or append to it.
0281: //
0282:
0283: // Set the position of the header and the next available space.
0284: try {
0285: headerPosition = this .stream.getStreamPosition();
0286: try {
0287: // Read byte order and magic number.
0288: byte[] b = new byte[4];
0289: stream.readFully(b);
0290:
0291: // Check bytes for TIFF header.
0292: if ((b[0] == (byte) 0x49 && b[1] == (byte) 0x49
0293: && b[2] == (byte) 0x2a && b[3] == (byte) 0x00)
0294: || (b[0] == (byte) 0x4d
0295: && b[1] == (byte) 0x4d
0296: && b[2] == (byte) 0x00 && b[3] == (byte) 0x2a)) {
0297: // TIFF header.
0298: this .nextSpace = stream.length();
0299: } else {
0300: // Neither TIFF header nor EOF: overwrite.
0301: this .nextSpace = headerPosition;
0302: }
0303: } catch (IOException io) { // thrown by readFully()
0304: // At EOF or not at a TIFF header.
0305: this .nextSpace = headerPosition;
0306: }
0307: stream.seek(headerPosition);
0308: } catch (IOException ioe) { // thrown by getStreamPosition()
0309: // Assume it's at zero.
0310: this .nextSpace = headerPosition = 0L;
0311: }
0312: } else {
0313: this .stream = null;
0314: }
0315: }
0316:
0317: public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
0318: return new TIFFStreamMetadata();
0319: }
0320:
0321: public IIOMetadata getDefaultImageMetadata(
0322: ImageTypeSpecifier imageType, ImageWriteParam param) {
0323:
0324: List tagSets = new ArrayList(1);
0325: tagSets.add(BaselineTIFFTagSet.getInstance());
0326: // XXX Should add Fax/EXIF/GeoTIFF TagSets?
0327: TIFFImageMetadata imageMetadata = new TIFFImageMetadata(tagSets);
0328:
0329: if (imageType != null) {
0330: TIFFImageMetadata im = (TIFFImageMetadata) convertImageMetadata(
0331: imageMetadata, imageType, param);
0332: if (im != null) {
0333: imageMetadata = im;
0334: }
0335: }
0336:
0337: return imageMetadata;
0338: }
0339:
0340: public IIOMetadata convertStreamMetadata(IIOMetadata inData,
0341: ImageWriteParam param) {
0342: // Check arguments.
0343: if (inData == null) {
0344: throw new IllegalArgumentException("inData == null!");
0345: }
0346:
0347: // Note: param is irrelevant as it does not contain byte order.
0348:
0349: TIFFStreamMetadata outData = null;
0350: if (inData instanceof TIFFStreamMetadata) {
0351: outData = new TIFFStreamMetadata();
0352: outData.byteOrder = ((TIFFStreamMetadata) inData).byteOrder;
0353: return outData;
0354: } else if (Arrays.asList(inData.getMetadataFormatNames())
0355: .contains(TIFFStreamMetadata.nativeMetadataFormatName)) {
0356: outData = new TIFFStreamMetadata();
0357: String format = TIFFStreamMetadata.nativeMetadataFormatName;
0358: try {
0359: outData.mergeTree(format, inData.getAsTree(format));
0360: } catch (IIOInvalidTreeException e) {
0361: // XXX Warning
0362: }
0363: }
0364:
0365: return outData;
0366: }
0367:
0368: public IIOMetadata convertImageMetadata(IIOMetadata inData,
0369: ImageTypeSpecifier imageType, ImageWriteParam param) {
0370: // Check arguments.
0371: if (inData == null) {
0372: throw new IllegalArgumentException("inData == null!");
0373: }
0374: if (imageType == null) {
0375: throw new IllegalArgumentException("imageType == null!");
0376: }
0377:
0378: TIFFImageMetadata outData = null;
0379:
0380: // Obtain a TIFFImageMetadata object.
0381: if (inData instanceof TIFFImageMetadata) {
0382: // Create a new metadata object from a clone of the input IFD.
0383: TIFFIFD inIFD = ((TIFFImageMetadata) inData).getRootIFD();
0384: outData = new TIFFImageMetadata(inIFD.getShallowClone());
0385: } else if (Arrays.asList(inData.getMetadataFormatNames())
0386: .contains(TIFFImageMetadata.nativeMetadataFormatName)) {
0387: // Initialize from the native metadata form of the input tree.
0388: try {
0389: outData = convertNativeImageMetadata(inData);
0390: } catch (IIOInvalidTreeException e) {
0391: // XXX Warning
0392: }
0393: } else if (inData.isStandardMetadataFormatSupported()) {
0394: // Initialize from the standard metadata form of the input tree.
0395: try {
0396: outData = convertStandardImageMetadata(inData);
0397: } catch (IIOInvalidTreeException e) {
0398: // XXX Warning
0399: }
0400: }
0401:
0402: // Update the metadata per the image type and param.
0403: if (outData != null) {
0404: TIFFImageWriter bogusWriter = new TIFFImageWriter(
0405: this .originatingProvider);
0406: bogusWriter.imageMetadata = outData;
0407: bogusWriter.param = param;
0408: SampleModel sm = imageType.getSampleModel();
0409: try {
0410: bogusWriter.setupMetadata(imageType.getColorModel(),
0411: sm, sm.getWidth(), sm.getHeight());
0412: return bogusWriter.imageMetadata;
0413: } catch (IIOException e) {
0414: // XXX Warning
0415: return null;
0416: }
0417: }
0418:
0419: return outData;
0420: }
0421:
0422: /**
0423: * Converts a standard <code>javax_imageio_1.0</code> tree to a
0424: * <code>TIFFImageMetadata</code> object.
0425: *
0426: * @param inData The metadata object.
0427: * @return a <code>TIFFImageMetadata</code> or <code>null</code> if
0428: * the standard tree derived from the input object is <code>null</code>.
0429: * @throws IllegalArgumentException if <code>inData</code> is
0430: * <code>null</code> or does not support the standard metadata format.
0431: * @throws IIOInvalidTreeException if <code>inData</code> generates an
0432: * invalid standard metadata tree.
0433: */
0434: private TIFFImageMetadata convertStandardImageMetadata(
0435: IIOMetadata inData) throws IIOInvalidTreeException {
0436:
0437: if (inData == null) {
0438: throw new IllegalArgumentException("inData == null!");
0439: } else if (!inData.isStandardMetadataFormatSupported()) {
0440: throw new IllegalArgumentException(
0441: "inData does not support standard metadata format!");
0442: }
0443:
0444: TIFFImageMetadata outData = null;
0445:
0446: String formatName = IIOMetadataFormatImpl.standardMetadataFormatName;
0447: Node tree = inData.getAsTree(formatName);
0448: if (tree != null) {
0449: List tagSets = new ArrayList(1);
0450: tagSets.add(BaselineTIFFTagSet.getInstance());
0451: outData = new TIFFImageMetadata(tagSets);
0452: outData.setFromTree(formatName, tree);
0453: }
0454:
0455: return outData;
0456: }
0457:
0458: /**
0459: * Converts a native
0460: * <code>com_sun_media_imageio_plugins_tiff_image_1.0</code> tree to a
0461: * <code>TIFFImageMetadata</code> object.
0462: *
0463: * @param inData The metadata object.
0464: * @return a <code>TIFFImageMetadata</code> or <code>null</code> if
0465: * the native tree derived from the input object is <code>null</code>.
0466: * @throws IllegalArgumentException if <code>inData</code> is
0467: * <code>null</code> or does not support the native metadata format.
0468: * @throws IIOInvalidTreeException if <code>inData</code> generates an
0469: * invalid native metadata tree.
0470: */
0471: private TIFFImageMetadata convertNativeImageMetadata(
0472: IIOMetadata inData) throws IIOInvalidTreeException {
0473:
0474: if (inData == null) {
0475: throw new IllegalArgumentException("inData == null!");
0476: } else if (!Arrays.asList(inData.getMetadataFormatNames())
0477: .contains(TIFFImageMetadata.nativeMetadataFormatName)) {
0478: throw new IllegalArgumentException(
0479: "inData does not support native metadata format!");
0480: }
0481:
0482: TIFFImageMetadata outData = null;
0483:
0484: String formatName = TIFFImageMetadata.nativeMetadataFormatName;
0485: Node tree = inData.getAsTree(formatName);
0486: if (tree != null) {
0487: List tagSets = new ArrayList(1);
0488: tagSets.add(BaselineTIFFTagSet.getInstance());
0489: outData = new TIFFImageMetadata(tagSets);
0490: outData.setFromTree(formatName, tree);
0491: }
0492:
0493: return outData;
0494: }
0495:
0496: /**
0497: * Sets up the output metadata adding, removing, and overriding fields
0498: * as needed. The destination image dimensions are provided as parameters
0499: * because these might differ from those of the source due to subsampling.
0500: *
0501: * @param cm The <code>ColorModel</code> of the image being written.
0502: * @param sm The <code>SampleModel</code> of the image being written.
0503: * @param destWidth The width of the written image after subsampling.
0504: * @param destHeight The height of the written image after subsampling.
0505: */
0506: void setupMetadata(ColorModel cm, SampleModel sm, int destWidth,
0507: int destHeight) throws IIOException {
0508: // Get initial IFD from metadata
0509:
0510: // Always emit these fields:
0511: //
0512: // Override values from metadata:
0513: //
0514: // planarConfiguration -> chunky (planar not supported on output)
0515: //
0516: // Override values from metadata with image-derived values:
0517: //
0518: // bitsPerSample (if not bilivel)
0519: // colorMap (if palette color)
0520: // photometricInterpretation (derive from image)
0521: // imageLength
0522: // imageWidth
0523: //
0524: // rowsPerStrip \ / tileLength
0525: // stripOffsets | OR | tileOffsets
0526: // stripByteCounts / | tileByteCounts
0527: // \ tileWidth
0528: //
0529: //
0530: // Override values from metadata with write param values:
0531: //
0532: // compression
0533:
0534: // Use values from metadata if present for these fields,
0535: // otherwise use defaults:
0536: //
0537: // resolutionUnit
0538: // XResolution (take from metadata if present)
0539: // YResolution
0540: // rowsPerStrip
0541: // sampleFormat
0542:
0543: TIFFIFD rootIFD = imageMetadata.getRootIFD();
0544:
0545: BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance();
0546:
0547: // If PlanarConfiguration field present, set value to chunky.
0548:
0549: TIFFField f = rootIFD
0550: .getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
0551: if (f != null
0552: && f.getAsInt(0) != BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY) {
0553: // XXX processWarningOccurred()
0554: TIFFField planarConfigurationField = new TIFFField(
0555: base
0556: .getTag(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION),
0557: BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY);
0558: rootIFD.addTIFFField(planarConfigurationField);
0559: }
0560:
0561: char[] extraSamples = null;
0562:
0563: this .photometricInterpretation = -1;
0564: boolean forcePhotometricInterpretation = false;
0565:
0566: f = rootIFD
0567: .getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
0568: if (f != null) {
0569: photometricInterpretation = f.getAsInt(0);
0570: if (photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR
0571: && !(cm instanceof IndexColorModel)) {
0572: photometricInterpretation = -1;
0573: } else {
0574: forcePhotometricInterpretation = true;
0575: }
0576: }
0577:
0578: // f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES);
0579: // if (f != null) {
0580: // extraSamples = f.getAsChars();
0581: // }
0582:
0583: // f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
0584: // if (f != null) {
0585: // bitsPerSample = f.getAsChars();
0586: // }
0587:
0588: int[] sampleSize = sm.getSampleSize();
0589:
0590: int numBands = sm.getNumBands();
0591: int numExtraSamples = 0;
0592:
0593: // Check that numBands > 1 here because TIFF requires that
0594: // SamplesPerPixel = numBands + numExtraSamples and numBands
0595: // cannot be zero.
0596: if (numBands > 1 && cm != null && cm.hasAlpha()) {
0597: --numBands;
0598: numExtraSamples = 1;
0599: extraSamples = new char[1];
0600: if (cm.isAlphaPremultiplied()) {
0601: extraSamples[0] = BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA;
0602: } else {
0603: extraSamples[0] = BaselineTIFFTagSet.EXTRA_SAMPLES_UNASSOCIATED_ALPHA;
0604: }
0605: }
0606:
0607: if (numBands == 3) {
0608: this .nativePhotometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
0609: if (photometricInterpretation == -1) {
0610: photometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB;
0611: }
0612: } else if (sm.getNumBands() == 1
0613: && cm instanceof IndexColorModel) {
0614: IndexColorModel icm = (IndexColorModel) cm;
0615: int r0 = icm.getRed(0);
0616: int r1 = icm.getRed(1);
0617: if (icm.getMapSize() == 2 && (r0 == icm.getGreen(0))
0618: && (r0 == icm.getBlue(0))
0619: && (r1 == icm.getGreen(1))
0620: && (r1 == icm.getBlue(1)) && (r0 == 0 || r0 == 255)
0621: && (r1 == 0 || r1 == 255) && (r0 != r1)) {
0622: // Black/white image
0623:
0624: if (r0 == 0) {
0625: nativePhotometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
0626: } else {
0627: nativePhotometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
0628: }
0629:
0630: // If photometricInterpretation is already set to
0631: // WhiteIsZero or BlackIsZero, leave it alone
0632: if (photometricInterpretation != BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO
0633: && photometricInterpretation != BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) {
0634: photometricInterpretation = r0 == 0 ? BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO
0635: : BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
0636: }
0637: } else {
0638: nativePhotometricInterpretation = photometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR;
0639: }
0640: } else {
0641: if (cm != null) {
0642: switch (cm.getColorSpace().getType()) {
0643: case ColorSpace.TYPE_Lab:
0644: nativePhotometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB;
0645: break;
0646: case ColorSpace.TYPE_YCbCr:
0647: nativePhotometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR;
0648: break;
0649: case ColorSpace.TYPE_CMYK:
0650: nativePhotometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK;
0651: break;
0652: default:
0653: nativePhotometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
0654: }
0655: } else {
0656: nativePhotometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
0657: }
0658: if (photometricInterpretation == -1) {
0659: photometricInterpretation = nativePhotometricInterpretation;
0660: }
0661: }
0662:
0663: // Set the compressor and color converter.
0664:
0665: this .compressor = null;
0666: this .colorConverter = null;
0667: if (param instanceof TIFFImageWriteParam) {
0668: TIFFImageWriteParam tparam = (TIFFImageWriteParam) param;
0669: if (tparam.getCompressionMode() == tparam.MODE_EXPLICIT) {
0670: compressor = tparam.getTIFFCompressor();
0671: String compressionType = param.getCompressionType();
0672: if (compressor != null
0673: && !compressor.getCompressionType().equals(
0674: compressionType)) {
0675: // Unset the TIFFCompressor if its compression type is
0676: // not the one selected.
0677: compressor = null;
0678: }
0679: } else {
0680: // Compression mode not MODE_EXPLICIT.
0681: compressor = null;
0682: }
0683: colorConverter = tparam.getColorConverter();
0684: if (colorConverter != null) {
0685: photometricInterpretation = tparam
0686: .getPhotometricInterpretation();
0687: }
0688: }
0689:
0690: // Emit compression tag
0691:
0692: int compressionMode = param instanceof TIFFImageWriteParam ? param
0693: .getCompressionMode()
0694: : ImageWriteParam.MODE_DEFAULT;
0695: switch (compressionMode) {
0696: case ImageWriteParam.MODE_EXPLICIT: {
0697: String compressionType = param.getCompressionType();
0698: if (compressionType == null) {
0699: this .compression = BaselineTIFFTagSet.COMPRESSION_NONE;
0700: } else {
0701: // Determine corresponding compression tag value.
0702: int len = compressionTypes.length;
0703: for (int i = 0; i < len; i++) {
0704: if (compressionType.equals(compressionTypes[i])) {
0705: this .compression = compressionNumbers[i];
0706: }
0707: }
0708: }
0709:
0710: // Ensure the compressor, if any, matches compression setting
0711: // with the precedence described in TIFFImageWriteParam.
0712: if (compressor != null
0713: && compressor.getCompressionTagValue() != this .compression) {
0714: // Does not match: unset the compressor.
0715: compressor = null;
0716: }
0717: }
0718: break;
0719: case ImageWriteParam.MODE_COPY_FROM_METADATA: {
0720: TIFFField compField = rootIFD
0721: .getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
0722: if (compField != null) {
0723: this .compression = compField.getAsInt(0);
0724: break;
0725: }
0726: }
0727: case ImageWriteParam.MODE_DEFAULT:
0728: case ImageWriteParam.MODE_DISABLED:
0729: default:
0730: this .compression = BaselineTIFFTagSet.COMPRESSION_NONE;
0731: }
0732:
0733: TIFFField predictorField = rootIFD
0734: .getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR);
0735: if (predictorField != null) {
0736: this .predictor = predictorField.getAsInt(0);
0737:
0738: // We only support Horizontal Predictor for a bitDepth of 8
0739: if (sampleSize[0] != 8 ||
0740: // Check the value of the tag for validity
0741: (predictor != BaselineTIFFTagSet.PREDICTOR_NONE && predictor != BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING)) {
0742: // XXX processWarningOccured ???
0743: // Set to default
0744: predictor = BaselineTIFFTagSet.PREDICTOR_NONE;
0745:
0746: // Emit this changed predictor value to metadata
0747: TIFFField newPredictorField = new TIFFField(base
0748: .getTag(BaselineTIFFTagSet.TAG_PREDICTOR),
0749: predictor);
0750: rootIFD.addTIFFField(newPredictorField);
0751: }
0752:
0753: // XXX Do we need to ensure that predictor is not passed on if
0754: // the compression is not either Deflate or LZW?
0755: }
0756:
0757: TIFFField compressionField = new TIFFField(base
0758: .getTag(BaselineTIFFTagSet.TAG_COMPRESSION),
0759: compression);
0760: rootIFD.addTIFFField(compressionField);
0761:
0762: // Set EXIF flag. Note that there is no way to determine definitively
0763: // when an uncompressed thumbnail is being written as the EXIF IFD
0764: // pointer field is optional for thumbnails.
0765: boolean isEXIF = false;
0766: if (numBands == 3 && sampleSize[0] == 8 && sampleSize[1] == 8
0767: && sampleSize[2] == 8) {
0768: // Three bands with 8 bits per sample.
0769: if (rootIFD
0770: .getTIFFField(EXIFParentTIFFTagSet.TAG_EXIF_IFD_POINTER) != null) {
0771: // EXIF IFD pointer present.
0772: if (compression == BaselineTIFFTagSet.COMPRESSION_NONE
0773: && (photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB || photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR)) {
0774: // Uncompressed RGB or YCbCr.
0775: isEXIF = true;
0776: } else if (compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
0777: // Compressed.
0778: isEXIF = true;
0779: }
0780: } else if (compressionMode == ImageWriteParam.MODE_EXPLICIT
0781: && EXIF_JPEG_COMPRESSION_TYPE.equals(param
0782: .getCompressionType())) {
0783: // EXIF IFD pointer absent but EXIF JPEG compression set.
0784: isEXIF = true;
0785: }
0786: }
0787:
0788: // Initialize JPEG interchange format flag which is used to
0789: // indicate that the image is stored as a single JPEG stream.
0790: // This flag is separated from the 'isEXIF' flag in case JPEG
0791: // interchange format is eventually supported for non-EXIF images.
0792: boolean isJPEGInterchange = isEXIF
0793: && compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG;
0794:
0795: if (compressor == null) {
0796: if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) {
0797: if (PackageUtil.isCodecLibAvailable()) {
0798: try {
0799: compressor = new TIFFCodecLibRLECompressor();
0800: if (DEBUG) {
0801: System.out
0802: .println("Using codecLib RLE compressor");
0803: }
0804: } catch (RuntimeException e) {
0805: if (DEBUG) {
0806: System.out.println(e);
0807: }
0808: }
0809: }
0810:
0811: if (compressor == null) {
0812: compressor = new TIFFRLECompressor();
0813: if (DEBUG) {
0814: System.out.println("Using Java RLE compressor");
0815: }
0816: }
0817:
0818: if (!forcePhotometricInterpretation) {
0819: photometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
0820: }
0821: } else if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) {
0822: if (PackageUtil.isCodecLibAvailable()) {
0823: try {
0824: compressor = new TIFFCodecLibT4Compressor();
0825: if (DEBUG) {
0826: System.out
0827: .println("Using codecLib T.4 compressor");
0828: }
0829: } catch (RuntimeException e) {
0830: if (DEBUG) {
0831: System.out.println(e);
0832: }
0833: }
0834: }
0835:
0836: if (compressor == null) {
0837: compressor = new TIFFT4Compressor();
0838: if (DEBUG) {
0839: System.out.println("Using Java T.4 compressor");
0840: }
0841: }
0842:
0843: if (!forcePhotometricInterpretation) {
0844: photometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
0845: }
0846: } else if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) {
0847: if (PackageUtil.isCodecLibAvailable()) {
0848: try {
0849: compressor = new TIFFCodecLibT6Compressor();
0850: if (DEBUG) {
0851: System.out
0852: .println("Using codecLib T.6 compressor");
0853: }
0854: } catch (RuntimeException e) {
0855: if (DEBUG) {
0856: System.out.println(e);
0857: }
0858: }
0859: }
0860:
0861: if (compressor == null) {
0862: compressor = new TIFFT6Compressor();
0863: if (DEBUG) {
0864: System.out.println("Using Java T.6 compressor");
0865: }
0866: }
0867:
0868: if (!forcePhotometricInterpretation) {
0869: photometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;
0870: }
0871: } else if (compression == BaselineTIFFTagSet.COMPRESSION_LZW) {
0872: compressor = new TIFFLZWCompressor(predictor);
0873: } else if (compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
0874: if (isEXIF) {
0875: compressor = new TIFFEXIFJPEGCompressor(param);
0876: } else {
0877: throw new IIOException(
0878: "Old JPEG compression not supported!");
0879: }
0880: } else if (compression == BaselineTIFFTagSet.COMPRESSION_JPEG) {
0881: if (numBands == 3 && sampleSize[0] == 8
0882: && sampleSize[1] == 8 && sampleSize[2] == 8) {
0883: photometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR;
0884: } else if (numBands == 1 && sampleSize[0] == 8) {
0885: photometricInterpretation = BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO;
0886: } else {
0887: throw new IIOException(
0888: "JPEG compression supported for 1- and 3-band byte images only!");
0889: }
0890: compressor = new TIFFJPEGCompressor(param);
0891: } else if (compression == BaselineTIFFTagSet.COMPRESSION_ZLIB) {
0892: compressor = new TIFFZLibCompressor(param, predictor);
0893: } else if (compression == BaselineTIFFTagSet.COMPRESSION_PACKBITS) {
0894: compressor = new TIFFPackBitsCompressor();
0895: } else if (compression == BaselineTIFFTagSet.COMPRESSION_DEFLATE) {
0896: compressor = new TIFFDeflateCompressor(param, predictor);
0897: } else {
0898: // Determine inverse fill setting.
0899: f = rootIFD
0900: .getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER);
0901: boolean inverseFill = (f != null && f.getAsInt(0) == 2);
0902:
0903: if (inverseFill) {
0904: compressor = new TIFFLSBCompressor();
0905: } else {
0906: compressor = new TIFFNullCompressor();
0907: }
0908: } // compression == ?
0909: } // compressor == null
0910:
0911: if (DEBUG) {
0912: if (param != null
0913: && param.getCompressionMode() == param.MODE_EXPLICIT) {
0914: System.out.println("compressionType = "
0915: + param.getCompressionType());
0916: }
0917: if (compressor != null) {
0918: System.out.println("compressor = "
0919: + compressor.getClass().getName());
0920: }
0921: }
0922:
0923: if (colorConverter == null) {
0924: if (cm != null
0925: && cm.getColorSpace().getType() == ColorSpace.TYPE_RGB) {
0926: //
0927: // Perform color conversion only if image has RGB color space.
0928: //
0929: if (photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR
0930: && compression != BaselineTIFFTagSet.COMPRESSION_JPEG) {
0931: //
0932: // Convert RGB to YCbCr only if compression type is not
0933: // JPEG in which case this is handled implicitly by the
0934: // compressor.
0935: //
0936: colorConverter = new TIFFYCbCrColorConverter(
0937: imageMetadata);
0938: } else if (photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB) {
0939: colorConverter = new TIFFCIELabColorConverter();
0940: }
0941: }
0942: }
0943:
0944: //
0945: // Cannot at this time do YCbCr subsampling so set the
0946: // YCbCrSubsampling field value to [1, 1] and the YCbCrPositioning
0947: // field value to "cosited".
0948: //
0949: if (photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR
0950: && compression != BaselineTIFFTagSet.COMPRESSION_JPEG) {
0951: // Remove old subsampling and positioning fields.
0952: rootIFD
0953: .removeTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
0954: rootIFD
0955: .removeTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING);
0956:
0957: // Add unity chrominance subsampling factors.
0958: rootIFD
0959: .addTIFFField(new TIFFField(
0960: base
0961: .getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING),
0962: TIFFTag.TIFF_SHORT, 2, new char[] {
0963: (char) 1, (char) 1 }));
0964:
0965: // Add cosited positioning.
0966: rootIFD
0967: .addTIFFField(new TIFFField(
0968: base
0969: .getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING),
0970: TIFFTag.TIFF_SHORT,
0971: 1,
0972: new char[] { (char) BaselineTIFFTagSet.Y_CB_CR_POSITIONING_COSITED }));
0973: }
0974:
0975: TIFFField photometricInterpretationField = new TIFFField(
0976: base
0977: .getTag(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION),
0978: photometricInterpretation);
0979: rootIFD.addTIFFField(photometricInterpretationField);
0980:
0981: this .bitsPerSample = new char[numBands + numExtraSamples];
0982: this .bitDepth = 0;
0983: for (int i = 0; i < numBands; i++) {
0984: this .bitDepth = Math.max(bitDepth, sampleSize[i]);
0985: }
0986: if (bitDepth == 3) {
0987: bitDepth = 4;
0988: } else if (bitDepth > 4 && bitDepth < 8) {
0989: bitDepth = 8;
0990: } else if (bitDepth > 8 && bitDepth < 16) {
0991: bitDepth = 16;
0992: } else if (bitDepth > 16) {
0993: bitDepth = 32;
0994: }
0995:
0996: for (int i = 0; i < bitsPerSample.length; i++) {
0997: bitsPerSample[i] = (char) bitDepth;
0998: }
0999:
1000: // Emit BitsPerSample. If the image is bilevel, emit if and only
1001: // if already in the metadata and correct (count and value == 1).
1002: if (bitsPerSample.length != 1 || bitsPerSample[0] != 1) {
1003: TIFFField bitsPerSampleField = new TIFFField(base
1004: .getTag(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE),
1005: TIFFTag.TIFF_SHORT, bitsPerSample.length,
1006: bitsPerSample);
1007: rootIFD.addTIFFField(bitsPerSampleField);
1008: } else { // bitsPerSample.length == 1 && bitsPerSample[0] == 1
1009: TIFFField bitsPerSampleField = rootIFD
1010: .getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
1011: if (bitsPerSampleField != null) {
1012: int[] bps = bitsPerSampleField.getAsInts();
1013: if (bps == null || bps.length != 1 || bps[0] != 1) {
1014: rootIFD
1015: .removeTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
1016: }
1017: }
1018: }
1019:
1020: // Prepare SampleFormat field.
1021: f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
1022: if (f == null && (bitDepth == 16 || bitDepth == 32)) {
1023: // Set up default content for 16- and 32-bit cases.
1024: char sampleFormatValue;
1025: int dataType = sm.getDataType();
1026: if (bitDepth == 16 && dataType == DataBuffer.TYPE_USHORT) {
1027: sampleFormatValue = (char) BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER;
1028: } else if (bitDepth == 32
1029: && dataType == DataBuffer.TYPE_FLOAT) {
1030: sampleFormatValue = (char) BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT;
1031: } else {
1032: sampleFormatValue = BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER;
1033: }
1034: this .sampleFormat = (int) sampleFormatValue;
1035: char[] sampleFormatArray = new char[bitsPerSample.length];
1036: Arrays.fill(sampleFormatArray, sampleFormatValue);
1037:
1038: // Update the metadata.
1039: TIFFTag sampleFormatTag = base
1040: .getTag(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT);
1041:
1042: TIFFField sampleFormatField = new TIFFField(
1043: sampleFormatTag, TIFFTag.TIFF_SHORT,
1044: sampleFormatArray.length, sampleFormatArray);
1045:
1046: rootIFD.addTIFFField(sampleFormatField);
1047: } else if (f != null) {
1048: // Get whatever was provided.
1049: sampleFormat = f.getAsInt(0);
1050: } else {
1051: // Set default value for internal use only.
1052: sampleFormat = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED;
1053: }
1054:
1055: if (extraSamples != null) {
1056: TIFFField extraSamplesField = new TIFFField(base
1057: .getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES),
1058: TIFFTag.TIFF_SHORT, extraSamples.length,
1059: extraSamples);
1060: rootIFD.addTIFFField(extraSamplesField);
1061: } else {
1062: rootIFD
1063: .removeTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES);
1064: }
1065:
1066: TIFFField samplesPerPixelField = new TIFFField(base
1067: .getTag(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL),
1068: bitsPerSample.length);
1069: rootIFD.addTIFFField(samplesPerPixelField);
1070:
1071: // Emit ColorMap if image is of palette color type
1072: if (photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR
1073: && cm instanceof IndexColorModel) {
1074: char[] colorMap = new char[3 * (1 << bitsPerSample[0])];
1075:
1076: IndexColorModel icm = (IndexColorModel) cm;
1077:
1078: // mapSize is determined by BitsPerSample, not by incoming ICM.
1079: int mapSize = 1 << bitsPerSample[0];
1080: int indexBound = Math.min(mapSize, icm.getMapSize());
1081: for (int i = 0; i < indexBound; i++) {
1082: colorMap[i] = (char) ((icm.getRed(i) * 65535) / 255);
1083: colorMap[mapSize + i] = (char) ((icm.getGreen(i) * 65535) / 255);
1084: colorMap[2 * mapSize + i] = (char) ((icm.getBlue(i) * 65535) / 255);
1085: }
1086:
1087: TIFFField colorMapField = new TIFFField(base
1088: .getTag(BaselineTIFFTagSet.TAG_COLOR_MAP),
1089: TIFFTag.TIFF_SHORT, colorMap.length, colorMap);
1090: rootIFD.addTIFFField(colorMapField);
1091: } else {
1092: rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP);
1093: }
1094:
1095: // Emit ICCProfile if there is no ICCProfile field already in the
1096: // metadata and the ColorSpace is non-standard ICC.
1097: if (cm != null
1098: && rootIFD
1099: .getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE) == null
1100: && ImageUtil.isNonStandardICCColorSpace(cm
1101: .getColorSpace())) {
1102: ICC_ColorSpace iccColorSpace = (ICC_ColorSpace) cm
1103: .getColorSpace();
1104: byte[] iccProfileData = iccColorSpace.getProfile()
1105: .getData();
1106: TIFFField iccProfileField = new TIFFField(base
1107: .getTag(BaselineTIFFTagSet.TAG_ICC_PROFILE),
1108: TIFFTag.TIFF_UNDEFINED, iccProfileData.length,
1109: iccProfileData);
1110: rootIFD.addTIFFField(iccProfileField);
1111: }
1112:
1113: // Always emit XResolution and YResolution.
1114:
1115: TIFFField XResolutionField = rootIFD
1116: .getTIFFField(BaselineTIFFTagSet.TAG_X_RESOLUTION);
1117: TIFFField YResolutionField = rootIFD
1118: .getTIFFField(BaselineTIFFTagSet.TAG_Y_RESOLUTION);
1119:
1120: if (XResolutionField == null && YResolutionField == null) {
1121: long[][] resRational = new long[1][2];
1122: resRational[0] = new long[2];
1123:
1124: TIFFField ResolutionUnitField = rootIFD
1125: .getTIFFField(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT);
1126:
1127: // Don't force dimensionless if one of the other dimensional
1128: // quantities is present.
1129: if (ResolutionUnitField == null
1130: && rootIFD
1131: .getTIFFField(BaselineTIFFTagSet.TAG_X_POSITION) == null
1132: && rootIFD
1133: .getTIFFField(BaselineTIFFTagSet.TAG_Y_POSITION) == null) {
1134: // Set resolution to unit and units to dimensionless.
1135: resRational[0][0] = 1;
1136: resRational[0][1] = 1;
1137:
1138: ResolutionUnitField = new TIFFField(
1139: rootIFD
1140: .getTag(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
1141: BaselineTIFFTagSet.RESOLUTION_UNIT_NONE);
1142: rootIFD.addTIFFField(ResolutionUnitField);
1143: } else {
1144: // Set resolution to a value which would make the maximum
1145: // image dimension equal to 4 inches as arbitrarily stated
1146: // in the description of ResolutionUnit in the TIFF 6.0
1147: // specification. If the ResolutionUnit field specifies
1148: // "none" then set the resolution to unity (1/1).
1149: int resolutionUnit = ResolutionUnitField != null ? ResolutionUnitField
1150: .getAsInt(0)
1151: : BaselineTIFFTagSet.RESOLUTION_UNIT_INCH;
1152: int maxDimension = Math.max(destWidth, destHeight);
1153: switch (resolutionUnit) {
1154: case BaselineTIFFTagSet.RESOLUTION_UNIT_INCH:
1155: resRational[0][0] = maxDimension;
1156: resRational[0][1] = 4;
1157: break;
1158: case BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER:
1159: resRational[0][0] = 100L * maxDimension; // divide out 100
1160: resRational[0][1] = 4 * 254; // 2.54 cm/inch * 100
1161: break;
1162: default:
1163: resRational[0][0] = 1;
1164: resRational[0][1] = 1;
1165: }
1166: }
1167:
1168: XResolutionField = new TIFFField(rootIFD
1169: .getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION),
1170: TIFFTag.TIFF_RATIONAL, 1, resRational);
1171: rootIFD.addTIFFField(XResolutionField);
1172:
1173: YResolutionField = new TIFFField(rootIFD
1174: .getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION),
1175: TIFFTag.TIFF_RATIONAL, 1, resRational);
1176: rootIFD.addTIFFField(YResolutionField);
1177: } else if (XResolutionField == null && YResolutionField != null) {
1178: // Set XResolution to YResolution.
1179: long[] yResolution = (long[]) YResolutionField
1180: .getAsRational(0).clone();
1181: XResolutionField = new TIFFField(rootIFD
1182: .getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION),
1183: TIFFTag.TIFF_RATIONAL, 1, yResolution);
1184: rootIFD.addTIFFField(XResolutionField);
1185: } else if (XResolutionField != null && YResolutionField == null) {
1186: // Set YResolution to XResolution.
1187: long[] xResolution = (long[]) XResolutionField
1188: .getAsRational(0).clone();
1189: YResolutionField = new TIFFField(rootIFD
1190: .getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION),
1191: TIFFTag.TIFF_RATIONAL, 1, xResolution);
1192: rootIFD.addTIFFField(YResolutionField);
1193: }
1194:
1195: // Set mandatory fields, overriding metadata passed in
1196:
1197: int width = destWidth;
1198: TIFFField imageWidthField = new TIFFField(base
1199: .getTag(BaselineTIFFTagSet.TAG_IMAGE_WIDTH), width);
1200: rootIFD.addTIFFField(imageWidthField);
1201:
1202: int height = destHeight;
1203: TIFFField imageLengthField = new TIFFField(base
1204: .getTag(BaselineTIFFTagSet.TAG_IMAGE_LENGTH), height);
1205: rootIFD.addTIFFField(imageLengthField);
1206:
1207: // Determine rowsPerStrip
1208:
1209: int rowsPerStrip;
1210:
1211: TIFFField rowsPerStripField = rootIFD
1212: .getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
1213: if (rowsPerStripField != null) {
1214: rowsPerStrip = rowsPerStripField.getAsInt(0);
1215: if (rowsPerStrip < 0) {
1216: rowsPerStrip = height;
1217: }
1218: } else {
1219: int bitsPerPixel = bitDepth * (numBands + numExtraSamples);
1220: int bytesPerRow = (bitsPerPixel * width + 7) / 8;
1221: rowsPerStrip = Math.max(Math.max(DEFAULT_BYTES_PER_STRIP
1222: / bytesPerRow, 1), 8);
1223: }
1224: rowsPerStrip = Math.min(rowsPerStrip, height);
1225:
1226: // Tiling flag.
1227: boolean useTiling = false;
1228:
1229: // Analyze tiling parameters
1230: int tilingMode = param instanceof TIFFImageWriteParam ? param
1231: .getTilingMode() : ImageWriteParam.MODE_DEFAULT;
1232: if (tilingMode == ImageWriteParam.MODE_DISABLED
1233: || tilingMode == ImageWriteParam.MODE_DEFAULT) {
1234: this .tileWidth = width;
1235: this .tileLength = rowsPerStrip;
1236: useTiling = false;
1237: } else if (tilingMode == ImageWriteParam.MODE_EXPLICIT) {
1238: tileWidth = param.getTileWidth();
1239: tileLength = param.getTileHeight();
1240: useTiling = true;
1241: } else if (tilingMode == ImageWriteParam.MODE_COPY_FROM_METADATA) {
1242: f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
1243: if (f == null) {
1244: tileWidth = width;
1245: useTiling = false;
1246: } else {
1247: tileWidth = f.getAsInt(0);
1248: useTiling = true;
1249: }
1250:
1251: f = rootIFD
1252: .getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH);
1253: if (f == null) {
1254: tileLength = rowsPerStrip;
1255: } else {
1256: tileLength = f.getAsInt(0);
1257: useTiling = true;
1258: }
1259: } else {
1260: throw new IIOException("Illegal value of tilingMode!");
1261: }
1262:
1263: if (compression == BaselineTIFFTagSet.COMPRESSION_JPEG) {
1264: // Reset tile size per TTN2 spec for JPEG compression.
1265: int subX;
1266: int subY;
1267: if (numBands == 1) {
1268: subX = subY = 1;
1269: } else {
1270: subX = subY = TIFFJPEGCompressor.CHROMA_SUBSAMPLING;
1271: }
1272: if (useTiling) {
1273: int MCUMultipleX = 8 * subX;
1274: int MCUMultipleY = 8 * subY;
1275: tileWidth = Math
1276: .max(
1277: MCUMultipleX
1278: * ((tileWidth + MCUMultipleX / 2) / MCUMultipleX),
1279: MCUMultipleX);
1280: tileLength = Math
1281: .max(
1282: MCUMultipleY
1283: * ((tileLength + MCUMultipleY / 2) / MCUMultipleY),
1284: MCUMultipleY);
1285: } else if (rowsPerStrip < height) {
1286: int MCUMultiple = 8 * Math.max(subX, subY);
1287: rowsPerStrip = tileLength = Math
1288: .max(
1289: MCUMultiple
1290: * ((tileLength + MCUMultiple / 2) / MCUMultiple),
1291: MCUMultiple);
1292: }
1293: } else if (isJPEGInterchange) {
1294: // Force tile size to equal image size.
1295: tileWidth = width;
1296: tileLength = height;
1297: } else if (useTiling) {
1298: // Round tile size to multiple of 16 per TIFF 6.0 specification
1299: // (see pages 67-68 of version 6.0.1 from Adobe).
1300: int tileWidthRemainder = tileWidth % 16;
1301: if (tileWidthRemainder != 0) {
1302: // Round to nearest multiple of 16 not less than 16.
1303: tileWidth = Math.max(16 * ((tileWidth + 8) / 16), 16);
1304: // XXX insert processWarningOccurred(int,String);
1305: }
1306:
1307: int tileLengthRemainder = tileLength % 16;
1308: if (tileLengthRemainder != 0) {
1309: // Round to nearest multiple of 16 not less than 16.
1310: tileLength = Math.max(16 * ((tileLength + 8) / 16), 16);
1311: // XXX insert processWarningOccurred(int,String);
1312: }
1313: }
1314:
1315: this .tilesAcross = (width + tileWidth - 1) / tileWidth;
1316: this .tilesDown = (height + tileLength - 1) / tileLength;
1317:
1318: if (!useTiling) {
1319: this .isTiled = false;
1320:
1321: rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH);
1322: rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH);
1323: rootIFD
1324: .removeTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
1325: rootIFD
1326: .removeTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS);
1327:
1328: rowsPerStripField = new TIFFField(base
1329: .getTag(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP),
1330: rowsPerStrip);
1331: rootIFD.addTIFFField(rowsPerStripField);
1332:
1333: TIFFField stripOffsetsField = new TIFFField(base
1334: .getTag(BaselineTIFFTagSet.TAG_STRIP_OFFSETS),
1335: TIFFTag.TIFF_LONG, tilesDown);
1336: rootIFD.addTIFFField(stripOffsetsField);
1337:
1338: TIFFField stripByteCountsField = new TIFFField(base
1339: .getTag(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS),
1340: TIFFTag.TIFF_LONG, tilesDown);
1341: rootIFD.addTIFFField(stripByteCountsField);
1342: } else {
1343: this .isTiled = true;
1344:
1345: rootIFD
1346: .removeTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
1347: rootIFD
1348: .removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
1349: rootIFD
1350: .removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
1351:
1352: TIFFField tileWidthField = new TIFFField(base
1353: .getTag(BaselineTIFFTagSet.TAG_TILE_WIDTH),
1354: tileWidth);
1355: rootIFD.addTIFFField(tileWidthField);
1356:
1357: TIFFField tileLengthField = new TIFFField(base
1358: .getTag(BaselineTIFFTagSet.TAG_TILE_LENGTH),
1359: tileLength);
1360: rootIFD.addTIFFField(tileLengthField);
1361:
1362: TIFFField tileOffsetsField = new TIFFField(base
1363: .getTag(BaselineTIFFTagSet.TAG_TILE_OFFSETS),
1364: TIFFTag.TIFF_LONG, tilesDown * tilesAcross);
1365: rootIFD.addTIFFField(tileOffsetsField);
1366:
1367: TIFFField tileByteCountsField = new TIFFField(base
1368: .getTag(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS),
1369: TIFFTag.TIFF_LONG, tilesDown * tilesAcross);
1370: rootIFD.addTIFFField(tileByteCountsField);
1371: }
1372:
1373: if (isEXIF) {
1374: //
1375: // Ensure presence of mandatory fields and absence of prohibited
1376: // fields and those that duplicate information in JPEG marker
1377: // segments per tables 14-18 of the EXIF 2.2 specification.
1378: //
1379:
1380: // If an empty image is being written or inserted then infer
1381: // that the primary IFD is being set up.
1382: boolean isPrimaryIFD = isEncodingEmpty();
1383:
1384: // Handle TIFF fields in order of increasing tag number.
1385: if (compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
1386: // ImageWidth
1387: rootIFD
1388: .removeTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
1389:
1390: // ImageLength
1391: rootIFD
1392: .removeTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
1393:
1394: // BitsPerSample
1395: rootIFD
1396: .removeTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
1397: // Compression
1398: if (isPrimaryIFD) {
1399: rootIFD
1400: .removeTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
1401: }
1402:
1403: // PhotometricInterpretation
1404: rootIFD
1405: .removeTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION);
1406:
1407: // StripOffsets
1408: rootIFD
1409: .removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
1410:
1411: // SamplesPerPixel
1412: rootIFD
1413: .removeTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL);
1414:
1415: // RowsPerStrip
1416: rootIFD
1417: .removeTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP);
1418:
1419: // StripByteCounts
1420: rootIFD
1421: .removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
1422: // XResolution and YResolution are handled above for all TIFFs.
1423:
1424: // PlanarConfiguration
1425: rootIFD
1426: .removeTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION);
1427:
1428: // ResolutionUnit
1429: if (rootIFD
1430: .getTIFFField(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT) == null) {
1431: f = new TIFFField(
1432: base
1433: .getTag(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
1434: BaselineTIFFTagSet.RESOLUTION_UNIT_INCH);
1435: rootIFD.addTIFFField(f);
1436: }
1437:
1438: if (isPrimaryIFD) {
1439: // JPEGInterchangeFormat
1440: rootIFD
1441: .removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
1442:
1443: // JPEGInterchangeFormatLength
1444: rootIFD
1445: .removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
1446:
1447: // YCbCrSubsampling
1448: rootIFD
1449: .removeTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
1450:
1451: // YCbCrPositioning
1452: if (rootIFD
1453: .getTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING) == null) {
1454: f = new TIFFField(
1455: base
1456: .getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING),
1457: TIFFTag.TIFF_SHORT,
1458: 1,
1459: new char[] { (char) BaselineTIFFTagSet.Y_CB_CR_POSITIONING_CENTERED });
1460: rootIFD.addTIFFField(f);
1461: }
1462: } else { // Thumbnail IFD
1463: // JPEGInterchangeFormat
1464: f = new TIFFField(
1465: base
1466: .getTag(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT),
1467: TIFFTag.TIFF_LONG, 1);
1468: rootIFD.addTIFFField(f);
1469:
1470: // JPEGInterchangeFormatLength
1471: f = new TIFFField(
1472: base
1473: .getTag(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH),
1474: TIFFTag.TIFF_LONG, 1);
1475: rootIFD.addTIFFField(f);
1476:
1477: // YCbCrSubsampling
1478: rootIFD
1479: .removeTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
1480: }
1481: } else { // Uncompressed
1482: // ImageWidth through PlanarConfiguration are set above.
1483: // XResolution and YResolution are handled above for all TIFFs.
1484:
1485: // ResolutionUnit
1486: if (rootIFD
1487: .getTIFFField(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT) == null) {
1488: f = new TIFFField(
1489: base
1490: .getTag(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT),
1491: BaselineTIFFTagSet.RESOLUTION_UNIT_INCH);
1492: rootIFD.addTIFFField(f);
1493: }
1494:
1495: // JPEGInterchangeFormat
1496: rootIFD
1497: .removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT);
1498:
1499: // JPEGInterchangeFormatLength
1500: rootIFD
1501: .removeTIFFField(BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
1502:
1503: if (photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB) {
1504: // YCbCrCoefficients
1505: rootIFD
1506: .removeTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS);
1507:
1508: // YCbCrSubsampling
1509: rootIFD
1510: .removeTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING);
1511:
1512: // YCbCrPositioning
1513: rootIFD
1514: .removeTIFFField(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING);
1515: }
1516: }
1517:
1518: // Get EXIF tags.
1519: TIFFTagSet exifTags = EXIFTIFFTagSet.getInstance();
1520:
1521: // Retrieve or create the EXIF IFD.
1522: TIFFIFD exifIFD = null;
1523: f = rootIFD
1524: .getTIFFField(EXIFParentTIFFTagSet.TAG_EXIF_IFD_POINTER);
1525: if (f != null) {
1526: // Retrieve the EXIF IFD.
1527: exifIFD = (TIFFIFD) f.getData();
1528: } else if (isPrimaryIFD) {
1529: // Create the EXIF IFD.
1530: List exifTagSets = new ArrayList(1);
1531: exifTagSets.add(exifTags);
1532: exifIFD = new TIFFIFD(exifTagSets);
1533:
1534: // Add it to the root IFD.
1535: TIFFTagSet tagSet = EXIFParentTIFFTagSet.getInstance();
1536: TIFFTag exifIFDTag = tagSet
1537: .getTag(EXIFParentTIFFTagSet.TAG_EXIF_IFD_POINTER);
1538: rootIFD.addTIFFField(new TIFFField(exifIFDTag,
1539: TIFFTag.TIFF_LONG, 1, exifIFD));
1540: }
1541:
1542: if (exifIFD != null) {
1543: // Handle EXIF private fields in order of increasing
1544: // tag number.
1545:
1546: // ExifVersion
1547: if (exifIFD
1548: .getTIFFField(EXIFTIFFTagSet.TAG_EXIF_VERSION) == null) {
1549: f = new TIFFField(exifTags
1550: .getTag(EXIFTIFFTagSet.TAG_EXIF_VERSION),
1551: TIFFTag.TIFF_UNDEFINED, 4,
1552: EXIFTIFFTagSet.EXIF_VERSION_2_2);
1553: exifIFD.addTIFFField(f);
1554: }
1555:
1556: if (compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
1557: // ComponentsConfiguration
1558: if (exifIFD
1559: .getTIFFField(EXIFTIFFTagSet.TAG_COMPONENTS_CONFIGURATION) == null) {
1560: f = new TIFFField(
1561: exifTags
1562: .getTag(EXIFTIFFTagSet.TAG_COMPONENTS_CONFIGURATION),
1563: TIFFTag.TIFF_UNDEFINED,
1564: 4,
1565: new byte[] {
1566: (byte) EXIFTIFFTagSet.COMPONENTS_CONFIGURATION_Y,
1567: (byte) EXIFTIFFTagSet.COMPONENTS_CONFIGURATION_CB,
1568: (byte) EXIFTIFFTagSet.COMPONENTS_CONFIGURATION_CR,
1569: (byte) 0 });
1570: exifIFD.addTIFFField(f);
1571: }
1572: } else {
1573: // ComponentsConfiguration
1574: exifIFD
1575: .removeTIFFField(EXIFTIFFTagSet.TAG_COMPONENTS_CONFIGURATION);
1576:
1577: // CompressedBitsPerPixel
1578: exifIFD
1579: .removeTIFFField(EXIFTIFFTagSet.TAG_COMPRESSED_BITS_PER_PIXEL);
1580: }
1581:
1582: // FlashpixVersion
1583: if (exifIFD
1584: .getTIFFField(EXIFTIFFTagSet.TAG_FLASHPIX_VERSION) == null) {
1585: f = new TIFFField(
1586: exifTags
1587: .getTag(EXIFTIFFTagSet.TAG_FLASHPIX_VERSION),
1588: TIFFTag.TIFF_UNDEFINED, 4, new byte[] {
1589: (byte) '0', (byte) '1', (byte) '0',
1590: (byte) '0' });
1591: exifIFD.addTIFFField(f);
1592: }
1593:
1594: // ColorSpace
1595: if (exifIFD
1596: .getTIFFField(EXIFTIFFTagSet.TAG_COLOR_SPACE) == null) {
1597: f = new TIFFField(
1598: exifTags
1599: .getTag(EXIFTIFFTagSet.TAG_COLOR_SPACE),
1600: TIFFTag.TIFF_SHORT,
1601: 1,
1602: new char[] { (char) EXIFTIFFTagSet.COLOR_SPACE_SRGB });
1603: exifIFD.addTIFFField(f);
1604: }
1605:
1606: if (compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) {
1607: // PixelXDimension
1608: if (exifIFD
1609: .getTIFFField(EXIFTIFFTagSet.TAG_PIXEL_X_DIMENSION) == null) {
1610: f = new TIFFField(
1611: exifTags
1612: .getTag(EXIFTIFFTagSet.TAG_PIXEL_X_DIMENSION),
1613: width);
1614: exifIFD.addTIFFField(f);
1615: }
1616:
1617: // PixelYDimension
1618: if (exifIFD
1619: .getTIFFField(EXIFTIFFTagSet.TAG_PIXEL_Y_DIMENSION) == null) {
1620: f = new TIFFField(
1621: exifTags
1622: .getTag(EXIFTIFFTagSet.TAG_PIXEL_Y_DIMENSION),
1623: height);
1624: exifIFD.addTIFFField(f);
1625: }
1626: } else {
1627: exifIFD
1628: .removeTIFFField(EXIFTIFFTagSet.TAG_INTEROPERABILITY_IFD_POINTER);
1629: }
1630: }
1631:
1632: } // if(isEXIF)
1633: }
1634:
1635: /**
1636: @param tileRect The area to be written which might be outside the image.
1637: */
1638: private int writeTile(Rectangle tileRect, TIFFCompressor compressor)
1639: throws IOException {
1640: // Determine the rectangle which will actually be written
1641: // and set the padding flag. Padding will occur only when the
1642: // image is written as a tiled TIFF and the tile bounds are not
1643: // contained within the image bounds.
1644: Rectangle activeRect;
1645: boolean isPadded;
1646: Rectangle imageBounds = new Rectangle(image.getMinX(), image
1647: .getMinY(), image.getWidth(), image.getHeight());
1648: if (!isTiled) {
1649: // Stripped
1650: activeRect = tileRect.intersection(imageBounds);
1651: tileRect = activeRect;
1652: isPadded = false;
1653: } else if (imageBounds.contains(tileRect)) {
1654: // Tiled, tile within image bounds
1655: activeRect = tileRect;
1656: isPadded = false;
1657: } else {
1658: // Tiled, tile not within image bounds
1659: activeRect = imageBounds.intersection(tileRect);
1660: isPadded = true;
1661: }
1662:
1663: // Shouldn't happen, but return early if empty intersection.
1664: if (activeRect.isEmpty()) {
1665: return 0;
1666: }
1667:
1668: int minX = tileRect.x;
1669: int minY = tileRect.y;
1670: int width = tileRect.width;
1671: int height = tileRect.height;
1672:
1673: if (isImageSimple) {
1674:
1675: SampleModel sm = image.getSampleModel();
1676:
1677: // Read only data from the active rectangle.
1678: Raster raster = image.getData(activeRect);
1679:
1680: // If padding is required, create a larger Raster and fill
1681: // it from the active rectangle.
1682: if (isPadded) {
1683: WritableRaster wr = raster
1684: .createCompatibleWritableRaster(minX, minY,
1685: width, height);
1686: wr.setRect(raster);
1687: raster = wr;
1688: }
1689:
1690: if (isBilevel) {
1691: /* XXX
1692: MultiPixelPackedSampleModel mppsm =
1693: (MultiPixelPackedSampleModel)raster.getSampleModel();
1694:
1695: byte[] buf;
1696: int off;
1697: int lineStride;
1698: if(mppsm.getDataBitOffset() == 0 &&
1699: raster.getDataBuffer() instanceof DataBufferByte) {
1700: buf = ((DataBufferByte)raster.getDataBuffer()).getData();
1701: off = mppsm.getOffset(tileRect.x -
1702: raster.getSampleModelTranslateX(),
1703: tileRect.y -
1704: raster.getSampleModelTranslateY());
1705: lineStride = mppsm.getScanlineStride();
1706: } else {
1707: buf = ImageUtil.getPackedBinaryData(raster,
1708: tileRect);
1709: off = 0;
1710: lineStride = (tileRect.width + 7)/8;
1711: }
1712: */
1713: byte[] buf = ImageUtil.getPackedBinaryData(raster,
1714: tileRect);
1715:
1716: if (isInverted) {
1717: DataBuffer dbb = raster.getDataBuffer();
1718: if (dbb instanceof DataBufferByte
1719: && buf == ((DataBufferByte) dbb).getData()) {
1720: byte[] bbuf = new byte[buf.length];
1721: int len = buf.length;
1722: for (int i = 0; i < len; i++) {
1723: bbuf[i] = (byte) (buf[i] ^ 0xff);
1724: }
1725: buf = bbuf;
1726: } else {
1727: int len = buf.length;
1728: for (int i = 0; i < len; i++) {
1729: buf[i] ^= 0xff;
1730: }
1731: }
1732: }
1733:
1734: if (DEBUG) {
1735: System.out.println("Optimized bilevel case");
1736: }
1737:
1738: return compressor.encode(buf, 0, width, height,
1739: sampleSize, (tileRect.width + 7) / 8);
1740: } else if (bitDepth == 8
1741: && sm.getDataType() == DataBuffer.TYPE_BYTE) {
1742: ComponentSampleModel csm = (ComponentSampleModel) raster
1743: .getSampleModel();
1744:
1745: byte[] buf = ((DataBufferByte) raster.getDataBuffer())
1746: .getData();
1747:
1748: int off = csm.getOffset(minX
1749: - raster.getSampleModelTranslateX(), minY
1750: - raster.getSampleModelTranslateY());
1751:
1752: if (DEBUG) {
1753: System.out.println("Optimized component case");
1754: }
1755:
1756: return compressor.encode(buf, off, width, height,
1757: sampleSize, csm.getScanlineStride());
1758: }
1759: }
1760:
1761: // Set offsets and skips based on source subsampling factors
1762: int xOffset = minX;
1763: int xSkip = periodX;
1764: int yOffset = minY;
1765: int ySkip = periodY;
1766:
1767: // Early exit if no data for this pass
1768: int hpixels = (width + xSkip - 1) / xSkip;
1769: int vpixels = (height + ySkip - 1) / ySkip;
1770: if (hpixels == 0 || vpixels == 0) {
1771: return 0;
1772: }
1773:
1774: // Convert X offset and skip from pixels to samples
1775: xOffset *= numBands;
1776: xSkip *= numBands;
1777:
1778: // Initialize sizes
1779: int samplesPerByte = 8 / bitDepth;
1780: int numSamples = width * numBands;
1781: int bytesPerRow = hpixels * numBands;
1782:
1783: // Update number of bytes per row.
1784: if (bitDepth < 8) {
1785: bytesPerRow = (bytesPerRow + samplesPerByte - 1)
1786: / samplesPerByte;
1787: } else if (bitDepth == 16) {
1788: bytesPerRow *= 2;
1789: } else if (bitDepth == 32) {
1790: bytesPerRow *= 4;
1791: }
1792:
1793: // Create row buffers
1794: int[] samples = null;
1795: float[] fsamples = null;
1796: if (sampleFormat == BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
1797: fsamples = new float[numSamples];
1798: } else {
1799: samples = new int[numSamples];
1800: }
1801:
1802: // Create tile buffer
1803: byte[] currTile = new byte[bytesPerRow * vpixels];
1804:
1805: // Sub-optimal case: shy of "isImageSimple" only by virtue of
1806: // not being contiguous.
1807: if (!isInverted && // no inversion
1808: !isRescaling && // no value rescaling
1809: sourceBands == null && // no subbanding
1810: periodX == 1 && periodY == 1 && // no subsampling
1811: colorConverter == null) {
1812:
1813: SampleModel sm = image.getSampleModel();
1814:
1815: if (sm instanceof ComponentSampleModel && // component
1816: bitDepth == 8 && // 8 bits/sample
1817: sm.getDataType() == DataBuffer.TYPE_BYTE) { // byte type
1818:
1819: if (DEBUG) {
1820: System.out
1821: .println("Sub-optimal byte component case");
1822: System.out.println(sm.getClass().getName());
1823: }
1824:
1825: // Read only data from the active rectangle.
1826: Raster raster = image.getData(activeRect);
1827:
1828: // If padding is required, create a larger Raster and fill
1829: // it from the active rectangle.
1830: if (isPadded) {
1831: WritableRaster wr = raster
1832: .createCompatibleWritableRaster(minX, minY,
1833: width, height);
1834: wr.setRect(raster);
1835: raster = wr;
1836: }
1837:
1838: // Get SampleModel info.
1839: ComponentSampleModel csm = (ComponentSampleModel) raster
1840: .getSampleModel();
1841: int[] bankIndices = csm.getBankIndices();
1842: byte[][] bankData = ((DataBufferByte) raster
1843: .getDataBuffer()).getBankData();
1844: int lineStride = csm.getScanlineStride();
1845: int pixelStride = csm.getPixelStride();
1846:
1847: // Copy the data into a contiguous pixel interleaved buffer.
1848: for (int k = 0; k < numBands; k++) {
1849: byte[] bandData = bankData[bankIndices[k]];
1850: int lineOffset = csm.getOffset(raster.getMinX()
1851: - raster.getSampleModelTranslateX(), raster
1852: .getMinY()
1853: - raster.getSampleModelTranslateY(), k);
1854: int idx = k;
1855: for (int j = 0; j < vpixels; j++) {
1856: int offset = lineOffset;
1857: for (int i = 0; i < hpixels; i++) {
1858: currTile[idx] = bandData[offset];
1859: idx += numBands;
1860: offset += pixelStride;
1861: }
1862: lineOffset += lineStride;
1863: }
1864: }
1865:
1866: // Compressor and return.
1867: return compressor.encode(currTile, 0, width, height,
1868: sampleSize, width * numBands);
1869: }
1870: }
1871:
1872: if (DEBUG) {
1873: System.out.println("Unoptimized case for bit depth "
1874: + bitDepth);
1875: SampleModel sm = image.getSampleModel();
1876: System.out.println("isRescaling = " + isRescaling);
1877: System.out.println("sourceBands = " + sourceBands);
1878: System.out.println("periodX = " + periodX);
1879: System.out.println("periodY = " + periodY);
1880: System.out.println(sm.getClass().getName());
1881: System.out.println(sm.getDataType());
1882: if (sm instanceof ComponentSampleModel) {
1883: ComponentSampleModel csm = (ComponentSampleModel) sm;
1884: System.out.println(csm.getNumBands());
1885: System.out.println(csm.getPixelStride());
1886: int[] bankIndices = csm.getBankIndices();
1887: for (int b = 0; b < numBands; b++) {
1888: System.out.print(bankIndices[b] + " ");
1889: }
1890: int[] bandOffsets = csm.getBandOffsets();
1891: for (int b = 0; b < numBands; b++) {
1892: System.out.print(bandOffsets[b] + " ");
1893: }
1894: System.out.println("");
1895: }
1896: }
1897:
1898: int tcount = 0;
1899:
1900: // Save active rectangle variables.
1901: int activeMinX = activeRect.x;
1902: int activeMinY = activeRect.y;
1903: int activeMaxY = activeMinY + activeRect.height - 1;
1904: int activeWidth = activeRect.width;
1905:
1906: // Set a SampleModel for use in padding.
1907: SampleModel rowSampleModel = null;
1908: if (isPadded) {
1909: rowSampleModel = image.getSampleModel()
1910: .createCompatibleSampleModel(width, 1);
1911: }
1912:
1913: for (int row = yOffset; row < yOffset + height; row += ySkip) {
1914: Raster ras = null;
1915: if (isPadded) {
1916: // Create a raster for the entire row.
1917: WritableRaster wr = Raster.createWritableRaster(
1918: rowSampleModel, new Point(minX, row));
1919:
1920: // Populate the raster from the active sub-row, if any.
1921: if (row >= activeMinY && row <= activeMaxY) {
1922: Rectangle rect = new Rectangle(activeMinX, row,
1923: activeWidth, 1);
1924: ras = image.getData(rect);
1925: wr.setRect(ras);
1926: }
1927:
1928: // Update the raster variable.
1929: ras = wr;
1930: } else {
1931: Rectangle rect = new Rectangle(minX, row, width, 1);
1932: ras = image.getData(rect);
1933: }
1934: if (sourceBands != null) {
1935: ras = ras.createChild(minX, row, width, 1, minX, row,
1936: sourceBands);
1937: }
1938:
1939: if (sampleFormat == BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
1940: ras.getPixels(minX, row, width, 1, fsamples);
1941: } else {
1942: ras.getPixels(minX, row, width, 1, samples);
1943:
1944: if ((nativePhotometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO && photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO)
1945: || (nativePhotometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO && photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO)) {
1946: int bitMask = (1 << bitDepth) - 1;
1947: for (int s = 0; s < numSamples; s++) {
1948: samples[s] ^= bitMask;
1949: }
1950: }
1951: }
1952:
1953: if (colorConverter != null) {
1954: int idx = 0;
1955: float[] result = new float[3];
1956:
1957: if (sampleFormat == BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
1958: for (int i = 0; i < width; i++) {
1959: float r = fsamples[idx];
1960: float g = fsamples[idx + 1];
1961: float b = fsamples[idx + 2];
1962:
1963: colorConverter.fromRGB(r, g, b, result);
1964:
1965: fsamples[idx] = result[0];
1966: fsamples[idx + 1] = result[1];
1967: fsamples[idx + 2] = result[2];
1968:
1969: idx += 3;
1970: }
1971: } else {
1972: for (int i = 0; i < width; i++) {
1973: float r = (float) samples[idx];
1974: float g = (float) samples[idx + 1];
1975: float b = (float) samples[idx + 2];
1976:
1977: colorConverter.fromRGB(r, g, b, result);
1978:
1979: samples[idx] = (int) (result[0]);
1980: samples[idx + 1] = (int) (result[1]);
1981: samples[idx + 2] = (int) (result[2]);
1982:
1983: idx += 3;
1984: }
1985: }
1986: }
1987:
1988: int tmp = 0;
1989: int pos = 0;
1990:
1991: switch (bitDepth) {
1992: case 1:
1993: case 2:
1994: case 4:
1995: // Image can only have a single band
1996:
1997: if (isRescaling) {
1998: for (int s = 0; s < numSamples; s += xSkip) {
1999: byte val = scale0[samples[s]];
2000: tmp = (tmp << bitDepth) | val;
2001:
2002: if (++pos == samplesPerByte) {
2003: currTile[tcount++] = (byte) tmp;
2004: tmp = 0;
2005: pos = 0;
2006: }
2007: }
2008: } else {
2009: for (int s = 0; s < numSamples; s += xSkip) {
2010: byte val = (byte) samples[s];
2011: tmp = (tmp << bitDepth) | val;
2012:
2013: if (++pos == samplesPerByte) {
2014: currTile[tcount++] = (byte) tmp;
2015: tmp = 0;
2016: pos = 0;
2017: }
2018: }
2019: }
2020:
2021: // Left shift the last byte
2022: if (pos != 0) {
2023: tmp <<= ((8 / bitDepth) - pos) * bitDepth;
2024: currTile[tcount++] = (byte) tmp;
2025: }
2026: break;
2027:
2028: case 8:
2029: if (numBands == 1) {
2030: if (isRescaling) {
2031: for (int s = 0; s < numSamples; s += xSkip) {
2032: currTile[tcount++] = scale0[samples[s]];
2033: }
2034: } else {
2035: for (int s = 0; s < numSamples; s += xSkip) {
2036: currTile[tcount++] = (byte) samples[s];
2037: }
2038: }
2039: } else {
2040: if (isRescaling) {
2041: for (int s = 0; s < numSamples; s += xSkip) {
2042: for (int b = 0; b < numBands; b++) {
2043: currTile[tcount++] = scale[b][samples[s
2044: + b]];
2045: }
2046: }
2047: } else {
2048: for (int s = 0; s < numSamples; s += xSkip) {
2049: for (int b = 0; b < numBands; b++) {
2050: currTile[tcount++] = (byte) samples[s
2051: + b];
2052: }
2053: }
2054: }
2055: }
2056: break;
2057:
2058: case 16:
2059: // XXX Need to verify this rescaling for signed vs. unsigned.
2060: if (isRescaling) {
2061: if (stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
2062: for (int s = 0; s < numSamples; s += xSkip) {
2063: for (int b = 0; b < numBands; b++) {
2064: int sample = samples[s + b];
2065: currTile[tcount++] = scaleh[b][sample];
2066: currTile[tcount++] = scalel[b][sample];
2067: }
2068: }
2069: } else { // ByteOrder.LITLE_ENDIAN
2070: for (int s = 0; s < numSamples; s += xSkip) {
2071: for (int b = 0; b < numBands; b++) {
2072: int sample = samples[s + b];
2073: currTile[tcount++] = scalel[b][sample];
2074: currTile[tcount++] = scaleh[b][sample];
2075: }
2076: }
2077: }
2078: } else {
2079: if (stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
2080: for (int s = 0; s < numSamples; s += xSkip) {
2081: for (int b = 0; b < numBands; b++) {
2082: int sample = samples[s + b];
2083: currTile[tcount++] = (byte) ((sample >>> 8) & 0xff);
2084: currTile[tcount++] = (byte) (sample & 0xff);
2085: }
2086: }
2087: } else { // ByteOrder.LITLE_ENDIAN
2088: for (int s = 0; s < numSamples; s += xSkip) {
2089: for (int b = 0; b < numBands; b++) {
2090: int sample = samples[s + b];
2091: currTile[tcount++] = (byte) (sample & 0xff);
2092: currTile[tcount++] = (byte) ((sample >>> 8) & 0xff);
2093: }
2094: }
2095: }
2096: }
2097: break;
2098:
2099: case 32:
2100: if (sampleFormat == BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) {
2101: if (stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
2102: for (int s = 0; s < numSamples; s += xSkip) {
2103: for (int b = 0; b < numBands; b++) {
2104: float fsample = fsamples[s + b];
2105: int isample = Float
2106: .floatToIntBits(fsample);
2107: currTile[tcount++] = (byte) ((isample & 0xff000000) >> 24);
2108: currTile[tcount++] = (byte) ((isample & 0x00ff0000) >> 16);
2109: currTile[tcount++] = (byte) ((isample & 0x0000ff00) >> 8);
2110: currTile[tcount++] = (byte) (isample & 0x000000ff);
2111: }
2112: }
2113: } else { // ByteOrder.LITLE_ENDIAN
2114: for (int s = 0; s < numSamples; s += xSkip) {
2115: for (int b = 0; b < numBands; b++) {
2116: float fsample = fsamples[s + b];
2117: int isample = Float
2118: .floatToIntBits(fsample);
2119: currTile[tcount++] = (byte) (isample & 0x000000ff);
2120: currTile[tcount++] = (byte) ((isample & 0x0000ff00) >> 8);
2121: currTile[tcount++] = (byte) ((isample & 0x00ff0000) >> 16);
2122: currTile[tcount++] = (byte) ((isample & 0xff000000) >> 24);
2123: }
2124: }
2125: }
2126: } else {
2127: if (isRescaling) {
2128: // XXX Need to verify this for signed vs. unsigned.
2129: // XXX The following gives saturated results when the
2130: // original data are in the signed integer range.
2131: long[] maxIn = new long[numBands];
2132: long[] halfIn = new long[numBands];
2133: long maxOut = (1L << (long) bitDepth) - 1L;
2134:
2135: for (int b = 0; b < numBands; b++) {
2136: maxIn[b] = ((1L << (long) sampleSize[b]) - 1L);
2137: halfIn[b] = maxIn[b] / 2;
2138: }
2139:
2140: if (stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
2141: for (int s = 0; s < numSamples; s += xSkip) {
2142: for (int b = 0; b < numBands; b++) {
2143: long sampleOut = (samples[s + b]
2144: * maxOut + halfIn[b])
2145: / maxIn[b];
2146: currTile[tcount++] = (byte) ((sampleOut & 0xff000000) >> 24);
2147: currTile[tcount++] = (byte) ((sampleOut & 0x00ff0000) >> 16);
2148: currTile[tcount++] = (byte) ((sampleOut & 0x0000ff00) >> 8);
2149: currTile[tcount++] = (byte) (sampleOut & 0x000000ff);
2150: }
2151: }
2152: } else { // ByteOrder.LITLE_ENDIAN
2153: for (int s = 0; s < numSamples; s += xSkip) {
2154: for (int b = 0; b < numBands; b++) {
2155: long sampleOut = (samples[s + b]
2156: * maxOut + halfIn[b])
2157: / maxIn[b];
2158: currTile[tcount++] = (byte) (sampleOut & 0x000000ff);
2159: currTile[tcount++] = (byte) ((sampleOut & 0x0000ff00) >> 8);
2160: currTile[tcount++] = (byte) ((sampleOut & 0x00ff0000) >> 16);
2161: currTile[tcount++] = (byte) ((sampleOut & 0xff000000) >> 24);
2162: }
2163: }
2164: }
2165: } else {
2166: if (stream.getByteOrder() == ByteOrder.BIG_ENDIAN) {
2167: for (int s = 0; s < numSamples; s += xSkip) {
2168: for (int b = 0; b < numBands; b++) {
2169: int isample = samples[s + b];
2170: currTile[tcount++] = (byte) ((isample & 0xff000000) >> 24);
2171: currTile[tcount++] = (byte) ((isample & 0x00ff0000) >> 16);
2172: currTile[tcount++] = (byte) ((isample & 0x0000ff00) >> 8);
2173: currTile[tcount++] = (byte) (isample & 0x000000ff);
2174: }
2175: }
2176: } else { // ByteOrder.LITLE_ENDIAN
2177: for (int s = 0; s < numSamples; s += xSkip) {
2178: for (int b = 0; b < numBands; b++) {
2179: int isample = samples[s + b];
2180: currTile[tcount++] = (byte) (isample & 0x000000ff);
2181: currTile[tcount++] = (byte) ((isample & 0x0000ff00) >> 8);
2182: currTile[tcount++] = (byte) ((isample & 0x00ff0000) >> 16);
2183: currTile[tcount++] = (byte) ((isample & 0xff000000) >> 24);
2184: }
2185: }
2186: }
2187: }
2188: }
2189: break;
2190: }
2191: }
2192:
2193: int[] bitsPerSample = new int[numBands];
2194: for (int i = 0; i < bitsPerSample.length; i++) {
2195: bitsPerSample[i] = bitDepth;
2196: }
2197:
2198: int byteCount = compressor.encode(currTile, 0, hpixels,
2199: vpixels, bitsPerSample, bytesPerRow);
2200: return byteCount;
2201: }
2202:
2203: // Check two int arrays for value equality, always returns false
2204: // if either array is null
2205: private boolean equals(int[] s0, int[] s1) {
2206: if (s0 == null || s1 == null) {
2207: return false;
2208: }
2209: if (s0.length != s1.length) {
2210: return false;
2211: }
2212: for (int i = 0; i < s0.length; i++) {
2213: if (s0[i] != s1[i]) {
2214: return false;
2215: }
2216: }
2217: return true;
2218: }
2219:
2220: // Initialize the scale/scale0 or scaleh/scalel arrays to
2221: // hold the results of scaling an input value to the desired
2222: // output bit depth
2223: // XXX Need to verify this rescaling for signed vs. unsigned.
2224: private void initializeScaleTables(int[] sampleSize) {
2225: // Save the sample size in the instance variable.
2226:
2227: // If the existing tables are still valid, just return.
2228: if (bitDepth == scalingBitDepth
2229: && equals(sampleSize, this .sampleSize)) {
2230: if (DEBUG) {
2231: System.out
2232: .println("Returning from initializeScaleTables()");
2233: }
2234: return;
2235: }
2236:
2237: // Reset scaling variables.
2238: isRescaling = false;
2239: scalingBitDepth = -1;
2240: scale = scalel = scaleh = null;
2241: scale0 = null;
2242:
2243: // Set global sample size to parameter.
2244: this .sampleSize = sampleSize;
2245:
2246: // Check whether rescaling is called for.
2247: if (bitDepth <= 16) {
2248: for (int b = 0; b < numBands; b++) {
2249: if (sampleSize[b] != bitDepth) {
2250: isRescaling = true;
2251: break;
2252: }
2253: }
2254: }
2255:
2256: if (DEBUG) {
2257: System.out.println("isRescaling = " + isRescaling);
2258: }
2259:
2260: // If not rescaling then return after saving the sample size.
2261: if (!isRescaling) {
2262: return;
2263: }
2264:
2265: // Compute new tables
2266: this .scalingBitDepth = bitDepth;
2267: int maxOutSample = (1 << bitDepth) - 1;
2268: if (bitDepth <= 8) {
2269: scale = new byte[numBands][];
2270: for (int b = 0; b < numBands; b++) {
2271: int maxInSample = (1 << sampleSize[b]) - 1;
2272: int halfMaxInSample = maxInSample / 2;
2273: scale[b] = new byte[maxInSample + 1];
2274: for (int s = 0; s <= maxInSample; s++) {
2275: scale[b][s] = (byte) ((s * maxOutSample + halfMaxInSample) / maxInSample);
2276: }
2277: }
2278: scale0 = scale[0];
2279: scaleh = scalel = null;
2280: } else if (bitDepth <= 16) {
2281: // Divide scaling table into high and low bytes
2282: scaleh = new byte[numBands][];
2283: scalel = new byte[numBands][];
2284:
2285: for (int b = 0; b < numBands; b++) {
2286: int maxInSample = (1 << sampleSize[b]) - 1;
2287: int halfMaxInSample = maxInSample / 2;
2288: scaleh[b] = new byte[maxInSample + 1];
2289: scalel[b] = new byte[maxInSample + 1];
2290: for (int s = 0; s <= maxInSample; s++) {
2291: int val = (s * maxOutSample + halfMaxInSample)
2292: / maxInSample;
2293: scaleh[b][s] = (byte) (val >> 8);
2294: scalel[b][s] = (byte) (val & 0xff);
2295: }
2296: }
2297: scale = null;
2298: scale0 = null;
2299: }
2300: }
2301:
2302: public void write(IIOMetadata sm, IIOImage iioimage,
2303: ImageWriteParam p) throws IOException {
2304: write(sm, iioimage, p, true, true);
2305: }
2306:
2307: private void writeHeader() throws IOException {
2308: if (streamMetadata != null) {
2309: this .byteOrder = streamMetadata.byteOrder;
2310: } else {
2311: this .byteOrder = ByteOrder.BIG_ENDIAN;
2312: }
2313:
2314: stream.setByteOrder(byteOrder);
2315: if (byteOrder == ByteOrder.BIG_ENDIAN) {
2316: stream.writeShort(0x4d4d);
2317: } else {
2318: stream.writeShort(0x4949);
2319: }
2320:
2321: stream.writeShort(42); // Magic number
2322: stream.writeInt(0); // Offset of first IFD (0 == none)
2323:
2324: nextSpace = stream.getStreamPosition();
2325: headerPosition = nextSpace - 8;
2326: }
2327:
2328: private void write(IIOMetadata sm, IIOImage iioimage,
2329: ImageWriteParam p, boolean writeHeader, boolean writeData)
2330: throws IOException {
2331: if (stream == null) {
2332: throw new IllegalStateException("output == null!");
2333: }
2334: if (iioimage == null) {
2335: throw new IllegalArgumentException("image == null!");
2336: }
2337: if (iioimage.hasRaster() && !canWriteRasters()) {
2338: throw new UnsupportedOperationException(
2339: "TIFF ImageWriter cannot write Rasters!");
2340: }
2341:
2342: this .image = iioimage.getRenderedImage();
2343: SampleModel sampleModel = image.getSampleModel();
2344:
2345: this .sourceXOffset = image.getMinX();
2346: this .sourceYOffset = image.getMinY();
2347: this .sourceWidth = image.getWidth();
2348: this .sourceHeight = image.getHeight();
2349:
2350: Rectangle imageBounds = new Rectangle(sourceXOffset,
2351: sourceYOffset, sourceWidth, sourceHeight);
2352:
2353: ColorModel colorModel = null;
2354: if (p == null) {
2355: this .param = getDefaultWriteParam();
2356: this .sourceBands = null;
2357: this .periodX = 1;
2358: this .periodY = 1;
2359: this .numBands = sampleModel.getNumBands();
2360: colorModel = image.getColorModel();
2361: } else {
2362: this .param = p;
2363:
2364: // Get source region and subsampling factors
2365: Rectangle sourceRegion = param.getSourceRegion();
2366: if (sourceRegion != null) {
2367: // Clip to actual image bounds
2368: sourceRegion = sourceRegion.intersection(imageBounds);
2369:
2370: sourceXOffset = sourceRegion.x;
2371: sourceYOffset = sourceRegion.y;
2372: sourceWidth = sourceRegion.width;
2373: sourceHeight = sourceRegion.height;
2374: }
2375:
2376: // Adjust for subsampling offsets
2377: int gridX = param.getSubsamplingXOffset();
2378: int gridY = param.getSubsamplingYOffset();
2379: this .sourceXOffset += gridX;
2380: this .sourceYOffset += gridY;
2381: this .sourceWidth -= gridX;
2382: this .sourceHeight -= gridY;
2383:
2384: // Get subsampling factors
2385: this .periodX = param.getSourceXSubsampling();
2386: this .periodY = param.getSourceYSubsampling();
2387:
2388: int[] sBands = param.getSourceBands();
2389: if (sBands != null) {
2390: sourceBands = sBands;
2391: this .numBands = sourceBands.length;
2392: } else {
2393: this .numBands = sampleModel.getNumBands();
2394: }
2395:
2396: ImageTypeSpecifier destType = p.getDestinationType();
2397: if (destType != null) {
2398: ColorModel cm = destType.getColorModel();
2399: if (cm.getNumComponents() == numBands) {
2400: colorModel = cm;
2401: }
2402: }
2403:
2404: if (colorModel == null) {
2405: colorModel = image.getColorModel();
2406: }
2407: }
2408:
2409: this .imageType = new ImageTypeSpecifier(colorModel, sampleModel);
2410:
2411: ImageUtil.canEncodeImage(this , this .imageType);
2412:
2413: // Compute output dimensions
2414: int destWidth = (sourceWidth + periodX - 1) / periodX;
2415: int destHeight = (sourceHeight + periodY - 1) / periodY;
2416: if (destWidth <= 0 || destHeight <= 0) {
2417: throw new IllegalArgumentException("Empty source region!");
2418: }
2419:
2420: // this.bitDepth = 8; // XXX fix?
2421:
2422: clearAbortRequest();
2423: processImageStarted(0);
2424:
2425: // Optionally write the header.
2426: if (writeHeader) {
2427: // Clear previous stream metadata.
2428: this .streamMetadata = null;
2429:
2430: // Try to convert non-null input stream metadata.
2431: if (sm != null) {
2432: this .streamMetadata = (TIFFStreamMetadata) convertStreamMetadata(
2433: sm, param);
2434: }
2435:
2436: // Set to default if not converted.
2437: if (this .streamMetadata == null) {
2438: this .streamMetadata = (TIFFStreamMetadata) getDefaultStreamMetadata(param);
2439: }
2440:
2441: // Write the header.
2442: writeHeader();
2443:
2444: // Seek to the position of the IFD pointer in the header.
2445: stream.seek(headerPosition + 4);
2446:
2447: // Ensure IFD is written on a word boundary
2448: nextSpace = (nextSpace + 3) & ~0x3;
2449:
2450: // Write the pointer to the first IFD after the header.
2451: stream.writeInt((int) nextSpace);
2452: }
2453:
2454: // Write out the IFD and any sub IFDs, followed by a zero
2455:
2456: // Clear previous image metadata.
2457: this .imageMetadata = null;
2458:
2459: // Initialize the metadata object.
2460: IIOMetadata im = iioimage.getMetadata();
2461: if (im != null) {
2462: if (im instanceof TIFFImageMetadata) {
2463: // Clone the one passed in.
2464: this .imageMetadata = ((TIFFImageMetadata) im)
2465: .getShallowClone();
2466: } else if (Arrays.asList(im.getMetadataFormatNames())
2467: .contains(
2468: TIFFImageMetadata.nativeMetadataFormatName)) {
2469: this .imageMetadata = convertNativeImageMetadata(im);
2470: } else if (im.isStandardMetadataFormatSupported()) {
2471: try {
2472: // Convert standard metadata.
2473: this .imageMetadata = convertStandardImageMetadata(im);
2474: } catch (IIOInvalidTreeException e) {
2475: // XXX Warning
2476: }
2477: }
2478: }
2479:
2480: // Use default metadata if still null.
2481: if (this .imageMetadata == null) {
2482: this .imageMetadata = (TIFFImageMetadata) getDefaultImageMetadata(
2483: this .imageType, this .param);
2484: }
2485:
2486: // Set or overwrite mandatory fields in the root IFD
2487: setupMetadata(colorModel, sampleModel, destWidth, destHeight);
2488:
2489: // Set compressor fields.
2490: compressor.setWriter(this );
2491: // Metadata needs to be set on the compressor before the IFD is
2492: // written as the compressor could modify the metadata.
2493: compressor.setMetadata(imageMetadata);
2494: compressor.setStream(stream);
2495:
2496: // Initialize scaling tables for this image
2497: int[] sampleSize = sampleModel.getSampleSize();
2498: initializeScaleTables(sampleModel.getSampleSize());
2499:
2500: // Determine whether bilevel.
2501: this .isBilevel = ImageUtil
2502: .isBinary(this .image.getSampleModel());
2503:
2504: // Check for photometric inversion.
2505: this .isInverted = (nativePhotometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO && photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO)
2506: || (nativePhotometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO && photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO);
2507:
2508: // Analyze image data suitability for direct copy.
2509: this .isImageSimple = (isBilevel || (!isInverted && ImageUtil
2510: .imageIsContiguous(this .image)))
2511: && !isRescaling && // no value rescaling
2512: sourceBands == null && // no subbanding
2513: periodX == 1 && periodY == 1 && // no subsampling
2514: colorConverter == null;
2515:
2516: TIFFIFD rootIFD = imageMetadata.getRootIFD();
2517:
2518: rootIFD.writeToStream(stream);
2519:
2520: this .nextIFDPointerPos = stream.getStreamPosition();
2521: stream.writeInt(0);
2522:
2523: // Seek to end of IFD data
2524: long lastIFDPosition = rootIFD.getLastPosition();
2525: stream.seek(lastIFDPosition);
2526: if (lastIFDPosition > this .nextSpace) {
2527: this .nextSpace = lastIFDPosition;
2528: }
2529:
2530: // If not writing the image data, i.e., if writing or inserting an
2531: // empty image, return.
2532: if (!writeData) {
2533: return;
2534: }
2535:
2536: // Get positions of fields within the IFD to update as we write
2537: // each strip or tile
2538: long stripOrTileByteCountsPosition = rootIFD
2539: .getStripOrTileByteCountsPosition();
2540: long stripOrTileOffsetsPosition = rootIFD
2541: .getStripOrTileOffsetsPosition();
2542:
2543: // Compute total number of pixels for progress notification
2544: this .totalPixels = tileWidth * tileLength * tilesDown
2545: * tilesAcross;
2546: this .pixelsDone = 0;
2547:
2548: // Write the image, a strip or tile at a time
2549: for (int tj = 0; tj < tilesDown; tj++) {
2550: for (int ti = 0; ti < tilesAcross; ti++) {
2551: long pos = stream.getStreamPosition();
2552:
2553: // Write the (possibly compressed) tile data
2554:
2555: Rectangle tileRect = new Rectangle(sourceXOffset + ti
2556: * tileWidth * periodX, sourceYOffset + tj
2557: * tileLength * periodY, tileWidth * periodX,
2558: tileLength * periodY);
2559: // tileRect = tileRect.intersection(imageBounds); // XXX
2560:
2561: try {
2562: int byteCount = writeTile(tileRect, compressor);
2563:
2564: if (pos + byteCount > nextSpace) {
2565: nextSpace = pos + byteCount;
2566: }
2567:
2568: pixelsDone += tileRect.width * tileRect.height;
2569: processImageProgress(100.0F * pixelsDone
2570: / totalPixels);
2571:
2572: // Fill in the offset and byte count for the file
2573: stream.mark();
2574: stream.seek(stripOrTileOffsetsPosition);
2575: stream.writeInt((int) pos);
2576: stripOrTileOffsetsPosition += 4;
2577:
2578: stream.seek(stripOrTileByteCountsPosition);
2579: stream.writeInt(byteCount);
2580: stripOrTileByteCountsPosition += 4;
2581: stream.reset();
2582: } catch (IOException e) {
2583: throw new IIOException(
2584: "I/O error writing TIFF file!", e);
2585: }
2586:
2587: if (abortRequested()) {
2588: processWriteAborted();
2589: return;
2590: }
2591: }
2592: }
2593:
2594: processImageComplete();
2595: }
2596:
2597: public boolean canWriteSequence() {
2598: return true;
2599: }
2600:
2601: public void prepareWriteSequence(IIOMetadata streamMetadata)
2602: throws IOException {
2603: if (getOutput() == null) {
2604: throw new IllegalStateException("getOutput() == null!");
2605: }
2606:
2607: // Set up stream metadata.
2608: if (streamMetadata != null) {
2609: streamMetadata = convertStreamMetadata(streamMetadata, null);
2610: }
2611: if (streamMetadata == null) {
2612: streamMetadata = getDefaultStreamMetadata(null);
2613: }
2614: this .streamMetadata = (TIFFStreamMetadata) streamMetadata;
2615:
2616: // Write the header.
2617: writeHeader();
2618:
2619: // Set the sequence flag.
2620: this .isWritingSequence = true;
2621: }
2622:
2623: public void writeToSequence(IIOImage image, ImageWriteParam param)
2624: throws IOException {
2625: // Check sequence flag.
2626: if (!this .isWritingSequence) {
2627: throw new IllegalStateException(
2628: "prepareWriteSequence() has not been called!");
2629: }
2630:
2631: // Append image.
2632: writeInsert(-1, image, param);
2633: }
2634:
2635: public void endWriteSequence() throws IOException {
2636: // Check output.
2637: if (getOutput() == null) {
2638: throw new IllegalStateException("getOutput() == null!");
2639: }
2640:
2641: // Check sequence flag.
2642: if (!isWritingSequence) {
2643: throw new IllegalStateException(
2644: "prepareWriteSequence() has not been called!");
2645: }
2646:
2647: // Unset sequence flag.
2648: this .isWritingSequence = false;
2649: }
2650:
2651: public boolean canInsertImage(int imageIndex) throws IOException {
2652: if (getOutput() == null) {
2653: throw new IllegalStateException("getOutput() == null!");
2654: }
2655:
2656: // Mark position as locateIFD() will seek to IFD at imageIndex.
2657: stream.mark();
2658:
2659: // locateIFD() will throw an IndexOutOfBoundsException if
2660: // imageIndex is < -1 or is too big thereby satisfying the spec.
2661: long[] ifdpos = new long[1];
2662: long[] ifd = new long[1];
2663: locateIFD(imageIndex, ifdpos, ifd);
2664:
2665: // Reset to position before locateIFD().
2666: stream.reset();
2667:
2668: return true;
2669: }
2670:
2671: // Locate start of IFD for image.
2672: // Throws IIOException if not at a TIFF header and
2673: // IndexOutOfBoundsException if imageIndex is < -1 or is too big.
2674: private void locateIFD(int imageIndex, long[] ifdpos, long[] ifd)
2675: throws IOException {
2676:
2677: if (imageIndex < -1) {
2678: throw new IndexOutOfBoundsException("imageIndex < -1!");
2679: }
2680:
2681: long startPos = stream.getStreamPosition();
2682:
2683: stream.seek(headerPosition);
2684: int byteOrder = stream.readUnsignedShort();
2685: if (byteOrder == 0x4d4d) {
2686: stream.setByteOrder(ByteOrder.BIG_ENDIAN);
2687: } else if (byteOrder == 0x4949) {
2688: stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
2689: } else {
2690: stream.seek(startPos);
2691: throw new IIOException("Illegal byte order");
2692: }
2693: if (stream.readUnsignedShort() != 42) {
2694: stream.seek(startPos);
2695: throw new IIOException("Illegal magic number");
2696: }
2697:
2698: ifdpos[0] = stream.getStreamPosition();
2699: ifd[0] = stream.readUnsignedInt();
2700: if (ifd[0] == 0) {
2701: // imageIndex has to be >= -1 due to check above.
2702: if (imageIndex > 0) {
2703: stream.seek(startPos);
2704: throw new IndexOutOfBoundsException(
2705: "imageIndex is greater than the largest available index!");
2706: }
2707: return;
2708: }
2709: stream.seek(ifd[0]);
2710:
2711: for (int i = 0; imageIndex == -1 || i < imageIndex; i++) {
2712: int numFields;
2713: try {
2714: numFields = stream.readShort();
2715: } catch (EOFException eof) {
2716: stream.seek(startPos);
2717: ifd[0] = 0;
2718: return;
2719: }
2720:
2721: stream.skipBytes(12 * numFields);
2722:
2723: ifdpos[0] = stream.getStreamPosition();
2724: ifd[0] = stream.readUnsignedInt();
2725: if (ifd[0] == 0) {
2726: if (imageIndex != -1 && i < imageIndex - 1) {
2727: stream.seek(startPos);
2728: throw new IndexOutOfBoundsException(
2729: "imageIndex is greater than the largest available index!");
2730: }
2731: break;
2732: }
2733: stream.seek(ifd[0]);
2734: }
2735: }
2736:
2737: public void writeInsert(int imageIndex, IIOImage image,
2738: ImageWriteParam param) throws IOException {
2739: insert(imageIndex, image, param, true);
2740: }
2741:
2742: private void insert(int imageIndex, IIOImage image,
2743: ImageWriteParam param, boolean writeData)
2744: throws IOException {
2745: if (stream == null) {
2746: throw new IllegalStateException("Output not set!");
2747: }
2748: if (image == null) {
2749: throw new IllegalArgumentException("image == null!");
2750: }
2751:
2752: // Locate the position of the old IFD (ifd) and the location
2753: // of the pointer to that position (ifdpos).
2754: long[] ifdpos = new long[1];
2755: long[] ifd = new long[1];
2756:
2757: // locateIFD() will throw an IndexOutOfBoundsException if
2758: // imageIndex is < -1 or is too big thereby satisfying the spec.
2759: locateIFD(imageIndex, ifdpos, ifd);
2760:
2761: // Seek to the position containing the pointer to the old IFD.
2762: stream.seek(ifdpos[0]);
2763:
2764: // Update next space pointer in anticipation of next write.
2765: if (ifdpos[0] + 4 > nextSpace) {
2766: nextSpace = ifdpos[0] + 4;
2767: }
2768:
2769: // Ensure IFD is written on a word boundary
2770: nextSpace = (nextSpace + 3) & ~0x3;
2771:
2772: // Update the value to point to the next available space.
2773: stream.writeInt((int) nextSpace);
2774:
2775: // Seek to the next available space.
2776: stream.seek(nextSpace);
2777:
2778: // Write the image (IFD and data).
2779: write(null, image, param, false, writeData);
2780:
2781: // Seek to the position containing the pointer in the new IFD.
2782: stream.seek(nextIFDPointerPos);
2783:
2784: // Update the new IFD to point to the old IFD.
2785: stream.writeInt((int) ifd[0]);
2786: // Don't need to update nextSpace here as already done in write().
2787: }
2788:
2789: // ----- BEGIN insert/writeEmpty methods -----
2790:
2791: // XXX Move local variable(s) up.
2792: private boolean isInsertingEmpty = false;
2793: private boolean isWritingEmpty = false;
2794:
2795: private boolean isEncodingEmpty() {
2796: return isInsertingEmpty || isWritingEmpty;
2797: }
2798:
2799: public boolean canInsertEmpty(int imageIndex) throws IOException {
2800: return canInsertImage(imageIndex);
2801: }
2802:
2803: public boolean canWriteEmpty() throws IOException {
2804: if (getOutput() == null) {
2805: throw new IllegalStateException("getOutput() == null!");
2806: }
2807: return true;
2808: }
2809:
2810: // Check state and parameters for writing or inserting empty images.
2811: private void checkParamsEmpty(ImageTypeSpecifier imageType,
2812: int width, int height, List thumbnails) {
2813: if (getOutput() == null) {
2814: throw new IllegalStateException("getOutput() == null!");
2815: }
2816:
2817: if (imageType == null) {
2818: throw new IllegalArgumentException("imageType == null!");
2819: }
2820:
2821: if (width < 1 || height < 1) {
2822: throw new IllegalArgumentException(
2823: "width < 1 || height < 1!");
2824: }
2825:
2826: if (thumbnails != null) {
2827: int numThumbs = thumbnails.size();
2828: for (int i = 0; i < numThumbs; i++) {
2829: Object thumb = thumbnails.get(i);
2830: if (thumb == null || !(thumb instanceof BufferedImage)) {
2831: throw new IllegalArgumentException(
2832: "thumbnails contains null references or objects other than BufferedImages!");
2833: }
2834: }
2835: }
2836:
2837: if (this .isInsertingEmpty) {
2838: throw new IllegalStateException(
2839: "Previous call to prepareInsertEmpty() without corresponding call to endInsertEmpty()!");
2840: }
2841:
2842: if (this .isWritingEmpty) {
2843: throw new IllegalStateException(
2844: "Previous call to prepareWriteEmpty() without corresponding call to endWriteEmpty()!");
2845: }
2846: }
2847:
2848: public void prepareInsertEmpty(int imageIndex,
2849: ImageTypeSpecifier imageType, int width, int height,
2850: IIOMetadata imageMetadata, List thumbnails,
2851: ImageWriteParam param) throws IOException {
2852: checkParamsEmpty(imageType, width, height, thumbnails);
2853:
2854: this .isInsertingEmpty = true;
2855:
2856: SampleModel emptySM = imageType.getSampleModel();
2857: RenderedImage emptyImage = new EmptyImage(0, 0, width, height,
2858: 0, 0, emptySM.getWidth(), emptySM.getHeight(), emptySM,
2859: imageType.getColorModel());
2860:
2861: insert(imageIndex,
2862: new IIOImage(emptyImage, null, imageMetadata), param,
2863: false);
2864: }
2865:
2866: public void prepareWriteEmpty(IIOMetadata streamMetadata,
2867: ImageTypeSpecifier imageType, int width, int height,
2868: IIOMetadata imageMetadata, List thumbnails,
2869: ImageWriteParam param) throws IOException {
2870: checkParamsEmpty(imageType, width, height, thumbnails);
2871:
2872: this .isWritingEmpty = true;
2873:
2874: SampleModel emptySM = imageType.getSampleModel();
2875: RenderedImage emptyImage = new EmptyImage(0, 0, width, height,
2876: 0, 0, emptySM.getWidth(), emptySM.getHeight(), emptySM,
2877: imageType.getColorModel());
2878:
2879: write(streamMetadata, new IIOImage(emptyImage, null,
2880: imageMetadata), param, true, false);
2881: }
2882:
2883: public void endInsertEmpty() throws IOException {
2884: if (getOutput() == null) {
2885: throw new IllegalStateException("getOutput() == null!");
2886: }
2887:
2888: if (!this .isInsertingEmpty) {
2889: throw new IllegalStateException(
2890: "No previous call to prepareInsertEmpty()!");
2891: }
2892:
2893: if (this .isWritingEmpty) {
2894: throw new IllegalStateException(
2895: "Previous call to prepareWriteEmpty() without corresponding call to endWriteEmpty()!");
2896: }
2897:
2898: if (inReplacePixelsNest) {
2899: throw new IllegalStateException(
2900: "In nested call to prepareReplacePixels!");
2901: }
2902:
2903: this .isInsertingEmpty = false;
2904: }
2905:
2906: public void endWriteEmpty() throws IOException {
2907: if (getOutput() == null) {
2908: throw new IllegalStateException("getOutput() == null!");
2909: }
2910:
2911: if (!this .isWritingEmpty) {
2912: throw new IllegalStateException(
2913: "No previous call to prepareWriteEmpty()!");
2914: }
2915:
2916: if (this .isInsertingEmpty) {
2917: throw new IllegalStateException(
2918: "Previous call to prepareInsertEmpty() without corresponding call to endInsertEmpty()!");
2919: }
2920:
2921: if (inReplacePixelsNest) {
2922: throw new IllegalStateException(
2923: "In nested call to prepareReplacePixels!");
2924: }
2925:
2926: this .isWritingEmpty = false;
2927: }
2928:
2929: // ----- END insert/writeEmpty methods -----
2930:
2931: // ----- BEGIN replacePixels methods -----
2932:
2933: private TIFFIFD readIFD(int imageIndex) throws IOException {
2934: if (stream == null) {
2935: throw new IllegalStateException("Output not set!");
2936: }
2937: if (imageIndex < 0) {
2938: throw new IndexOutOfBoundsException("imageIndex < 0!");
2939: }
2940:
2941: stream.mark();
2942: long[] ifdpos = new long[1];
2943: long[] ifd = new long[1];
2944: locateIFD(imageIndex, ifdpos, ifd);
2945: if (ifd[0] == 0) {
2946: stream.reset();
2947: throw new IndexOutOfBoundsException(
2948: "imageIndex out of bounds!");
2949: }
2950:
2951: List tagSets = new ArrayList(1);
2952: tagSets.add(BaselineTIFFTagSet.getInstance());
2953: TIFFIFD rootIFD = new TIFFIFD(tagSets);
2954: // XXX Ignore unknown fields in metadata presumably because
2955: // any fields needed to write pixels would be known?
2956: rootIFD.initialize(stream, true);
2957: stream.reset();
2958:
2959: return rootIFD;
2960: }
2961:
2962: public boolean canReplacePixels(int imageIndex) throws IOException {
2963: if (getOutput() == null) {
2964: throw new IllegalStateException("getOutput() == null!");
2965: }
2966:
2967: TIFFIFD rootIFD = readIFD(imageIndex);
2968: TIFFField f = rootIFD
2969: .getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
2970: int compression = f.getAsInt(0);
2971:
2972: return compression == BaselineTIFFTagSet.COMPRESSION_NONE;
2973: }
2974:
2975: private Object replacePixelsLock = new Object();
2976:
2977: private int replacePixelsIndex = -1;
2978: private TIFFImageMetadata replacePixelsMetadata = null;
2979: private long[] replacePixelsTileOffsets = null;
2980: private long[] replacePixelsByteCounts = null;
2981: private long replacePixelsOffsetsPosition = 0L;
2982: private long replacePixelsByteCountsPosition = 0L;
2983: private Rectangle replacePixelsRegion = null;
2984: private boolean inReplacePixelsNest = false;
2985:
2986: private TIFFImageReader reader = null;
2987:
2988: public void prepareReplacePixels(int imageIndex, Rectangle region)
2989: throws IOException {
2990: synchronized (replacePixelsLock) {
2991: // Check state and parameters vis-a-vis ImageWriter specification.
2992: if (stream == null) {
2993: throw new IllegalStateException("Output not set!");
2994: }
2995: if (region == null) {
2996: throw new IllegalArgumentException("region == null!");
2997: }
2998: if (region.getWidth() < 1) {
2999: throw new IllegalArgumentException(
3000: "region.getWidth() < 1!");
3001: }
3002: if (region.getHeight() < 1) {
3003: throw new IllegalArgumentException(
3004: "region.getHeight() < 1!");
3005: }
3006: if (inReplacePixelsNest) {
3007: throw new IllegalStateException(
3008: "In nested call to prepareReplacePixels!");
3009: }
3010:
3011: // Read the IFD for the pixel replacement index.
3012: TIFFIFD replacePixelsIFD = readIFD(imageIndex);
3013:
3014: // Ensure that compression is "none".
3015: TIFFField f = replacePixelsIFD
3016: .getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION);
3017: int compression = f.getAsInt(0);
3018: if (compression != BaselineTIFFTagSet.COMPRESSION_NONE) {
3019: throw new UnsupportedOperationException(
3020: "canReplacePixels(imageIndex) == false!");
3021: }
3022:
3023: // Get the image dimensions.
3024: f = replacePixelsIFD
3025: .getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH);
3026: if (f == null) {
3027: throw new IIOException("Cannot read ImageWidth field.");
3028: }
3029: int w = f.getAsInt(0);
3030:
3031: f = replacePixelsIFD
3032: .getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH);
3033: if (f == null) {
3034: throw new IIOException("Cannot read ImageHeight field.");
3035: }
3036: int h = f.getAsInt(0);
3037:
3038: // Create image bounds.
3039: Rectangle bounds = new Rectangle(0, 0, w, h);
3040:
3041: // Intersect region with bounds.
3042: region = region.intersection(bounds);
3043:
3044: // Check for empty intersection.
3045: if (region.isEmpty()) {
3046: throw new IIOException(
3047: "Region does not intersect image bounds");
3048: }
3049:
3050: // Save the region.
3051: replacePixelsRegion = region;
3052:
3053: // Get the tile offsets.
3054: f = replacePixelsIFD
3055: .getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS);
3056: if (f == null) {
3057: f = replacePixelsIFD
3058: .getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS);
3059: }
3060: replacePixelsTileOffsets = f.getAsLongs();
3061:
3062: // Get the byte counts.
3063: f = replacePixelsIFD
3064: .getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS);
3065: if (f == null) {
3066: f = replacePixelsIFD
3067: .getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS);
3068: }
3069: replacePixelsByteCounts = f.getAsLongs();
3070:
3071: replacePixelsOffsetsPosition = replacePixelsIFD
3072: .getStripOrTileOffsetsPosition();
3073: replacePixelsByteCountsPosition = replacePixelsIFD
3074: .getStripOrTileByteCountsPosition();
3075:
3076: // Get the image metadata.
3077: replacePixelsMetadata = new TIFFImageMetadata(
3078: replacePixelsIFD);
3079:
3080: // Save the image index.
3081: replacePixelsIndex = imageIndex;
3082:
3083: // Set the pixel replacement flag.
3084: inReplacePixelsNest = true;
3085: }
3086: }
3087:
3088: private Raster subsample(Raster raster, int[] sourceBands,
3089: int subOriginX, int subOriginY, int subPeriodX,
3090: int subPeriodY, int dstOffsetX, int dstOffsetY,
3091: Rectangle target) {
3092:
3093: int x = raster.getMinX();
3094: int y = raster.getMinY();
3095: int w = raster.getWidth();
3096: int h = raster.getHeight();
3097: int b = raster.getSampleModel().getNumBands();
3098: int t = raster.getSampleModel().getDataType();
3099:
3100: int outMinX = XToTileX(x, subOriginX, subPeriodX) + dstOffsetX;
3101: int outMinY = YToTileY(y, subOriginY, subPeriodY) + dstOffsetY;
3102: int outMaxX = XToTileX(x + w - 1, subOriginX, subPeriodX)
3103: + dstOffsetX;
3104: int outMaxY = YToTileY(y + h - 1, subOriginY, subPeriodY)
3105: + dstOffsetY;
3106: int outWidth = outMaxX - outMinX + 1;
3107: int outHeight = outMaxY - outMinY + 1;
3108:
3109: if (outWidth <= 0 || outHeight <= 0)
3110: return null;
3111:
3112: int inMinX = (outMinX - dstOffsetX) * subPeriodX + subOriginX;
3113: int inMaxX = (outMaxX - dstOffsetX) * subPeriodX + subOriginX;
3114: int inWidth = inMaxX - inMinX + 1;
3115: int inMinY = (outMinY - dstOffsetY) * subPeriodY + subOriginY;
3116: int inMaxY = (outMaxY - dstOffsetY) * subPeriodY + subOriginY;
3117: int inHeight = inMaxY - inMinY + 1;
3118:
3119: WritableRaster wr = raster.createCompatibleWritableRaster(
3120: outMinX, outMinY, outWidth, outHeight);
3121:
3122: int jMax = inMinY + inHeight;
3123:
3124: if (t == DataBuffer.TYPE_FLOAT || t == DataBuffer.TYPE_DOUBLE) {
3125: float[] fsamples = new float[inWidth];
3126: float[] fsubsamples = new float[outWidth];
3127:
3128: for (int k = 0; k < b; k++) {
3129: int outY = outMinY;
3130: for (int j = inMinY; j < jMax; j += subPeriodY) {
3131: raster.getSamples(inMinX, j, inWidth, 1, k,
3132: fsamples);
3133: int s = 0;
3134: for (int i = 0; i < inWidth; i += subPeriodX) {
3135: fsubsamples[s++] = fsamples[i];
3136: }
3137: wr.setSamples(outMinX, outY++, outWidth, 1, k,
3138: fsubsamples);
3139: }
3140: }
3141: } else {
3142: int[] samples = new int[inWidth];
3143: int[] subsamples = new int[outWidth];
3144:
3145: for (int k = 0; k < b; k++) {
3146: int outY = outMinY;
3147: for (int j = inMinY; j < jMax; j += subPeriodY) {
3148: raster
3149: .getSamples(inMinX, j, inWidth, 1, k,
3150: samples);
3151: int s = 0;
3152: for (int i = 0; i < inWidth; i += subPeriodX) {
3153: subsamples[s++] = samples[i];
3154: }
3155: wr.setSamples(outMinX, outY++, outWidth, 1, k,
3156: subsamples);
3157: }
3158: }
3159: }
3160:
3161: return wr.createChild(outMinX, outMinY, target.width,
3162: target.height, target.x, target.y, sourceBands);
3163: }
3164:
3165: public void replacePixels(RenderedImage image, ImageWriteParam param)
3166: throws IOException {
3167:
3168: synchronized (replacePixelsLock) {
3169: // Check state and parameters vis-a-vis ImageWriter specification.
3170: if (stream == null) {
3171: throw new IllegalStateException("stream == null!");
3172: }
3173:
3174: if (image == null) {
3175: throw new IllegalArgumentException("image == null!");
3176: }
3177:
3178: if (!inReplacePixelsNest) {
3179: throw new IllegalStateException(
3180: "No previous call to prepareReplacePixels!");
3181: }
3182:
3183: // Subsampling values.
3184: int stepX = 1, stepY = 1, gridX = 0, gridY = 0;
3185:
3186: // Initialize the ImageWriteParam.
3187: if (param == null) {
3188: // Use the default.
3189: param = getDefaultWriteParam();
3190: } else {
3191: // Make a copy of the ImageWriteParam.
3192: ImageWriteParam paramCopy = getDefaultWriteParam();
3193:
3194: // Force uncompressed.
3195: paramCopy
3196: .setCompressionMode(ImageWriteParam.MODE_DISABLED);
3197:
3198: // Force tiling to remain as in the already written image.
3199: paramCopy
3200: .setTilingMode(ImageWriteParam.MODE_COPY_FROM_METADATA);
3201:
3202: // Retain source and destination region and band settings.
3203: paramCopy.setDestinationOffset(param
3204: .getDestinationOffset());
3205: paramCopy.setSourceBands(param.getSourceBands());
3206: paramCopy.setSourceRegion(param.getSourceRegion());
3207:
3208: // Save original subsampling values for subsampling the
3209: // replacement data - not the data re-read from the image.
3210: stepX = param.getSourceXSubsampling();
3211: stepY = param.getSourceYSubsampling();
3212: gridX = param.getSubsamplingXOffset();
3213: gridY = param.getSubsamplingYOffset();
3214:
3215: // Replace the param.
3216: param = paramCopy;
3217: }
3218:
3219: // Check band count and bit depth compatibility.
3220: TIFFField f = replacePixelsMetadata
3221: .getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE);
3222: if (f == null) {
3223: throw new IIOException(
3224: "Cannot read destination BitsPerSample");
3225: }
3226: int[] dstBitsPerSample = f.getAsInts();
3227: int[] srcBitsPerSample = image.getSampleModel()
3228: .getSampleSize();
3229: int[] sourceBands = param.getSourceBands();
3230: if (sourceBands != null) {
3231: if (sourceBands.length != dstBitsPerSample.length) {
3232: throw new IIOException(
3233: "Source and destination have different SamplesPerPixel");
3234: }
3235: for (int i = 0; i < sourceBands.length; i++) {
3236: if (dstBitsPerSample[i] != srcBitsPerSample[sourceBands[i]]) {
3237: throw new IIOException(
3238: "Source and destination have different BitsPerSample");
3239: }
3240: }
3241: } else {
3242: int srcNumBands = image.getSampleModel().getNumBands();
3243: if (srcNumBands != dstBitsPerSample.length) {
3244: throw new IIOException(
3245: "Source and destination have different SamplesPerPixel");
3246: }
3247: for (int i = 0; i < srcNumBands; i++) {
3248: if (dstBitsPerSample[i] != srcBitsPerSample[i]) {
3249: throw new IIOException(
3250: "Source and destination have different BitsPerSample");
3251: }
3252: }
3253: }
3254:
3255: // Get the source image bounds.
3256: Rectangle srcImageBounds = new Rectangle(image.getMinX(),
3257: image.getMinY(), image.getWidth(), image
3258: .getHeight());
3259:
3260: // Initialize the source rect.
3261: Rectangle srcRect = param.getSourceRegion();
3262: if (srcRect == null) {
3263: srcRect = srcImageBounds;
3264: }
3265:
3266: // Set subsampling grid parameters.
3267: int subPeriodX = stepX;
3268: int subPeriodY = stepY;
3269: int subOriginX = gridX + srcRect.x;
3270: int subOriginY = gridY + srcRect.y;
3271:
3272: // Intersect with the source bounds.
3273: if (!srcRect.equals(srcImageBounds)) {
3274: srcRect = srcRect.intersection(srcImageBounds);
3275: if (srcRect.isEmpty()) {
3276: throw new IllegalArgumentException(
3277: "Source region does not intersect source image!");
3278: }
3279: }
3280:
3281: // Get the destination offset.
3282: Point dstOffset = param.getDestinationOffset();
3283:
3284: // Forward map source rectangle to determine destination width.
3285: int dMinX = XToTileX(srcRect.x, subOriginX, subPeriodX)
3286: + dstOffset.x;
3287: int dMinY = YToTileY(srcRect.y, subOriginY, subPeriodY)
3288: + dstOffset.y;
3289: int dMaxX = XToTileX(srcRect.x + srcRect.width, subOriginX,
3290: subPeriodX)
3291: + dstOffset.x;
3292: int dMaxY = YToTileY(srcRect.y + srcRect.height,
3293: subOriginY, subPeriodY)
3294: + dstOffset.y;
3295:
3296: // Initialize the destination rectangle.
3297: Rectangle dstRect = new Rectangle(dstOffset.x, dstOffset.y,
3298: dMaxX - dMinX, dMaxY - dMinY);
3299:
3300: // Intersect with the replacement region.
3301: dstRect = dstRect.intersection(replacePixelsRegion);
3302: if (dstRect.isEmpty()) {
3303: throw new IllegalArgumentException(
3304: "Forward mapped source region does not intersect destination region!");
3305: }
3306:
3307: // Backward map to the active source region.
3308: int activeSrcMinX = (dstRect.x - dstOffset.x) * subPeriodX
3309: + subOriginX;
3310: int sxmax = (dstRect.x + dstRect.width - 1 - dstOffset.x)
3311: * subPeriodX + subOriginX;
3312: int activeSrcWidth = sxmax - activeSrcMinX + 1;
3313:
3314: int activeSrcMinY = (dstRect.y - dstOffset.y) * subPeriodY
3315: + subOriginY;
3316: int symax = (dstRect.y + dstRect.height - 1 - dstOffset.y)
3317: * subPeriodY + subOriginY;
3318: int activeSrcHeight = symax - activeSrcMinY + 1;
3319: Rectangle activeSrcRect = new Rectangle(activeSrcMinX,
3320: activeSrcMinY, activeSrcWidth, activeSrcHeight);
3321: if (activeSrcRect.intersection(srcImageBounds).isEmpty()) {
3322: throw new IllegalArgumentException(
3323: "Backward mapped destination region does not intersect source image!");
3324: }
3325:
3326: if (reader == null) {
3327: reader = new TIFFImageReader(new TIFFImageReaderSpi());
3328: } else {
3329: reader.reset();
3330: }
3331:
3332: stream.mark();
3333:
3334: try {
3335: stream.seek(headerPosition);
3336: reader.setInput(stream);
3337:
3338: this .imageMetadata = replacePixelsMetadata;
3339: this .param = param;
3340: SampleModel sm = image.getSampleModel();
3341: ColorModel cm = image.getColorModel();
3342: this .numBands = sm.getNumBands();
3343: this .imageType = new ImageTypeSpecifier(image);
3344: this .periodX = param.getSourceXSubsampling();
3345: this .periodY = param.getSourceYSubsampling();
3346: this .sourceBands = null;
3347: int[] sBands = param.getSourceBands();
3348: if (sBands != null) {
3349: this .sourceBands = sBands;
3350: this .numBands = sourceBands.length;
3351: }
3352: setupMetadata(cm, sm, reader
3353: .getWidth(replacePixelsIndex), reader
3354: .getHeight(replacePixelsIndex));
3355: int[] scaleSampleSize = sm.getSampleSize();
3356: initializeScaleTables(scaleSampleSize);
3357:
3358: // Determine whether bilevel.
3359: this .isBilevel = ImageUtil.isBinary(image
3360: .getSampleModel());
3361:
3362: // Check for photometric inversion.
3363: this .isInverted = (nativePhotometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO && photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO)
3364: || (nativePhotometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO && photometricInterpretation == BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO);
3365:
3366: // Analyze image data suitability for direct copy.
3367: this .isImageSimple = (isBilevel || (!isInverted && ImageUtil
3368: .imageIsContiguous(image)))
3369: && !isRescaling && // no value rescaling
3370: sourceBands == null && // no subbanding
3371: periodX == 1 && periodY == 1 && // no subsampling
3372: colorConverter == null;
3373:
3374: int minTileX = XToTileX(dstRect.x, 0, tileWidth);
3375: int minTileY = YToTileY(dstRect.y, 0, tileLength);
3376: int maxTileX = XToTileX(dstRect.x + dstRect.width - 1,
3377: 0, tileWidth);
3378: int maxTileY = YToTileY(dstRect.y + dstRect.height - 1,
3379: 0, tileLength);
3380:
3381: TIFFCompressor encoder = new TIFFNullCompressor();
3382: encoder.setWriter(this );
3383: encoder.setStream(stream);
3384: encoder.setMetadata(this .imageMetadata);
3385:
3386: Rectangle tileRect = new Rectangle();
3387: for (int ty = minTileY; ty <= maxTileY; ty++) {
3388: for (int tx = minTileX; tx <= maxTileX; tx++) {
3389: int tileIndex = ty * tilesAcross + tx;
3390: boolean isEmpty = replacePixelsByteCounts[tileIndex] == 0L;
3391: WritableRaster raster;
3392: if (isEmpty) {
3393: SampleModel tileSM = sm
3394: .createCompatibleSampleModel(
3395: tileWidth, tileLength);
3396: raster = Raster.createWritableRaster(
3397: tileSM, null);
3398: } else {
3399: BufferedImage tileImage = reader.readTile(
3400: replacePixelsIndex, tx, ty);
3401: raster = tileImage.getRaster();
3402: }
3403:
3404: tileRect.setLocation(tx * tileWidth, ty
3405: * tileLength);
3406: tileRect.setSize(raster.getWidth(), raster
3407: .getHeight());
3408: raster = raster.createWritableTranslatedChild(
3409: tileRect.x, tileRect.y);
3410:
3411: Rectangle replacementRect = tileRect
3412: .intersection(dstRect);
3413:
3414: int srcMinX = (replacementRect.x - dstOffset.x)
3415: * subPeriodX + subOriginX;
3416: int srcXmax = (replacementRect.x
3417: + replacementRect.width - 1 - dstOffset.x)
3418: * subPeriodX + subOriginX;
3419: int srcWidth = srcXmax - srcMinX + 1;
3420:
3421: int srcMinY = (replacementRect.y - dstOffset.y)
3422: * subPeriodY + subOriginY;
3423: int srcYMax = (replacementRect.y
3424: + replacementRect.height - 1 - dstOffset.y)
3425: * subPeriodY + subOriginY;
3426: int srcHeight = srcYMax - srcMinY + 1;
3427: Rectangle srcTileRect = new Rectangle(srcMinX,
3428: srcMinY, srcWidth, srcHeight);
3429:
3430: Raster replacementData = image
3431: .getData(srcTileRect);
3432: if (subPeriodX == 1 && subPeriodY == 1
3433: && subOriginX == 0 && subOriginY == 0) {
3434: replacementData = replacementData
3435: .createChild(srcTileRect.x,
3436: srcTileRect.y,
3437: srcTileRect.width,
3438: srcTileRect.height,
3439: replacementRect.x,
3440: replacementRect.y,
3441: sourceBands);
3442: } else {
3443: replacementData = subsample(
3444: replacementData, sourceBands,
3445: subOriginX, subOriginY, subPeriodX,
3446: subPeriodY, dstOffset.x,
3447: dstOffset.y, replacementRect);
3448: if (replacementData == null) {
3449: continue;
3450: }
3451: }
3452:
3453: raster.setRect(replacementData);
3454:
3455: if (isEmpty) {
3456: stream.seek(nextSpace);
3457: } else {
3458: stream
3459: .seek(replacePixelsTileOffsets[tileIndex]);
3460: }
3461:
3462: this .image = new SingleTileRenderedImage(
3463: raster, cm);
3464:
3465: int numBytes = writeTile(tileRect, encoder);
3466:
3467: if (isEmpty) {
3468: // Update Strip/TileOffsets and
3469: // Strip/TileByteCounts fields.
3470: stream.mark();
3471: stream.seek(replacePixelsOffsetsPosition
3472: + 4 * tileIndex);
3473: stream.writeInt((int) nextSpace);
3474: stream.seek(replacePixelsByteCountsPosition
3475: + 4 * tileIndex);
3476: stream.writeInt(numBytes);
3477: stream.reset();
3478:
3479: // Increment location of next available space.
3480: nextSpace += numBytes;
3481: }
3482: }
3483: }
3484:
3485: } catch (IOException e) {
3486: throw e;
3487: } finally {
3488: stream.reset();
3489: }
3490: }
3491: }
3492:
3493: public void replacePixels(Raster raster, ImageWriteParam param)
3494: throws IOException {
3495: if (raster == null) {
3496: throw new IllegalArgumentException("raster == null!");
3497: }
3498:
3499: replacePixels(new SingleTileRenderedImage(raster, image
3500: .getColorModel()), param);
3501: }
3502:
3503: public void endReplacePixels() throws IOException {
3504: synchronized (replacePixelsLock) {
3505: if (!this .inReplacePixelsNest) {
3506: throw new IllegalStateException(
3507: "No previous call to prepareReplacePixels()!");
3508: }
3509: replacePixelsIndex = -1;
3510: replacePixelsMetadata = null;
3511: replacePixelsTileOffsets = null;
3512: replacePixelsByteCounts = null;
3513: replacePixelsOffsetsPosition = 0L;
3514: replacePixelsByteCountsPosition = 0L;
3515: replacePixelsRegion = null;
3516: inReplacePixelsNest = false;
3517: }
3518: }
3519:
3520: // ----- END replacePixels methods -----
3521:
3522: public void reset() {
3523: super .reset();
3524:
3525: stream = null;
3526: image = null;
3527: imageType = null;
3528: byteOrder = null;
3529: param = null;
3530: compressor = null;
3531: colorConverter = null;
3532: streamMetadata = null;
3533: imageMetadata = null;
3534:
3535: isWritingSequence = false;
3536: isWritingEmpty = false;
3537: isInsertingEmpty = false;
3538:
3539: replacePixelsIndex = -1;
3540: replacePixelsMetadata = null;
3541: replacePixelsTileOffsets = null;
3542: replacePixelsByteCounts = null;
3543: replacePixelsOffsetsPosition = 0L;
3544: replacePixelsByteCountsPosition = 0L;
3545: replacePixelsRegion = null;
3546: inReplacePixelsNest = false;
3547: }
3548:
3549: public void dispose() {
3550: reset();
3551: super .dispose();
3552: }
3553: }
3554:
3555: class EmptyImage extends SimpleRenderedImage {
3556: EmptyImage(int minX, int minY, int width, int height,
3557: int tileGridXOffset, int tileGridYOffset, int tileWidth,
3558: int tileHeight, SampleModel sampleModel,
3559: ColorModel colorModel) {
3560: this .minX = minX;
3561: this .minY = minY;
3562: this .width = width;
3563: this .height = height;
3564: this .tileGridXOffset = tileGridXOffset;
3565: this .tileGridYOffset = tileGridYOffset;
3566: this .tileWidth = tileWidth;
3567: this .tileHeight = tileHeight;
3568: this .sampleModel = sampleModel;
3569: this .colorModel = colorModel;
3570: }
3571:
3572: public Raster getTile(int tileX, int tileY) {
3573: return null;
3574: }
3575: }
|