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