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