001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.imaging.formats.tiff.write; 018 019import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.DEFAULT_TIFF_BYTE_ORDER; 020import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_1D; 021import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_3; 022import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_4; 023import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_LZW; 024import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_PACKBITS; 025import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED; 026import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_DEFLATE_ADOBE; 027import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_FLAG_T6_OPTIONS_UNCOMPRESSED_MODE; 028import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_HEADER_SIZE; 029 030import java.awt.image.BufferedImage; 031import java.awt.image.ColorModel; 032import java.io.IOException; 033import java.io.OutputStream; 034import java.nio.ByteOrder; 035import java.nio.charset.StandardCharsets; 036import java.util.ArrayList; 037import java.util.Collections; 038import java.util.HashMap; 039import java.util.HashSet; 040import java.util.List; 041import java.util.Map; 042 043import org.apache.commons.imaging.ImageWriteException; 044import org.apache.commons.imaging.PixelDensity; 045import org.apache.commons.imaging.common.BinaryOutputStream; 046import org.apache.commons.imaging.common.PackBits; 047import org.apache.commons.imaging.common.RationalNumber; 048import org.apache.commons.imaging.common.itu_t4.T4AndT6Compression; 049import org.apache.commons.imaging.common.mylzw.MyLzwCompressor; 050import org.apache.commons.imaging.common.ZlibDeflate; 051import org.apache.commons.imaging.formats.tiff.TiffElement; 052import org.apache.commons.imaging.formats.tiff.TiffImageData; 053import org.apache.commons.imaging.formats.tiff.TiffImagingParameters; 054import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants; 055import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryConstants; 056import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants; 057 058public abstract class TiffImageWriterBase { 059 060 protected final ByteOrder byteOrder; 061 062 public TiffImageWriterBase() { 063 this.byteOrder = DEFAULT_TIFF_BYTE_ORDER; 064 } 065 066 public TiffImageWriterBase(final ByteOrder byteOrder) { 067 this.byteOrder = byteOrder; 068 } 069 070 protected static int imageDataPaddingLength(final int dataLength) { 071 return (4 - (dataLength % 4)) % 4; 072 } 073 074 public abstract void write(OutputStream os, TiffOutputSet outputSet) 075 throws IOException, ImageWriteException; 076 077 protected TiffOutputSummary validateDirectories(final TiffOutputSet outputSet) 078 throws ImageWriteException { 079 final List<TiffOutputDirectory> directories = outputSet.getDirectories(); 080 081 if (directories.isEmpty()) { 082 throw new ImageWriteException("No directories."); 083 } 084 085 TiffOutputDirectory exifDirectory = null; 086 TiffOutputDirectory gpsDirectory = null; 087 TiffOutputDirectory interoperabilityDirectory = null; 088 TiffOutputField exifDirectoryOffsetField = null; 089 TiffOutputField gpsDirectoryOffsetField = null; 090 TiffOutputField interoperabilityDirectoryOffsetField = null; 091 092 final List<Integer> directoryIndices = new ArrayList<>(); 093 final Map<Integer, TiffOutputDirectory> directoryTypeMap = new HashMap<>(); 094 for (final TiffOutputDirectory directory : directories) { 095 final int dirType = directory.type; 096 directoryTypeMap.put(dirType, directory); 097 // Debug.debug("validating dirType", dirType + " (" 098 // + directory.getFields().size() + " fields)"); 099 100 if (dirType < 0) { 101 switch (dirType) { 102 case TiffDirectoryConstants.DIRECTORY_TYPE_EXIF: 103 if (exifDirectory != null) { 104 throw new ImageWriteException( 105 "More than one EXIF directory."); 106 } 107 exifDirectory = directory; 108 break; 109 110 case TiffDirectoryConstants.DIRECTORY_TYPE_GPS: 111 if (gpsDirectory != null) { 112 throw new ImageWriteException( 113 "More than one GPS directory."); 114 } 115 gpsDirectory = directory; 116 break; 117 118 case TiffDirectoryConstants.DIRECTORY_TYPE_INTEROPERABILITY: 119 if (interoperabilityDirectory != null) { 120 throw new ImageWriteException( 121 "More than one Interoperability directory."); 122 } 123 interoperabilityDirectory = directory; 124 break; 125 default: 126 throw new ImageWriteException("Unknown directory: " 127 + dirType); 128 } 129 } else { 130 if (directoryIndices.contains(dirType)) { 131 throw new ImageWriteException( 132 "More than one directory with index: " + dirType 133 + "."); 134 } 135 directoryIndices.add(dirType); 136 // dirMap.put(arg0, arg1) 137 } 138 139 final HashSet<Integer> fieldTags = new HashSet<>(); 140 final List<TiffOutputField> fields = directory.getFields(); 141 for (final TiffOutputField field : fields) { 142 if (fieldTags.contains(field.tag)) { 143 throw new ImageWriteException("Tag (" 144 + field.tagInfo.getDescription() 145 + ") appears twice in directory."); 146 } 147 fieldTags.add(field.tag); 148 149 if (field.tag == ExifTagConstants.EXIF_TAG_EXIF_OFFSET.tag) { 150 if (exifDirectoryOffsetField != null) { 151 throw new ImageWriteException( 152 "More than one Exif directory offset field."); 153 } 154 exifDirectoryOffsetField = field; 155 } else if (field.tag == ExifTagConstants.EXIF_TAG_INTEROP_OFFSET.tag) { 156 if (interoperabilityDirectoryOffsetField != null) { 157 throw new ImageWriteException( 158 "More than one Interoperability directory offset field."); 159 } 160 interoperabilityDirectoryOffsetField = field; 161 } else if (field.tag == ExifTagConstants.EXIF_TAG_GPSINFO.tag) { 162 if (gpsDirectoryOffsetField != null) { 163 throw new ImageWriteException( 164 "More than one GPS directory offset field."); 165 } 166 gpsDirectoryOffsetField = field; 167 } 168 } 169 // directory. 170 } 171 172 if (directoryIndices.isEmpty()) { 173 throw new ImageWriteException("Missing root directory."); 174 } 175 176 // "normal" TIFF directories should have continous indices starting with 177 // 0, ie. 0, 1, 2... 178 directoryIndices.sort(null); 179 180 TiffOutputDirectory previousDirectory = null; 181 for (int i = 0; i < directoryIndices.size(); i++) { 182 final Integer index = directoryIndices.get(i); 183 if (index != i) { 184 throw new ImageWriteException("Missing directory: " + i + "."); 185 } 186 187 // set up chain of directory references for "normal" directories. 188 final TiffOutputDirectory directory = directoryTypeMap.get(index); 189 if (null != previousDirectory) { 190 previousDirectory.setNextDirectory(directory); 191 } 192 previousDirectory = directory; 193 } 194 195 final TiffOutputDirectory rootDirectory = directoryTypeMap.get( 196 TiffDirectoryConstants.DIRECTORY_TYPE_ROOT); 197 198 // prepare results 199 final TiffOutputSummary result = new TiffOutputSummary(byteOrder, 200 rootDirectory, directoryTypeMap); 201 202 if (interoperabilityDirectory == null 203 && interoperabilityDirectoryOffsetField != null) { 204 // perhaps we should just discard field? 205 throw new ImageWriteException( 206 "Output set has Interoperability Directory Offset field, but no Interoperability Directory"); 207 } 208 if (interoperabilityDirectory != null) { 209 if (exifDirectory == null) { 210 exifDirectory = outputSet.addExifDirectory(); 211 } 212 213 if (interoperabilityDirectoryOffsetField == null) { 214 interoperabilityDirectoryOffsetField = 215 TiffOutputField.createOffsetField( 216 ExifTagConstants.EXIF_TAG_INTEROP_OFFSET, 217 byteOrder); 218 exifDirectory.add(interoperabilityDirectoryOffsetField); 219 } 220 221 result.add(interoperabilityDirectory, 222 interoperabilityDirectoryOffsetField); 223 } 224 225 // make sure offset fields and offset'd directories correspond. 226 if (exifDirectory == null && exifDirectoryOffsetField != null) { 227 // perhaps we should just discard field? 228 throw new ImageWriteException( 229 "Output set has Exif Directory Offset field, but no Exif Directory"); 230 } 231 if (exifDirectory != null) { 232 if (exifDirectoryOffsetField == null) { 233 exifDirectoryOffsetField = TiffOutputField.createOffsetField( 234 ExifTagConstants.EXIF_TAG_EXIF_OFFSET, byteOrder); 235 rootDirectory.add(exifDirectoryOffsetField); 236 } 237 238 result.add(exifDirectory, exifDirectoryOffsetField); 239 } 240 241 if (gpsDirectory == null && gpsDirectoryOffsetField != null) { 242 // perhaps we should just discard field? 243 throw new ImageWriteException( 244 "Output set has GPS Directory Offset field, but no GPS Directory"); 245 } 246 if (gpsDirectory != null) { 247 if (gpsDirectoryOffsetField == null) { 248 gpsDirectoryOffsetField = TiffOutputField.createOffsetField( 249 ExifTagConstants.EXIF_TAG_GPSINFO, byteOrder); 250 rootDirectory.add(gpsDirectoryOffsetField); 251 } 252 253 result.add(gpsDirectory, gpsDirectoryOffsetField); 254 } 255 256 return result; 257 258 // Debug.debug(); 259 } 260 261 private static final int MAX_PIXELS_FOR_RGB = 1024*1024; 262 /** 263 * Check an image to see if any of its pixels are non-opaque. 264 * @param src a valid image 265 * @return true if at least one non-opaque pixel is found. 266 */ 267 private boolean checkForActualAlpha(final BufferedImage src){ 268 // to conserve memory, very large images may be read 269 // in pieces. 270 final int width = src.getWidth(); 271 final int height = src.getHeight(); 272 int nRowsPerRead = MAX_PIXELS_FOR_RGB/width; 273 if(nRowsPerRead<1){ 274 nRowsPerRead = 1; 275 } 276 final int nReads = (height+nRowsPerRead-1)/nRowsPerRead; 277 final int []argb = new int[nRowsPerRead*width]; 278 for(int iRead=0; iRead<nReads; iRead++){ 279 final int i0 = iRead*nRowsPerRead; 280 final int i1 = i0+nRowsPerRead>height? height: i0+nRowsPerRead; 281 src.getRGB(0, i0, width, i1-i0, argb, 0, width); 282 final int n = (i1-i0)*width; 283 for(int i=0; i<n; i++){ 284 if((argb[i]&0xff000000)!=0xff000000){ 285 return true; 286 } 287 } 288 } 289 return false; 290 } 291 292 private void applyPredictor(final int width, final int bytesPerSample, final byte[] b) { 293 final int nBytesPerRow = bytesPerSample * width; 294 final int nRows = b.length / nBytesPerRow; 295 for (int iRow = 0; iRow < nRows; iRow++) { 296 final int offset = iRow * nBytesPerRow; 297 for (int i = nBytesPerRow-1; i >= bytesPerSample; i--) { 298 b[offset + i] -= b[offset + i - bytesPerSample]; 299 } 300 } 301 } 302 303 public void writeImage(final BufferedImage src, final OutputStream os, TiffImagingParameters params) 304 throws ImageWriteException, IOException { 305 TiffOutputSet userExif = params.getOutputSet(); 306 307 String xmpXml = params.getXmpXml(); 308 309 PixelDensity pixelDensity = params.getPixelDensity(); 310 if (pixelDensity == null) { 311 pixelDensity = PixelDensity.createFromPixelsPerInch(72, 72); 312 } 313 314 final int width = src.getWidth(); 315 final int height = src.getHeight(); 316 317 // If the source image has a color model that supports alpha, 318 // this module performs a call to checkForActualAlpha() to see whether 319 // the image that was supplied to the API actually contains 320 // non-opaque data in its alpha channel. It is common for applications 321 // to create a BufferedImage using TYPE_INT_ARGB, and fill the entire 322 // image with opaque pixels. In such a case, the file size of the output 323 // can be reduced by 25 percent by storing the image in an 3-byte RGB 324 // format. This approach will also make a small reduction in the runtime 325 // to read the resulting file when it is accessed by an application. 326 final ColorModel cModel = src.getColorModel(); 327 final boolean hasAlpha = cModel.hasAlpha() && checkForActualAlpha(src); 328 329 330 // 10/2020: In the case of an image with pre-multiplied alpha 331 // (what the TIFF specification calls "associated alpha"), the 332 // Java getRGB method adjusts the value to a non-premultiplied 333 // alpha state. However, this class could access the pre-multiplied 334 // alpha data by obtaining the underlying raster. At this time, 335 // the value of such a little-used feature does not seem 336 // commensurate with the complexity of the extra code it would require. 337 338 int compression = TIFF_COMPRESSION_LZW; 339 short predictor = TiffTagConstants.PREDICTOR_VALUE_NONE; 340 341 int stripSizeInBits = 64000; // the default from legacy implementation 342 Integer compressionParameter = params.getCompression(); 343 if (compressionParameter != null) { 344 compression = compressionParameter; 345 final Integer stripSizeInBytes = params.getLzwCompressionBlockSize(); 346 if (stripSizeInBytes != null) { 347 if (stripSizeInBytes < 8000) { 348 throw new ImageWriteException( 349 "Block size parameter " + stripSizeInBytes 350 + " is less than 8000 minimum"); 351 } 352 stripSizeInBits = stripSizeInBytes * 8; 353 } 354 } 355 356 int samplesPerPixel; 357 int bitsPerSample; 358 int photometricInterpretation; 359 if (compression == TIFF_COMPRESSION_CCITT_1D 360 || compression == TIFF_COMPRESSION_CCITT_GROUP_3 361 || compression == TIFF_COMPRESSION_CCITT_GROUP_4) { 362 samplesPerPixel = 1; 363 bitsPerSample = 1; 364 photometricInterpretation = 0; 365 } else { 366 samplesPerPixel = hasAlpha? 4: 3; 367 bitsPerSample = 8; 368 photometricInterpretation = 2; 369 } 370 371 int rowsPerStrip = stripSizeInBits / (width * bitsPerSample * samplesPerPixel); 372 rowsPerStrip = Math.max(1, rowsPerStrip); // must have at least one. 373 374 final byte[][] strips = getStrips(src, samplesPerPixel, bitsPerSample, rowsPerStrip); 375 376 // System.out.println("width: " + width); 377 // System.out.println("height: " + height); 378 // System.out.println("fRowsPerStrip: " + fRowsPerStrip); 379 // System.out.println("fSamplesPerPixel: " + fSamplesPerPixel); 380 // System.out.println("stripCount: " + stripCount); 381 382 int t4Options = 0; 383 int t6Options = 0; 384 if (compression == TIFF_COMPRESSION_CCITT_1D) { 385 for (int i = 0; i < strips.length; i++) { 386 strips[i] = T4AndT6Compression.compressModifiedHuffman( 387 strips[i], width, strips[i].length / ((width + 7) / 8)); 388 } 389 } else if (compression == TIFF_COMPRESSION_CCITT_GROUP_3) { 390 final Integer t4Parameter = params.getT4Options(); 391 if (t4Parameter != null) { 392 t4Options = t4Parameter.intValue(); 393 } 394 t4Options &= 0x7; 395 final boolean is2D = (t4Options & 1) != 0; 396 final boolean usesUncompressedMode = (t4Options & 2) != 0; 397 if (usesUncompressedMode) { 398 throw new ImageWriteException( 399 "T.4 compression with the uncompressed mode extension is not yet supported"); 400 } 401 final boolean hasFillBitsBeforeEOL = (t4Options & 4) != 0; 402 for (int i = 0; i < strips.length; i++) { 403 if (is2D) { 404 strips[i] = T4AndT6Compression.compressT4_2D(strips[i], 405 width, strips[i].length / ((width + 7) / 8), 406 hasFillBitsBeforeEOL, rowsPerStrip); 407 } else { 408 strips[i] = T4AndT6Compression.compressT4_1D(strips[i], 409 width, strips[i].length / ((width + 7) / 8), 410 hasFillBitsBeforeEOL); 411 } 412 } 413 } else if (compression == TIFF_COMPRESSION_CCITT_GROUP_4) { 414 final Integer t6Parameter = params.getT6Options(); 415 if (t6Parameter != null) { 416 t6Options = t6Parameter.intValue(); 417 } 418 t6Options &= 0x4; 419 final boolean usesUncompressedMode = (t6Options & TIFF_FLAG_T6_OPTIONS_UNCOMPRESSED_MODE) != 0; 420 if (usesUncompressedMode) { 421 throw new ImageWriteException( 422 "T.6 compression with the uncompressed mode extension is not yet supported"); 423 } 424 for (int i = 0; i < strips.length; i++) { 425 strips[i] = T4AndT6Compression.compressT6(strips[i], width, 426 strips[i].length / ((width + 7) / 8)); 427 } 428 } else if (compression == TIFF_COMPRESSION_PACKBITS) { 429 for (int i = 0; i < strips.length; i++) { 430 strips[i] = new PackBits().compress(strips[i]); 431 } 432 } else if (compression == TIFF_COMPRESSION_LZW) { 433 predictor = TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING; 434 for (int i = 0; i < strips.length; i++) { 435 final byte[] uncompressed = strips[i]; 436 this.applyPredictor(width, samplesPerPixel, strips[i]); 437 438 final int LZW_MINIMUM_CODE_SIZE = 8; 439 final MyLzwCompressor compressor = new MyLzwCompressor( 440 LZW_MINIMUM_CODE_SIZE, ByteOrder.BIG_ENDIAN, true); 441 final byte[] compressed = compressor.compress(uncompressed); 442 strips[i] = compressed; 443 } 444 } else if (compression == TIFF_COMPRESSION_DEFLATE_ADOBE) { 445 predictor = TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING; 446 for (int i = 0; i < strips.length; i++) { 447 this.applyPredictor(width, samplesPerPixel, strips[i]); 448 strips[i] = ZlibDeflate.compress(strips[i]); 449 } 450 } else if (compression == TIFF_COMPRESSION_UNCOMPRESSED) { 451 // do nothing. 452 } else { 453 throw new ImageWriteException( 454 "Invalid compression parameter (Only CCITT 1D/Group 3/Group 4, LZW, Packbits, Zlib Deflate and uncompressed supported)."); 455 } 456 457 final TiffElement.DataElement[] imageData = new TiffElement.DataElement[strips.length]; 458 for (int i = 0; i < strips.length; i++) { 459 imageData[i] = new TiffImageData.Data(0, strips[i].length, strips[i]); 460 } 461 462 final TiffOutputSet outputSet = new TiffOutputSet(byteOrder); 463 final TiffOutputDirectory directory = outputSet.addRootDirectory(); 464 465 // WriteField stripOffsetsField; 466 467 { 468 469 directory.add(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, width); 470 directory.add(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, height); 471 directory.add(TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION, 472 (short) photometricInterpretation); 473 directory.add(TiffTagConstants.TIFF_TAG_COMPRESSION, 474 (short) compression); 475 directory.add(TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL, 476 (short) samplesPerPixel); 477 478 switch (samplesPerPixel) { 479 case 3: 480 directory.add(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE, 481 (short) bitsPerSample, (short) bitsPerSample, 482 (short) bitsPerSample); 483 break; 484 case 4: 485 directory.add(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE, 486 (short) bitsPerSample, (short) bitsPerSample, 487 (short) bitsPerSample, (short) bitsPerSample); 488 directory.add(TiffTagConstants.TIFF_TAG_EXTRA_SAMPLES, 489 (short)TiffTagConstants.EXTRA_SAMPLE_UNASSOCIATED_ALPHA); 490 break; 491 case 1: 492 directory.add(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE, 493 (short) bitsPerSample); 494 break; 495 default: 496 break; 497 } 498 // { 499 // stripOffsetsField = new WriteField(TIFF_TAG_STRIP_OFFSETS, 500 // FIELD_TYPE_LONG, stripOffsets.length, FIELD_TYPE_LONG 501 // .writeData(stripOffsets, byteOrder)); 502 // directory.add(stripOffsetsField); 503 // } 504 // { 505 // WriteField field = new WriteField(TIFF_TAG_STRIP_BYTE_COUNTS, 506 // FIELD_TYPE_LONG, stripByteCounts.length, 507 // FIELD_TYPE_LONG.writeData(stripByteCounts, 508 // WRITE_BYTE_ORDER)); 509 // directory.add(field); 510 // } 511 directory.add(TiffTagConstants.TIFF_TAG_ROWS_PER_STRIP, 512 rowsPerStrip); 513 if (pixelDensity.isUnitless()) { 514 directory.add(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT, 515 (short) 0); 516 directory.add(TiffTagConstants.TIFF_TAG_XRESOLUTION, 517 RationalNumber.valueOf(pixelDensity.getRawHorizontalDensity())); 518 directory.add(TiffTagConstants.TIFF_TAG_YRESOLUTION, 519 RationalNumber.valueOf(pixelDensity.getRawVerticalDensity())); 520 } else if (pixelDensity.isInInches()) { 521 directory.add(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT, 522 (short) 2); 523 directory.add(TiffTagConstants.TIFF_TAG_XRESOLUTION, 524 RationalNumber.valueOf(pixelDensity.horizontalDensityInches())); 525 directory.add(TiffTagConstants.TIFF_TAG_YRESOLUTION, 526 RationalNumber.valueOf(pixelDensity.verticalDensityInches())); 527 } else { 528 directory.add(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT, 529 (short) 1); 530 directory.add(TiffTagConstants.TIFF_TAG_XRESOLUTION, 531 RationalNumber.valueOf(pixelDensity.horizontalDensityCentimetres())); 532 directory.add(TiffTagConstants.TIFF_TAG_YRESOLUTION, 533 RationalNumber.valueOf(pixelDensity.verticalDensityCentimetres())); 534 } 535 if (t4Options != 0) { 536 directory.add(TiffTagConstants.TIFF_TAG_T4_OPTIONS, t4Options); 537 } 538 if (t6Options != 0) { 539 directory.add(TiffTagConstants.TIFF_TAG_T6_OPTIONS, t6Options); 540 } 541 542 if (null != xmpXml) { 543 final byte[] xmpXmlBytes = xmpXml.getBytes(StandardCharsets.UTF_8); 544 directory.add(TiffTagConstants.TIFF_TAG_XMP, xmpXmlBytes); 545 } 546 547 if(predictor==TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING){ 548 directory.add(TiffTagConstants.TIFF_TAG_PREDICTOR, predictor); 549 } 550 551 } 552 553 final TiffImageData tiffImageData = new TiffImageData.Strips(imageData, 554 rowsPerStrip); 555 directory.setTiffImageData(tiffImageData); 556 557 if (userExif != null) { 558 combineUserExifIntoFinalExif(userExif, outputSet); 559 } 560 561 write(os, outputSet); 562 } 563 564 private void combineUserExifIntoFinalExif(final TiffOutputSet userExif, 565 final TiffOutputSet outputSet) throws ImageWriteException { 566 final List<TiffOutputDirectory> outputDirectories = outputSet.getDirectories(); 567 outputDirectories.sort(TiffOutputDirectory.COMPARATOR); 568 for (final TiffOutputDirectory userDirectory : userExif.getDirectories()) { 569 final int location = Collections.binarySearch(outputDirectories, 570 userDirectory, TiffOutputDirectory.COMPARATOR); 571 if (location < 0) { 572 outputSet.addDirectory(userDirectory); 573 } else { 574 final TiffOutputDirectory outputDirectory = outputDirectories.get(location); 575 for (final TiffOutputField userField : userDirectory.getFields()) { 576 if (outputDirectory.findField(userField.tagInfo) == null) { 577 outputDirectory.add(userField); 578 } 579 } 580 } 581 } 582 } 583 584 private byte[][] getStrips(final BufferedImage src, final int samplesPerPixel, 585 final int bitsPerSample, final int rowsPerStrip) { 586 final int width = src.getWidth(); 587 final int height = src.getHeight(); 588 589 final int stripCount = (height + rowsPerStrip - 1) / rowsPerStrip; 590 591 byte[][] result; 592 { // Write Strips 593 result = new byte[stripCount][]; 594 595 int remainingRows = height; 596 597 for (int i = 0; i < stripCount; i++) { 598 final int rowsInStrip = Math.min(rowsPerStrip, remainingRows); 599 remainingRows -= rowsInStrip; 600 601 final int bitsInRow = bitsPerSample * samplesPerPixel * width; 602 final int bytesPerRow = (bitsInRow + 7) / 8; 603 final int bytesInStrip = rowsInStrip * bytesPerRow; 604 605 final byte[] uncompressed = new byte[bytesInStrip]; 606 607 int counter = 0; 608 int y = i * rowsPerStrip; 609 final int stop = i * rowsPerStrip + rowsPerStrip; 610 611 for (; (y < height) && (y < stop); y++) { 612 int bitCache = 0; 613 int bitsInCache = 0; 614 for (int x = 0; x < width; x++) { 615 final int rgb = src.getRGB(x, y); 616 final int red = 0xff & (rgb >> 16); 617 final int green = 0xff & (rgb >> 8); 618 final int blue = 0xff & (rgb >> 0); 619 620 if (bitsPerSample == 1) { 621 int sample = (red + green + blue) / 3; 622 if (sample > 127) { 623 sample = 0; 624 } else { 625 sample = 1; 626 } 627 bitCache <<= 1; 628 bitCache |= sample; 629 bitsInCache++; 630 if (bitsInCache == 8) { 631 uncompressed[counter++] = (byte) bitCache; 632 bitCache = 0; 633 bitsInCache = 0; 634 } 635 } else if(samplesPerPixel==4){ 636 uncompressed[counter++] = (byte) red; 637 uncompressed[counter++] = (byte) green; 638 uncompressed[counter++] = (byte) blue; 639 uncompressed[counter++] = (byte) (rgb>>24); 640 }else { 641 // samples per pixel is 3 642 uncompressed[counter++] = (byte) red; 643 uncompressed[counter++] = (byte) green; 644 uncompressed[counter++] = (byte) blue; 645 } 646 } 647 if (bitsInCache > 0) { 648 bitCache <<= (8 - bitsInCache); 649 uncompressed[counter++] = (byte) bitCache; 650 } 651 } 652 653 result[i] = uncompressed; 654 } 655 656 } 657 658 return result; 659 } 660 661 protected void writeImageFileHeader(final BinaryOutputStream bos) 662 throws IOException { 663 writeImageFileHeader(bos, TIFF_HEADER_SIZE); 664 } 665 666 protected void writeImageFileHeader(final BinaryOutputStream bos, 667 final long offsetToFirstIFD) throws IOException { 668 if (byteOrder == ByteOrder.LITTLE_ENDIAN) { 669 bos.write('I'); 670 bos.write('I'); 671 } else { 672 bos.write('M'); 673 bos.write('M'); 674 } 675 676 bos.write2Bytes(42); // tiffVersion 677 678 bos.write4Bytes((int) offsetToFirstIFD); 679 } 680 681}