001/* 002 * $RCSfile: TIFFImageWriter.java,v $ 003 * 004 * 005 * Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved. 006 * 007 * Redistribution and use in source and binary forms, with or without 008 * modification, are permitted provided that the following conditions 009 * are met: 010 * 011 * - Redistribution of source code must retain the above copyright 012 * notice, this list of conditions and the following disclaimer. 013 * 014 * - Redistribution in binary form must reproduce the above copyright 015 * notice, this list of conditions and the following disclaimer in 016 * the documentation and/or other materials provided with the 017 * distribution. 018 * 019 * Neither the name of Sun Microsystems, Inc. or the names of 020 * contributors may be used to endorse or promote products derived 021 * from this software without specific prior written permission. 022 * 023 * This software is provided "AS IS," without a warranty of any 024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY 027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS 030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, 032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND 033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR 034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE 035 * POSSIBILITY OF SUCH DAMAGES. 036 * 037 * You acknowledge that this software is not designed or intended for 038 * use in the design, construction, operation or maintenance of any 039 * nuclear facility. 040 * 041 * $Revision: 1.24 $ 042 * $Date: 2007/09/01 00:27:20 $ 043 * $State: Exp $ 044 */ 045package com.github.jaiimageio.impl.plugins.tiff; 046 047import java.awt.Point; 048import java.awt.Rectangle; 049import java.awt.color.ColorSpace; 050import java.awt.color.ICC_ColorSpace; 051import java.awt.image.BufferedImage; 052import java.awt.image.ColorModel; 053import java.awt.image.ComponentSampleModel; 054import java.awt.image.DataBuffer; 055import java.awt.image.DataBufferByte; 056import java.awt.image.IndexColorModel; 057import java.awt.image.Raster; 058import java.awt.image.RenderedImage; 059import java.awt.image.SampleModel; 060import java.awt.image.WritableRaster; 061import java.io.EOFException; 062import java.io.IOException; 063import java.nio.ByteOrder; 064import java.util.ArrayList; 065import java.util.Arrays; 066import java.util.List; 067 068import javax.imageio.IIOException; 069import javax.imageio.IIOImage; 070import javax.imageio.ImageTypeSpecifier; 071import javax.imageio.ImageWriteParam; 072import javax.imageio.ImageWriter; 073import javax.imageio.metadata.IIOInvalidTreeException; 074import javax.imageio.metadata.IIOMetadata; 075import javax.imageio.metadata.IIOMetadataFormatImpl; 076import javax.imageio.spi.ImageWriterSpi; 077import javax.imageio.stream.ImageOutputStream; 078 079import org.w3c.dom.Node; 080 081import com.github.jaiimageio.impl.common.ImageUtil; 082import com.github.jaiimageio.impl.common.SimpleRenderedImage; 083import com.github.jaiimageio.impl.common.SingleTileRenderedImage; 084import com.github.jaiimageio.plugins.tiff.BaselineTIFFTagSet; 085import com.github.jaiimageio.plugins.tiff.EXIFParentTIFFTagSet; 086import com.github.jaiimageio.plugins.tiff.EXIFTIFFTagSet; 087import com.github.jaiimageio.plugins.tiff.TIFFColorConverter; 088import com.github.jaiimageio.plugins.tiff.TIFFCompressor; 089import com.github.jaiimageio.plugins.tiff.TIFFField; 090import com.github.jaiimageio.plugins.tiff.TIFFImageWriteParam; 091import com.github.jaiimageio.plugins.tiff.TIFFTag; 092import com.github.jaiimageio.plugins.tiff.TIFFTagSet; 093 094public class TIFFImageWriter extends ImageWriter { 095 096 private static final boolean DEBUG = false; // XXX false for release! 097 098 static final String EXIF_JPEG_COMPRESSION_TYPE = "EXIF JPEG"; 099 100 public static final int DEFAULT_BYTES_PER_STRIP = 8192; 101 102 /** 103 * Supported TIFF compression types. 104 */ 105 public static final String[] TIFFCompressionTypes = { 106 "CCITT RLE", 107 "CCITT T.4", 108 "CCITT T.6", 109 "LZW", 110 // "Old JPEG", 111 "JPEG", 112 "ZLib", 113 "PackBits", 114 "Deflate", 115 EXIF_JPEG_COMPRESSION_TYPE 116 }; 117 118 // 119 // !!! The lengths of the arrays 'compressionTypes', 120 // !!! 'isCompressionLossless', and 'compressionNumbers' 121 // !!! must be equal. 122 // 123 124 /** 125 * Known TIFF compression types. 126 */ 127 public static final String[] compressionTypes = { 128 "CCITT RLE", 129 "CCITT T.4", 130 "CCITT T.6", 131 "LZW", 132 "Old JPEG", 133 "JPEG", 134 "ZLib", 135 "PackBits", 136 "Deflate", 137 EXIF_JPEG_COMPRESSION_TYPE 138 }; 139 140 /** 141 * Lossless flag for known compression types. 142 */ 143 public static final boolean[] isCompressionLossless = { 144 true, // RLE 145 true, // T.4 146 true, // T.6 147 true, // LZW 148 false, // Old JPEG 149 false, // JPEG 150 true, // ZLib 151 true, // PackBits 152 true, // DEFLATE 153 false // EXIF JPEG 154 }; 155 156 /** 157 * Compression tag values for known compression types. 158 */ 159 public static final int[] compressionNumbers = { 160 BaselineTIFFTagSet.COMPRESSION_CCITT_RLE, 161 BaselineTIFFTagSet.COMPRESSION_CCITT_T_4, 162 BaselineTIFFTagSet.COMPRESSION_CCITT_T_6, 163 BaselineTIFFTagSet.COMPRESSION_LZW, 164 BaselineTIFFTagSet.COMPRESSION_OLD_JPEG, 165 BaselineTIFFTagSet.COMPRESSION_JPEG, 166 BaselineTIFFTagSet.COMPRESSION_ZLIB, 167 BaselineTIFFTagSet.COMPRESSION_PACKBITS, 168 BaselineTIFFTagSet.COMPRESSION_DEFLATE, 169 BaselineTIFFTagSet.COMPRESSION_OLD_JPEG, // EXIF JPEG 170 }; 171 172 ImageOutputStream stream; 173 long headerPosition; 174 RenderedImage image; 175 ImageTypeSpecifier imageType; 176 ByteOrder byteOrder; 177 ImageWriteParam param; 178 TIFFCompressor compressor; 179 TIFFColorConverter colorConverter; 180 181 TIFFStreamMetadata streamMetadata; 182 TIFFImageMetadata imageMetadata; 183 184 int sourceXOffset; 185 int sourceYOffset; 186 int sourceWidth; 187 int sourceHeight; 188 int[] sourceBands; 189 int periodX; 190 int periodY; 191 192 int bitDepth; // bits per channel 193 int numBands; 194 int tileWidth; 195 int tileLength; 196 int tilesAcross; 197 int tilesDown; 198 199 int[] sampleSize = null; // Input sample size per band, in bits 200 int scalingBitDepth = -1; // Output bit depth of the scaling tables 201 boolean isRescaling = false; // Whether rescaling is needed. 202 203 boolean isBilevel; // Whether image is bilevel 204 boolean isImageSimple; // Whether image can be copied into directly 205 boolean isInverted; // Whether photometric inversion is required 206 207 boolean isTiled; // Whether the image is tiled (true) or stipped (false). 208 209 int nativePhotometricInterpretation; 210 int photometricInterpretation; 211 212 char[] bitsPerSample; // Output sample size per band 213 int sampleFormat = 214 BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED; // Output sample format 215 216 // Tables for 1, 2, 4, or 8 bit output 217 byte[][] scale = null; // 8 bit table 218 byte[] scale0 = null; // equivalent to scale[0] 219 220 // Tables for 16 bit output 221 byte[][] scaleh = null; // High bytes of output 222 byte[][] scalel = null; // Low bytes of output 223 224 int compression; 225 int predictor; 226 227 int totalPixels; 228 int pixelsDone; 229 230 long nextIFDPointerPos; 231 232 // Next available space. 233 long nextSpace = 0L; 234 235 // Whether a sequence is being written. 236 boolean isWritingSequence = false; 237 238 /** 239 * Converts a pixel's X coordinate into a horizontal tile index 240 * relative to a given tile grid layout specified by its X offset 241 * and tile width. 242 * 243 * <p> If <code>tileWidth < 0</code>, the results of this method 244 * are undefined. If <code>tileWidth == 0</code>, an 245 * <code>ArithmeticException</code> will be thrown. 246 * 247 * @throws ArithmeticException If <code>tileWidth == 0</code>. 248 */ 249 public static int XToTileX(int x, int tileGridXOffset, int tileWidth) { 250 x -= tileGridXOffset; 251 if (x < 0) { 252 x += 1 - tileWidth; // force round to -infinity (ceiling) 253 } 254 return x/tileWidth; 255 } 256 257 /** 258 * Converts a pixel's Y coordinate into a vertical tile index 259 * relative to a given tile grid layout specified by its Y offset 260 * and tile height. 261 * 262 * <p> If <code>tileHeight < 0</code>, the results of this method 263 * are undefined. If <code>tileHeight == 0</code>, an 264 * <code>ArithmeticException</code> will be thrown. 265 * 266 * @throws ArithmeticException If <code>tileHeight == 0</code>. 267 */ 268 public static int YToTileY(int y, int tileGridYOffset, int tileHeight) { 269 y -= tileGridYOffset; 270 if (y < 0) { 271 y += 1 - tileHeight; // force round to -infinity (ceiling) 272 } 273 return y/tileHeight; 274 } 275 276 public TIFFImageWriter(ImageWriterSpi originatingProvider) { 277 super(originatingProvider); 278 } 279 280 public ImageWriteParam getDefaultWriteParam() { 281 return new TIFFImageWriteParam(getLocale()); 282 } 283 284 public void setOutput(Object output) { 285 super.setOutput(output); 286 287 if (output != null) { 288 if (!(output instanceof ImageOutputStream)) { 289 throw new IllegalArgumentException 290 ("output not an ImageOutputStream!"); 291 } 292 this.stream = (ImageOutputStream)output; 293 294 // 295 // The output is expected to be positioned at a TIFF header 296 // or at some arbitrary location which may or may not be 297 // the EOF. In the former case the writer should be able 298 // either to overwrite the existing sequence or append to it. 299 // 300 301 // Set the position of the header and the next available space. 302 try { 303 headerPosition = this.stream.getStreamPosition(); 304 try { 305 // Read byte order and magic number. 306 byte[] b = new byte[4]; 307 stream.readFully(b); 308 309 // Check bytes for TIFF header. 310 if((b[0] == (byte)0x49 && b[1] == (byte)0x49 && 311 b[2] == (byte)0x2a && b[3] == (byte)0x00) || 312 (b[0] == (byte)0x4d && b[1] == (byte)0x4d && 313 b[2] == (byte)0x00 && b[3] == (byte)0x2a)) { 314 // TIFF header. 315 this.nextSpace = stream.length(); 316 } else { 317 // Neither TIFF header nor EOF: overwrite. 318 this.nextSpace = headerPosition; 319 } 320 } catch(IOException io) { // thrown by readFully() 321 // At EOF or not at a TIFF header. 322 this.nextSpace = headerPosition; 323 } 324 stream.seek(headerPosition); 325 } catch(IOException ioe) { // thrown by getStreamPosition() 326 // Assume it's at zero. 327 this.nextSpace = headerPosition = 0L; 328 } 329 } else { 330 this.stream = null; 331 } 332 } 333 334 public IIOMetadata 335 getDefaultStreamMetadata(ImageWriteParam param) { 336 return new TIFFStreamMetadata(); 337 } 338 339 public IIOMetadata 340 getDefaultImageMetadata(ImageTypeSpecifier imageType, 341 ImageWriteParam param) { 342 343 List tagSets = new ArrayList(1); 344 tagSets.add(BaselineTIFFTagSet.getInstance()); 345 // XXX Should add Fax/EXIF/GeoTIFF TagSets? 346 TIFFImageMetadata imageMetadata = new TIFFImageMetadata(tagSets); 347 348 if(imageType != null) { 349 TIFFImageMetadata im = 350 (TIFFImageMetadata)convertImageMetadata(imageMetadata, 351 imageType, 352 param); 353 if(im != null) { 354 imageMetadata = im; 355 } 356 } 357 358 return imageMetadata; 359 } 360 361 public IIOMetadata convertStreamMetadata(IIOMetadata inData, 362 ImageWriteParam param) { 363 // Check arguments. 364 if(inData == null) { 365 throw new IllegalArgumentException("inData == null!"); 366 } 367 368 // Note: param is irrelevant as it does not contain byte order. 369 370 TIFFStreamMetadata outData = null; 371 if(inData instanceof TIFFStreamMetadata) { 372 outData = new TIFFStreamMetadata(); 373 outData.byteOrder = ((TIFFStreamMetadata)inData).byteOrder; 374 return outData; 375 } else if(Arrays.asList(inData.getMetadataFormatNames()).contains( 376 TIFFStreamMetadata.nativeMetadataFormatName)) { 377 outData = new TIFFStreamMetadata(); 378 String format = TIFFStreamMetadata.nativeMetadataFormatName; 379 try { 380 outData.mergeTree(format, inData.getAsTree(format)); 381 } catch(IIOInvalidTreeException e) { 382 // XXX Warning 383 } 384 } 385 386 return outData; 387 } 388 389 public IIOMetadata 390 convertImageMetadata(IIOMetadata inData, 391 ImageTypeSpecifier imageType, 392 ImageWriteParam param) { 393 // Check arguments. 394 if(inData == null) { 395 throw new IllegalArgumentException("inData == null!"); 396 } 397 if(imageType == null) { 398 throw new IllegalArgumentException("imageType == null!"); 399 } 400 401 TIFFImageMetadata outData = null; 402 403 // Obtain a TIFFImageMetadata object. 404 if(inData instanceof TIFFImageMetadata) { 405 // Create a new metadata object from a clone of the input IFD. 406 TIFFIFD inIFD = ((TIFFImageMetadata)inData).getRootIFD(); 407 outData = new TIFFImageMetadata(inIFD.getShallowClone()); 408 } else if(Arrays.asList(inData.getMetadataFormatNames()).contains( 409 TIFFImageMetadata.nativeMetadataFormatName)) { 410 // Initialize from the native metadata form of the input tree. 411 try { 412 outData = convertNativeImageMetadata(inData); 413 } catch(IIOInvalidTreeException e) { 414 // XXX Warning 415 } 416 } else if(inData.isStandardMetadataFormatSupported()) { 417 // Initialize from the standard metadata form of the input tree. 418 try { 419 outData = convertStandardImageMetadata(inData); 420 } catch(IIOInvalidTreeException e) { 421 // XXX Warning 422 } 423 } 424 425 // Update the metadata per the image type and param. 426 if(outData != null) { 427 TIFFImageWriter bogusWriter = 428 new TIFFImageWriter(this.originatingProvider); 429 bogusWriter.imageMetadata = outData; 430 bogusWriter.param = param; 431 SampleModel sm = imageType.getSampleModel(); 432 try { 433 bogusWriter.setupMetadata(imageType.getColorModel(), sm, 434 sm.getWidth(), sm.getHeight()); 435 return bogusWriter.imageMetadata; 436 } catch(IIOException e) { 437 // XXX Warning 438 return null; 439 } finally { 440 bogusWriter.dispose(); 441 } 442 } 443 444 return outData; 445 } 446 447 /** 448 * Converts a standard <code>javax_imageio_1.0</code> tree to a 449 * <code>TIFFImageMetadata</code> object. 450 * 451 * @param inData The metadata object. 452 * @return a <code>TIFFImageMetadata</code> or <code>null</code> if 453 * the standard tree derived from the input object is <code>null</code>. 454 * @throws IllegalArgumentException if <code>inData</code> is 455 * <code>null</code> or does not support the standard metadata format. 456 * @throws IIOInvalidTreeException if <code>inData</code> generates an 457 * invalid standard metadata tree. 458 */ 459 private TIFFImageMetadata convertStandardImageMetadata(IIOMetadata inData) 460 throws IIOInvalidTreeException { 461 462 if(inData == null) { 463 throw new IllegalArgumentException("inData == null!"); 464 } else if(!inData.isStandardMetadataFormatSupported()) { 465 throw new IllegalArgumentException 466 ("inData does not support standard metadata format!"); 467 } 468 469 TIFFImageMetadata outData = null; 470 471 String formatName = IIOMetadataFormatImpl.standardMetadataFormatName; 472 Node tree = inData.getAsTree(formatName); 473 if (tree != null) { 474 List tagSets = new ArrayList(1); 475 tagSets.add(BaselineTIFFTagSet.getInstance()); 476 outData = new TIFFImageMetadata(tagSets); 477 outData.setFromTree(formatName, tree); 478 } 479 480 return outData; 481 } 482 483 /** 484 * Converts a native 485 * <code>com_sun_media_imageio_plugins_tiff_image_1.0</code> tree to a 486 * <code>TIFFImageMetadata</code> object. 487 * 488 * @param inData The metadata object. 489 * @return a <code>TIFFImageMetadata</code> or <code>null</code> if 490 * the native tree derived from the input object is <code>null</code>. 491 * @throws IllegalArgumentException if <code>inData</code> is 492 * <code>null</code> or does not support the native metadata format. 493 * @throws IIOInvalidTreeException if <code>inData</code> generates an 494 * invalid native metadata tree. 495 */ 496 private TIFFImageMetadata convertNativeImageMetadata(IIOMetadata inData) 497 throws IIOInvalidTreeException { 498 499 if(inData == null) { 500 throw new IllegalArgumentException("inData == null!"); 501 } else if(!Arrays.asList(inData.getMetadataFormatNames()).contains( 502 TIFFImageMetadata.nativeMetadataFormatName)) { 503 throw new IllegalArgumentException 504 ("inData does not support native metadata format!"); 505 } 506 507 TIFFImageMetadata outData = null; 508 509 String formatName = TIFFImageMetadata.nativeMetadataFormatName; 510 Node tree = inData.getAsTree(formatName); 511 if (tree != null) { 512 List tagSets = new ArrayList(1); 513 tagSets.add(BaselineTIFFTagSet.getInstance()); 514 outData = new TIFFImageMetadata(tagSets); 515 outData.setFromTree(formatName, tree); 516 } 517 518 return outData; 519 } 520 521 /** 522 * Sets up the output metadata adding, removing, and overriding fields 523 * as needed. The destination image dimensions are provided as parameters 524 * because these might differ from those of the source due to subsampling. 525 * 526 * @param cm The <code>ColorModel</code> of the image being written. 527 * @param sm The <code>SampleModel</code> of the image being written. 528 * @param destWidth The width of the written image after subsampling. 529 * @param destHeight The height of the written image after subsampling. 530 */ 531 void setupMetadata(ColorModel cm, SampleModel sm, 532 int destWidth, int destHeight) 533 throws IIOException { 534 // Get initial IFD from metadata 535 536 // Always emit these fields: 537 // 538 // Override values from metadata: 539 // 540 // planarConfiguration -> chunky (planar not supported on output) 541 // 542 // Override values from metadata with image-derived values: 543 // 544 // bitsPerSample (if not bilivel) 545 // colorMap (if palette color) 546 // photometricInterpretation (derive from image) 547 // imageLength 548 // imageWidth 549 // 550 // rowsPerStrip \ / tileLength 551 // stripOffsets | OR | tileOffsets 552 // stripByteCounts / | tileByteCounts 553 // \ tileWidth 554 // 555 // 556 // Override values from metadata with write param values: 557 // 558 // compression 559 560 // Use values from metadata if present for these fields, 561 // otherwise use defaults: 562 // 563 // resolutionUnit 564 // XResolution (take from metadata if present) 565 // YResolution 566 // rowsPerStrip 567 // sampleFormat 568 569 TIFFIFD rootIFD = imageMetadata.getRootIFD(); 570 571 BaselineTIFFTagSet base = BaselineTIFFTagSet.getInstance(); 572 573 // If PlanarConfiguration field present, set value to chunky. 574 575 TIFFField f = 576 rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION); 577 if(f != null && 578 f.getAsInt(0) != BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY) { 579 // XXX processWarningOccurred() 580 TIFFField planarConfigurationField = 581 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION), 582 BaselineTIFFTagSet.PLANAR_CONFIGURATION_CHUNKY); 583 rootIFD.addTIFFField(planarConfigurationField); 584 } 585 586 char[] extraSamples = null; 587 588 this.photometricInterpretation = -1; 589 boolean forcePhotometricInterpretation = false; 590 591 f = 592 rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION); 593 if (f != null) { 594 photometricInterpretation = f.getAsInt(0); 595 if(photometricInterpretation == 596 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR && 597 !(cm instanceof IndexColorModel)) { 598 photometricInterpretation = -1; 599 } else { 600 forcePhotometricInterpretation = true; 601 } 602 } 603 604// f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES); 605// if (f != null) { 606// extraSamples = f.getAsChars(); 607// } 608 609// f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); 610// if (f != null) { 611// bitsPerSample = f.getAsChars(); 612// } 613 614 int[] sampleSize = sm.getSampleSize(); 615 616 int numBands = sm.getNumBands(); 617 int numExtraSamples = 0; 618 619 // Check that numBands > 1 here because TIFF requires that 620 // SamplesPerPixel = numBands + numExtraSamples and numBands 621 // cannot be zero. 622 if (numBands > 1 && cm != null && cm.hasAlpha()) { 623 --numBands; 624 numExtraSamples = 1; 625 extraSamples = new char[1]; 626 if (cm.isAlphaPremultiplied()) { 627 extraSamples[0] = 628 BaselineTIFFTagSet.EXTRA_SAMPLES_ASSOCIATED_ALPHA; 629 } else { 630 extraSamples[0] = 631 BaselineTIFFTagSet.EXTRA_SAMPLES_UNASSOCIATED_ALPHA; 632 } 633 } 634 635 if (numBands == 3) { 636 this.nativePhotometricInterpretation = 637 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB; 638 if (photometricInterpretation == -1) { 639 photometricInterpretation = 640 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB; 641 } 642 } else if (sm.getNumBands() == 1 && cm instanceof IndexColorModel) { 643 IndexColorModel icm = (IndexColorModel)cm; 644 int r0 = icm.getRed(0); 645 int r1 = icm.getRed(1); 646 if (icm.getMapSize() == 2 && 647 (r0 == icm.getGreen(0)) && (r0 == icm.getBlue(0)) && 648 (r1 == icm.getGreen(1)) && (r1 == icm.getBlue(1)) && 649 (r0 == 0 || r0 == 255) && 650 (r1 == 0 || r1 == 255) && 651 (r0 != r1)) { 652 // Black/white image 653 654 if (r0 == 0) { 655 nativePhotometricInterpretation = 656 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO; 657 } else { 658 nativePhotometricInterpretation = 659 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO; 660 } 661 662 663 // If photometricInterpretation is already set to 664 // WhiteIsZero or BlackIsZero, leave it alone 665 if (photometricInterpretation != 666 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO && 667 photometricInterpretation != 668 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) { 669 photometricInterpretation = 670 r0 == 0 ? 671 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO : 672 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO; 673 } 674 } else { 675 nativePhotometricInterpretation = 676 photometricInterpretation = 677 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR; 678 } 679 } else { 680 if(cm != null) { 681 switch(cm.getColorSpace().getType()) { 682 case ColorSpace.TYPE_Lab: 683 nativePhotometricInterpretation = 684 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB; 685 break; 686 case ColorSpace.TYPE_YCbCr: 687 nativePhotometricInterpretation = 688 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR; 689 break; 690 case ColorSpace.TYPE_CMYK: 691 nativePhotometricInterpretation = 692 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CMYK; 693 break; 694 default: 695 nativePhotometricInterpretation = 696 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO; 697 } 698 } else { 699 nativePhotometricInterpretation = 700 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO; 701 } 702 if (photometricInterpretation == -1) { 703 photometricInterpretation = nativePhotometricInterpretation; 704 } 705 } 706 707 // Set the compressor and color converter. 708 709 this.compressor = null; 710 this.colorConverter = null; 711 if (param instanceof TIFFImageWriteParam) { 712 TIFFImageWriteParam tparam = (TIFFImageWriteParam)param; 713 if(tparam.getCompressionMode() == tparam.MODE_EXPLICIT) { 714 compressor = tparam.getTIFFCompressor(); 715 String compressionType = param.getCompressionType(); 716 if(compressor != null && 717 !compressor.getCompressionType().equals(compressionType)) { 718 // Unset the TIFFCompressor if its compression type is 719 // not the one selected. 720 compressor = null; 721 } 722 } else { 723 // Compression mode not MODE_EXPLICIT. 724 compressor = null; 725 } 726 colorConverter = tparam.getColorConverter(); 727 if (colorConverter != null) { 728 photometricInterpretation = 729 tparam.getPhotometricInterpretation(); 730 } 731 } 732 733 // Emit compression tag 734 735 int compressionMode = param instanceof TIFFImageWriteParam ? 736 param.getCompressionMode() : ImageWriteParam.MODE_DEFAULT; 737 switch(compressionMode) { 738 case ImageWriteParam.MODE_EXPLICIT: 739 { 740 String compressionType = param.getCompressionType(); 741 if (compressionType == null) { 742 this.compression = BaselineTIFFTagSet.COMPRESSION_NONE; 743 } else { 744 // Determine corresponding compression tag value. 745 int len = compressionTypes.length; 746 for (int i = 0; i < len; i++) { 747 if (compressionType.equals(compressionTypes[i])) { 748 this.compression = compressionNumbers[i]; 749 } 750 } 751 } 752 753 // Ensure the compressor, if any, matches compression setting 754 // with the precedence described in TIFFImageWriteParam. 755 if(compressor != null && 756 compressor.getCompressionTagValue() != this.compression) { 757 // Does not match: unset the compressor. 758 compressor = null; 759 } 760 } 761 break; 762 case ImageWriteParam.MODE_COPY_FROM_METADATA: 763 { 764 TIFFField compField = 765 rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION); 766 if(compField != null) { 767 this.compression = compField.getAsInt(0); 768 break; 769 } 770 } 771 case ImageWriteParam.MODE_DEFAULT: 772 case ImageWriteParam.MODE_DISABLED: 773 default: 774 this.compression = BaselineTIFFTagSet.COMPRESSION_NONE; 775 } 776 777 TIFFField predictorField = 778 rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_PREDICTOR); 779 if (predictorField != null) { 780 this.predictor = predictorField.getAsInt(0); 781 782 // We only support Horizontal Predictor for a bitDepth of 8 783 if (sampleSize[0] != 8 || 784 // Check the value of the tag for validity 785 (predictor != BaselineTIFFTagSet.PREDICTOR_NONE && 786 predictor != 787 BaselineTIFFTagSet.PREDICTOR_HORIZONTAL_DIFFERENCING)) { 788 // XXX processWarningOccured ??? 789 // Set to default 790 predictor = BaselineTIFFTagSet.PREDICTOR_NONE; 791 792 // Emit this changed predictor value to metadata 793 TIFFField newPredictorField = 794 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_PREDICTOR), 795 predictor); 796 rootIFD.addTIFFField(newPredictorField); 797 } 798 799 // XXX Do we need to ensure that predictor is not passed on if 800 // the compression is not either Deflate or LZW? 801 } 802 803 TIFFField compressionField = 804 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_COMPRESSION), 805 compression); 806 rootIFD.addTIFFField(compressionField); 807 808 // Set EXIF flag. Note that there is no way to determine definitively 809 // when an uncompressed thumbnail is being written as the EXIF IFD 810 // pointer field is optional for thumbnails. 811 boolean isEXIF = false; 812 if(numBands == 3 && 813 sampleSize[0] == 8 && sampleSize[1] == 8 && sampleSize[2] == 8) { 814 // Three bands with 8 bits per sample. 815 if(rootIFD.getTIFFField(EXIFParentTIFFTagSet.TAG_EXIF_IFD_POINTER) 816 != null) { 817 // EXIF IFD pointer present. 818 if(compression == BaselineTIFFTagSet.COMPRESSION_NONE && 819 (photometricInterpretation == 820 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB || 821 photometricInterpretation == 822 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR)) { 823 // Uncompressed RGB or YCbCr. 824 isEXIF = true; 825 } else if(compression == 826 BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { 827 // Compressed. 828 isEXIF = true; 829 } 830 } else if(compressionMode == ImageWriteParam.MODE_EXPLICIT && 831 EXIF_JPEG_COMPRESSION_TYPE.equals 832 (param.getCompressionType())) { 833 // EXIF IFD pointer absent but EXIF JPEG compression set. 834 isEXIF = true; 835 } 836 } 837 838 // Initialize JPEG interchange format flag which is used to 839 // indicate that the image is stored as a single JPEG stream. 840 // This flag is separated from the 'isEXIF' flag in case JPEG 841 // interchange format is eventually supported for non-EXIF images. 842 boolean isJPEGInterchange = 843 isEXIF && compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG; 844 845 if (compressor == null) { 846 if (compression == BaselineTIFFTagSet.COMPRESSION_CCITT_RLE) { 847 848 if(compressor == null) { 849 compressor = new TIFFRLECompressor(); 850 if(DEBUG) { 851 System.out.println("Using Java RLE compressor"); 852 } 853 } 854 855 if (!forcePhotometricInterpretation) { 856 photometricInterpretation = 857 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO; 858 } 859 } else if (compression == 860 BaselineTIFFTagSet.COMPRESSION_CCITT_T_4) { 861 862 863 if(compressor == null) { 864 compressor = new TIFFT4Compressor(); 865 if(DEBUG) { 866 System.out.println("Using Java T.4 compressor"); 867 } 868 } 869 870 if (!forcePhotometricInterpretation) { 871 photometricInterpretation = 872 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO; 873 } 874 } else if (compression == 875 BaselineTIFFTagSet.COMPRESSION_CCITT_T_6) { 876 877 878 if(compressor == null) { 879 compressor = new TIFFT6Compressor(); 880 if(DEBUG) { 881 System.out.println("Using Java T.6 compressor"); 882 } 883 } 884 885 if (!forcePhotometricInterpretation) { 886 photometricInterpretation = 887 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO; 888 } 889 } else if (compression == 890 BaselineTIFFTagSet.COMPRESSION_LZW) { 891 compressor = new TIFFLZWCompressor(predictor); 892 } else if (compression == 893 BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { 894 if(isEXIF) { 895 compressor = new TIFFEXIFJPEGCompressor(param); 896 } else { 897 throw new IIOException 898 ("Old JPEG compression not supported!"); 899 } 900 } else if (compression == 901 BaselineTIFFTagSet.COMPRESSION_JPEG) { 902 if(numBands == 3 && sampleSize[0] == 8 && 903 sampleSize[1] == 8 && sampleSize[2] == 8) { 904 photometricInterpretation = 905 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR; 906 } else if(numBands == 1 && sampleSize[0] == 8) { 907 photometricInterpretation = 908 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO; 909 } else { 910 throw new IIOException 911 ("JPEG compression supported for 1- and 3-band byte images only!"); 912 } 913 compressor = new TIFFJPEGCompressor(param); 914 } else if (compression == 915 BaselineTIFFTagSet.COMPRESSION_ZLIB) { 916 compressor = new TIFFZLibCompressor(param, predictor); 917 } else if (compression == 918 BaselineTIFFTagSet.COMPRESSION_PACKBITS) { 919 compressor = new TIFFPackBitsCompressor(); 920 } else if (compression == 921 BaselineTIFFTagSet.COMPRESSION_DEFLATE) { 922 compressor = new TIFFDeflateCompressor(param, predictor); 923 } else { 924 // Determine inverse fill setting. 925 f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_FILL_ORDER); 926 boolean inverseFill = (f != null && f.getAsInt(0) == 2); 927 928 if(inverseFill) { 929 compressor = new TIFFLSBCompressor(); 930 } else { 931 compressor = new TIFFNullCompressor(); 932 } 933 } // compression == ? 934 } // compressor == null 935 936 if(DEBUG) { 937 if(param != null && 938 param.getCompressionMode() == param.MODE_EXPLICIT) { 939 System.out.println("compressionType = "+ 940 param.getCompressionType()); 941 } 942 if(compressor != null) { 943 System.out.println("compressor = "+ 944 compressor.getClass().getName()); 945 } 946 } 947 948 if (colorConverter == null) { 949 if(cm != null && 950 cm.getColorSpace().getType() == ColorSpace.TYPE_RGB) { 951 // 952 // Perform color conversion only if image has RGB color space. 953 // 954 if (photometricInterpretation == 955 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR && 956 compression != 957 BaselineTIFFTagSet.COMPRESSION_JPEG) { 958 // 959 // Convert RGB to YCbCr only if compression type is not 960 // JPEG in which case this is handled implicitly by the 961 // compressor. 962 // 963 colorConverter = 964 new TIFFYCbCrColorConverter(imageMetadata); 965 } else if (photometricInterpretation == 966 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_CIELAB) { 967 colorConverter = new TIFFCIELabColorConverter(); 968 } 969 } 970 } 971 972 // 973 // Cannot at this time do YCbCr subsampling so set the 974 // YCbCrSubsampling field value to [1, 1] and the YCbCrPositioning 975 // field value to "cosited". 976 // 977 if(photometricInterpretation == 978 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_Y_CB_CR && 979 compression != 980 BaselineTIFFTagSet.COMPRESSION_JPEG) { 981 // Remove old subsampling and positioning fields. 982 rootIFD.removeTIFFField 983 (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING); 984 rootIFD.removeTIFFField 985 (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING); 986 987 // Add unity chrominance subsampling factors. 988 rootIFD.addTIFFField 989 (new TIFFField 990 (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING), 991 TIFFTag.TIFF_SHORT, 992 2, 993 new char[] {(char)1, (char)1})); 994 995 // Add cosited positioning. 996 rootIFD.addTIFFField 997 (new TIFFField 998 (base.getTag(BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING), 999 TIFFTag.TIFF_SHORT, 1000 1, 1001 new char[] { 1002 (char)BaselineTIFFTagSet.Y_CB_CR_POSITIONING_COSITED 1003 })); 1004 } 1005 1006 TIFFField photometricInterpretationField = 1007 new TIFFField( 1008 base.getTag(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION), 1009 photometricInterpretation); 1010 rootIFD.addTIFFField(photometricInterpretationField); 1011 1012 this.bitsPerSample = new char[numBands + numExtraSamples]; 1013 this.bitDepth = 0; 1014 for (int i = 0; i < numBands; i++) { 1015 this.bitDepth = Math.max(bitDepth, sampleSize[i]); 1016 } 1017 if (bitDepth == 3) { 1018 bitDepth = 4; 1019 } else if (bitDepth > 4 && bitDepth < 8) { 1020 bitDepth = 8; 1021 } else if (bitDepth > 8 && bitDepth < 16) { 1022 bitDepth = 16; 1023 } else if (bitDepth > 16) { 1024 bitDepth = 32; 1025 } 1026 1027 for (int i = 0; i < bitsPerSample.length; i++) { 1028 bitsPerSample[i] = (char)bitDepth; 1029 } 1030 1031 // Emit BitsPerSample. If the image is bilevel, emit if and only 1032 // if already in the metadata and correct (count and value == 1). 1033 if (bitsPerSample.length != 1 || bitsPerSample[0] != 1) { 1034 TIFFField bitsPerSampleField = 1035 new TIFFField( 1036 base.getTag(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE), 1037 TIFFTag.TIFF_SHORT, 1038 bitsPerSample.length, 1039 bitsPerSample); 1040 rootIFD.addTIFFField(bitsPerSampleField); 1041 } else { // bitsPerSample.length == 1 && bitsPerSample[0] == 1 1042 TIFFField bitsPerSampleField = 1043 rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); 1044 if(bitsPerSampleField != null) { 1045 int[] bps = bitsPerSampleField.getAsInts(); 1046 if(bps == null || bps.length != 1 || bps[0] != 1) { 1047 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); 1048 } 1049 } 1050 } 1051 1052 // Prepare SampleFormat field. 1053 f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT); 1054 if(f == null && (bitDepth == 16 || bitDepth == 32)) { 1055 // Set up default content for 16- and 32-bit cases. 1056 char sampleFormatValue; 1057 int dataType = sm.getDataType(); 1058 if(bitDepth == 16 && dataType == DataBuffer.TYPE_USHORT) { 1059 sampleFormatValue = 1060 (char)BaselineTIFFTagSet.SAMPLE_FORMAT_UNSIGNED_INTEGER; 1061 } else if(bitDepth == 32 && dataType == DataBuffer.TYPE_FLOAT) { 1062 sampleFormatValue = 1063 (char)BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT; 1064 } else { 1065 sampleFormatValue = 1066 BaselineTIFFTagSet.SAMPLE_FORMAT_SIGNED_INTEGER; 1067 } 1068 this.sampleFormat = (int)sampleFormatValue; 1069 char[] sampleFormatArray = new char[bitsPerSample.length]; 1070 Arrays.fill(sampleFormatArray, sampleFormatValue); 1071 1072 // Update the metadata. 1073 TIFFTag sampleFormatTag = 1074 base.getTag(BaselineTIFFTagSet.TAG_SAMPLE_FORMAT); 1075 1076 TIFFField sampleFormatField = 1077 new TIFFField(sampleFormatTag, TIFFTag.TIFF_SHORT, 1078 sampleFormatArray.length, sampleFormatArray); 1079 1080 rootIFD.addTIFFField(sampleFormatField); 1081 } else if(f != null) { 1082 // Get whatever was provided. 1083 sampleFormat = f.getAsInt(0); 1084 } else { 1085 // Set default value for internal use only. 1086 sampleFormat = BaselineTIFFTagSet.SAMPLE_FORMAT_UNDEFINED; 1087 } 1088 1089 if (extraSamples != null) { 1090 TIFFField extraSamplesField = 1091 new TIFFField( 1092 base.getTag(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES), 1093 TIFFTag.TIFF_SHORT, 1094 extraSamples.length, 1095 extraSamples); 1096 rootIFD.addTIFFField(extraSamplesField); 1097 } else { 1098 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_EXTRA_SAMPLES); 1099 } 1100 1101 TIFFField samplesPerPixelField = 1102 new TIFFField( 1103 base.getTag(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL), 1104 bitsPerSample.length); 1105 rootIFD.addTIFFField(samplesPerPixelField); 1106 1107 // Emit ColorMap if image is of palette color type 1108 if (photometricInterpretation == 1109 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_PALETTE_COLOR && 1110 cm instanceof IndexColorModel) { 1111 char[] colorMap = new char[3*(1 << bitsPerSample[0])]; 1112 1113 IndexColorModel icm = (IndexColorModel)cm; 1114 1115 // mapSize is determined by BitsPerSample, not by incoming ICM. 1116 int mapSize = 1 << bitsPerSample[0]; 1117 int indexBound = Math.min(mapSize, icm.getMapSize()); 1118 for (int i = 0; i < indexBound; i++) { 1119 colorMap[i] = (char)((icm.getRed(i)*65535)/255); 1120 colorMap[mapSize + i] = (char)((icm.getGreen(i)*65535)/255); 1121 colorMap[2*mapSize + i] = (char)((icm.getBlue(i)*65535)/255); 1122 } 1123 1124 TIFFField colorMapField = 1125 new TIFFField( 1126 base.getTag(BaselineTIFFTagSet.TAG_COLOR_MAP), 1127 TIFFTag.TIFF_SHORT, 1128 colorMap.length, 1129 colorMap); 1130 rootIFD.addTIFFField(colorMapField); 1131 } else { 1132 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_COLOR_MAP); 1133 } 1134 1135 // Emit ICCProfile if there is no ICCProfile field already in the 1136 // metadata and the ColorSpace is non-standard ICC. 1137 if(cm != null && 1138 rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_ICC_PROFILE) == null && 1139 ImageUtil.isNonStandardICCColorSpace(cm.getColorSpace())) { 1140 ICC_ColorSpace iccColorSpace = (ICC_ColorSpace)cm.getColorSpace(); 1141 byte[] iccProfileData = iccColorSpace.getProfile().getData(); 1142 TIFFField iccProfileField = 1143 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_ICC_PROFILE), 1144 TIFFTag.TIFF_UNDEFINED, 1145 iccProfileData.length, 1146 iccProfileData); 1147 rootIFD.addTIFFField(iccProfileField); 1148 } 1149 1150 // Always emit XResolution and YResolution. 1151 1152 TIFFField XResolutionField = 1153 rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_X_RESOLUTION); 1154 TIFFField YResolutionField = 1155 rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_Y_RESOLUTION); 1156 1157 if(XResolutionField == null && YResolutionField == null) { 1158 long[][] resRational = new long[1][2]; 1159 resRational[0] = new long[2]; 1160 1161 TIFFField ResolutionUnitField = 1162 rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_RESOLUTION_UNIT); 1163 1164 // Don't force dimensionless if one of the other dimensional 1165 // quantities is present. 1166 if(ResolutionUnitField == null && 1167 rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_X_POSITION) == null && 1168 rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_Y_POSITION) == null) { 1169 // Set resolution to unit and units to dimensionless. 1170 resRational[0][0] = 1; 1171 resRational[0][1] = 1; 1172 1173 ResolutionUnitField = 1174 new TIFFField(rootIFD.getTag 1175 (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT), 1176 BaselineTIFFTagSet.RESOLUTION_UNIT_NONE); 1177 rootIFD.addTIFFField(ResolutionUnitField); 1178 } else { 1179 // Set resolution to a value which would make the maximum 1180 // image dimension equal to 4 inches as arbitrarily stated 1181 // in the description of ResolutionUnit in the TIFF 6.0 1182 // specification. If the ResolutionUnit field specifies 1183 // "none" then set the resolution to unity (1/1). 1184 int resolutionUnit = ResolutionUnitField != null ? 1185 ResolutionUnitField.getAsInt(0) : 1186 BaselineTIFFTagSet.RESOLUTION_UNIT_INCH; 1187 int maxDimension = Math.max(destWidth, destHeight); 1188 switch(resolutionUnit) { 1189 case BaselineTIFFTagSet.RESOLUTION_UNIT_INCH: 1190 resRational[0][0] = maxDimension; 1191 resRational[0][1] = 4; 1192 break; 1193 case BaselineTIFFTagSet.RESOLUTION_UNIT_CENTIMETER: 1194 resRational[0][0] = 100L*maxDimension; // divide out 100 1195 resRational[0][1] = 4*254; // 2.54 cm/inch * 100 1196 break; 1197 default: 1198 resRational[0][0] = 1; 1199 resRational[0][1] = 1; 1200 } 1201 } 1202 1203 XResolutionField = 1204 new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION), 1205 TIFFTag.TIFF_RATIONAL, 1206 1, 1207 resRational); 1208 rootIFD.addTIFFField(XResolutionField); 1209 1210 YResolutionField = 1211 new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION), 1212 TIFFTag.TIFF_RATIONAL, 1213 1, 1214 resRational); 1215 rootIFD.addTIFFField(YResolutionField); 1216 } else if(XResolutionField == null && YResolutionField != null) { 1217 // Set XResolution to YResolution. 1218 long[] yResolution = 1219 (long[])YResolutionField.getAsRational(0).clone(); 1220 XResolutionField = 1221 new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_X_RESOLUTION), 1222 TIFFTag.TIFF_RATIONAL, 1223 1, 1224 yResolution); 1225 rootIFD.addTIFFField(XResolutionField); 1226 } else if(XResolutionField != null && YResolutionField == null) { 1227 // Set YResolution to XResolution. 1228 long[] xResolution = 1229 (long[])XResolutionField.getAsRational(0).clone(); 1230 YResolutionField = 1231 new TIFFField(rootIFD.getTag(BaselineTIFFTagSet.TAG_Y_RESOLUTION), 1232 TIFFTag.TIFF_RATIONAL, 1233 1, 1234 xResolution); 1235 rootIFD.addTIFFField(YResolutionField); 1236 } 1237 1238 // Set mandatory fields, overriding metadata passed in 1239 1240 int width = destWidth; 1241 TIFFField imageWidthField = 1242 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_WIDTH), 1243 width); 1244 rootIFD.addTIFFField(imageWidthField); 1245 1246 int height = destHeight; 1247 TIFFField imageLengthField = 1248 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_IMAGE_LENGTH), 1249 height); 1250 rootIFD.addTIFFField(imageLengthField); 1251 1252 // Determine rowsPerStrip 1253 1254 int rowsPerStrip; 1255 1256 TIFFField rowsPerStripField = 1257 rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP); 1258 if (rowsPerStripField != null) { 1259 rowsPerStrip = rowsPerStripField.getAsInt(0); 1260 if(rowsPerStrip < 0) { 1261 rowsPerStrip = height; 1262 } 1263 } else { 1264 int bitsPerPixel = bitDepth*(numBands + numExtraSamples); 1265 int bytesPerRow = (bitsPerPixel*width + 7)/8; 1266 rowsPerStrip = 1267 Math.max(Math.max(DEFAULT_BYTES_PER_STRIP/bytesPerRow, 1), 8); 1268 } 1269 rowsPerStrip = Math.min(rowsPerStrip, height); 1270 1271 // Tiling flag. 1272 boolean useTiling = false; 1273 1274 // Analyze tiling parameters 1275 int tilingMode = param instanceof TIFFImageWriteParam ? 1276 param.getTilingMode() : ImageWriteParam.MODE_DEFAULT; 1277 if (tilingMode == ImageWriteParam.MODE_DISABLED || 1278 tilingMode == ImageWriteParam.MODE_DEFAULT) { 1279 this.tileWidth = width; 1280 this.tileLength = rowsPerStrip; 1281 useTiling = false; 1282 } else if (tilingMode == ImageWriteParam.MODE_EXPLICIT) { 1283 tileWidth = param.getTileWidth(); 1284 tileLength = param.getTileHeight(); 1285 useTiling = true; 1286 } else if (tilingMode == ImageWriteParam.MODE_COPY_FROM_METADATA) { 1287 f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH); 1288 if (f == null) { 1289 tileWidth = width; 1290 useTiling = false; 1291 } else { 1292 tileWidth = f.getAsInt(0); 1293 useTiling = true; 1294 } 1295 1296 f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH); 1297 if (f == null) { 1298 tileLength = rowsPerStrip; 1299 } else { 1300 tileLength = f.getAsInt(0); 1301 useTiling = true; 1302 } 1303 } else { 1304 throw new IIOException("Illegal value of tilingMode!"); 1305 } 1306 1307 if(compression == BaselineTIFFTagSet.COMPRESSION_JPEG) { 1308 // Reset tile size per TTN2 spec for JPEG compression. 1309 int subX; 1310 int subY; 1311 if(numBands == 1) { 1312 subX = subY = 1; 1313 } else { 1314 subX = subY = TIFFJPEGCompressor.CHROMA_SUBSAMPLING; 1315 } 1316 if(useTiling) { 1317 int MCUMultipleX = 8*subX; 1318 int MCUMultipleY = 8*subY; 1319 tileWidth = 1320 Math.max(MCUMultipleX*((tileWidth + 1321 MCUMultipleX/2)/MCUMultipleX), 1322 MCUMultipleX); 1323 tileLength = 1324 Math.max(MCUMultipleY*((tileLength + 1325 MCUMultipleY/2)/MCUMultipleY), 1326 MCUMultipleY); 1327 } else if(rowsPerStrip < height) { 1328 int MCUMultiple = 8*Math.max(subX, subY); 1329 rowsPerStrip = tileLength = 1330 Math.max(MCUMultiple*((tileLength + 1331 MCUMultiple/2)/MCUMultiple), 1332 MCUMultiple); 1333 } 1334 } else if(isJPEGInterchange) { 1335 // Force tile size to equal image size. 1336 tileWidth = width; 1337 tileLength = height; 1338 } else if(useTiling) { 1339 // Round tile size to multiple of 16 per TIFF 6.0 specification 1340 // (see pages 67-68 of version 6.0.1 from Adobe). 1341 int tileWidthRemainder = tileWidth % 16; 1342 if(tileWidthRemainder != 0) { 1343 // Round to nearest multiple of 16 not less than 16. 1344 tileWidth = Math.max(16*((tileWidth + 8)/16), 16); 1345 // XXX insert processWarningOccurred(int,String); 1346 } 1347 1348 int tileLengthRemainder = tileLength % 16; 1349 if(tileLengthRemainder != 0) { 1350 // Round to nearest multiple of 16 not less than 16. 1351 tileLength = Math.max(16*((tileLength + 8)/16), 16); 1352 // XXX insert processWarningOccurred(int,String); 1353 } 1354 } 1355 1356 this.tilesAcross = (width + tileWidth - 1)/tileWidth; 1357 this.tilesDown = (height + tileLength - 1)/tileLength; 1358 1359 if (!useTiling) { 1360 this.isTiled = false; 1361 1362 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_WIDTH); 1363 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_LENGTH); 1364 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS); 1365 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS); 1366 1367 rowsPerStripField = 1368 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP), 1369 rowsPerStrip); 1370 rootIFD.addTIFFField(rowsPerStripField); 1371 1372 TIFFField stripOffsetsField = 1373 new TIFFField( 1374 base.getTag(BaselineTIFFTagSet.TAG_STRIP_OFFSETS), 1375 TIFFTag.TIFF_LONG, 1376 tilesDown); 1377 rootIFD.addTIFFField(stripOffsetsField); 1378 1379 TIFFField stripByteCountsField = 1380 new TIFFField( 1381 base.getTag(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS), 1382 TIFFTag.TIFF_LONG, 1383 tilesDown); 1384 rootIFD.addTIFFField(stripByteCountsField); 1385 } else { 1386 this.isTiled = true; 1387 1388 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP); 1389 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS); 1390 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS); 1391 1392 TIFFField tileWidthField = 1393 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_TILE_WIDTH), 1394 tileWidth); 1395 rootIFD.addTIFFField(tileWidthField); 1396 1397 TIFFField tileLengthField = 1398 new TIFFField(base.getTag(BaselineTIFFTagSet.TAG_TILE_LENGTH), 1399 tileLength); 1400 rootIFD.addTIFFField(tileLengthField); 1401 1402 TIFFField tileOffsetsField = 1403 new TIFFField( 1404 base.getTag(BaselineTIFFTagSet.TAG_TILE_OFFSETS), 1405 TIFFTag.TIFF_LONG, 1406 tilesDown*tilesAcross); 1407 rootIFD.addTIFFField(tileOffsetsField); 1408 1409 TIFFField tileByteCountsField = 1410 new TIFFField( 1411 base.getTag(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS), 1412 TIFFTag.TIFF_LONG, 1413 tilesDown*tilesAcross); 1414 rootIFD.addTIFFField(tileByteCountsField); 1415 } 1416 1417 if(isEXIF) { 1418 // 1419 // Ensure presence of mandatory fields and absence of prohibited 1420 // fields and those that duplicate information in JPEG marker 1421 // segments per tables 14-18 of the EXIF 2.2 specification. 1422 // 1423 1424 // If an empty image is being written or inserted then infer 1425 // that the primary IFD is being set up. 1426 boolean isPrimaryIFD = isEncodingEmpty(); 1427 1428 // Handle TIFF fields in order of increasing tag number. 1429 if(compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { 1430 // ImageWidth 1431 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH); 1432 1433 // ImageLength 1434 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH); 1435 1436 // BitsPerSample 1437 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); 1438 // Compression 1439 if(isPrimaryIFD) { 1440 rootIFD.removeTIFFField 1441 (BaselineTIFFTagSet.TAG_COMPRESSION); 1442 } 1443 1444 // PhotometricInterpretation 1445 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_PHOTOMETRIC_INTERPRETATION); 1446 1447 // StripOffsets 1448 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS); 1449 1450 // SamplesPerPixel 1451 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_SAMPLES_PER_PIXEL); 1452 1453 // RowsPerStrip 1454 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_ROWS_PER_STRIP); 1455 1456 // StripByteCounts 1457 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS); 1458 // XResolution and YResolution are handled above for all TIFFs. 1459 1460 // PlanarConfiguration 1461 rootIFD.removeTIFFField(BaselineTIFFTagSet.TAG_PLANAR_CONFIGURATION); 1462 1463 // ResolutionUnit 1464 if(rootIFD.getTIFFField 1465 (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT) == null) { 1466 f = new TIFFField(base.getTag 1467 (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT), 1468 BaselineTIFFTagSet.RESOLUTION_UNIT_INCH); 1469 rootIFD.addTIFFField(f); 1470 } 1471 1472 if(isPrimaryIFD) { 1473 // JPEGInterchangeFormat 1474 rootIFD.removeTIFFField 1475 (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT); 1476 1477 // JPEGInterchangeFormatLength 1478 rootIFD.removeTIFFField 1479 (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); 1480 1481 // YCbCrSubsampling 1482 rootIFD.removeTIFFField 1483 (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING); 1484 1485 // YCbCrPositioning 1486 if(rootIFD.getTIFFField 1487 (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING) == null) { 1488 f = new TIFFField 1489 (base.getTag 1490 (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING), 1491 TIFFTag.TIFF_SHORT, 1492 1, 1493 new char[] { 1494 (char)BaselineTIFFTagSet.Y_CB_CR_POSITIONING_CENTERED 1495 }); 1496 rootIFD.addTIFFField(f); 1497 } 1498 } else { // Thumbnail IFD 1499 // JPEGInterchangeFormat 1500 f = new TIFFField 1501 (base.getTag 1502 (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT), 1503 TIFFTag.TIFF_LONG, 1504 1); 1505 rootIFD.addTIFFField(f); 1506 1507 // JPEGInterchangeFormatLength 1508 f = new TIFFField 1509 (base.getTag 1510 (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH), 1511 TIFFTag.TIFF_LONG, 1512 1); 1513 rootIFD.addTIFFField(f); 1514 1515 // YCbCrSubsampling 1516 rootIFD.removeTIFFField 1517 (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING); 1518 } 1519 } else { // Uncompressed 1520 // ImageWidth through PlanarConfiguration are set above. 1521 // XResolution and YResolution are handled above for all TIFFs. 1522 1523 // ResolutionUnit 1524 if(rootIFD.getTIFFField 1525 (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT) == null) { 1526 f = new TIFFField(base.getTag 1527 (BaselineTIFFTagSet.TAG_RESOLUTION_UNIT), 1528 BaselineTIFFTagSet.RESOLUTION_UNIT_INCH); 1529 rootIFD.addTIFFField(f); 1530 } 1531 1532 1533 // JPEGInterchangeFormat 1534 rootIFD.removeTIFFField 1535 (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT); 1536 1537 // JPEGInterchangeFormatLength 1538 rootIFD.removeTIFFField 1539 (BaselineTIFFTagSet.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); 1540 1541 if(photometricInterpretation == 1542 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_RGB) { 1543 // YCbCrCoefficients 1544 rootIFD.removeTIFFField 1545 (BaselineTIFFTagSet.TAG_Y_CB_CR_COEFFICIENTS); 1546 1547 // YCbCrSubsampling 1548 rootIFD.removeTIFFField 1549 (BaselineTIFFTagSet.TAG_Y_CB_CR_SUBSAMPLING); 1550 1551 // YCbCrPositioning 1552 rootIFD.removeTIFFField 1553 (BaselineTIFFTagSet.TAG_Y_CB_CR_POSITIONING); 1554 } 1555 } 1556 1557 // Get EXIF tags. 1558 TIFFTagSet exifTags = EXIFTIFFTagSet.getInstance(); 1559 1560 // Retrieve or create the EXIF IFD. 1561 TIFFIFD exifIFD = null; 1562 f = rootIFD.getTIFFField 1563 (EXIFParentTIFFTagSet.TAG_EXIF_IFD_POINTER); 1564 if(f != null) { 1565 // Retrieve the EXIF IFD. 1566 exifIFD = (TIFFIFD)f.getData(); 1567 } else if(isPrimaryIFD) { 1568 // Create the EXIF IFD. 1569 List exifTagSets = new ArrayList(1); 1570 exifTagSets.add(exifTags); 1571 exifIFD = new TIFFIFD(exifTagSets); 1572 1573 // Add it to the root IFD. 1574 TIFFTagSet tagSet = EXIFParentTIFFTagSet.getInstance(); 1575 TIFFTag exifIFDTag = 1576 tagSet.getTag(EXIFParentTIFFTagSet.TAG_EXIF_IFD_POINTER); 1577 rootIFD.addTIFFField(new TIFFField(exifIFDTag, 1578 TIFFTag.TIFF_LONG, 1579 1, 1580 exifIFD)); 1581 } 1582 1583 if(exifIFD != null) { 1584 // Handle EXIF private fields in order of increasing 1585 // tag number. 1586 1587 // ExifVersion 1588 if(exifIFD.getTIFFField 1589 (EXIFTIFFTagSet.TAG_EXIF_VERSION) == null) { 1590 f = new TIFFField 1591 (exifTags.getTag(EXIFTIFFTagSet.TAG_EXIF_VERSION), 1592 TIFFTag.TIFF_UNDEFINED, 1593 4, 1594 EXIFTIFFTagSet.EXIF_VERSION_2_2); 1595 exifIFD.addTIFFField(f); 1596 } 1597 1598 if(compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { 1599 // ComponentsConfiguration 1600 if(exifIFD.getTIFFField 1601 (EXIFTIFFTagSet.TAG_COMPONENTS_CONFIGURATION) == null) { 1602 f = new TIFFField 1603 (exifTags.getTag 1604 (EXIFTIFFTagSet.TAG_COMPONENTS_CONFIGURATION), 1605 TIFFTag.TIFF_UNDEFINED, 1606 4, 1607 new byte[] { 1608 (byte)EXIFTIFFTagSet.COMPONENTS_CONFIGURATION_Y, 1609 (byte)EXIFTIFFTagSet.COMPONENTS_CONFIGURATION_CB, 1610 (byte)EXIFTIFFTagSet.COMPONENTS_CONFIGURATION_CR, 1611 (byte)0 1612 }); 1613 exifIFD.addTIFFField(f); 1614 } 1615 } else { 1616 // ComponentsConfiguration 1617 exifIFD.removeTIFFField 1618 (EXIFTIFFTagSet.TAG_COMPONENTS_CONFIGURATION); 1619 1620 // CompressedBitsPerPixel 1621 exifIFD.removeTIFFField 1622 (EXIFTIFFTagSet.TAG_COMPRESSED_BITS_PER_PIXEL); 1623 } 1624 1625 // FlashpixVersion 1626 if(exifIFD.getTIFFField 1627 (EXIFTIFFTagSet.TAG_FLASHPIX_VERSION) == null) { 1628 f = new TIFFField 1629 (exifTags.getTag(EXIFTIFFTagSet.TAG_FLASHPIX_VERSION), 1630 TIFFTag.TIFF_UNDEFINED, 1631 4, 1632 new byte[] {(byte)'0', (byte)'1', (byte)'0', (byte)'0'}); 1633 exifIFD.addTIFFField(f); 1634 } 1635 1636 // ColorSpace 1637 if(exifIFD.getTIFFField 1638 (EXIFTIFFTagSet.TAG_COLOR_SPACE) == null) { 1639 f = new TIFFField 1640 (exifTags.getTag(EXIFTIFFTagSet.TAG_COLOR_SPACE), 1641 TIFFTag.TIFF_SHORT, 1642 1, 1643 new char[] { 1644 (char)EXIFTIFFTagSet.COLOR_SPACE_SRGB 1645 }); 1646 exifIFD.addTIFFField(f); 1647 } 1648 1649 if(compression == BaselineTIFFTagSet.COMPRESSION_OLD_JPEG) { 1650 // PixelXDimension 1651 if(exifIFD.getTIFFField 1652 (EXIFTIFFTagSet.TAG_PIXEL_X_DIMENSION) == null) { 1653 f = new TIFFField 1654 (exifTags.getTag(EXIFTIFFTagSet.TAG_PIXEL_X_DIMENSION), 1655 width); 1656 exifIFD.addTIFFField(f); 1657 } 1658 1659 // PixelYDimension 1660 if(exifIFD.getTIFFField 1661 (EXIFTIFFTagSet.TAG_PIXEL_Y_DIMENSION) == null) { 1662 f = new TIFFField 1663 (exifTags.getTag(EXIFTIFFTagSet.TAG_PIXEL_Y_DIMENSION), 1664 height); 1665 exifIFD.addTIFFField(f); 1666 } 1667 } else { 1668 exifIFD.removeTIFFField 1669 (EXIFTIFFTagSet.TAG_INTEROPERABILITY_IFD_POINTER); 1670 } 1671 } 1672 1673 } // if(isEXIF) 1674 } 1675 1676 /** 1677 @param tileRect The area to be written which might be outside the image. 1678 */ 1679 private int writeTile(Rectangle tileRect, TIFFCompressor compressor) 1680 throws IOException { 1681 // Determine the rectangle which will actually be written 1682 // and set the padding flag. Padding will occur only when the 1683 // image is written as a tiled TIFF and the tile bounds are not 1684 // contained within the image bounds. 1685 Rectangle activeRect; 1686 boolean isPadded; 1687 Rectangle imageBounds = 1688 new Rectangle(image.getMinX(), image.getMinY(), 1689 image.getWidth(), image.getHeight()); 1690 if(!isTiled) { 1691 // Stripped 1692 activeRect = tileRect.intersection(imageBounds); 1693 tileRect = activeRect; 1694 isPadded = false; 1695 } else if(imageBounds.contains(tileRect)) { 1696 // Tiled, tile within image bounds 1697 activeRect = tileRect; 1698 isPadded = false; 1699 } else { 1700 // Tiled, tile not within image bounds 1701 activeRect = imageBounds.intersection(tileRect); 1702 isPadded = true; 1703 } 1704 1705 // Shouldn't happen, but return early if empty intersection. 1706 if(activeRect.isEmpty()) { 1707 return 0; 1708 } 1709 1710 int minX = tileRect.x; 1711 int minY = tileRect.y; 1712 int width = tileRect.width; 1713 int height = tileRect.height; 1714 1715 if(isImageSimple) { 1716 1717 SampleModel sm = image.getSampleModel(); 1718 1719 // Read only data from the active rectangle. 1720 Raster raster = image.getData(activeRect); 1721 1722 // If padding is required, create a larger Raster and fill 1723 // it from the active rectangle. 1724 if(isPadded) { 1725 WritableRaster wr = 1726 raster.createCompatibleWritableRaster(minX, minY, 1727 width, height); 1728 wr.setRect(raster); 1729 raster = wr; 1730 } 1731 1732 if(isBilevel) { 1733 /* XXX 1734 MultiPixelPackedSampleModel mppsm = 1735 (MultiPixelPackedSampleModel)raster.getSampleModel(); 1736 1737 byte[] buf; 1738 int off; 1739 int lineStride; 1740 if(mppsm.getDataBitOffset() == 0 && 1741 raster.getDataBuffer() instanceof DataBufferByte) { 1742 buf = ((DataBufferByte)raster.getDataBuffer()).getData(); 1743 off = mppsm.getOffset(tileRect.x - 1744 raster.getSampleModelTranslateX(), 1745 tileRect.y - 1746 raster.getSampleModelTranslateY()); 1747 lineStride = mppsm.getScanlineStride(); 1748 } else { 1749 buf = ImageUtil.getPackedBinaryData(raster, 1750 tileRect); 1751 off = 0; 1752 lineStride = (tileRect.width + 7)/8; 1753 } 1754 */ 1755 byte[] buf = ImageUtil.getPackedBinaryData(raster, 1756 tileRect); 1757 1758 if(isInverted) { 1759 DataBuffer dbb = raster.getDataBuffer(); 1760 if(dbb instanceof DataBufferByte && 1761 buf == ((DataBufferByte)dbb).getData()) { 1762 byte[] bbuf = new byte[buf.length]; 1763 int len = buf.length; 1764 for(int i = 0; i < len; i++) { 1765 bbuf[i] = (byte)(buf[i] ^ 0xff); 1766 } 1767 buf = bbuf; 1768 } else { 1769 int len = buf.length; 1770 for(int i = 0; i < len; i++) { 1771 buf[i] ^= 0xff; 1772 } 1773 } 1774 } 1775 1776 if(DEBUG) { 1777 System.out.println("Optimized bilevel case"); 1778 } 1779 1780 return compressor.encode(buf, 0, 1781 width, height, sampleSize, 1782 (tileRect.width + 7)/8); 1783 } else if(bitDepth == 8 && 1784 sm.getDataType() == DataBuffer.TYPE_BYTE) { 1785 ComponentSampleModel csm = 1786 (ComponentSampleModel)raster.getSampleModel(); 1787 1788 byte[] buf = 1789 ((DataBufferByte)raster.getDataBuffer()).getData(); 1790 1791 int off = 1792 csm.getOffset(minX - 1793 raster.getSampleModelTranslateX(), 1794 minY - 1795 raster.getSampleModelTranslateY()); 1796 1797 if(DEBUG) { 1798 System.out.println("Optimized component case"); 1799 } 1800 1801 return compressor.encode(buf, off, 1802 width, height, sampleSize, 1803 csm.getScanlineStride()); 1804 } 1805 } 1806 1807 // Set offsets and skips based on source subsampling factors 1808 int xOffset = minX; 1809 int xSkip = periodX; 1810 int yOffset = minY; 1811 int ySkip = periodY; 1812 1813 // Early exit if no data for this pass 1814 int hpixels = (width + xSkip - 1)/xSkip; 1815 int vpixels = (height + ySkip - 1)/ySkip; 1816 if (hpixels == 0 || vpixels == 0) { 1817 return 0; 1818 } 1819 1820 // Convert X offset and skip from pixels to samples 1821 xOffset *= numBands; 1822 xSkip *= numBands; 1823 1824 // Initialize sizes 1825 int samplesPerByte = 8/bitDepth; 1826 int numSamples = width*numBands; 1827 int bytesPerRow = hpixels*numBands; 1828 1829 // Update number of bytes per row. 1830 if (bitDepth < 8) { 1831 bytesPerRow = (bytesPerRow + samplesPerByte - 1)/samplesPerByte; 1832 } else if (bitDepth == 16) { 1833 bytesPerRow *= 2; 1834 } else if (bitDepth == 32) { 1835 bytesPerRow *= 4; 1836 } 1837 1838 // Create row buffers 1839 int[] samples = null; 1840 float[] fsamples = null; 1841 if(sampleFormat == BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { 1842 fsamples = new float[numSamples]; 1843 } else { 1844 samples = new int[numSamples]; 1845 } 1846 1847 // Create tile buffer 1848 byte[] currTile = new byte[bytesPerRow*vpixels]; 1849 1850 // Sub-optimal case: shy of "isImageSimple" only by virtue of 1851 // not being contiguous. 1852 if(!isInverted && // no inversion 1853 !isRescaling && // no value rescaling 1854 sourceBands == null && // no subbanding 1855 periodX == 1 && periodY == 1 && // no subsampling 1856 colorConverter == null) { 1857 1858 SampleModel sm = image.getSampleModel(); 1859 1860 if(sm instanceof ComponentSampleModel && // component 1861 bitDepth == 8 && // 8 bits/sample 1862 sm.getDataType() == DataBuffer.TYPE_BYTE) { // byte type 1863 1864 if(DEBUG) { 1865 System.out.println("Sub-optimal byte component case"); 1866 System.out.println(sm.getClass().getName()); 1867 } 1868 1869 // Read only data from the active rectangle. 1870 Raster raster = image.getData(activeRect); 1871 1872 // If padding is required, create a larger Raster and fill 1873 // it from the active rectangle. 1874 if(isPadded) { 1875 WritableRaster wr = 1876 raster.createCompatibleWritableRaster(minX, minY, 1877 width, height); 1878 wr.setRect(raster); 1879 raster = wr; 1880 } 1881 1882 // Get SampleModel info. 1883 ComponentSampleModel csm = 1884 (ComponentSampleModel)raster.getSampleModel(); 1885 int[] bankIndices = csm.getBankIndices(); 1886 byte[][] bankData = 1887 ((DataBufferByte)raster.getDataBuffer()).getBankData(); 1888 int lineStride = csm.getScanlineStride(); 1889 int pixelStride = csm.getPixelStride(); 1890 1891 // Copy the data into a contiguous pixel interleaved buffer. 1892 for(int k = 0; k < numBands; k++) { 1893 byte[] bandData = bankData[bankIndices[k]]; 1894 int lineOffset = 1895 csm.getOffset(raster.getMinX() - 1896 raster.getSampleModelTranslateX(), 1897 raster.getMinY() - 1898 raster.getSampleModelTranslateY(), k); 1899 int idx = k; 1900 for(int j = 0; j < vpixels; j++) { 1901 int offset = lineOffset; 1902 for(int i = 0; i < hpixels; i++) { 1903 currTile[idx] = bandData[offset]; 1904 idx += numBands; 1905 offset += pixelStride; 1906 } 1907 lineOffset += lineStride; 1908 } 1909 } 1910 1911 // Compressor and return. 1912 return compressor.encode(currTile, 0, 1913 width, height, sampleSize, 1914 width*numBands); 1915 } 1916 } 1917 1918 if(DEBUG) { 1919 System.out.println("Unoptimized case for bit depth "+bitDepth); 1920 SampleModel sm = image.getSampleModel(); 1921 System.out.println("isRescaling = "+isRescaling); 1922 System.out.println("sourceBands = "+sourceBands); 1923 System.out.println("periodX = "+periodX); 1924 System.out.println("periodY = "+periodY); 1925 System.out.println(sm.getClass().getName()); 1926 System.out.println(sm.getDataType()); 1927 if(sm instanceof ComponentSampleModel) { 1928 ComponentSampleModel csm = (ComponentSampleModel)sm; 1929 System.out.println(csm.getNumBands()); 1930 System.out.println(csm.getPixelStride()); 1931 int[] bankIndices = csm.getBankIndices(); 1932 for(int b = 0; b < numBands; b++) { 1933 System.out.print(bankIndices[b]+" "); 1934 } 1935 int[] bandOffsets = csm.getBandOffsets(); 1936 for(int b = 0; b < numBands; b++) { 1937 System.out.print(bandOffsets[b]+" "); 1938 } 1939 System.out.println(""); 1940 } 1941 } 1942 1943 int tcount = 0; 1944 1945 // Save active rectangle variables. 1946 int activeMinX = activeRect.x; 1947 int activeMinY = activeRect.y; 1948 int activeMaxY = activeMinY + activeRect.height - 1; 1949 int activeWidth = activeRect.width; 1950 1951 // Set a SampleModel for use in padding. 1952 SampleModel rowSampleModel = null; 1953 if(isPadded) { 1954 rowSampleModel = 1955 image.getSampleModel().createCompatibleSampleModel(width, 1); 1956 } 1957 1958 for (int row = yOffset; row < yOffset + height; row += ySkip) { 1959 Raster ras = null; 1960 if(isPadded) { 1961 // Create a raster for the entire row. 1962 WritableRaster wr = 1963 Raster.createWritableRaster(rowSampleModel, 1964 new Point(minX, row)); 1965 1966 // Populate the raster from the active sub-row, if any. 1967 if(row >= activeMinY && row <= activeMaxY) { 1968 Rectangle rect = 1969 new Rectangle(activeMinX, row, activeWidth, 1); 1970 ras = image.getData(rect); 1971 wr.setRect(ras); 1972 } 1973 1974 // Update the raster variable. 1975 ras = wr; 1976 } else { 1977 Rectangle rect = new Rectangle(minX, row, width, 1); 1978 ras = image.getData(rect); 1979 } 1980 if (sourceBands != null) { 1981 ras = ras.createChild(minX, row, width, 1, minX, row, 1982 sourceBands); 1983 } 1984 1985 if(sampleFormat == 1986 BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { 1987 ras.getPixels(minX, row, width, 1, fsamples); 1988 } else { 1989 ras.getPixels(minX, row, width, 1, samples); 1990 1991 if ((nativePhotometricInterpretation == 1992 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO && 1993 photometricInterpretation == 1994 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) || 1995 (nativePhotometricInterpretation == 1996 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO && 1997 photometricInterpretation == 1998 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO)) { 1999 int bitMask = (1 << bitDepth) - 1; 2000 for (int s = 0; s < numSamples; s++) { 2001 samples[s] ^= bitMask; 2002 } 2003 } 2004 } 2005 2006 if (colorConverter != null) { 2007 int idx = 0; 2008 float[] result = new float[3]; 2009 2010 if(sampleFormat == 2011 BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { 2012 for (int i = 0; i < width; i++) { 2013 float r = fsamples[idx]; 2014 float g = fsamples[idx + 1]; 2015 float b = fsamples[idx + 2]; 2016 2017 colorConverter.fromRGB(r, g, b, result); 2018 2019 fsamples[idx] = result[0]; 2020 fsamples[idx + 1] = result[1]; 2021 fsamples[idx + 2] = result[2]; 2022 2023 idx += 3; 2024 } 2025 } else { 2026 for (int i = 0; i < width; i++) { 2027 float r = (float)samples[idx]; 2028 float g = (float)samples[idx + 1]; 2029 float b = (float)samples[idx + 2]; 2030 2031 colorConverter.fromRGB(r, g, b, result); 2032 2033 samples[idx] = (int)(result[0]); 2034 samples[idx + 1] = (int)(result[1]); 2035 samples[idx + 2] = (int)(result[2]); 2036 2037 idx += 3; 2038 } 2039 } 2040 } 2041 2042 int tmp = 0; 2043 int pos = 0; 2044 2045 switch (bitDepth) { 2046 case 1: case 2: case 4: 2047 // Image can only have a single band 2048 2049 if(isRescaling) { 2050 for (int s = 0; s < numSamples; s += xSkip) { 2051 byte val = scale0[samples[s]]; 2052 tmp = (tmp << bitDepth) | val; 2053 2054 if (++pos == samplesPerByte) { 2055 currTile[tcount++] = (byte)tmp; 2056 tmp = 0; 2057 pos = 0; 2058 } 2059 } 2060 } else { 2061 for (int s = 0; s < numSamples; s += xSkip) { 2062 byte val = (byte)samples[s]; 2063 tmp = (tmp << bitDepth) | val; 2064 2065 if (++pos == samplesPerByte) { 2066 currTile[tcount++] = (byte)tmp; 2067 tmp = 0; 2068 pos = 0; 2069 } 2070 } 2071 } 2072 2073 // Left shift the last byte 2074 if (pos != 0) { 2075 tmp <<= ((8/bitDepth) - pos)*bitDepth; 2076 currTile[tcount++] = (byte)tmp; 2077 } 2078 break; 2079 2080 case 8: 2081 if (numBands == 1) { 2082 if(isRescaling) { 2083 for (int s = 0; s < numSamples; s += xSkip) { 2084 currTile[tcount++] = scale0[samples[s]]; 2085 } 2086 } else { 2087 for (int s = 0; s < numSamples; s += xSkip) { 2088 currTile[tcount++] = (byte)samples[s]; 2089 } 2090 } 2091 } else { 2092 if(isRescaling) { 2093 for (int s = 0; s < numSamples; s += xSkip) { 2094 for (int b = 0; b < numBands; b++) { 2095 currTile[tcount++] = scale[b][samples[s + b]]; 2096 } 2097 } 2098 } else { 2099 for (int s = 0; s < numSamples; s += xSkip) { 2100 for (int b = 0; b < numBands; b++) { 2101 currTile[tcount++] = (byte)samples[s + b]; 2102 } 2103 } 2104 } 2105 } 2106 break; 2107 2108 case 16: 2109 // XXX Need to verify this rescaling for signed vs. unsigned. 2110 if(isRescaling) { 2111 if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { 2112 for (int s = 0; s < numSamples; s += xSkip) { 2113 for (int b = 0; b < numBands; b++) { 2114 int sample = samples[s + b]; 2115 currTile[tcount++] = scaleh[b][sample]; 2116 currTile[tcount++] = scalel[b][sample]; 2117 } 2118 } 2119 } else { // ByteOrder.LITLE_ENDIAN 2120 for (int s = 0; s < numSamples; s += xSkip) { 2121 for (int b = 0; b < numBands; b++) { 2122 int sample = samples[s + b]; 2123 currTile[tcount++] = scalel[b][sample]; 2124 currTile[tcount++] = scaleh[b][sample]; 2125 } 2126 } 2127 } 2128 } else { 2129 if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { 2130 for (int s = 0; s < numSamples; s += xSkip) { 2131 for (int b = 0; b < numBands; b++) { 2132 int sample = samples[s + b]; 2133 currTile[tcount++] = 2134 (byte)((sample >>> 8) & 0xff); 2135 currTile[tcount++] = 2136 (byte)(sample & 0xff); 2137 } 2138 } 2139 } else { // ByteOrder.LITLE_ENDIAN 2140 for (int s = 0; s < numSamples; s += xSkip) { 2141 for (int b = 0; b < numBands; b++) { 2142 int sample = samples[s + b]; 2143 currTile[tcount++] = 2144 (byte)(sample & 0xff); 2145 currTile[tcount++] = 2146 (byte)((sample >>> 8) & 0xff); 2147 } 2148 } 2149 } 2150 } 2151 break; 2152 2153 case 32: 2154 if(sampleFormat == 2155 BaselineTIFFTagSet.SAMPLE_FORMAT_FLOATING_POINT) { 2156 if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { 2157 for (int s = 0; s < numSamples; s += xSkip) { 2158 for (int b = 0; b < numBands; b++) { 2159 float fsample = fsamples[s + b]; 2160 int isample = Float.floatToIntBits(fsample); 2161 currTile[tcount++] = 2162 (byte)((isample & 0xff000000) >> 24); 2163 currTile[tcount++] = 2164 (byte)((isample & 0x00ff0000) >> 16); 2165 currTile[tcount++] = 2166 (byte)((isample & 0x0000ff00) >> 8); 2167 currTile[tcount++] = 2168 (byte)(isample & 0x000000ff); 2169 } 2170 } 2171 } else { // ByteOrder.LITLE_ENDIAN 2172 for (int s = 0; s < numSamples; s += xSkip) { 2173 for (int b = 0; b < numBands; b++) { 2174 float fsample = fsamples[s + b]; 2175 int isample = Float.floatToIntBits(fsample); 2176 currTile[tcount++] = 2177 (byte)(isample & 0x000000ff); 2178 currTile[tcount++] = 2179 (byte)((isample & 0x0000ff00) >> 8); 2180 currTile[tcount++] = 2181 (byte)((isample & 0x00ff0000) >> 16); 2182 currTile[tcount++] = 2183 (byte)((isample & 0xff000000) >> 24); 2184 } 2185 } 2186 } 2187 } else { 2188 if(isRescaling) { 2189 // XXX Need to verify this for signed vs. unsigned. 2190 // XXX The following gives saturated results when the 2191 // original data are in the signed integer range. 2192 long[] maxIn = new long[numBands]; 2193 long[] halfIn = new long[numBands]; 2194 long maxOut = (1L << (long)bitDepth) - 1L; 2195 2196 for (int b = 0; b < numBands; b++) { 2197 maxIn[b] = ((1L << (long)sampleSize[b]) - 1L); 2198 halfIn[b] = maxIn[b]/2; 2199 } 2200 2201 if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { 2202 for (int s = 0; s < numSamples; s += xSkip) { 2203 for (int b = 0; b < numBands; b++) { 2204 long sampleOut = 2205 (samples[s + b]*maxOut + halfIn[b])/ 2206 maxIn[b]; 2207 currTile[tcount++] = 2208 (byte)((sampleOut & 0xff000000) >> 24); 2209 currTile[tcount++] = 2210 (byte)((sampleOut & 0x00ff0000) >> 16); 2211 currTile[tcount++] = 2212 (byte)((sampleOut & 0x0000ff00) >> 8); 2213 currTile[tcount++] = 2214 (byte)(sampleOut & 0x000000ff); 2215 } 2216 } 2217 } else { // ByteOrder.LITLE_ENDIAN 2218 for (int s = 0; s < numSamples; s += xSkip) { 2219 for (int b = 0; b < numBands; b++) { 2220 long sampleOut = 2221 (samples[s + b]*maxOut + halfIn[b])/ 2222 maxIn[b]; 2223 currTile[tcount++] = 2224 (byte)(sampleOut & 0x000000ff); 2225 currTile[tcount++] = 2226 (byte)((sampleOut & 0x0000ff00) >> 8); 2227 currTile[tcount++] = 2228 (byte)((sampleOut & 0x00ff0000) >> 16); 2229 currTile[tcount++] = 2230 (byte)((sampleOut & 0xff000000) >> 24); 2231 } 2232 } 2233 } 2234 } else { 2235 if(stream.getByteOrder() == ByteOrder.BIG_ENDIAN) { 2236 for (int s = 0; s < numSamples; s += xSkip) { 2237 for (int b = 0; b < numBands; b++) { 2238 int isample = samples[s + b]; 2239 currTile[tcount++] = 2240 (byte)((isample & 0xff000000) >> 24); 2241 currTile[tcount++] = 2242 (byte)((isample & 0x00ff0000) >> 16); 2243 currTile[tcount++] = 2244 (byte)((isample & 0x0000ff00) >> 8); 2245 currTile[tcount++] = 2246 (byte)(isample & 0x000000ff); 2247 } 2248 } 2249 } else { // ByteOrder.LITLE_ENDIAN 2250 for (int s = 0; s < numSamples; s += xSkip) { 2251 for (int b = 0; b < numBands; b++) { 2252 int isample = samples[s + b]; 2253 currTile[tcount++] = 2254 (byte)(isample & 0x000000ff); 2255 currTile[tcount++] = 2256 (byte)((isample & 0x0000ff00) >> 8); 2257 currTile[tcount++] = 2258 (byte)((isample & 0x00ff0000) >> 16); 2259 currTile[tcount++] = 2260 (byte)((isample & 0xff000000) >> 24); 2261 } 2262 } 2263 } 2264 } 2265 } 2266 break; 2267 } 2268 } 2269 2270 int[] bitsPerSample = new int[numBands]; 2271 for (int i = 0; i < bitsPerSample.length; i++) { 2272 bitsPerSample[i] = bitDepth; 2273 } 2274 2275 int byteCount = compressor.encode(currTile, 0, 2276 hpixels, vpixels, 2277 bitsPerSample, 2278 bytesPerRow); 2279 return byteCount; 2280 } 2281 2282 // Check two int arrays for value equality, always returns false 2283 // if either array is null 2284 private boolean equals(int[] s0, int[] s1) { 2285 if (s0 == null || s1 == null) { 2286 return false; 2287 } 2288 if (s0.length != s1.length) { 2289 return false; 2290 } 2291 for (int i = 0; i < s0.length; i++) { 2292 if (s0[i] != s1[i]) { 2293 return false; 2294 } 2295 } 2296 return true; 2297 } 2298 2299 // Initialize the scale/scale0 or scaleh/scalel arrays to 2300 // hold the results of scaling an input value to the desired 2301 // output bit depth 2302 // XXX Need to verify this rescaling for signed vs. unsigned. 2303 private void initializeScaleTables(int[] sampleSize) { 2304 // Save the sample size in the instance variable. 2305 2306 // If the existing tables are still valid, just return. 2307 if (bitDepth == scalingBitDepth && 2308 equals(sampleSize, this.sampleSize)) { 2309 if(DEBUG) { 2310 System.out.println("Returning from initializeScaleTables()"); 2311 } 2312 return; 2313 } 2314 2315 // Reset scaling variables. 2316 isRescaling = false; 2317 scalingBitDepth = -1; 2318 scale = scalel = scaleh = null; 2319 scale0 = null; 2320 2321 // Set global sample size to parameter. 2322 this.sampleSize = sampleSize; 2323 2324 // Check whether rescaling is called for. 2325 if(bitDepth <= 16) { 2326 for(int b = 0; b < numBands; b++) { 2327 if(sampleSize[b] != bitDepth) { 2328 isRescaling = true; 2329 break; 2330 } 2331 } 2332 } 2333 2334 if(DEBUG) { 2335 System.out.println("isRescaling = "+isRescaling); 2336 } 2337 2338 // If not rescaling then return after saving the sample size. 2339 if(!isRescaling) { 2340 return; 2341 } 2342 2343 // Compute new tables 2344 this.scalingBitDepth = bitDepth; 2345 int maxOutSample = (1 << bitDepth) - 1; 2346 if (bitDepth <= 8) { 2347 scale = new byte[numBands][]; 2348 for (int b = 0; b < numBands; b++) { 2349 int maxInSample = (1 << sampleSize[b]) - 1; 2350 int halfMaxInSample = maxInSample/2; 2351 scale[b] = new byte[maxInSample + 1]; 2352 for (int s = 0; s <= maxInSample; s++) { 2353 scale[b][s] = 2354 (byte)((s*maxOutSample + halfMaxInSample)/maxInSample); 2355 } 2356 } 2357 scale0 = scale[0]; 2358 scaleh = scalel = null; 2359 } else if(bitDepth <= 16) { 2360 // Divide scaling table into high and low bytes 2361 scaleh = new byte[numBands][]; 2362 scalel = new byte[numBands][]; 2363 2364 for (int b = 0; b < numBands; b++) { 2365 int maxInSample = (1 << sampleSize[b]) - 1; 2366 int halfMaxInSample = maxInSample/2; 2367 scaleh[b] = new byte[maxInSample + 1]; 2368 scalel[b] = new byte[maxInSample + 1]; 2369 for (int s = 0; s <= maxInSample; s++) { 2370 int val = (s*maxOutSample + halfMaxInSample)/maxInSample; 2371 scaleh[b][s] = (byte)(val >> 8); 2372 scalel[b][s] = (byte)(val & 0xff); 2373 } 2374 } 2375 scale = null; 2376 scale0 = null; 2377 } 2378 } 2379 2380 public void write(IIOMetadata sm, 2381 IIOImage iioimage, 2382 ImageWriteParam p) throws IOException { 2383 write(sm, iioimage, p, true, true); 2384 } 2385 2386 private void writeHeader() throws IOException { 2387 if (streamMetadata != null) { 2388 this.byteOrder = streamMetadata.byteOrder; 2389 } else { 2390 this.byteOrder = ByteOrder.BIG_ENDIAN; 2391 } 2392 2393 stream.setByteOrder(byteOrder); 2394 if (byteOrder == ByteOrder.BIG_ENDIAN) { 2395 stream.writeShort(0x4d4d); 2396 } else { 2397 stream.writeShort(0x4949); 2398 } 2399 2400 stream.writeShort(42); // Magic number 2401 stream.writeInt(0); // Offset of first IFD (0 == none) 2402 2403 nextSpace = stream.getStreamPosition(); 2404 headerPosition = nextSpace - 8; 2405 } 2406 2407 private void write(IIOMetadata sm, 2408 IIOImage iioimage, 2409 ImageWriteParam p, 2410 boolean writeHeader, 2411 boolean writeData) throws IOException { 2412 if (stream == null) { 2413 throw new IllegalStateException("output == null!"); 2414 } 2415 if (iioimage == null) { 2416 throw new IllegalArgumentException("image == null!"); 2417 } 2418 if(iioimage.hasRaster() && !canWriteRasters()) { 2419 throw new UnsupportedOperationException 2420 ("TIFF ImageWriter cannot write Rasters!"); 2421 } 2422 2423 this.image = iioimage.getRenderedImage(); 2424 SampleModel sampleModel = image.getSampleModel(); 2425 2426 this.sourceXOffset = image.getMinX(); 2427 this.sourceYOffset = image.getMinY(); 2428 this.sourceWidth = image.getWidth(); 2429 this.sourceHeight = image.getHeight(); 2430 2431 Rectangle imageBounds = new Rectangle(sourceXOffset, 2432 sourceYOffset, 2433 sourceWidth, 2434 sourceHeight); 2435 2436 ColorModel colorModel = null; 2437 if (p == null) { 2438 this.param = getDefaultWriteParam(); 2439 this.sourceBands = null; 2440 this.periodX = 1; 2441 this.periodY = 1; 2442 this.numBands = sampleModel.getNumBands(); 2443 colorModel = image.getColorModel(); 2444 } else { 2445 this.param = p; 2446 2447 // Get source region and subsampling factors 2448 Rectangle sourceRegion = param.getSourceRegion(); 2449 if (sourceRegion != null) { 2450 // Clip to actual image bounds 2451 sourceRegion = sourceRegion.intersection(imageBounds); 2452 2453 sourceXOffset = sourceRegion.x; 2454 sourceYOffset = sourceRegion.y; 2455 sourceWidth = sourceRegion.width; 2456 sourceHeight = sourceRegion.height; 2457 } 2458 2459 // Adjust for subsampling offsets 2460 int gridX = param.getSubsamplingXOffset(); 2461 int gridY = param.getSubsamplingYOffset(); 2462 this.sourceXOffset += gridX; 2463 this.sourceYOffset += gridY; 2464 this.sourceWidth -= gridX; 2465 this.sourceHeight -= gridY; 2466 2467 // Get subsampling factors 2468 this.periodX = param.getSourceXSubsampling(); 2469 this.periodY = param.getSourceYSubsampling(); 2470 2471 int[] sBands = param.getSourceBands(); 2472 if (sBands != null) { 2473 sourceBands = sBands; 2474 this.numBands = sourceBands.length; 2475 } else { 2476 this.numBands = sampleModel.getNumBands(); 2477 } 2478 2479 ImageTypeSpecifier destType = p.getDestinationType(); 2480 if(destType != null) { 2481 ColorModel cm = destType.getColorModel(); 2482 if(cm.getNumComponents() == numBands) { 2483 colorModel = cm; 2484 } 2485 } 2486 2487 if(colorModel == null) { 2488 colorModel = image.getColorModel(); 2489 } 2490 } 2491 2492 this.imageType = new ImageTypeSpecifier(colorModel, sampleModel); 2493 2494 ImageUtil.canEncodeImage(this, this.imageType); 2495 2496 // Compute output dimensions 2497 int destWidth = (sourceWidth + periodX - 1)/periodX; 2498 int destHeight = (sourceHeight + periodY - 1)/periodY; 2499 if (destWidth <= 0 || destHeight <= 0) { 2500 throw new IllegalArgumentException("Empty source region!"); 2501 } 2502 2503 // this.bitDepth = 8; // XXX fix? 2504 2505 clearAbortRequest(); 2506 processImageStarted(0); 2507 2508 // Optionally write the header. 2509 if (writeHeader) { 2510 // Clear previous stream metadata. 2511 this.streamMetadata = null; 2512 2513 // Try to convert non-null input stream metadata. 2514 if (sm != null) { 2515 this.streamMetadata = 2516 (TIFFStreamMetadata)convertStreamMetadata(sm, param); 2517 } 2518 2519 // Set to default if not converted. 2520 if(this.streamMetadata == null) { 2521 this.streamMetadata = 2522 (TIFFStreamMetadata)getDefaultStreamMetadata(param); 2523 } 2524 2525 // Write the header. 2526 writeHeader(); 2527 2528 // Seek to the position of the IFD pointer in the header. 2529 stream.seek(headerPosition + 4); 2530 2531 // Ensure IFD is written on a word boundary 2532 nextSpace = (nextSpace + 3) & ~0x3; 2533 2534 // Write the pointer to the first IFD after the header. 2535 stream.writeInt((int)nextSpace); 2536 } 2537 2538 // Write out the IFD and any sub IFDs, followed by a zero 2539 2540 // Clear previous image metadata. 2541 this.imageMetadata = null; 2542 2543 // Initialize the metadata object. 2544 IIOMetadata im = iioimage.getMetadata(); 2545 if(im != null) { 2546 if (im instanceof TIFFImageMetadata) { 2547 // Clone the one passed in. 2548 this.imageMetadata = ((TIFFImageMetadata)im).getShallowClone(); 2549 } else if(Arrays.asList(im.getMetadataFormatNames()).contains( 2550 TIFFImageMetadata.nativeMetadataFormatName)) { 2551 this.imageMetadata = convertNativeImageMetadata(im); 2552 } else if(im.isStandardMetadataFormatSupported()) { 2553 try { 2554 // Convert standard metadata. 2555 this.imageMetadata = convertStandardImageMetadata(im); 2556 } catch(IIOInvalidTreeException e) { 2557 // XXX Warning 2558 } 2559 } 2560 } 2561 2562 // Use default metadata if still null. 2563 if(this.imageMetadata == null) { 2564 this.imageMetadata = 2565 (TIFFImageMetadata)getDefaultImageMetadata(this.imageType, 2566 this.param); 2567 } 2568 2569 // Set or overwrite mandatory fields in the root IFD 2570 setupMetadata(colorModel, sampleModel, destWidth, destHeight); 2571 2572 // Set compressor fields. 2573 compressor.setWriter(this); 2574 // Metadata needs to be set on the compressor before the IFD is 2575 // written as the compressor could modify the metadata. 2576 compressor.setMetadata(imageMetadata); 2577 compressor.setStream(stream); 2578 2579 // Initialize scaling tables for this image 2580 int[] sampleSize = sampleModel.getSampleSize(); 2581 initializeScaleTables(sampleModel.getSampleSize()); 2582 2583 // Determine whether bilevel. 2584 this.isBilevel = ImageUtil.isBinary(this.image.getSampleModel()); 2585 2586 // Check for photometric inversion. 2587 this.isInverted = 2588 (nativePhotometricInterpretation == 2589 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO && 2590 photometricInterpretation == 2591 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) || 2592 (nativePhotometricInterpretation == 2593 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO && 2594 photometricInterpretation == 2595 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO); 2596 2597 // Analyze image data suitability for direct copy. 2598 this.isImageSimple = 2599 (isBilevel || 2600 (!isInverted && ImageUtil.imageIsContiguous(this.image))) && 2601 !isRescaling && // no value rescaling 2602 sourceBands == null && // no subbanding 2603 periodX == 1 && periodY == 1 && // no subsampling 2604 colorConverter == null; 2605 2606 TIFFIFD rootIFD = imageMetadata.getRootIFD(); 2607 2608 rootIFD.writeToStream(stream); 2609 2610 this.nextIFDPointerPos = stream.getStreamPosition(); 2611 stream.writeInt(0); 2612 2613 // Seek to end of IFD data 2614 long lastIFDPosition = rootIFD.getLastPosition(); 2615 stream.seek(lastIFDPosition); 2616 if(lastIFDPosition > this.nextSpace) { 2617 this.nextSpace = lastIFDPosition; 2618 } 2619 2620 // If not writing the image data, i.e., if writing or inserting an 2621 // empty image, return. 2622 if(!writeData) { 2623 return; 2624 } 2625 2626 // Get positions of fields within the IFD to update as we write 2627 // each strip or tile 2628 long stripOrTileByteCountsPosition = 2629 rootIFD.getStripOrTileByteCountsPosition(); 2630 long stripOrTileOffsetsPosition = 2631 rootIFD.getStripOrTileOffsetsPosition(); 2632 2633 // Compute total number of pixels for progress notification 2634 this.totalPixels = tileWidth*tileLength*tilesDown*tilesAcross; 2635 this.pixelsDone = 0; 2636 2637 // Write the image, a strip or tile at a time 2638 for (int tj = 0; tj < tilesDown; tj++) { 2639 for (int ti = 0; ti < tilesAcross; ti++) { 2640 long pos = stream.getStreamPosition(); 2641 2642 // Write the (possibly compressed) tile data 2643 2644 Rectangle tileRect = 2645 new Rectangle(sourceXOffset + ti*tileWidth*periodX, 2646 sourceYOffset + tj*tileLength*periodY, 2647 tileWidth*periodX, 2648 tileLength*periodY); 2649 // tileRect = tileRect.intersection(imageBounds); // XXX 2650 2651 try { 2652 int byteCount = writeTile(tileRect, compressor); 2653 2654 if(pos + byteCount > nextSpace) { 2655 nextSpace = pos + byteCount; 2656 } 2657 2658 pixelsDone += tileRect.width*tileRect.height; 2659 processImageProgress(100.0F*pixelsDone/totalPixels); 2660 2661 // Fill in the offset and byte count for the file 2662 stream.mark(); 2663 stream.seek(stripOrTileOffsetsPosition); 2664 stream.writeInt((int)pos); 2665 stripOrTileOffsetsPosition += 4; 2666 2667 stream.seek(stripOrTileByteCountsPosition); 2668 stream.writeInt(byteCount); 2669 stripOrTileByteCountsPosition += 4; 2670 stream.reset(); 2671 } catch (IOException e) { 2672 throw new IIOException("I/O error writing TIFF file!", e); 2673 } 2674 2675 if (abortRequested()) { 2676 processWriteAborted(); 2677 return; 2678 } 2679 } 2680 } 2681 2682 processImageComplete(); 2683 } 2684 2685 public boolean canWriteSequence() { 2686 return true; 2687 } 2688 2689 public void prepareWriteSequence(IIOMetadata streamMetadata) 2690 throws IOException { 2691 if (getOutput() == null) { 2692 throw new IllegalStateException("getOutput() == null!"); 2693 } 2694 2695 // Set up stream metadata. 2696 if (streamMetadata != null) { 2697 streamMetadata = convertStreamMetadata(streamMetadata, null); 2698 } 2699 if(streamMetadata == null) { 2700 streamMetadata = getDefaultStreamMetadata(null); 2701 } 2702 this.streamMetadata = (TIFFStreamMetadata)streamMetadata; 2703 2704 // Write the header. 2705 writeHeader(); 2706 2707 // Set the sequence flag. 2708 this.isWritingSequence = true; 2709 } 2710 2711 public void writeToSequence(IIOImage image, ImageWriteParam param) 2712 throws IOException { 2713 // Check sequence flag. 2714 if(!this.isWritingSequence) { 2715 throw new IllegalStateException 2716 ("prepareWriteSequence() has not been called!"); 2717 } 2718 2719 // Append image. 2720 writeInsert(-1, image, param); 2721 } 2722 2723 public void endWriteSequence() throws IOException { 2724 // Check output. 2725 if (getOutput() == null) { 2726 throw new IllegalStateException("getOutput() == null!"); 2727 } 2728 2729 // Check sequence flag. 2730 if(!isWritingSequence) { 2731 throw new IllegalStateException 2732 ("prepareWriteSequence() has not been called!"); 2733 } 2734 2735 // Unset sequence flag. 2736 this.isWritingSequence = false; 2737 } 2738 2739 public boolean canInsertImage(int imageIndex) throws IOException { 2740 if (getOutput() == null) { 2741 throw new IllegalStateException("getOutput() == null!"); 2742 } 2743 2744 // Mark position as locateIFD() will seek to IFD at imageIndex. 2745 stream.mark(); 2746 2747 // locateIFD() will throw an IndexOutOfBoundsException if 2748 // imageIndex is < -1 or is too big thereby satisfying the spec. 2749 long[] ifdpos = new long[1]; 2750 long[] ifd = new long[1]; 2751 locateIFD(imageIndex, ifdpos, ifd); 2752 2753 // Reset to position before locateIFD(). 2754 stream.reset(); 2755 2756 return true; 2757 } 2758 2759 // Locate start of IFD for image. 2760 // Throws IIOException if not at a TIFF header and 2761 // IndexOutOfBoundsException if imageIndex is < -1 or is too big. 2762 private void locateIFD(int imageIndex, long[] ifdpos, long[] ifd) 2763 throws IOException { 2764 2765 if(imageIndex < -1) { 2766 throw new IndexOutOfBoundsException("imageIndex < -1!"); 2767 } 2768 2769 long startPos = stream.getStreamPosition(); 2770 2771 stream.seek(headerPosition); 2772 int byteOrder = stream.readUnsignedShort(); 2773 if (byteOrder == 0x4d4d) { 2774 stream.setByteOrder(ByteOrder.BIG_ENDIAN); 2775 } else if (byteOrder == 0x4949) { 2776 stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); 2777 } else { 2778 stream.seek(startPos); 2779 throw new IIOException("Illegal byte order"); 2780 } 2781 if (stream.readUnsignedShort() != 42) { 2782 stream.seek(startPos); 2783 throw new IIOException("Illegal magic number"); 2784 } 2785 2786 ifdpos[0] = stream.getStreamPosition(); 2787 ifd[0] = stream.readUnsignedInt(); 2788 if (ifd[0] == 0) { 2789 // imageIndex has to be >= -1 due to check above. 2790 if(imageIndex > 0) { 2791 stream.seek(startPos); 2792 throw new IndexOutOfBoundsException 2793 ("imageIndex is greater than the largest available index!"); 2794 } 2795 return; 2796 } 2797 stream.seek(ifd[0]); 2798 2799 for (int i = 0; imageIndex == -1 || i < imageIndex; i++) { 2800 int numFields; 2801 try { 2802 numFields = stream.readShort(); 2803 } catch (EOFException eof) { 2804 stream.seek(startPos); 2805 ifd[0] = 0; 2806 return; 2807 } 2808 2809 stream.skipBytes(12*numFields); 2810 2811 ifdpos[0] = stream.getStreamPosition(); 2812 ifd[0] = stream.readUnsignedInt(); 2813 if (ifd[0] == 0) { 2814 if (imageIndex != -1 && i < imageIndex - 1) { 2815 stream.seek(startPos); 2816 throw new IndexOutOfBoundsException( 2817 "imageIndex is greater than the largest available index!"); 2818 } 2819 break; 2820 } 2821 stream.seek(ifd[0]); 2822 } 2823 } 2824 2825 public void writeInsert(int imageIndex, 2826 IIOImage image, 2827 ImageWriteParam param) throws IOException { 2828 insert(imageIndex, image, param, true); 2829 } 2830 2831 private void insert(int imageIndex, 2832 IIOImage image, 2833 ImageWriteParam param, 2834 boolean writeData) throws IOException { 2835 if (stream == null) { 2836 throw new IllegalStateException("Output not set!"); 2837 } 2838 if (image == null) { 2839 throw new IllegalArgumentException("image == null!"); 2840 } 2841 2842 // Locate the position of the old IFD (ifd) and the location 2843 // of the pointer to that position (ifdpos). 2844 long[] ifdpos = new long[1]; 2845 long[] ifd = new long[1]; 2846 2847 // locateIFD() will throw an IndexOutOfBoundsException if 2848 // imageIndex is < -1 or is too big thereby satisfying the spec. 2849 locateIFD(imageIndex, ifdpos, ifd); 2850 2851 // Seek to the position containing the pointer to the old IFD. 2852 stream.seek(ifdpos[0]); 2853 2854 // Update next space pointer in anticipation of next write. 2855 if(ifdpos[0] + 4 > nextSpace) { 2856 nextSpace = ifdpos[0] + 4; 2857 } 2858 2859 // Ensure IFD is written on a word boundary 2860 nextSpace = (nextSpace + 3) & ~0x3; 2861 2862 // Update the value to point to the next available space. 2863 stream.writeInt((int)nextSpace); 2864 2865 // Seek to the next available space. 2866 stream.seek(nextSpace); 2867 2868 // Write the image (IFD and data). 2869 write(null, image, param, false, writeData); 2870 2871 // Seek to the position containing the pointer in the new IFD. 2872 stream.seek(nextIFDPointerPos); 2873 2874 // Update the new IFD to point to the old IFD. 2875 stream.writeInt((int)ifd[0]); 2876 // Don't need to update nextSpace here as already done in write(). 2877 } 2878 2879 // ----- BEGIN insert/writeEmpty methods ----- 2880 2881 // XXX Move local variable(s) up. 2882 private boolean isInsertingEmpty = false; 2883 private boolean isWritingEmpty = false; 2884 2885 private boolean isEncodingEmpty() { 2886 return isInsertingEmpty || isWritingEmpty; 2887 } 2888 2889 public boolean canInsertEmpty(int imageIndex) throws IOException { 2890 return canInsertImage(imageIndex); 2891 } 2892 2893 public boolean canWriteEmpty() throws IOException { 2894 if (getOutput() == null) { 2895 throw new IllegalStateException("getOutput() == null!"); 2896 } 2897 return true; 2898 } 2899 2900 // Check state and parameters for writing or inserting empty images. 2901 private void checkParamsEmpty(ImageTypeSpecifier imageType, 2902 int width, 2903 int height, 2904 List thumbnails) { 2905 if (getOutput() == null) { 2906 throw new IllegalStateException("getOutput() == null!"); 2907 } 2908 2909 if(imageType == null) { 2910 throw new IllegalArgumentException("imageType == null!"); 2911 } 2912 2913 if(width < 1 || height < 1) { 2914 throw new IllegalArgumentException("width < 1 || height < 1!"); 2915 } 2916 2917 if(thumbnails != null) { 2918 int numThumbs = thumbnails.size(); 2919 for(int i = 0; i < numThumbs; i++) { 2920 Object thumb = thumbnails.get(i); 2921 if(thumb == null || !(thumb instanceof BufferedImage)) { 2922 throw new IllegalArgumentException 2923 ("thumbnails contains null references or objects other than BufferedImages!"); 2924 } 2925 } 2926 } 2927 2928 if(this.isInsertingEmpty) { 2929 throw new IllegalStateException 2930 ("Previous call to prepareInsertEmpty() without corresponding call to endInsertEmpty()!"); 2931 } 2932 2933 if(this.isWritingEmpty) { 2934 throw new IllegalStateException 2935 ("Previous call to prepareWriteEmpty() without corresponding call to endWriteEmpty()!"); 2936 } 2937 } 2938 2939 public void prepareInsertEmpty(int imageIndex, 2940 ImageTypeSpecifier imageType, 2941 int width, 2942 int height, 2943 IIOMetadata imageMetadata, 2944 List thumbnails, 2945 ImageWriteParam param) throws IOException { 2946 checkParamsEmpty(imageType, width, height, thumbnails); 2947 2948 this.isInsertingEmpty = true; 2949 2950 SampleModel emptySM = imageType.getSampleModel(); 2951 RenderedImage emptyImage = 2952 new EmptyImage(0, 0, width, height, 2953 0, 0, emptySM.getWidth(), emptySM.getHeight(), 2954 emptySM, imageType.getColorModel()); 2955 2956 insert(imageIndex, new IIOImage(emptyImage, null, imageMetadata), 2957 param, false); 2958 } 2959 2960 public void prepareWriteEmpty(IIOMetadata streamMetadata, 2961 ImageTypeSpecifier imageType, 2962 int width, 2963 int height, 2964 IIOMetadata imageMetadata, 2965 List thumbnails, 2966 ImageWriteParam param) throws IOException { 2967 checkParamsEmpty(imageType, width, height, thumbnails); 2968 2969 this.isWritingEmpty = true; 2970 2971 SampleModel emptySM = imageType.getSampleModel(); 2972 RenderedImage emptyImage = 2973 new EmptyImage(0, 0, width, height, 2974 0, 0, emptySM.getWidth(), emptySM.getHeight(), 2975 emptySM, imageType.getColorModel()); 2976 2977 write(streamMetadata, new IIOImage(emptyImage, null, imageMetadata), 2978 param, true, false); 2979 } 2980 2981 public void endInsertEmpty() throws IOException { 2982 if (getOutput() == null) { 2983 throw new IllegalStateException("getOutput() == null!"); 2984 } 2985 2986 if(!this.isInsertingEmpty) { 2987 throw new IllegalStateException 2988 ("No previous call to prepareInsertEmpty()!"); 2989 } 2990 2991 if(this.isWritingEmpty) { 2992 throw new IllegalStateException 2993 ("Previous call to prepareWriteEmpty() without corresponding call to endWriteEmpty()!"); 2994 } 2995 2996 if (inReplacePixelsNest) { 2997 throw new IllegalStateException 2998 ("In nested call to prepareReplacePixels!"); 2999 } 3000 3001 this.isInsertingEmpty = false; 3002 } 3003 3004 public void endWriteEmpty() throws IOException { 3005 if (getOutput() == null) { 3006 throw new IllegalStateException("getOutput() == null!"); 3007 } 3008 3009 if(!this.isWritingEmpty) { 3010 throw new IllegalStateException 3011 ("No previous call to prepareWriteEmpty()!"); 3012 } 3013 3014 if(this.isInsertingEmpty) { 3015 throw new IllegalStateException 3016 ("Previous call to prepareInsertEmpty() without corresponding call to endInsertEmpty()!"); 3017 } 3018 3019 if (inReplacePixelsNest) { 3020 throw new IllegalStateException 3021 ("In nested call to prepareReplacePixels!"); 3022 } 3023 3024 this.isWritingEmpty = false; 3025 } 3026 3027 // ----- END insert/writeEmpty methods ----- 3028 3029 // ----- BEGIN replacePixels methods ----- 3030 3031 private TIFFIFD readIFD(int imageIndex) throws IOException { 3032 if (stream == null) { 3033 throw new IllegalStateException("Output not set!"); 3034 } 3035 if (imageIndex < 0) { 3036 throw new IndexOutOfBoundsException("imageIndex < 0!"); 3037 } 3038 3039 stream.mark(); 3040 long[] ifdpos = new long[1]; 3041 long[] ifd = new long[1]; 3042 locateIFD(imageIndex, ifdpos, ifd); 3043 if (ifd[0] == 0) { 3044 stream.reset(); 3045 throw new IndexOutOfBoundsException 3046 ("imageIndex out of bounds!"); 3047 } 3048 3049 List tagSets = new ArrayList(1); 3050 tagSets.add(BaselineTIFFTagSet.getInstance()); 3051 TIFFIFD rootIFD = new TIFFIFD(tagSets); 3052 // XXX Ignore unknown fields in metadata presumably because 3053 // any fields needed to write pixels would be known? 3054 rootIFD.initialize(stream, true); 3055 stream.reset(); 3056 3057 return rootIFD; 3058 } 3059 3060 public boolean canReplacePixels(int imageIndex) throws IOException { 3061 if (getOutput() == null) { 3062 throw new IllegalStateException("getOutput() == null!"); 3063 } 3064 3065 TIFFIFD rootIFD = readIFD(imageIndex); 3066 TIFFField f = rootIFD.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION); 3067 int compression = f.getAsInt(0); 3068 3069 return compression == BaselineTIFFTagSet.COMPRESSION_NONE; 3070 } 3071 3072 private Object replacePixelsLock = new Object(); 3073 3074 private int replacePixelsIndex = -1; 3075 private TIFFImageMetadata replacePixelsMetadata = null; 3076 private long[] replacePixelsTileOffsets = null; 3077 private long[] replacePixelsByteCounts = null; 3078 private long replacePixelsOffsetsPosition = 0L; 3079 private long replacePixelsByteCountsPosition = 0L; 3080 private Rectangle replacePixelsRegion = null; 3081 private boolean inReplacePixelsNest = false; 3082 3083 private TIFFImageReader reader = null; 3084 3085 public void prepareReplacePixels(int imageIndex, 3086 Rectangle region) throws IOException { 3087 synchronized(replacePixelsLock) { 3088 // Check state and parameters vis-a-vis ImageWriter specification. 3089 if (stream == null) { 3090 throw new IllegalStateException("Output not set!"); 3091 } 3092 if (region == null) { 3093 throw new IllegalArgumentException("region == null!"); 3094 } 3095 if (region.getWidth() < 1) { 3096 throw new IllegalArgumentException("region.getWidth() < 1!"); 3097 } 3098 if (region.getHeight() < 1) { 3099 throw new IllegalArgumentException("region.getHeight() < 1!"); 3100 } 3101 if (inReplacePixelsNest) { 3102 throw new IllegalStateException 3103 ("In nested call to prepareReplacePixels!"); 3104 } 3105 3106 // Read the IFD for the pixel replacement index. 3107 TIFFIFD replacePixelsIFD = readIFD(imageIndex); 3108 3109 // Ensure that compression is "none". 3110 TIFFField f = 3111 replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_COMPRESSION); 3112 int compression = f.getAsInt(0); 3113 if (compression != BaselineTIFFTagSet.COMPRESSION_NONE) { 3114 throw new UnsupportedOperationException 3115 ("canReplacePixels(imageIndex) == false!"); 3116 } 3117 3118 // Get the image dimensions. 3119 f = 3120 replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_WIDTH); 3121 if(f == null) { 3122 throw new IIOException("Cannot read ImageWidth field."); 3123 } 3124 int w = f.getAsInt(0); 3125 3126 f = 3127 replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_IMAGE_LENGTH); 3128 if(f == null) { 3129 throw new IIOException("Cannot read ImageHeight field."); 3130 } 3131 int h = f.getAsInt(0); 3132 3133 // Create image bounds. 3134 Rectangle bounds = new Rectangle(0, 0, w, h); 3135 3136 // Intersect region with bounds. 3137 region = region.intersection(bounds); 3138 3139 // Check for empty intersection. 3140 if(region.isEmpty()) { 3141 throw new IIOException("Region does not intersect image bounds"); 3142 } 3143 3144 // Save the region. 3145 replacePixelsRegion = region; 3146 3147 // Get the tile offsets. 3148 f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_OFFSETS); 3149 if(f == null) { 3150 f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_OFFSETS); 3151 } 3152 replacePixelsTileOffsets = f.getAsLongs(); 3153 3154 // Get the byte counts. 3155 f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_TILE_BYTE_COUNTS); 3156 if(f == null) { 3157 f = replacePixelsIFD.getTIFFField(BaselineTIFFTagSet.TAG_STRIP_BYTE_COUNTS); 3158 } 3159 replacePixelsByteCounts = f.getAsLongs(); 3160 3161 replacePixelsOffsetsPosition = 3162 replacePixelsIFD.getStripOrTileOffsetsPosition(); 3163 replacePixelsByteCountsPosition = 3164 replacePixelsIFD.getStripOrTileByteCountsPosition(); 3165 3166 // Get the image metadata. 3167 replacePixelsMetadata = new TIFFImageMetadata(replacePixelsIFD); 3168 3169 // Save the image index. 3170 replacePixelsIndex = imageIndex; 3171 3172 // Set the pixel replacement flag. 3173 inReplacePixelsNest = true; 3174 } 3175 } 3176 3177 private Raster subsample(Raster raster, int[] sourceBands, 3178 int subOriginX, int subOriginY, 3179 int subPeriodX, int subPeriodY, 3180 int dstOffsetX, int dstOffsetY, 3181 Rectangle target) { 3182 3183 int x = raster.getMinX(); 3184 int y = raster.getMinY(); 3185 int w = raster.getWidth(); 3186 int h = raster.getHeight(); 3187 int b = raster.getSampleModel().getNumBands(); 3188 int t = raster.getSampleModel().getDataType(); 3189 3190 int outMinX = XToTileX(x, subOriginX, subPeriodX) + dstOffsetX; 3191 int outMinY = YToTileY(y, subOriginY, subPeriodY) + dstOffsetY; 3192 int outMaxX = XToTileX(x + w - 1, subOriginX, subPeriodX) + dstOffsetX; 3193 int outMaxY = YToTileY(y + h - 1, subOriginY, subPeriodY) + dstOffsetY; 3194 int outWidth = outMaxX - outMinX + 1; 3195 int outHeight = outMaxY - outMinY + 1; 3196 3197 if(outWidth <= 0 || outHeight <= 0) return null; 3198 3199 int inMinX = (outMinX - dstOffsetX)*subPeriodX + subOriginX; 3200 int inMaxX = (outMaxX - dstOffsetX)*subPeriodX + subOriginX; 3201 int inWidth = inMaxX - inMinX + 1; 3202 int inMinY = (outMinY - dstOffsetY)*subPeriodY + subOriginY; 3203 int inMaxY = (outMaxY - dstOffsetY)*subPeriodY + subOriginY; 3204 int inHeight = inMaxY - inMinY + 1; 3205 3206 WritableRaster wr = 3207 raster.createCompatibleWritableRaster(outMinX, outMinY, 3208 outWidth, outHeight); 3209 3210 int jMax = inMinY + inHeight; 3211 3212 if(t == DataBuffer.TYPE_FLOAT || t == DataBuffer.TYPE_DOUBLE) { 3213 float[] fsamples = new float[inWidth]; 3214 float[] fsubsamples = new float[outWidth]; 3215 3216 for(int k = 0; k < b; k++) { 3217 int outY = outMinY; 3218 for(int j = inMinY; j < jMax; j += subPeriodY) { 3219 raster.getSamples(inMinX, j, inWidth, 1, k, fsamples); 3220 int s = 0; 3221 for(int i = 0; i < inWidth; i += subPeriodX) { 3222 fsubsamples[s++] = fsamples[i]; 3223 } 3224 wr.setSamples(outMinX, outY++, outWidth, 1, k, 3225 fsubsamples); 3226 } 3227 } 3228 } else { 3229 int[] samples = new int[inWidth]; 3230 int[] subsamples = new int[outWidth]; 3231 3232 for(int k = 0; k < b; k++) { 3233 int outY = outMinY; 3234 for(int j = inMinY; j < jMax; j += subPeriodY) { 3235 raster.getSamples(inMinX, j, inWidth, 1, k, samples); 3236 int s = 0; 3237 for(int i = 0; i < inWidth; i += subPeriodX) { 3238 subsamples[s++] = samples[i]; 3239 } 3240 wr.setSamples(outMinX, outY++, outWidth, 1, k, 3241 subsamples); 3242 } 3243 } 3244 } 3245 3246 return wr.createChild(outMinX, outMinY, 3247 target.width, target.height, 3248 target.x, target.y, 3249 sourceBands); 3250 } 3251 3252 public void replacePixels(RenderedImage image, ImageWriteParam param) 3253 throws IOException { 3254 3255 synchronized(replacePixelsLock) { 3256 // Check state and parameters vis-a-vis ImageWriter specification. 3257 if (stream == null) { 3258 throw new IllegalStateException("stream == null!"); 3259 } 3260 3261 if (image == null) { 3262 throw new IllegalArgumentException("image == null!"); 3263 } 3264 3265 if (!inReplacePixelsNest) { 3266 throw new IllegalStateException 3267 ("No previous call to prepareReplacePixels!"); 3268 } 3269 3270 // Subsampling values. 3271 int stepX = 1, stepY = 1, gridX = 0, gridY = 0; 3272 3273 // Initialize the ImageWriteParam. 3274 if (param == null) { 3275 // Use the default. 3276 param = getDefaultWriteParam(); 3277 } else { 3278 // Make a copy of the ImageWriteParam. 3279 ImageWriteParam paramCopy = getDefaultWriteParam(); 3280 3281 // Force uncompressed. 3282 paramCopy.setCompressionMode(ImageWriteParam.MODE_DISABLED); 3283 3284 // Force tiling to remain as in the already written image. 3285 paramCopy.setTilingMode(ImageWriteParam.MODE_COPY_FROM_METADATA); 3286 3287 // Retain source and destination region and band settings. 3288 paramCopy.setDestinationOffset(param.getDestinationOffset()); 3289 paramCopy.setSourceBands(param.getSourceBands()); 3290 paramCopy.setSourceRegion(param.getSourceRegion()); 3291 3292 // Save original subsampling values for subsampling the 3293 // replacement data - not the data re-read from the image. 3294 stepX = param.getSourceXSubsampling(); 3295 stepY = param.getSourceYSubsampling(); 3296 gridX = param.getSubsamplingXOffset(); 3297 gridY = param.getSubsamplingYOffset(); 3298 3299 // Replace the param. 3300 param = paramCopy; 3301 } 3302 3303 // Check band count and bit depth compatibility. 3304 TIFFField f = 3305 replacePixelsMetadata.getTIFFField(BaselineTIFFTagSet.TAG_BITS_PER_SAMPLE); 3306 if(f == null) { 3307 throw new IIOException 3308 ("Cannot read destination BitsPerSample"); 3309 } 3310 int[] dstBitsPerSample = f.getAsInts(); 3311 int[] srcBitsPerSample = image.getSampleModel().getSampleSize(); 3312 int[] sourceBands = param.getSourceBands(); 3313 if(sourceBands != null) { 3314 if(sourceBands.length != dstBitsPerSample.length) { 3315 throw new IIOException 3316 ("Source and destination have different SamplesPerPixel"); 3317 } 3318 for(int i = 0; i < sourceBands.length; i++) { 3319 if(dstBitsPerSample[i] != 3320 srcBitsPerSample[sourceBands[i]]) { 3321 throw new IIOException 3322 ("Source and destination have different BitsPerSample"); 3323 } 3324 } 3325 } else { 3326 int srcNumBands = image.getSampleModel().getNumBands(); 3327 if(srcNumBands != dstBitsPerSample.length) { 3328 throw new IIOException 3329 ("Source and destination have different SamplesPerPixel"); 3330 } 3331 for(int i = 0; i < srcNumBands; i++) { 3332 if(dstBitsPerSample[i] != srcBitsPerSample[i]) { 3333 throw new IIOException 3334 ("Source and destination have different BitsPerSample"); 3335 } 3336 } 3337 } 3338 3339 // Get the source image bounds. 3340 Rectangle srcImageBounds = 3341 new Rectangle(image.getMinX(), image.getMinY(), 3342 image.getWidth(), image.getHeight()); 3343 3344 // Initialize the source rect. 3345 Rectangle srcRect = param.getSourceRegion(); 3346 if(srcRect == null) { 3347 srcRect = srcImageBounds; 3348 } 3349 3350 // Set subsampling grid parameters. 3351 int subPeriodX = stepX; 3352 int subPeriodY = stepY; 3353 int subOriginX = gridX + srcRect.x; 3354 int subOriginY = gridY + srcRect.y; 3355 3356 // Intersect with the source bounds. 3357 if(!srcRect.equals(srcImageBounds)) { 3358 srcRect = srcRect.intersection(srcImageBounds); 3359 if(srcRect.isEmpty()) { 3360 throw new IllegalArgumentException 3361 ("Source region does not intersect source image!"); 3362 } 3363 } 3364 3365 // Get the destination offset. 3366 Point dstOffset = param.getDestinationOffset(); 3367 3368 // Forward map source rectangle to determine destination width. 3369 int dMinX = XToTileX(srcRect.x, subOriginX, subPeriodX) + 3370 dstOffset.x; 3371 int dMinY = YToTileY(srcRect.y, subOriginY, subPeriodY) + 3372 dstOffset.y; 3373 int dMaxX = XToTileX(srcRect.x + srcRect.width, 3374 subOriginX, subPeriodX) + dstOffset.x; 3375 int dMaxY = YToTileY(srcRect.y + srcRect.height, 3376 subOriginY, subPeriodY) + dstOffset.y; 3377 3378 // Initialize the destination rectangle. 3379 Rectangle dstRect = 3380 new Rectangle(dstOffset.x, dstOffset.y, 3381 dMaxX - dMinX, dMaxY - dMinY); 3382 3383 // Intersect with the replacement region. 3384 dstRect = dstRect.intersection(replacePixelsRegion); 3385 if(dstRect.isEmpty()) { 3386 throw new IllegalArgumentException 3387 ("Forward mapped source region does not intersect destination region!"); 3388 } 3389 3390 // Backward map to the active source region. 3391 int activeSrcMinX = (dstRect.x - dstOffset.x)*subPeriodX + 3392 subOriginX; 3393 int sxmax = 3394 (dstRect.x + dstRect.width - 1 - dstOffset.x)*subPeriodX + 3395 subOriginX; 3396 int activeSrcWidth = sxmax - activeSrcMinX + 1; 3397 3398 int activeSrcMinY = (dstRect.y - dstOffset.y)*subPeriodY + 3399 subOriginY; 3400 int symax = 3401 (dstRect.y + dstRect.height - 1 - dstOffset.y)*subPeriodY + 3402 subOriginY; 3403 int activeSrcHeight = symax - activeSrcMinY + 1; 3404 Rectangle activeSrcRect = 3405 new Rectangle(activeSrcMinX, activeSrcMinY, 3406 activeSrcWidth, activeSrcHeight); 3407 if(activeSrcRect.intersection(srcImageBounds).isEmpty()) { 3408 throw new IllegalArgumentException 3409 ("Backward mapped destination region does not intersect source image!"); 3410 } 3411 3412 if(reader == null) { 3413 reader = new TIFFImageReader(new TIFFImageReaderSpi()); 3414 } else { 3415 reader.reset(); 3416 } 3417 3418 stream.mark(); 3419 3420 try { 3421 stream.seek(headerPosition); 3422 reader.setInput(stream); 3423 3424 this.imageMetadata = replacePixelsMetadata; 3425 this.param = param; 3426 SampleModel sm = image.getSampleModel(); 3427 ColorModel cm = image.getColorModel(); 3428 this.numBands = sm.getNumBands(); 3429 this.imageType = new ImageTypeSpecifier(image); 3430 this.periodX = param.getSourceXSubsampling(); 3431 this.periodY = param.getSourceYSubsampling(); 3432 this.sourceBands = null; 3433 int[] sBands = param.getSourceBands(); 3434 if (sBands != null) { 3435 this.sourceBands = sBands; 3436 this.numBands = sourceBands.length; 3437 } 3438 setupMetadata(cm, sm, 3439 reader.getWidth(replacePixelsIndex), 3440 reader.getHeight(replacePixelsIndex)); 3441 int[] scaleSampleSize = sm.getSampleSize(); 3442 initializeScaleTables(scaleSampleSize); 3443 3444 // Determine whether bilevel. 3445 this.isBilevel = ImageUtil.isBinary(image.getSampleModel()); 3446 3447 // Check for photometric inversion. 3448 this.isInverted = 3449 (nativePhotometricInterpretation == 3450 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO && 3451 photometricInterpretation == 3452 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO) || 3453 (nativePhotometricInterpretation == 3454 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO && 3455 photometricInterpretation == 3456 BaselineTIFFTagSet.PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO); 3457 3458 // Analyze image data suitability for direct copy. 3459 this.isImageSimple = 3460 (isBilevel || 3461 (!isInverted && ImageUtil.imageIsContiguous(image))) && 3462 !isRescaling && // no value rescaling 3463 sourceBands == null && // no subbanding 3464 periodX == 1 && periodY == 1 && // no subsampling 3465 colorConverter == null; 3466 3467 int minTileX = XToTileX(dstRect.x, 0, tileWidth); 3468 int minTileY = YToTileY(dstRect.y, 0, tileLength); 3469 int maxTileX = XToTileX(dstRect.x + dstRect.width - 1, 3470 0, tileWidth); 3471 int maxTileY = YToTileY(dstRect.y + dstRect.height - 1, 3472 0, tileLength); 3473 3474 TIFFCompressor encoder = new TIFFNullCompressor(); 3475 encoder.setWriter(this); 3476 encoder.setStream(stream); 3477 encoder.setMetadata(this.imageMetadata); 3478 3479 Rectangle tileRect = new Rectangle(); 3480 for(int ty = minTileY; ty <= maxTileY; ty++) { 3481 for(int tx = minTileX; tx <= maxTileX; tx++) { 3482 int tileIndex = ty*tilesAcross + tx; 3483 boolean isEmpty = 3484 replacePixelsByteCounts[tileIndex] == 0L; 3485 WritableRaster raster; 3486 if(isEmpty) { 3487 SampleModel tileSM = 3488 sm.createCompatibleSampleModel(tileWidth, 3489 tileLength); 3490 raster = Raster.createWritableRaster(tileSM, null); 3491 } else { 3492 BufferedImage tileImage = 3493 reader.readTile(replacePixelsIndex, tx, ty); 3494 raster = tileImage.getRaster(); 3495 } 3496 3497 tileRect.setLocation(tx*tileWidth, 3498 ty*tileLength); 3499 tileRect.setSize(raster.getWidth(), 3500 raster.getHeight()); 3501 raster = 3502 raster.createWritableTranslatedChild(tileRect.x, 3503 tileRect.y); 3504 3505 Rectangle replacementRect = 3506 tileRect.intersection(dstRect); 3507 3508 int srcMinX = 3509 (replacementRect.x - dstOffset.x)*subPeriodX + 3510 subOriginX; 3511 int srcXmax = 3512 (replacementRect.x + replacementRect.width - 1 - 3513 dstOffset.x)*subPeriodX + subOriginX; 3514 int srcWidth = srcXmax - srcMinX + 1; 3515 3516 int srcMinY = 3517 (replacementRect.y - dstOffset.y)*subPeriodY + 3518 subOriginY; 3519 int srcYMax = 3520 (replacementRect.y + replacementRect.height - 1 - 3521 dstOffset.y)*subPeriodY + subOriginY; 3522 int srcHeight = srcYMax - srcMinY + 1; 3523 Rectangle srcTileRect = 3524 new Rectangle(srcMinX, srcMinY, 3525 srcWidth, srcHeight); 3526 3527 Raster replacementData = image.getData(srcTileRect); 3528 if(subPeriodX == 1 && subPeriodY == 1 && 3529 subOriginX == 0 && subOriginY == 0) { 3530 replacementData = 3531 replacementData.createChild(srcTileRect.x, 3532 srcTileRect.y, 3533 srcTileRect.width, 3534 srcTileRect.height, 3535 replacementRect.x, 3536 replacementRect.y, 3537 sourceBands); 3538 } else { 3539 replacementData = subsample(replacementData, 3540 sourceBands, 3541 subOriginX, 3542 subOriginY, 3543 subPeriodX, 3544 subPeriodY, 3545 dstOffset.x, 3546 dstOffset.y, 3547 replacementRect); 3548 if(replacementData == null) { 3549 continue; 3550 } 3551 } 3552 3553 raster.setRect(replacementData); 3554 3555 if(isEmpty) { 3556 stream.seek(nextSpace); 3557 } else { 3558 stream.seek(replacePixelsTileOffsets[tileIndex]); 3559 } 3560 3561 this.image = new SingleTileRenderedImage(raster, cm); 3562 3563 int numBytes = writeTile(tileRect, encoder); 3564 3565 if(isEmpty) { 3566 // Update Strip/TileOffsets and 3567 // Strip/TileByteCounts fields. 3568 stream.mark(); 3569 stream.seek(replacePixelsOffsetsPosition + 3570 4*tileIndex); 3571 stream.writeInt((int)nextSpace); 3572 stream.seek(replacePixelsByteCountsPosition + 3573 4*tileIndex); 3574 stream.writeInt(numBytes); 3575 stream.reset(); 3576 3577 // Increment location of next available space. 3578 nextSpace += numBytes; 3579 } 3580 } 3581 } 3582 3583 } catch(IOException e) { 3584 throw e; 3585 } finally { 3586 stream.reset(); 3587 } 3588 } 3589 } 3590 3591 public void replacePixels(Raster raster, ImageWriteParam param) 3592 throws IOException { 3593 if (raster == null) { 3594 throw new IllegalArgumentException("raster == null!"); 3595 } 3596 3597 replacePixels(new SingleTileRenderedImage(raster, 3598 image.getColorModel()), 3599 param); 3600 } 3601 3602 public void endReplacePixels() throws IOException { 3603 synchronized(replacePixelsLock) { 3604 if(!this.inReplacePixelsNest) { 3605 throw new IllegalStateException 3606 ("No previous call to prepareReplacePixels()!"); 3607 } 3608 replacePixelsIndex = -1; 3609 replacePixelsMetadata = null; 3610 replacePixelsTileOffsets = null; 3611 replacePixelsByteCounts = null; 3612 replacePixelsOffsetsPosition = 0L; 3613 replacePixelsByteCountsPosition = 0L; 3614 replacePixelsRegion = null; 3615 inReplacePixelsNest = false; 3616 } 3617 } 3618 3619 // ----- END replacePixels methods ----- 3620 3621 public void reset() { 3622 super.reset(); 3623 3624 stream = null; 3625 image = null; 3626 imageType = null; 3627 byteOrder = null; 3628 param = null; 3629 if(compressor != null){ 3630 compressor.dispose(); 3631 } 3632 compressor = null; 3633 colorConverter = null; 3634 streamMetadata = null; 3635 imageMetadata = null; 3636 3637 isWritingSequence = false; 3638 isWritingEmpty = false; 3639 isInsertingEmpty = false; 3640 3641 replacePixelsIndex = -1; 3642 replacePixelsMetadata = null; 3643 replacePixelsTileOffsets = null; 3644 replacePixelsByteCounts = null; 3645 replacePixelsOffsetsPosition = 0L; 3646 replacePixelsByteCountsPosition = 0L; 3647 replacePixelsRegion = null; 3648 inReplacePixelsNest = false; 3649 } 3650 3651 public void dispose() { 3652 reset(); 3653 super.dispose(); 3654 } 3655} 3656 3657class EmptyImage extends SimpleRenderedImage { 3658 EmptyImage(int minX, int minY, int width, int height, 3659 int tileGridXOffset, int tileGridYOffset, 3660 int tileWidth, int tileHeight, 3661 SampleModel sampleModel, ColorModel colorModel) { 3662 this.minX = minX; 3663 this.minY = minY; 3664 this.width = width; 3665 this.height = height; 3666 this.tileGridXOffset = tileGridXOffset; 3667 this.tileGridYOffset = tileGridYOffset; 3668 this.tileWidth = tileWidth; 3669 this.tileHeight = tileHeight; 3670 this.sampleModel = sampleModel; 3671 this.colorModel = colorModel; 3672 } 3673 3674 public Raster getTile(int tileX, int tileY) { 3675 return null; 3676 } 3677}