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; 018 019import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_1D; 020import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_3; 021import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_CCITT_GROUP_4; 022import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_JPEG; 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_1; 026import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.TIFF_COMPRESSION_UNCOMPRESSED_2; 027 028import java.awt.Dimension; 029import java.awt.Rectangle; 030import java.awt.image.BufferedImage; 031import java.io.IOException; 032import java.io.OutputStream; 033import java.io.PrintWriter; 034import java.nio.ByteOrder; 035import java.nio.charset.StandardCharsets; 036import java.util.ArrayList; 037import java.util.List; 038 039import org.apache.commons.imaging.FormatCompliance; 040import org.apache.commons.imaging.ImageFormat; 041import org.apache.commons.imaging.ImageFormats; 042import org.apache.commons.imaging.ImageInfo; 043import org.apache.commons.imaging.ImageParser; 044import org.apache.commons.imaging.ImageReadException; 045import org.apache.commons.imaging.ImageWriteException; 046import org.apache.commons.imaging.common.ImageBuilder; 047import org.apache.commons.imaging.common.ImageMetadata; 048import org.apache.commons.imaging.common.XmpEmbeddable; 049import org.apache.commons.imaging.common.XmpImagingParameters; 050import org.apache.commons.imaging.common.bytesource.ByteSource; 051import org.apache.commons.imaging.formats.tiff.TiffDirectory.ImageDataElement; 052import org.apache.commons.imaging.formats.tiff.constants.TiffEpTagConstants; 053import org.apache.commons.imaging.formats.tiff.constants.TiffPlanarConfiguration; 054import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants; 055import org.apache.commons.imaging.formats.tiff.datareaders.ImageDataReader; 056import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreter; 057import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterBiLevel; 058import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterCieLab; 059import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterCmyk; 060import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterLogLuv; 061import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterPalette; 062import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterRgb; 063import org.apache.commons.imaging.formats.tiff.photometricinterpreters.PhotometricInterpreterYCbCr; 064import org.apache.commons.imaging.formats.tiff.write.TiffImageWriterLossy; 065 066public class TiffImageParser extends ImageParser<TiffImagingParameters> implements XmpEmbeddable { 067 private static final String DEFAULT_EXTENSION = ImageFormats.TIFF.getDefaultExtension(); 068 private static final String[] ACCEPTED_EXTENSIONS = ImageFormats.TIFF.getExtensions(); 069 070 @Override 071 public TiffImagingParameters getDefaultParameters() { 072 return new TiffImagingParameters(); 073 } 074 075 @Override 076 public String getName() { 077 return "Tiff-Custom"; 078 } 079 080 @Override 081 public String getDefaultExtension() { 082 return DEFAULT_EXTENSION; 083 } 084 085 @Override 086 protected String[] getAcceptedExtensions() { 087 return ACCEPTED_EXTENSIONS; 088 } 089 090 @Override 091 protected ImageFormat[] getAcceptedTypes() { 092 return new ImageFormat[] { ImageFormats.TIFF, // 093 }; 094 } 095 096 @Override 097 public byte[] getICCProfileBytes(final ByteSource byteSource, final TiffImagingParameters params) 098 throws ImageReadException, IOException { 099 final FormatCompliance formatCompliance = FormatCompliance.getDefault(); 100 final TiffContents contents = new TiffReader(params != null && params.isStrict()).readFirstDirectory( 101 byteSource, false, formatCompliance); 102 final TiffDirectory directory = contents.directories.get(0); 103 104 return directory.getFieldValue(TiffEpTagConstants.EXIF_TAG_INTER_COLOR_PROFILE, 105 false); 106 } 107 108 @Override 109 public Dimension getImageSize(final ByteSource byteSource, final TiffImagingParameters params) 110 throws ImageReadException, IOException { 111 final FormatCompliance formatCompliance = FormatCompliance.getDefault(); 112 final TiffContents contents = new TiffReader(params != null && params.isStrict()) 113 .readFirstDirectory(byteSource, false, formatCompliance); 114 final TiffDirectory directory = contents.directories.get(0); 115 116 final TiffField widthField = directory.findField( 117 TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, true); 118 final TiffField heightField = directory.findField( 119 TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, true); 120 121 if ((widthField == null) || (heightField == null)) { 122 throw new ImageReadException("TIFF image missing size info."); 123 } 124 125 final int height = heightField.getIntValue(); 126 final int width = widthField.getIntValue(); 127 128 return new Dimension(width, height); 129 } 130 131 @Override 132 public ImageMetadata getMetadata(final ByteSource byteSource, TiffImagingParameters params) 133 throws ImageReadException, IOException { 134 if (params == null) { 135 params = this.getDefaultParameters(); 136 } 137 final FormatCompliance formatCompliance = FormatCompliance.getDefault(); 138 final TiffReader tiffReader = new TiffReader(params.isStrict()); 139 final TiffContents contents = tiffReader.readContents(byteSource, params, formatCompliance); 140 141 final List<TiffDirectory> directories = contents.directories; 142 143 final TiffImageMetadata result = new TiffImageMetadata(contents); 144 145 for (final TiffDirectory dir : directories) { 146 final TiffImageMetadata.Directory metadataDirectory = new TiffImageMetadata.Directory( 147 tiffReader.getByteOrder(), dir); 148 149 final List<TiffField> entries = dir.getDirectoryEntries(); 150 151 for (final TiffField entry : entries) { 152 metadataDirectory.add(entry); 153 } 154 155 result.add(metadataDirectory); 156 } 157 158 return result; 159 } 160 161 @Override 162 public ImageInfo getImageInfo(final ByteSource byteSource, final TiffImagingParameters params) 163 throws ImageReadException, IOException { 164 final FormatCompliance formatCompliance = FormatCompliance.getDefault(); 165 final TiffContents contents = new TiffReader(params != null && params.isStrict()).readDirectories( 166 byteSource, false, formatCompliance); 167 final TiffDirectory directory = contents.directories.get(0); 168 169 final TiffField widthField = directory.findField( 170 TiffTagConstants.TIFF_TAG_IMAGE_WIDTH, true); 171 final TiffField heightField = directory.findField( 172 TiffTagConstants.TIFF_TAG_IMAGE_LENGTH, true); 173 174 if ((widthField == null) || (heightField == null)) { 175 throw new ImageReadException("TIFF image missing size info."); 176 } 177 178 final int height = heightField.getIntValue(); 179 final int width = widthField.getIntValue(); 180 181 // ------------------- 182 183 final TiffField resolutionUnitField = directory.findField( 184 TiffTagConstants.TIFF_TAG_RESOLUTION_UNIT); 185 int resolutionUnit = 2; // Inch 186 if ((resolutionUnitField != null) 187 && (resolutionUnitField.getValue() != null)) { 188 resolutionUnit = resolutionUnitField.getIntValue(); 189 } 190 191 double unitsPerInch = -1; 192 switch (resolutionUnit) { 193 case 1: 194 break; 195 case 2: // Inch 196 unitsPerInch = 1.0; 197 break; 198 case 3: // Centimeter 199 unitsPerInch = 2.54; 200 break; 201 default: 202 break; 203 204 } 205 206 int physicalWidthDpi = -1; 207 float physicalWidthInch = -1; 208 int physicalHeightDpi = -1; 209 float physicalHeightInch = -1; 210 211 if (unitsPerInch > 0) { 212 final TiffField xResolutionField = directory.findField( 213 TiffTagConstants.TIFF_TAG_XRESOLUTION); 214 final TiffField yResolutionField = directory.findField( 215 TiffTagConstants.TIFF_TAG_YRESOLUTION); 216 217 if ((xResolutionField != null) 218 && (xResolutionField.getValue() != null)) { 219 final double xResolutionPixelsPerUnit = xResolutionField.getDoubleValue(); 220 physicalWidthDpi = (int) Math.round((xResolutionPixelsPerUnit * unitsPerInch)); 221 physicalWidthInch = (float) (width / (xResolutionPixelsPerUnit * unitsPerInch)); 222 } 223 if ((yResolutionField != null) 224 && (yResolutionField.getValue() != null)) { 225 final double yResolutionPixelsPerUnit = yResolutionField.getDoubleValue(); 226 physicalHeightDpi = (int) Math.round((yResolutionPixelsPerUnit * unitsPerInch)); 227 physicalHeightInch = (float) (height / (yResolutionPixelsPerUnit * unitsPerInch)); 228 } 229 } 230 231 // ------------------- 232 233 final TiffField bitsPerSampleField = directory.findField(TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE); 234 235 int bitsPerSample = 1; 236 if ((bitsPerSampleField != null) 237 && (bitsPerSampleField.getValue() != null)) { 238 bitsPerSample = bitsPerSampleField.getIntValueOrArraySum(); 239 } 240 241 final int bitsPerPixel = bitsPerSample; // assume grayscale; 242 // dunno if this handles colormapped images correctly. 243 244 // ------------------- 245 246 final List<TiffField> entries = directory.entries; 247 final List<String> comments = new ArrayList<>(entries.size()); 248 for (final TiffField field : entries) { 249 final String comment = field.toString(); 250 comments.add(comment); 251 } 252 253 final ImageFormat format = ImageFormats.TIFF; 254 final String formatName = "TIFF Tag-based Image File Format"; 255 final String mimeType = "image/tiff"; 256 final int numberOfImages = contents.directories.size(); 257 // not accurate ... only reflects first 258 final boolean progressive = false; 259 // is TIFF ever interlaced/progressive? 260 261 final String formatDetails = "Tiff v." + contents.header.tiffVersion; 262 263 final boolean transparent = false; // TODO: wrong 264 boolean usesPalette = false; 265 final TiffField colorMapField = directory.findField(TiffTagConstants.TIFF_TAG_COLOR_MAP); 266 if (colorMapField != null) { 267 usesPalette = true; 268 } 269 270 final ImageInfo.ColorType colorType = ImageInfo.ColorType.RGB; 271 272 final short compressionFieldValue; 273 if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) { 274 compressionFieldValue = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION); 275 } else { 276 compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1; 277 } 278 final int compression = 0xffff & compressionFieldValue; 279 ImageInfo.CompressionAlgorithm compressionAlgorithm; 280 281 switch (compression) { 282 case TIFF_COMPRESSION_UNCOMPRESSED_1: 283 compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE; 284 break; 285 case TIFF_COMPRESSION_CCITT_1D: 286 compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_1D; 287 break; 288 case TIFF_COMPRESSION_CCITT_GROUP_3: 289 compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_GROUP_3; 290 break; 291 case TIFF_COMPRESSION_CCITT_GROUP_4: 292 compressionAlgorithm = ImageInfo.CompressionAlgorithm.CCITT_GROUP_4; 293 break; 294 case TIFF_COMPRESSION_LZW: 295 compressionAlgorithm = ImageInfo.CompressionAlgorithm.LZW; 296 break; 297 case TIFF_COMPRESSION_JPEG: 298 compressionAlgorithm = ImageInfo.CompressionAlgorithm.JPEG; 299 break; 300 case TIFF_COMPRESSION_UNCOMPRESSED_2: 301 compressionAlgorithm = ImageInfo.CompressionAlgorithm.NONE; 302 break; 303 case TIFF_COMPRESSION_PACKBITS: 304 compressionAlgorithm = ImageInfo.CompressionAlgorithm.PACKBITS; 305 break; 306 default: 307 compressionAlgorithm = ImageInfo.CompressionAlgorithm.UNKNOWN; 308 break; 309 } 310 311 return new ImageInfo(formatDetails, bitsPerPixel, comments, 312 format, formatName, height, mimeType, numberOfImages, 313 physicalHeightDpi, physicalHeightInch, physicalWidthDpi, 314 physicalWidthInch, width, progressive, transparent, 315 usesPalette, colorType, compressionAlgorithm); 316 } 317 318 @Override 319 public String getXmpXml(final ByteSource byteSource, XmpImagingParameters params) 320 throws ImageReadException, IOException { 321 if (params == null) { 322 params = new XmpImagingParameters(); 323 } 324 final FormatCompliance formatCompliance = FormatCompliance.getDefault(); 325 final TiffContents contents = new TiffReader(params.isStrict()).readDirectories( 326 byteSource, false, formatCompliance); 327 final TiffDirectory directory = contents.directories.get(0); 328 329 final byte[] bytes = directory.getFieldValue(TiffTagConstants.TIFF_TAG_XMP, 330 false); 331 if (bytes == null) { 332 return null; 333 } 334 335 // segment data is UTF-8 encoded xml. 336 return new String(bytes, StandardCharsets.UTF_8); 337 } 338 339 @Override 340 public boolean dumpImageFile(final PrintWriter pw, final ByteSource byteSource) 341 throws ImageReadException, IOException { 342 try { 343 pw.println("tiff.dumpImageFile"); 344 345 { 346 final ImageInfo imageData = getImageInfo(byteSource); 347 if (imageData == null) { 348 return false; 349 } 350 351 imageData.toString(pw, ""); 352 } 353 354 pw.println(""); 355 356 // try 357 { 358 final FormatCompliance formatCompliance = FormatCompliance.getDefault(); 359 final TiffImagingParameters params = new TiffImagingParameters(); 360 final TiffContents contents = new TiffReader(true).readContents( 361 byteSource, params, formatCompliance); 362 363 final List<TiffDirectory> directories = contents.directories; 364 if (directories == null) { 365 return false; 366 } 367 368 for (int d = 0; d < directories.size(); d++) { 369 final TiffDirectory directory = directories.get(d); 370 371 final List<TiffField> entries = directory.entries; 372 373 if (entries == null) { 374 return false; 375 } 376 377 // Debug.debug("directory offset", directory.offset); 378 379 for (final TiffField field : entries) { 380 field.dump(pw, Integer.toString(d)); 381 } 382 } 383 384 pw.println(""); 385 } 386 // catch (Exception e) 387 // { 388 // Debug.debug(e); 389 // pw.println(""); 390 // return false; 391 // } 392 393 return true; 394 } finally { 395 pw.println(""); 396 } 397 } 398 399 @Override 400 public FormatCompliance getFormatCompliance(final ByteSource byteSource) 401 throws ImageReadException, IOException { 402 final FormatCompliance formatCompliance = FormatCompliance.getDefault(); 403 final TiffImagingParameters params = new TiffImagingParameters(); 404 new TiffReader(params.isStrict()).readContents(byteSource, params, 405 formatCompliance); 406 return formatCompliance; 407 } 408 409 public List<byte[]> collectRawImageData(final ByteSource byteSource, final TiffImagingParameters params) 410 throws ImageReadException, IOException { 411 final FormatCompliance formatCompliance = FormatCompliance.getDefault(); 412 final TiffContents contents = new TiffReader(params != null && params.isStrict()).readDirectories( 413 byteSource, true, formatCompliance); 414 415 final List<byte[]> result = new ArrayList<>(); 416 for (int i = 0; i < contents.directories.size(); i++) { 417 final TiffDirectory directory = contents.directories.get(i); 418 final List<ImageDataElement> dataElements = directory.getTiffRawImageDataElements(); 419 for (final ImageDataElement element : dataElements) { 420 final byte[] bytes = byteSource.getBlock(element.offset, 421 element.length); 422 result.add(bytes); 423 } 424 } 425 return result; 426 } 427 428 /** 429 * <p>Gets a buffered image specified by the byte source. 430 * The TiffImageParser class features support for a number of options that 431 * are unique to the TIFF format. These options can be specified by 432 * supplying the appropriate parameters using the keys from the 433 * TiffConstants class and the params argument for this method.</p> 434 * 435 * <p><strong>Loading Partial Images</strong></p> 436 * 437 * <p>The TIFF parser includes support for loading partial images without 438 * committing significantly more memory resources than are necessary 439 * to store the image. This feature is useful for conserving memory 440 * in applications that require a relatively small sub image from a 441 * very large TIFF file. The specifications for partial images are 442 * as follows:</p> 443 * 444 * <pre> 445 * TiffImagingParameters params = new TiffImagingParameters(); 446 * params.setSubImageX(x); 447 * params.setSubImageY(y); 448 * params.setSubImageWidth(width); 449 * params.setSubImageHeight(height); 450 * </pre> 451 * 452 * <p>Note that the arguments x, y, width, and height must specify a 453 * valid rectangular region that is fully contained within the 454 * source TIFF image.</p> 455 * 456 * @param byteSource A valid instance of ByteSource 457 * @param params Optional instructions for special-handling or 458 * interpretation of the input data (null objects are permitted and 459 * must be supported by implementations). 460 * @return A valid instance of BufferedImage. 461 * @throws ImageReadException In the event that the specified 462 * content does not conform to the format of the specific parser 463 * implementation. 464 * @throws IOException In the event of unsuccessful read or 465 * access operation. 466 */ 467 @Override 468 public BufferedImage getBufferedImage(final ByteSource byteSource, TiffImagingParameters params) 469 throws ImageReadException, IOException { 470 if (params == null) { 471 params = new TiffImagingParameters(); 472 } 473 final FormatCompliance formatCompliance = FormatCompliance.getDefault(); 474 final TiffReader reader = new TiffReader(params.isStrict()); 475 final TiffContents contents = reader.readFirstDirectory(byteSource, true, formatCompliance); 476 final ByteOrder byteOrder = reader.getByteOrder(); 477 final TiffDirectory directory = contents.directories.get(0); 478 final BufferedImage result = directory.getTiffImage(byteOrder, params); 479 if (null == result) { 480 throw new ImageReadException("TIFF does not contain an image."); 481 } 482 return result; 483 } 484 485 @Override 486 public List<BufferedImage> getAllBufferedImages(final ByteSource byteSource) 487 throws ImageReadException, IOException { 488 final FormatCompliance formatCompliance = FormatCompliance.getDefault(); 489 final TiffReader tiffReader = new TiffReader(true); 490 final TiffContents contents = tiffReader.readDirectories(byteSource, true, 491 formatCompliance); 492 final List<BufferedImage> results = new ArrayList<>(); 493 for (int i = 0; i < contents.directories.size(); i++) { 494 final TiffDirectory directory = contents.directories.get(i); 495 final BufferedImage result = directory.getTiffImage( 496 tiffReader.getByteOrder(), null); 497 if (result != null) { 498 results.add(result); 499 } 500 } 501 return results; 502 } 503 504 private Rectangle checkForSubImage( 505 final TiffImagingParameters params) 506 throws ImageReadException { 507 // the params class enforces a correct specification for the 508 // sub-image, but does not have knowledge of the actual 509 // dimensions of the image that is being read. This method 510 // returns the sub-image specification, if any, and leaves 511 // further tests to the calling module. 512 if (params.isSubImageSet()) { 513 final int ix0 = params.getSubImageX(); 514 final int iy0 = params.getSubImageY(); 515 final int iwidth = params.getSubImageWidth(); 516 final int iheight = params.getSubImageHeight(); 517 return new Rectangle(ix0, iy0, iwidth, iheight); 518 } else { 519 return null; 520 } 521 } 522 523 protected BufferedImage getBufferedImage(final TiffDirectory directory, 524 final ByteOrder byteOrder, final TiffImagingParameters params) 525 throws ImageReadException, IOException { 526 final List<TiffField> entries = directory.entries; 527 528 if (entries == null) { 529 throw new ImageReadException("TIFF missing entries"); 530 } 531 532 final short compressionFieldValue; 533 if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) { 534 compressionFieldValue = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION); 535 } else { 536 compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1; 537 } 538 final int compression = 0xffff & compressionFieldValue; 539 final int width = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH); 540 final int height = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH); 541 542 final Rectangle subImage = checkForSubImage(params); 543 if (subImage != null) { 544 // Check for valid subimage specification. The following checks 545 // are consistent with BufferedImage.getSubimage() 546 if (subImage.width <= 0) { 547 throw new ImageReadException("negative or zero subimage width"); 548 } 549 if (subImage.height <= 0) { 550 throw new ImageReadException("negative or zero subimage height"); 551 } 552 if (subImage.x < 0 || subImage.x >= width) { 553 throw new ImageReadException("subimage x is outside raster"); 554 } 555 if (subImage.x + subImage.width > width) { 556 throw new ImageReadException("subimage (x+width) is outside raster"); 557 } 558 if (subImage.y < 0 || subImage.y >= height) { 559 throw new ImageReadException("subimage y is outside raster"); 560 } 561 if (subImage.y + subImage.height > height) { 562 throw new ImageReadException("subimage (y+height) is outside raster"); 563 } 564 } 565 566 int samplesPerPixel = 1; 567 final TiffField samplesPerPixelField = directory.findField( 568 TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL); 569 if (samplesPerPixelField != null) { 570 samplesPerPixel = samplesPerPixelField.getIntValue(); 571 } 572 int[] bitsPerSample = { 1 }; 573 int bitsPerPixel = samplesPerPixel; 574 final TiffField bitsPerSampleField = directory.findField( 575 TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE); 576 if (bitsPerSampleField != null) { 577 bitsPerSample = bitsPerSampleField.getIntArrayValue(); 578 bitsPerPixel = bitsPerSampleField.getIntValueOrArraySum(); 579 } 580 581 // int bitsPerPixel = getTagAsValueOrArraySum(entries, 582 // TIFF_TAG_BITS_PER_SAMPLE); 583 584 int predictor = -1; 585 { 586 // dumpOptionalNumberTag(entries, TIFF_TAG_FILL_ORDER); 587 // dumpOptionalNumberTag(entries, TIFF_TAG_FREE_BYTE_COUNTS); 588 // dumpOptionalNumberTag(entries, TIFF_TAG_FREE_OFFSETS); 589 // dumpOptionalNumberTag(entries, TIFF_TAG_ORIENTATION); 590 // dumpOptionalNumberTag(entries, TIFF_TAG_PLANAR_CONFIGURATION); 591 final TiffField predictorField = directory.findField( 592 TiffTagConstants.TIFF_TAG_PREDICTOR); 593 if (null != predictorField) { 594 predictor = predictorField.getIntValueOrArraySum(); 595 } 596 } 597 598 if (samplesPerPixel != bitsPerSample.length) { 599 throw new ImageReadException("Tiff: samplesPerPixel (" 600 + samplesPerPixel + ")!=fBitsPerSample.length (" 601 + bitsPerSample.length + ")"); 602 } 603 604 605 final int photometricInterpretation = 0xffff & directory.getFieldValue( 606 TiffTagConstants.TIFF_TAG_PHOTOMETRIC_INTERPRETATION); 607 608 boolean hasAlpha = false; 609 boolean isAlphaPremultiplied = false; 610 if (photometricInterpretation == TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_RGB 611 && samplesPerPixel == 4) { 612 final TiffField extraSamplesField 613 = directory.findField(TiffTagConstants.TIFF_TAG_EXTRA_SAMPLES); 614 if (extraSamplesField == null) { 615 // this state is not defined in the TIFF specification 616 // and so this code will interpret it as meaning that the 617 // proper handling would be ARGB. 618 hasAlpha = true; 619 isAlphaPremultiplied = false; 620 } else { 621 final int extraSamplesValue = extraSamplesField.getIntValue(); 622 switch (extraSamplesValue) { 623 case TiffTagConstants.EXTRA_SAMPLE_UNASSOCIATED_ALPHA: 624 hasAlpha = true; 625 isAlphaPremultiplied = false; 626 break; 627 case TiffTagConstants.EXTRA_SAMPLE_ASSOCIATED_ALPHA: 628 hasAlpha = true; 629 isAlphaPremultiplied = true; 630 break; 631 case 0: 632 default: 633 hasAlpha = false; 634 isAlphaPremultiplied = false; 635 break; 636 } 637 } 638 } 639 640 PhotometricInterpreter photometricInterpreter = params.getCustomPhotometricInterpreter(); 641 if (photometricInterpreter == null) { 642 photometricInterpreter = getPhotometricInterpreter( 643 directory, photometricInterpretation, bitsPerPixel, 644 bitsPerSample, predictor, samplesPerPixel, width, height); 645 } 646 647 // Obtain the planar configuration 648 final TiffField pcField = directory.findField( 649 TiffTagConstants.TIFF_TAG_PLANAR_CONFIGURATION); 650 final TiffPlanarConfiguration planarConfiguration 651 = pcField == null 652 ? TiffPlanarConfiguration.CHUNKY 653 : TiffPlanarConfiguration.lenientValueOf(pcField.getIntValue()); 654 655 if (planarConfiguration == TiffPlanarConfiguration.PLANAR) { 656 // currently, we support the non-interleaved (non-chunky) 657 // option only in the case of a 24-bit RBG photometric interpreter 658 // and for strips (not for tiles). 659 if (photometricInterpretation 660 != TiffTagConstants.PHOTOMETRIC_INTERPRETATION_VALUE_RGB 661 || bitsPerPixel != 24) { 662 throw new ImageReadException("For planar configuration 2, only 24 bit RGB is currently supported"); 663 } 664 if (null == directory.findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS)) { 665 throw new ImageReadException("For planar configuration 2, only strips-organization is supported"); 666 } 667 } 668 669 final TiffImageData imageData = directory.getTiffImageData(); 670 671 final ImageDataReader dataReader = imageData.getDataReader(directory, 672 photometricInterpreter, bitsPerPixel, bitsPerSample, predictor, 673 samplesPerPixel, width, height, compression, 674 planarConfiguration, byteOrder); 675 676 final ImageBuilder iBuilder = dataReader.readImageData( 677 subImage, hasAlpha, isAlphaPremultiplied); 678 return iBuilder.getBufferedImage(); 679 } 680 681 private PhotometricInterpreter getPhotometricInterpreter( 682 final TiffDirectory directory, final int photometricInterpretation, 683 final int bitsPerPixel, final int[] bitsPerSample, final int predictor, 684 final int samplesPerPixel, final int width, final int height) 685 throws ImageReadException { 686 switch (photometricInterpretation) { 687 case 0: 688 case 1: 689 final boolean invert = photometricInterpretation == 0; 690 691 return new PhotometricInterpreterBiLevel(samplesPerPixel, 692 bitsPerSample, predictor, width, height, invert); 693 case 3: { 694 // Palette 695 final int[] colorMap = directory.findField( 696 TiffTagConstants.TIFF_TAG_COLOR_MAP, true).getIntArrayValue(); 697 698 final int expectedColormapSize = 3 * (1 << bitsPerPixel); 699 700 if (colorMap.length != expectedColormapSize) { 701 throw new ImageReadException("Tiff: fColorMap.length (" 702 + colorMap.length + ")!=expectedColormapSize (" 703 + expectedColormapSize + ")"); 704 } 705 706 return new PhotometricInterpreterPalette(samplesPerPixel, 707 bitsPerSample, predictor, width, height, colorMap); 708 } 709 case 2: // RGB 710 return new PhotometricInterpreterRgb(samplesPerPixel, 711 bitsPerSample, predictor, width, height); 712 case 5: // CMYK 713 return new PhotometricInterpreterCmyk(samplesPerPixel, 714 bitsPerSample, predictor, width, height); 715 case 6: { 716// final double yCbCrCoefficients[] = directory.findField( 717// TiffTagConstants.TIFF_TAG_YCBCR_COEFFICIENTS, true) 718// .getDoubleArrayValue(); 719// 720// final int yCbCrPositioning[] = directory.findField( 721// TiffTagConstants.TIFF_TAG_YCBCR_POSITIONING, true) 722// .getIntArrayValue(); 723// final int yCbCrSubSampling[] = directory.findField( 724// TiffTagConstants.TIFF_TAG_YCBCR_SUB_SAMPLING, true) 725// .getIntArrayValue(); 726// 727// final double referenceBlackWhite[] = directory.findField( 728// TiffTagConstants.TIFF_TAG_REFERENCE_BLACK_WHITE, true) 729// .getDoubleArrayValue(); 730 731 return new PhotometricInterpreterYCbCr(samplesPerPixel, 732 bitsPerSample, predictor, width, 733 height); 734 } 735 736 case 8: 737 return new PhotometricInterpreterCieLab(samplesPerPixel, 738 bitsPerSample, predictor, width, height); 739 740 case 32844: 741 case 32845: { 742// final boolean yonly = (photometricInterpretation == 32844); 743 return new PhotometricInterpreterLogLuv(samplesPerPixel, 744 bitsPerSample, predictor, width, height); 745 } 746 747 default: 748 throw new ImageReadException( 749 "TIFF: Unknown fPhotometricInterpretation: " 750 + photometricInterpretation); 751 } 752 } 753 754 @Override 755 public void writeImage(final BufferedImage src, final OutputStream os, TiffImagingParameters params) 756 throws ImageWriteException, IOException { 757 if (params == null) { 758 params = new TiffImagingParameters(); 759 } 760 new TiffImageWriterLossy().writeImage(src, os, params); 761 } 762 763 /** 764 * Reads the content of a TIFF file that contains numerical data samples 765 * rather than image-related pixels. 766 * <p> 767 * If desired, sub-image data can be read from the file by using a Java 768 * {@code TiffImagingParameters} instance to specify the subsection of the image that 769 * is required. The following code illustrates the approach: 770 * <pre> 771 * int x; // coordinate (column) of corner of sub-image 772 * int y; // coordinate (row) of corner of sub-image 773 * int width; // width of sub-image 774 * int height; // height of sub-image 775 * 776 * TiffImagingParameters params = new TiffImagingParameters(); 777 * params.setSubImageX(x); 778 * params.setSubImageY(y); 779 * params.setSubImageWidth(width); 780 * params.setSubImageHeight(height); 781 * TiffRasterData raster = 782 * readFloatingPointRasterData(directory, byteOrder, params); 783 * </pre> 784 * 785 * @param directory the TIFF directory pointing to the data to be extracted 786 * (TIFF files may contain multiple directories) 787 * @param byteOrder the byte order of the data to be extracted 788 * @param params an optional parameter object instance 789 * @return a valid instance 790 * @throws ImageReadException in the event of incompatible or malformed data 791 * @throws IOException in the event of an I/O error 792 */ 793 TiffRasterData getRasterData( 794 final TiffDirectory directory, 795 final ByteOrder byteOrder, 796 TiffImagingParameters params) 797 throws ImageReadException, IOException { 798 final List<TiffField> entries = directory.entries; 799 800 if (entries == null) { 801 throw new ImageReadException("TIFF missing entries"); 802 } 803 804 if (params == null) { 805 params = this.getDefaultParameters(); 806 } 807 808 final short[] sSampleFmt = directory.getFieldValue( 809 TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, true); 810 if (sSampleFmt == null || sSampleFmt.length < 1) { 811 throw new ImageReadException( 812 "Directory does not specify numeric raster data"); 813 } 814 815 int samplesPerPixel = 1; 816 final TiffField samplesPerPixelField = directory.findField( 817 TiffTagConstants.TIFF_TAG_SAMPLES_PER_PIXEL); 818 if (samplesPerPixelField != null) { 819 samplesPerPixel = samplesPerPixelField.getIntValue(); 820 } 821 822 int[] bitsPerSample = {1}; 823 int bitsPerPixel = samplesPerPixel; 824 final TiffField bitsPerSampleField = directory.findField( 825 TiffTagConstants.TIFF_TAG_BITS_PER_SAMPLE); 826 if (bitsPerSampleField != null) { 827 bitsPerSample = bitsPerSampleField.getIntArrayValue(); 828 bitsPerPixel = bitsPerSampleField.getIntValueOrArraySum(); 829 } 830 831 final short compressionFieldValue; 832 if (directory.findField(TiffTagConstants.TIFF_TAG_COMPRESSION) != null) { 833 compressionFieldValue 834 = directory.getFieldValue(TiffTagConstants.TIFF_TAG_COMPRESSION); 835 } else { 836 compressionFieldValue = TIFF_COMPRESSION_UNCOMPRESSED_1; 837 } 838 final int compression = 0xffff & compressionFieldValue; 839 840 final int width 841 = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_WIDTH); 842 final int height 843 = directory.getSingleFieldValue(TiffTagConstants.TIFF_TAG_IMAGE_LENGTH); 844 845 Rectangle subImage = checkForSubImage(params); 846 if (subImage != null) { 847 // Check for valid subimage specification. The following checks 848 // are consistent with BufferedImage.getSubimage() 849 if (subImage.width <= 0) { 850 throw new ImageReadException("negative or zero subimage width"); 851 } 852 if (subImage.height <= 0) { 853 throw new ImageReadException("negative or zero subimage height"); 854 } 855 if (subImage.x < 0 || subImage.x >= width) { 856 throw new ImageReadException("subimage x is outside raster"); 857 } 858 if (subImage.x + subImage.width > width) { 859 throw new ImageReadException("subimage (x+width) is outside raster"); 860 } 861 if (subImage.y < 0 || subImage.y >= height) { 862 throw new ImageReadException("subimage y is outside raster"); 863 } 864 if (subImage.y + subImage.height > height) { 865 throw new ImageReadException("subimage (y+height) is outside raster"); 866 } 867 868 // if the subimage is just the same thing as the whole 869 // image, suppress the subimage processing 870 if (subImage.x == 0 871 && subImage.y == 0 872 && subImage.width == width 873 && subImage.height == height) { 874 subImage = null; 875 } 876 } 877 878 // int bitsPerPixel = getTagAsValueOrArraySum(entries, 879 // TIFF_TAG_BITS_PER_SAMPLE); 880 int predictor = -1; 881 { 882 // dumpOptionalNumberTag(entries, TIFF_TAG_FILL_ORDER); 883 // dumpOptionalNumberTag(entries, TIFF_TAG_FREE_BYTE_COUNTS); 884 // dumpOptionalNumberTag(entries, TIFF_TAG_FREE_OFFSETS); 885 // dumpOptionalNumberTag(entries, TIFF_TAG_ORIENTATION); 886 // dumpOptionalNumberTag(entries, TIFF_TAG_PLANAR_CONFIGURATION); 887 final TiffField predictorField = directory.findField( 888 TiffTagConstants.TIFF_TAG_PREDICTOR); 889 if (null != predictorField) { 890 predictor = predictorField.getIntValueOrArraySum(); 891 } 892 } 893 894 // Obtain the planar configuration 895 final TiffField pcField = directory.findField( 896 TiffTagConstants.TIFF_TAG_PLANAR_CONFIGURATION); 897 final TiffPlanarConfiguration planarConfiguration 898 = pcField == null 899 ? TiffPlanarConfiguration.CHUNKY 900 : TiffPlanarConfiguration.lenientValueOf(pcField.getIntValue()); 901 902 if (sSampleFmt[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT) { 903 if (bitsPerSample[0] != 32 && bitsPerSample[0] != 64) { 904 throw new ImageReadException( 905 "TIFF floating-point data uses unsupported bits-per-sample: " 906 + bitsPerSample[0]); 907 } 908 909 if (predictor != -1 910 && predictor != TiffTagConstants.PREDICTOR_VALUE_NONE 911 && predictor != TiffTagConstants.PREDICTOR_VALUE_FLOATING_POINT_DIFFERENCING) { 912 throw new ImageReadException( 913 "TIFF floating-point data uses unsupported horizontal-differencing predictor"); 914 } 915 } else if (sSampleFmt[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER) { 916 917 if (samplesPerPixel != 1) { 918 throw new ImageReadException( 919 "TIFF integer data uses unsupported samples per pixel: " 920 + samplesPerPixel); 921 } 922 923 if (bitsPerPixel != 16 && bitsPerPixel != 32) { 924 throw new ImageReadException( 925 "TIFF integer data uses unsupported bits-per-pixel: " 926 + bitsPerPixel); 927 } 928 929 if (predictor != -1 930 && predictor != TiffTagConstants.PREDICTOR_VALUE_NONE 931 && predictor != TiffTagConstants.PREDICTOR_VALUE_HORIZONTAL_DIFFERENCING) { 932 throw new ImageReadException( 933 "TIFF integer data uses unsupported horizontal-differencing predictor"); 934 } 935 } else { 936 throw new ImageReadException("TIFF does not provide a supported raster-data format"); 937 } 938 939 // The photometric interpreter is not used, but the image-based 940 // data reader classes require one. So we create a dummy interpreter. 941 final PhotometricInterpreter photometricInterpreter 942 = new PhotometricInterpreterBiLevel(samplesPerPixel, 943 bitsPerSample, predictor, width, height, false); 944 945 final TiffImageData imageData = directory.getTiffImageData(); 946 947 final ImageDataReader dataReader = imageData.getDataReader(directory, 948 photometricInterpreter, bitsPerPixel, bitsPerSample, predictor, 949 samplesPerPixel, width, height, compression, 950 planarConfiguration, byteOrder); 951 952 return dataReader.readRasterData(subImage); 953 } 954 955}