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