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.PARAM_KEY_LZW_COMPRESSION_BLOCK_SIZE; 021import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.PARAM_KEY_T4_OPTIONS; 022import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.PARAM_KEY_T6_OPTIONS; 023import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_1D; 024import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_3; 025import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_4; 026import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_LZW; 027import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_PACKBITS; 028import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED; 029import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_DEFLATE_ADOBE; 030import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_FLAG_T6_OPTIONS_UNCOMPRESSED_MODE; 031import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_HEADER_SIZE; 032 033import java.awt.image.BufferedImage; 034import java.io.IOException; 035import java.io.OutputStream; 036import java.nio.ByteOrder; 037import java.nio.charset.StandardCharsets; 038import java.util.ArrayList; 039import java.util.Collections; 040import java.util.HashMap; 041import java.util.HashSet; 042import java.util.List; 043import java.util.Map; 044 045import org.apache.commons.imaging.ImageWriteException; 046import org.apache.commons.imaging.ImagingConstants; 047import org.apache.commons.imaging.PixelDensity; 048import org.apache.commons.imaging.common.BinaryOutputStream; 049import org.apache.commons.imaging.common.PackBits; 050import org.apache.commons.imaging.common.RationalNumber; 051import org.apache.commons.imaging.common.itu_t4.T4AndT6Compression; 052import org.apache.commons.imaging.common.mylzw.MyLzwCompressor; 053import org.apache.commons.imaging.common.ZlibDeflate; 054import org.apache.commons.imaging.formats.tiff.TiffElement; 055import org.apache.commons.imaging.formats.tiff.TiffImageData; 056import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants; 057import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryConstants; 058import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants; 059 060public abstract class TiffImageWriterBase { 061 062 protected final ByteOrder byteOrder; 063 064 public TiffImageWriterBase() { 065 this.byteOrder = DEFAULT_TIFF_BYTE_ORDER; 066 } 067 068 public TiffImageWriterBase(final ByteOrder byteOrder) { 069 this.byteOrder = byteOrder; 070 } 071 072 protected static int imageDataPaddingLength(final int dataLength) { 073 return (4 - (dataLength % 4)) % 4; 074 } 075 076 public abstract void write(OutputStream os, TiffOutputSet outputSet) 077 throws IOException, ImageWriteException; 078 079 protected TiffOutputSummary validateDirectories(final TiffOutputSet outputSet) 080 throws ImageWriteException { 081 final List<TiffOutputDirectory> directories = outputSet.getDirectories(); 082 083 if (directories.isEmpty()) { 084 throw new ImageWriteException("No directories."); 085 } 086 087 TiffOutputDirectory exifDirectory = null; 088 TiffOutputDirectory gpsDirectory = null; 089 TiffOutputDirectory interoperabilityDirectory = null; 090 TiffOutputField exifDirectoryOffsetField = null; 091 TiffOutputField gpsDirectoryOffsetField = null; 092 TiffOutputField interoperabilityDirectoryOffsetField = null; 093 094 final List<Integer> directoryIndices = new ArrayList<>(); 095 final Map<Integer, TiffOutputDirectory> directoryTypeMap = new HashMap<>(); 096 for (final TiffOutputDirectory directory : directories) { 097 final int dirType = directory.type; 098 directoryTypeMap.put(dirType, directory); 099 // Debug.debug("validating dirType", dirType + " (" 100 // + directory.getFields().size() + " fields)"); 101 102 if (dirType < 0) { 103 switch (dirType) { 104 case TiffDirectoryConstants.DIRECTORY_TYPE_EXIF: 105 if (exifDirectory != null) { 106 throw new ImageWriteException( 107 "More than one EXIF directory."); 108 } 109 exifDirectory = directory; 110 break; 111 112 case TiffDirectoryConstants.DIRECTORY_TYPE_GPS: 113 if (gpsDirectory != null) { 114 throw new ImageWriteException( 115 "More than one GPS directory."); 116 } 117 gpsDirectory = directory; 118 break; 119 120 case TiffDirectoryConstants.DIRECTORY_TYPE_INTEROPERABILITY: 121 if (interoperabilityDirectory != null) { 122 throw new ImageWriteException( 123 "More than one Interoperability directory."); 124 } 125 interoperabilityDirectory = directory; 126 break; 127 default: 128 throw new ImageWriteException("Unknown directory: " 129 + dirType); 130 } 131 } else { 132 if (directoryIndices.contains(dirType)) { 133 throw new ImageWriteException( 134 "More than one directory with index: " + dirType 135 + "."); 136 } 137 directoryIndices.add(dirType); 138 // dirMap.put(arg0, arg1) 139 } 140 141 final HashSet<Integer> fieldTags = new HashSet<>(); 142 final List<TiffOutputField> fields = directory.getFields(); 143 for (final TiffOutputField field : fields) { 144 if (fieldTags.contains(field.tag)) { 145 throw new ImageWriteException("Tag (" 146 + field.tagInfo.getDescription() 147 + ") appears twice in directory."); 148 } 149 fieldTags.add(field.tag); 150 151 if (field.tag == ExifTagConstants.EXIF_TAG_EXIF_OFFSET.tag) { 152 if (exifDirectoryOffsetField != null) { 153 throw new ImageWriteException( 154 "More than one Exif directory offset field."); 155 } 156 exifDirectoryOffsetField = field; 157 } else if (field.tag == ExifTagConstants.EXIF_TAG_INTEROP_OFFSET.tag) { 158 if (interoperabilityDirectoryOffsetField != null) { 159 throw new ImageWriteException( 160 "More than one Interoperability directory offset field."); 161 } 162 interoperabilityDirectoryOffsetField = field; 163 } else if (field.tag == ExifTagConstants.EXIF_TAG_GPSINFO.tag) { 164 if (gpsDirectoryOffsetField != null) { 165 throw new ImageWriteException( 166 "More than one GPS directory offset field."); 167 } 168 gpsDirectoryOffsetField = field; 169 } 170 } 171 // directory. 172 } 173 174 if (directoryIndices.isEmpty()) { 175 throw new ImageWriteException("Missing root directory."); 176 } 177 178 // "normal" TIFF directories should have continous indices starting with 179 // 0, ie. 0, 1, 2... 180 Collections.sort(directoryIndices); 181 182 TiffOutputDirectory previousDirectory = null; 183 for (int i = 0; i < directoryIndices.size(); i++) { 184 final Integer index = directoryIndices.get(i); 185 if (index != i) { 186 throw new ImageWriteException("Missing directory: " + i + "."); 187 } 188 189 // set up chain of directory references for "normal" directories. 190 final TiffOutputDirectory directory = directoryTypeMap.get(index); 191 if (null != previousDirectory) { 192 previousDirectory.setNextDirectory(directory); 193 } 194 previousDirectory = directory; 195 } 196 197 final TiffOutputDirectory rootDirectory = directoryTypeMap.get( 198 TiffDirectoryConstants.DIRECTORY_TYPE_ROOT); 199 200 // prepare results 201 final TiffOutputSummary result = new TiffOutputSummary(byteOrder, 202 rootDirectory, directoryTypeMap); 203 204 if (interoperabilityDirectory == null 205 && interoperabilityDirectoryOffsetField != null) { 206 // perhaps we should just discard field? 207 throw new ImageWriteException( 208 "Output set has Interoperability Directory Offset field, but no Interoperability Directory"); 209 } else if (interoperabilityDirectory != null) { 210 if (exifDirectory == null) { 211 exifDirectory = outputSet.addExifDirectory(); 212 } 213 214 if (interoperabilityDirectoryOffsetField == null) { 215 interoperabilityDirectoryOffsetField = 216 TiffOutputField.createOffsetField( 217 ExifTagConstants.EXIF_TAG_INTEROP_OFFSET, 218 byteOrder); 219 exifDirectory.add(interoperabilityDirectoryOffsetField); 220 } 221 222 result.add(interoperabilityDirectory, 223 interoperabilityDirectoryOffsetField); 224 } 225 226 // make sure offset fields and offset'd directories correspond. 227 if (exifDirectory == null && exifDirectoryOffsetField != null) { 228 // perhaps we should just discard field? 229 throw new ImageWriteException( 230 "Output set has Exif Directory Offset field, but no Exif Directory"); 231 } else 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 } else if (gpsDirectory != null) { 246 if (gpsDirectoryOffsetField == null) { 247 gpsDirectoryOffsetField = TiffOutputField.createOffsetField( 248 ExifTagConstants.EXIF_TAG_GPSINFO, byteOrder); 249 rootDirectory.add(gpsDirectoryOffsetField); 250 } 251 252 result.add(gpsDirectory, gpsDirectoryOffsetField); 253 } 254 255 return result; 256 257 // Debug.debug(); 258 } 259 260 public void writeImage(final BufferedImage src, final OutputStream os, Map<String, Object> params) 261 throws ImageWriteException, IOException { 262 // make copy of params; we'll clear keys as we consume them. 263 params = new HashMap<>(params); 264 265 // clear format key. 266 if (params.containsKey(ImagingConstants.PARAM_KEY_FORMAT)) { 267 params.remove(ImagingConstants.PARAM_KEY_FORMAT); 268 } 269 270 TiffOutputSet userExif = null; 271 if (params.containsKey(ImagingConstants.PARAM_KEY_EXIF)) { 272 userExif = (TiffOutputSet) params.remove(ImagingConstants.PARAM_KEY_EXIF); 273 } 274 275 String xmpXml = null; 276 if (params.containsKey(ImagingConstants.PARAM_KEY_XMP_XML)) { 277 xmpXml = (String) params.get(ImagingConstants.PARAM_KEY_XMP_XML); 278 params.remove(ImagingConstants.PARAM_KEY_XMP_XML); 279 } 280 281 PixelDensity pixelDensity = (PixelDensity) params.remove( 282 ImagingConstants.PARAM_KEY_PIXEL_DENSITY); 283 if (pixelDensity == null) { 284 pixelDensity = PixelDensity.createFromPixelsPerInch(72, 72); 285 } 286 287 final int width = src.getWidth(); 288 final int height = src.getHeight(); 289 290 int compression = TIFF_COMPRESSION_LZW; // LZW is default 291 int stripSizeInBits = 64000; // the default from legacy implementation 292 if (params.containsKey(ImagingConstants.PARAM_KEY_COMPRESSION)) { 293 final Object value = params.get(ImagingConstants.PARAM_KEY_COMPRESSION); 294 if (value != null) { 295 if (!(value instanceof Number)) { 296 throw new ImageWriteException( 297 "Invalid compression parameter, must be numeric: " 298 + value); 299 } 300 compression = ((Number) value).intValue(); 301 } 302 params.remove(ImagingConstants.PARAM_KEY_COMPRESSION); 303 if (params.containsKey(PARAM_KEY_LZW_COMPRESSION_BLOCK_SIZE)) { 304 final Object bValue = 305 params.get(PARAM_KEY_LZW_COMPRESSION_BLOCK_SIZE); 306 if (!(bValue instanceof Number)) { 307 throw new ImageWriteException( 308 "Invalid compression block-size parameter: " + value); 309 } 310 final int stripSizeInBytes = ((Number) bValue).intValue(); 311 if (stripSizeInBytes < 8000) { 312 throw new ImageWriteException( 313 "Block size parameter " + stripSizeInBytes 314 + " is less than 8000 minimum"); 315 } 316 stripSizeInBits = stripSizeInBytes*8; 317 params.remove(PARAM_KEY_LZW_COMPRESSION_BLOCK_SIZE); 318 } 319 } 320 final HashMap<String, Object> rawParams = new HashMap<>(params); 321 params.remove(PARAM_KEY_T4_OPTIONS); 322 params.remove(PARAM_KEY_T6_OPTIONS); 323 if (!params.isEmpty()) { 324 final Object firstKey = params.keySet().iterator().next(); 325 throw new ImageWriteException("Unknown parameter: " + firstKey); 326 } 327 328 int samplesPerPixel; 329 int bitsPerSample; 330 int photometricInterpretation; 331 if (compression == TIFF_COMPRESSION_CCITT_1D 332 || compression == TIFF_COMPRESSION_CCITT_GROUP_3 333 || compression == TIFF_COMPRESSION_CCITT_GROUP_4) { 334 samplesPerPixel = 1; 335 bitsPerSample = 1; 336 photometricInterpretation = 0; 337 } else { 338 samplesPerPixel = 3; 339 bitsPerSample = 8; 340 photometricInterpretation = 2; 341 } 342 343 int rowsPerStrip = stripSizeInBits / (width * bitsPerSample * samplesPerPixel); 344 rowsPerStrip = Math.max(1, rowsPerStrip); // must have at least one. 345 346 final byte[][] strips = getStrips(src, samplesPerPixel, bitsPerSample, rowsPerStrip); 347 348 // System.out.println("width: " + width); 349 // System.out.println("height: " + height); 350 // System.out.println("fRowsPerStrip: " + fRowsPerStrip); 351 // System.out.println("fSamplesPerPixel: " + fSamplesPerPixel); 352 // System.out.println("stripCount: " + stripCount); 353 354 int t4Options = 0; 355 int t6Options = 0; 356 if (compression == TIFF_COMPRESSION_CCITT_1D) { 357 for (int i = 0; i < strips.length; i++) { 358 strips[i] = T4AndT6Compression.compressModifiedHuffman( 359 strips[i], width, strips[i].length / ((width + 7) / 8)); 360 } 361 } else if (compression == TIFF_COMPRESSION_CCITT_GROUP_3) { 362 final Integer t4Parameter = (Integer) rawParams.get(PARAM_KEY_T4_OPTIONS); 363 if (t4Parameter != null) { 364 t4Options = t4Parameter.intValue(); 365 } 366 t4Options &= 0x7; 367 final boolean is2D = (t4Options & 1) != 0; 368 final boolean usesUncompressedMode = (t4Options & 2) != 0; 369 if (usesUncompressedMode) { 370 throw new ImageWriteException( 371 "T.4 compression with the uncompressed mode extension is not yet supported"); 372 } 373 final boolean hasFillBitsBeforeEOL = (t4Options & 4) != 0; 374 for (int i = 0; i < strips.length; i++) { 375 if (is2D) { 376 strips[i] = T4AndT6Compression.compressT4_2D(strips[i], 377 width, strips[i].length / ((width + 7) / 8), 378 hasFillBitsBeforeEOL, rowsPerStrip); 379 } else { 380 strips[i] = T4AndT6Compression.compressT4_1D(strips[i], 381 width, strips[i].length / ((width + 7) / 8), 382 hasFillBitsBeforeEOL); 383 } 384 } 385 } else if (compression == TIFF_COMPRESSION_CCITT_GROUP_4) { 386 final Integer t6Parameter = (Integer) rawParams.get(PARAM_KEY_T6_OPTIONS); 387 if (t6Parameter != null) { 388 t6Options = t6Parameter.intValue(); 389 } 390 t6Options &= 0x4; 391 final boolean usesUncompressedMode = (t6Options & TIFF_FLAG_T6_OPTIONS_UNCOMPRESSED_MODE) != 0; 392 if (usesUncompressedMode) { 393 throw new ImageWriteException( 394 "T.6 compression with the uncompressed mode extension is not yet supported"); 395 } 396 for (int i = 0; i < strips.length; i++) { 397 strips[i] = T4AndT6Compression.compressT6(strips[i], width, 398 strips[i].length / ((width + 7) / 8)); 399 } 400 } else if (compression == TIFF_COMPRESSION_PACKBITS) { 401 for (int i = 0; i < strips.length; i++) { 402 strips[i] = new PackBits().compress(strips[i]); 403 } 404 } else if (compression == TIFF_COMPRESSION_LZW) { 405 for (int i = 0; i < strips.length; i++) { 406 final byte[] uncompressed = strips[i]; 407 408 final int LZW_MINIMUM_CODE_SIZE = 8; 409 410 final MyLzwCompressor compressor = new MyLzwCompressor( 411 LZW_MINIMUM_CODE_SIZE, ByteOrder.BIG_ENDIAN, true); 412 final byte[] compressed = compressor.compress(uncompressed); 413 414 strips[i] = compressed; 415 } 416 } else if (compression == TIFF_COMPRESSION_DEFLATE_ADOBE) { 417 for (int i = 0; i < strips.length; i++) { 418 strips[i] = ZlibDeflate.compress(strips[i]); 419 } 420 } else if (compression == TIFF_COMPRESSION_UNCOMPRESSED) { 421 // do nothing. 422 } else { 423 throw new ImageWriteException( 424 "Invalid compression parameter (Only CCITT 1D/Group 3/Group 4, LZW, Packbits, Zlib Deflate and uncompressed supported)."); 425 } 426 427 final TiffElement.DataElement[] imageData = new TiffElement.DataElement[strips.length]; 428 for (int i = 0; i < strips.length; i++) { 429 imageData[i] = new TiffImageData.Data(0, strips[i].length, strips[i]); 430 } 431 432 final TiffOutputSet outputSet = new TiffOutputSet(byteOrder); 433 final TiffOutputDirectory directory = outputSet.addRootDirectory(); 434 435 // WriteField stripOffsetsField; 436 437 { 438 439 directory.add(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, width); 440 directory.add(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, height); 441 directory.add(TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION, 442 (short) photometricInterpretation); 443 directory.add(TiffTagConstants.TIFF_TAG_COMPRESSION, 444 (short) compression); 445 directory.add(TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL, 446 (short) samplesPerPixel); 447 448 if (samplesPerPixel == 3) { 449 directory.add(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE, 450 (short) bitsPerSample, (short) bitsPerSample, 451 (short) bitsPerSample); 452 } else if (samplesPerPixel == 1) { 453 directory.add(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE, 454 (short) bitsPerSample); 455 } 456 // { 457 // stripOffsetsField = new WriteField(TIFF_TAG_STRIP_OFFSETS, 458 // FIELD_TYPE_LONG, stripOffsets.length, FIELD_TYPE_LONG 459 // .writeData(stripOffsets, byteOrder)); 460 // directory.add(stripOffsetsField); 461 // } 462 // { 463 // WriteField field = new WriteField(TIFF_TAG_STRIP_BYTE_COUNTS, 464 // FIELD_TYPE_LONG, stripByteCounts.length, 465 // FIELD_TYPE_LONG.writeData(stripByteCounts, 466 // WRITE_BYTE_ORDER)); 467 // directory.add(field); 468 // } 469 directory.add(TiffTagConstants.TIFF_TAG_ROWS_PER_STRIP, 470 rowsPerStrip); 471 if (pixelDensity.isUnitless()) { 472 directory.add(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT, 473 (short) 0); 474 directory.add(TiffTagConstants.TIFF_TAG_XRESOLUTION, 475 RationalNumber.valueOf(pixelDensity.getRawHorizontalDensity())); 476 directory.add(TiffTagConstants.TIFF_TAG_YRESOLUTION, 477 RationalNumber.valueOf(pixelDensity.getRawVerticalDensity())); 478 } else if (pixelDensity.isInInches()) { 479 directory.add(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT, 480 (short) 2); 481 directory.add(TiffTagConstants.TIFF_TAG_XRESOLUTION, 482 RationalNumber.valueOf(pixelDensity.horizontalDensityInches())); 483 directory.add(TiffTagConstants.TIFF_TAG_YRESOLUTION, 484 RationalNumber.valueOf(pixelDensity.verticalDensityInches())); 485 } else { 486 directory.add(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT, 487 (short) 1); 488 directory.add(TiffTagConstants.TIFF_TAG_XRESOLUTION, 489 RationalNumber.valueOf(pixelDensity.horizontalDensityCentimetres())); 490 directory.add(TiffTagConstants.TIFF_TAG_YRESOLUTION, 491 RationalNumber.valueOf(pixelDensity.verticalDensityCentimetres())); 492 } 493 if (t4Options != 0) { 494 directory.add(TiffTagConstants.TIFF_TAG_T4_OPTIONS, t4Options); 495 } 496 if (t6Options != 0) { 497 directory.add(TiffTagConstants.TIFF_TAG_T6_OPTIONS, t6Options); 498 } 499 500 if (null != xmpXml) { 501 final byte[] xmpXmlBytes = xmpXml.getBytes(StandardCharsets.UTF_8); 502 directory.add(TiffTagConstants.TIFF_TAG_XMP, xmpXmlBytes); 503 } 504 505 } 506 507 final TiffImageData tiffImageData = new TiffImageData.Strips(imageData, 508 rowsPerStrip); 509 directory.setTiffImageData(tiffImageData); 510 511 if (userExif != null) { 512 combineUserExifIntoFinalExif(userExif, outputSet); 513 } 514 515 write(os, outputSet); 516 } 517 518 private void combineUserExifIntoFinalExif(final TiffOutputSet userExif, 519 final TiffOutputSet outputSet) throws ImageWriteException { 520 final List<TiffOutputDirectory> outputDirectories = outputSet.getDirectories(); 521 Collections.sort(outputDirectories, TiffOutputDirectory.COMPARATOR); 522 for (final TiffOutputDirectory userDirectory : userExif.getDirectories()) { 523 final int location = Collections.binarySearch(outputDirectories, 524 userDirectory, TiffOutputDirectory.COMPARATOR); 525 if (location < 0) { 526 outputSet.addDirectory(userDirectory); 527 } else { 528 final TiffOutputDirectory outputDirectory = outputDirectories.get(location); 529 for (final TiffOutputField userField : userDirectory.getFields()) { 530 if (outputDirectory.findField(userField.tagInfo) == null) { 531 outputDirectory.add(userField); 532 } 533 } 534 } 535 } 536 } 537 538 private byte[][] getStrips(final BufferedImage src, final int samplesPerPixel, 539 final int bitsPerSample, final int rowsPerStrip) { 540 final int width = src.getWidth(); 541 final int height = src.getHeight(); 542 543 final int stripCount = (height + rowsPerStrip - 1) / rowsPerStrip; 544 545 byte[][] result; 546 { // Write Strips 547 result = new byte[stripCount][]; 548 549 int remainingRows = height; 550 551 for (int i = 0; i < stripCount; i++) { 552 final int rowsInStrip = Math.min(rowsPerStrip, remainingRows); 553 remainingRows -= rowsInStrip; 554 555 final int bitsInRow = bitsPerSample * samplesPerPixel * width; 556 final int bytesPerRow = (bitsInRow + 7) / 8; 557 final int bytesInStrip = rowsInStrip * bytesPerRow; 558 559 final byte[] uncompressed = new byte[bytesInStrip]; 560 561 int counter = 0; 562 int y = i * rowsPerStrip; 563 final int stop = i * rowsPerStrip + rowsPerStrip; 564 565 for (; (y < height) && (y < stop); y++) { 566 int bitCache = 0; 567 int bitsInCache = 0; 568 for (int x = 0; x < width; x++) { 569 final int rgb = src.getRGB(x, y); 570 final int red = 0xff & (rgb >> 16); 571 final int green = 0xff & (rgb >> 8); 572 final int blue = 0xff & (rgb >> 0); 573 574 if (bitsPerSample == 1) { 575 int sample = (red + green + blue) / 3; 576 if (sample > 127) { 577 sample = 0; 578 } else { 579 sample = 1; 580 } 581 bitCache <<= 1; 582 bitCache |= sample; 583 bitsInCache++; 584 if (bitsInCache == 8) { 585 uncompressed[counter++] = (byte) bitCache; 586 bitCache = 0; 587 bitsInCache = 0; 588 } 589 } else { 590 uncompressed[counter++] = (byte) red; 591 uncompressed[counter++] = (byte) green; 592 uncompressed[counter++] = (byte) blue; 593 } 594 } 595 if (bitsInCache > 0) { 596 bitCache <<= (8 - bitsInCache); 597 uncompressed[counter++] = (byte) bitCache; 598 } 599 } 600 601 result[i] = uncompressed; 602 } 603 604 } 605 606 return result; 607 } 608 609 protected void writeImageFileHeader(final BinaryOutputStream bos) 610 throws IOException { 611 final int offsetToFirstIFD = TIFF_HEADER_SIZE; 612 613 writeImageFileHeader(bos, offsetToFirstIFD); 614 } 615 616 protected void writeImageFileHeader(final BinaryOutputStream bos, 617 final long offsetToFirstIFD) throws IOException { 618 if (byteOrder == ByteOrder.LITTLE_ENDIAN) { 619 bos.write('I'); 620 bos.write('I'); 621 } else { 622 bos.write('M'); 623 bos.write('M'); 624 } 625 626 bos.write2Bytes(42); // tiffVersion 627 628 bos.write4Bytes((int) offsetToFirstIFD); 629 } 630 631}