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