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