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_FLAG_T6_OPTIONS_UNCOMPRESSED_MODE; 030import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_HEADER_SIZE; 031 032import java.awt.image.BufferedImage; 033import java.io.IOException; 034import java.io.OutputStream; 035import java.nio.ByteOrder; 036import java.nio.charset.StandardCharsets; 037import java.util.ArrayList; 038import java.util.Collections; 039import java.util.HashMap; 040import java.util.HashSet; 041import java.util.List; 042import java.util.Map; 043 044import org.apache.commons.imaging.ImageWriteException; 045import org.apache.commons.imaging.ImagingConstants; 046import org.apache.commons.imaging.PixelDensity; 047import org.apache.commons.imaging.common.BinaryOutputStream; 048import org.apache.commons.imaging.common.PackBits; 049import org.apache.commons.imaging.common.RationalNumber; 050import org.apache.commons.imaging.common.itu_t4.T4AndT6Compression; 051import org.apache.commons.imaging.common.mylzw.MyLzwCompressor; 052import org.apache.commons.imaging.formats.tiff.TiffElement; 053import org.apache.commons.imaging.formats.tiff.TiffImageData; 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 Collections.sort(directoryIndices); 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 } else if (interoperabilityDirectory != null) { 208 if (exifDirectory == null) { 209 exifDirectory = outputSet.addExifDirectory(); 210 } 211 212 if (interoperabilityDirectoryOffsetField == null) { 213 interoperabilityDirectoryOffsetField = 214 TiffOutputField.createOffsetField( 215 ExifTagConstants.EXIF_TAG_INTEROP_OFFSET, 216 byteOrder); 217 exifDirectory.add(interoperabilityDirectoryOffsetField); 218 } 219 220 result.add(interoperabilityDirectory, 221 interoperabilityDirectoryOffsetField); 222 } 223 224 // make sure offset fields and offset'd directories correspond. 225 if (exifDirectory == null && exifDirectoryOffsetField != null) { 226 // perhaps we should just discard field? 227 throw new ImageWriteException( 228 "Output set has Exif Directory Offset field, but no Exif Directory"); 229 } else if (exifDirectory != null) { 230 if (exifDirectoryOffsetField == null) { 231 exifDirectoryOffsetField = TiffOutputField.createOffsetField( 232 ExifTagConstants.EXIF_TAG_EXIF_OFFSET, byteOrder); 233 rootDirectory.add(exifDirectoryOffsetField); 234 } 235 236 result.add(exifDirectory, exifDirectoryOffsetField); 237 } 238 239 if (gpsDirectory == null && gpsDirectoryOffsetField != null) { 240 // perhaps we should just discard field? 241 throw new ImageWriteException( 242 "Output set has GPS Directory Offset field, but no GPS Directory"); 243 } else if (gpsDirectory != null) { 244 if (gpsDirectoryOffsetField == null) { 245 gpsDirectoryOffsetField = TiffOutputField.createOffsetField( 246 ExifTagConstants.EXIF_TAG_GPSINFO, byteOrder); 247 rootDirectory.add(gpsDirectoryOffsetField); 248 } 249 250 result.add(gpsDirectory, gpsDirectoryOffsetField); 251 } 252 253 return result; 254 255 // Debug.debug(); 256 } 257 258 public void writeImage(final BufferedImage src, final OutputStream os, Map<String, Object> params) 259 throws ImageWriteException, IOException { 260 // make copy of params; we'll clear keys as we consume them. 261 params = new HashMap<>(params); 262 263 // clear format key. 264 if (params.containsKey(ImagingConstants.PARAM_KEY_FORMAT)) { 265 params.remove(ImagingConstants.PARAM_KEY_FORMAT); 266 } 267 268 TiffOutputSet userExif = null; 269 if (params.containsKey(ImagingConstants.PARAM_KEY_EXIF)) { 270 userExif = (TiffOutputSet) params.remove(ImagingConstants.PARAM_KEY_EXIF); 271 } 272 273 String xmpXml = null; 274 if (params.containsKey(ImagingConstants.PARAM_KEY_XMP_XML)) { 275 xmpXml = (String) params.get(ImagingConstants.PARAM_KEY_XMP_XML); 276 params.remove(ImagingConstants.PARAM_KEY_XMP_XML); 277 } 278 279 PixelDensity pixelDensity = (PixelDensity) params.remove( 280 ImagingConstants.PARAM_KEY_PIXEL_DENSITY); 281 if (pixelDensity == null) { 282 pixelDensity = PixelDensity.createFromPixelsPerInch(72, 72); 283 } 284 285 final int width = src.getWidth(); 286 final int height = src.getHeight(); 287 288 int compression = TIFF_COMPRESSION_LZW; // LZW is default 289 int stripSizeInBits = 64000; // the default from legacy implementation 290 if (params.containsKey(ImagingConstants.PARAM_KEY_COMPRESSION)) { 291 final Object value = params.get(ImagingConstants.PARAM_KEY_COMPRESSION); 292 if (value != null) { 293 if (!(value instanceof Number)) { 294 throw new ImageWriteException( 295 "Invalid compression parameter, must be numeric: " 296 + value); 297 } 298 compression = ((Number) value).intValue(); 299 } 300 params.remove(ImagingConstants.PARAM_KEY_COMPRESSION); 301 if (params.containsKey(PARAM_KEY_LZW_COMPRESSION_BLOCK_SIZE)) { 302 final Object bValue = 303 params.get(PARAM_KEY_LZW_COMPRESSION_BLOCK_SIZE); 304 if (!(bValue instanceof Number)) { 305 throw new ImageWriteException( 306 "Invalid compression block-size parameter: " + value); 307 } 308 final int stripSizeInBytes = ((Number) bValue).intValue(); 309 if (stripSizeInBytes < 8000) { 310 throw new ImageWriteException( 311 "Block size parameter " + stripSizeInBytes 312 + " is less than 8000 minimum"); 313 } 314 stripSizeInBits = stripSizeInBytes*8; 315 params.remove(PARAM_KEY_LZW_COMPRESSION_BLOCK_SIZE); 316 } 317 } 318 final HashMap<String, Object> rawParams = new HashMap<>(params); 319 params.remove(PARAM_KEY_T4_OPTIONS); 320 params.remove(PARAM_KEY_T6_OPTIONS); 321 if (!params.isEmpty()) { 322 final Object firstKey = params.keySet().iterator().next(); 323 throw new ImageWriteException("Unknown parameter: " + firstKey); 324 } 325 326 int samplesPerPixel; 327 int bitsPerSample; 328 int photometricInterpretation; 329 if (compression == TIFF_COMPRESSION_CCITT_1D 330 || compression == TIFF_COMPRESSION_CCITT_GROUP_3 331 || compression == TIFF_COMPRESSION_CCITT_GROUP_4) { 332 samplesPerPixel = 1; 333 bitsPerSample = 1; 334 photometricInterpretation = 0; 335 } else { 336 samplesPerPixel = 3; 337 bitsPerSample = 8; 338 photometricInterpretation = 2; 339 } 340 341 int rowsPerStrip = stripSizeInBits / (width * bitsPerSample * samplesPerPixel); 342 rowsPerStrip = Math.max(1, rowsPerStrip); // must have at least one. 343 344 final byte[][] strips = getStrips(src, samplesPerPixel, bitsPerSample, rowsPerStrip); 345 346 // System.out.println("width: " + width); 347 // System.out.println("height: " + height); 348 // System.out.println("fRowsPerStrip: " + fRowsPerStrip); 349 // System.out.println("fSamplesPerPixel: " + fSamplesPerPixel); 350 // System.out.println("stripCount: " + stripCount); 351 352 int t4Options = 0; 353 int t6Options = 0; 354 if (compression == TIFF_COMPRESSION_CCITT_1D) { 355 for (int i = 0; i < strips.length; i++) { 356 strips[i] = T4AndT6Compression.compressModifiedHuffman( 357 strips[i], width, strips[i].length / ((width + 7) / 8)); 358 } 359 } else if (compression == TIFF_COMPRESSION_CCITT_GROUP_3) { 360 final Integer t4Parameter = (Integer) rawParams.get(PARAM_KEY_T4_OPTIONS); 361 if (t4Parameter != null) { 362 t4Options = t4Parameter.intValue(); 363 } 364 t4Options &= 0x7; 365 final boolean is2D = (t4Options & 1) != 0; 366 final boolean usesUncompressedMode = (t4Options & 2) != 0; 367 if (usesUncompressedMode) { 368 throw new ImageWriteException( 369 "T.4 compression with the uncompressed mode extension is not yet supported"); 370 } 371 final boolean hasFillBitsBeforeEOL = (t4Options & 4) != 0; 372 for (int i = 0; i < strips.length; i++) { 373 if (is2D) { 374 strips[i] = T4AndT6Compression.compressT4_2D(strips[i], 375 width, strips[i].length / ((width + 7) / 8), 376 hasFillBitsBeforeEOL, rowsPerStrip); 377 } else { 378 strips[i] = T4AndT6Compression.compressT4_1D(strips[i], 379 width, strips[i].length / ((width + 7) / 8), 380 hasFillBitsBeforeEOL); 381 } 382 } 383 } else if (compression == TIFF_COMPRESSION_CCITT_GROUP_4) { 384 final Integer t6Parameter = (Integer) rawParams.get(PARAM_KEY_T6_OPTIONS); 385 if (t6Parameter != null) { 386 t6Options = t6Parameter.intValue(); 387 } 388 t6Options &= 0x4; 389 final boolean usesUncompressedMode = (t6Options & TIFF_FLAG_T6_OPTIONS_UNCOMPRESSED_MODE) != 0; 390 if (usesUncompressedMode) { 391 throw new ImageWriteException( 392 "T.6 compression with the uncompressed mode extension is not yet supported"); 393 } 394 for (int i = 0; i < strips.length; i++) { 395 strips[i] = T4AndT6Compression.compressT6(strips[i], width, 396 strips[i].length / ((width + 7) / 8)); 397 } 398 } else if (compression == TIFF_COMPRESSION_PACKBITS) { 399 for (int i = 0; i < strips.length; i++) { 400 strips[i] = new PackBits().compress(strips[i]); 401 } 402 } else if (compression == TIFF_COMPRESSION_LZW) { 403 for (int i = 0; i < strips.length; i++) { 404 final byte[] uncompressed = strips[i]; 405 406 final int LZW_MINIMUM_CODE_SIZE = 8; 407 408 final MyLzwCompressor compressor = new MyLzwCompressor( 409 LZW_MINIMUM_CODE_SIZE, ByteOrder.BIG_ENDIAN, true); 410 final byte[] compressed = compressor.compress(uncompressed); 411 412 strips[i] = compressed; 413 } 414 } else if (compression == TIFF_COMPRESSION_UNCOMPRESSED) { 415 // do nothing. 416 } else { 417 throw new ImageWriteException( 418 "Invalid compression parameter (Only CCITT 1D/Group 3/Group 4, LZW, Packbits and uncompressed supported)."); 419 } 420 421 final TiffElement.DataElement[] imageData = new TiffElement.DataElement[strips.length]; 422 for (int i = 0; i < strips.length; i++) { 423 imageData[i] = new TiffImageData.Data(0, strips[i].length, strips[i]); 424 } 425 426 final TiffOutputSet outputSet = new TiffOutputSet(byteOrder); 427 final TiffOutputDirectory directory = outputSet.addRootDirectory(); 428 429 // WriteField stripOffsetsField; 430 431 { 432 433 directory.add(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, width); 434 directory.add(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, height); 435 directory.add(TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION, 436 (short) photometricInterpretation); 437 directory.add(TiffTagConstants.TIFF_TAG_COMPRESSION, 438 (short) compression); 439 directory.add(TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL, 440 (short) samplesPerPixel); 441 442 if (samplesPerPixel == 3) { 443 directory.add(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE, 444 (short) bitsPerSample, (short) bitsPerSample, 445 (short) bitsPerSample); 446 } else if (samplesPerPixel == 1) { 447 directory.add(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE, 448 (short) bitsPerSample); 449 } 450 // { 451 // stripOffsetsField = new WriteField(TIFF_TAG_STRIP_OFFSETS, 452 // FIELD_TYPE_LONG, stripOffsets.length, FIELD_TYPE_LONG 453 // .writeData(stripOffsets, byteOrder)); 454 // directory.add(stripOffsetsField); 455 // } 456 // { 457 // WriteField field = new WriteField(TIFF_TAG_STRIP_BYTE_COUNTS, 458 // FIELD_TYPE_LONG, stripByteCounts.length, 459 // FIELD_TYPE_LONG.writeData(stripByteCounts, 460 // WRITE_BYTE_ORDER)); 461 // directory.add(field); 462 // } 463 directory.add(TiffTagConstants.TIFF_TAG_ROWS_PER_STRIP, 464 rowsPerStrip); 465 if (pixelDensity.isUnitless()) { 466 directory.add(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT, 467 (short) 0); 468 directory.add(TiffTagConstants.TIFF_TAG_XRESOLUTION, 469 RationalNumber.valueOf(pixelDensity.getRawHorizontalDensity())); 470 directory.add(TiffTagConstants.TIFF_TAG_YRESOLUTION, 471 RationalNumber.valueOf(pixelDensity.getRawVerticalDensity())); 472 } else if (pixelDensity.isInInches()) { 473 directory.add(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT, 474 (short) 2); 475 directory.add(TiffTagConstants.TIFF_TAG_XRESOLUTION, 476 RationalNumber.valueOf(pixelDensity.horizontalDensityInches())); 477 directory.add(TiffTagConstants.TIFF_TAG_YRESOLUTION, 478 RationalNumber.valueOf(pixelDensity.verticalDensityInches())); 479 } else { 480 directory.add(TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT, 481 (short) 1); 482 directory.add(TiffTagConstants.TIFF_TAG_XRESOLUTION, 483 RationalNumber.valueOf(pixelDensity.horizontalDensityCentimetres())); 484 directory.add(TiffTagConstants.TIFF_TAG_YRESOLUTION, 485 RationalNumber.valueOf(pixelDensity.verticalDensityCentimetres())); 486 } 487 if (t4Options != 0) { 488 directory.add(TiffTagConstants.TIFF_TAG_T4_OPTIONS, t4Options); 489 } 490 if (t6Options != 0) { 491 directory.add(TiffTagConstants.TIFF_TAG_T6_OPTIONS, t6Options); 492 } 493 494 if (null != xmpXml) { 495 final byte[] xmpXmlBytes = xmpXml.getBytes(StandardCharsets.UTF_8); 496 directory.add(TiffTagConstants.TIFF_TAG_XMP, xmpXmlBytes); 497 } 498 499 } 500 501 final TiffImageData tiffImageData = new TiffImageData.Strips(imageData, 502 rowsPerStrip); 503 directory.setTiffImageData(tiffImageData); 504 505 if (userExif != null) { 506 combineUserExifIntoFinalExif(userExif, outputSet); 507 } 508 509 write(os, outputSet); 510 } 511 512 private void combineUserExifIntoFinalExif(final TiffOutputSet userExif, 513 final TiffOutputSet outputSet) throws ImageWriteException { 514 final List<TiffOutputDirectory> outputDirectories = outputSet.getDirectories(); 515 Collections.sort(outputDirectories, TiffOutputDirectory.COMPARATOR); 516 for (final TiffOutputDirectory userDirectory : userExif.getDirectories()) { 517 final int location = Collections.binarySearch(outputDirectories, 518 userDirectory, TiffOutputDirectory.COMPARATOR); 519 if (location < 0) { 520 outputSet.addDirectory(userDirectory); 521 } else { 522 final TiffOutputDirectory outputDirectory = outputDirectories.get(location); 523 for (final TiffOutputField userField : userDirectory.getFields()) { 524 if (outputDirectory.findField(userField.tagInfo) == null) { 525 outputDirectory.add(userField); 526 } 527 } 528 } 529 } 530 } 531 532 private byte[][] getStrips(final BufferedImage src, final int samplesPerPixel, 533 final int bitsPerSample, final int rowsPerStrip) { 534 final int width = src.getWidth(); 535 final int height = src.getHeight(); 536 537 final int stripCount = (height + rowsPerStrip - 1) / rowsPerStrip; 538 539 byte[][] result; 540 { // Write Strips 541 result = new byte[stripCount][]; 542 543 int remainingRows = height; 544 545 for (int i = 0; i < stripCount; i++) { 546 final int rowsInStrip = Math.min(rowsPerStrip, remainingRows); 547 remainingRows -= rowsInStrip; 548 549 final int bitsInRow = bitsPerSample * samplesPerPixel * width; 550 final int bytesPerRow = (bitsInRow + 7) / 8; 551 final int bytesInStrip = rowsInStrip * bytesPerRow; 552 553 final byte[] uncompressed = new byte[bytesInStrip]; 554 555 int counter = 0; 556 int y = i * rowsPerStrip; 557 final int stop = i * rowsPerStrip + rowsPerStrip; 558 559 for (; (y < height) && (y < stop); y++) { 560 int bitCache = 0; 561 int bitsInCache = 0; 562 for (int x = 0; x < width; x++) { 563 final int rgb = src.getRGB(x, y); 564 final int red = 0xff & (rgb >> 16); 565 final int green = 0xff & (rgb >> 8); 566 final int blue = 0xff & (rgb >> 0); 567 568 if (bitsPerSample == 1) { 569 int sample = (red + green + blue) / 3; 570 if (sample > 127) { 571 sample = 0; 572 } else { 573 sample = 1; 574 } 575 bitCache <<= 1; 576 bitCache |= sample; 577 bitsInCache++; 578 if (bitsInCache == 8) { 579 uncompressed[counter++] = (byte) bitCache; 580 bitCache = 0; 581 bitsInCache = 0; 582 } 583 } else { 584 uncompressed[counter++] = (byte) red; 585 uncompressed[counter++] = (byte) green; 586 uncompressed[counter++] = (byte) blue; 587 } 588 } 589 if (bitsInCache > 0) { 590 bitCache <<= (8 - bitsInCache); 591 uncompressed[counter++] = (byte) bitCache; 592 } 593 } 594 595 result[i] = uncompressed; 596 } 597 598 } 599 600 return result; 601 } 602 603 protected void writeImageFileHeader(final BinaryOutputStream bos) 604 throws IOException { 605 final int offsetToFirstIFD = TIFF_HEADER_SIZE; 606 607 writeImageFileHeader(bos, offsetToFirstIFD); 608 } 609 610 protected void writeImageFileHeader(final BinaryOutputStream bos, 611 final long offsetToFirstIFD) throws IOException { 612 if (byteOrder == ByteOrder.LITTLE_ENDIAN) { 613 bos.write('I'); 614 bos.write('I'); 615 } else { 616 bos.write('M'); 617 bos.write('M'); 618 } 619 620 bos.write2Bytes(42); // tiffVersion 621 622 bos.write4Bytes((int) offsetToFirstIFD); 623 } 624 625}