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