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 java.awt.image.BufferedImage; 020import java.io.IOException; 021import java.nio.ByteOrder; 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.Iterator; 025import java.util.List; 026 027import org.apache.commons.imaging.ImagingException; 028import org.apache.commons.imaging.common.Allocator; 029import org.apache.commons.imaging.common.ByteConversions; 030import org.apache.commons.imaging.common.RationalNumber; 031import org.apache.commons.imaging.formats.tiff.constants.TiffConstants; 032import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryConstants; 033import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants; 034import org.apache.commons.imaging.formats.tiff.fieldtypes.AbstractFieldType; 035import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo; 036import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoAscii; 037import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoByte; 038import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoBytes; 039import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoDouble; 040import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoDoubles; 041import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoFloat; 042import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoFloats; 043import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoGpsText; 044import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoLong; 045import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoLongs; 046import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoRational; 047import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoRationals; 048import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSByte; 049import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSBytes; 050import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSLong; 051import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSLongs; 052import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSRational; 053import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSRationals; 054import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSShort; 055import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSShorts; 056import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShort; 057import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShortOrLong; 058import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShorts; 059import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoXpString; 060 061/** 062 * Provides methods and elements for accessing an Image File Directory (IFD) from a TIFF file. In the TIFF specification, the IFD is the main container for 063 * individual images or sets of metadata. While not all Directories contain images, images are always stored in a Directory. 064 */ 065public class TiffDirectory extends AbstractTiffElement implements Iterable<TiffField> { 066 067 public static final class ImageDataElement extends AbstractTiffElement { 068 public ImageDataElement(final long offset, final int length) { 069 super(offset, length); 070 } 071 072 @Override 073 public String getElementDescription() { 074 return "ImageDataElement"; 075 } 076 } 077 078 public static String description(final int type) { 079 switch (type) { 080 case TiffDirectoryConstants.DIRECTORY_TYPE_UNKNOWN: 081 return "Unknown"; 082 case TiffDirectoryConstants.DIRECTORY_TYPE_ROOT: 083 return "Root"; 084 case TiffDirectoryConstants.DIRECTORY_TYPE_SUB: 085 return "Sub"; 086 case TiffDirectoryConstants.DIRECTORY_TYPE_THUMBNAIL: 087 return "Thumbnail"; 088 case TiffDirectoryConstants.DIRECTORY_TYPE_EXIF: 089 return "Exif"; 090 case TiffDirectoryConstants.DIRECTORY_TYPE_GPS: 091 return "Gps"; 092 case TiffDirectoryConstants.DIRECTORY_TYPE_INTEROPERABILITY: 093 return "Interoperability"; 094 default: 095 return "Bad Type"; 096 } 097 } 098 099 private final List<TiffField> entries; 100 101 /** 102 * Preserves the byte order derived from the TIFF file header. Some of the legacy methods in this class require byte order as an argument, though that use 103 * could be phased out eventually. 104 */ 105 private final ByteOrder headerByteOrder; 106 107 private JpegImageData jpegImageData; 108 109 private final long nextDirectoryOffset; 110 111 private AbstractTiffImageData abstractTiffImageData; 112 113 public final int type; 114 115 public TiffDirectory(final int type, final List<TiffField> entries, final long offset, final long nextDirectoryOffset, final ByteOrder byteOrder) { 116 super(offset, 117 TiffConstants.DIRECTORY_HEADER_LENGTH + entries.size() * TiffConstants.ENTRY_LENGTH + TiffConstants.DIRECTORY_FOOTER_LENGTH); 118 119 this.type = type; 120 this.entries = Collections.unmodifiableList(entries); 121 this.nextDirectoryOffset = nextDirectoryOffset; 122 this.headerByteOrder = byteOrder; 123 } 124 125 public String description() { 126 return description(type); 127 } 128 129 public void dump() { 130 entries.forEach(TiffField::dump); 131 } 132 133 public TiffField findField(final TagInfo tag) throws ImagingException { 134 final boolean failIfMissing = false; 135 return findField(tag, failIfMissing); 136 } 137 138 public TiffField findField(final TagInfo tag, final boolean failIfMissing) throws ImagingException { 139 for (final TiffField field : entries) { 140 if (field.getTag() == tag.tag) { 141 return field; 142 } 143 } 144 145 if (failIfMissing) { 146 throw new ImagingException("Missing expected field: " + tag.getDescription()); 147 } 148 149 return null; 150 } 151 152 /** 153 * Gets the byte order used by the source file for storing this directory and its content. 154 * 155 * @return A valid byte order instance. 156 */ 157 public ByteOrder getByteOrder() { 158 return headerByteOrder; 159 } 160 161 public List<TiffField> getDirectoryEntries() { 162 return new ArrayList<>(entries); 163 } 164 165 @Override 166 public String getElementDescription() { 167 long entryOffset = offset + TiffConstants.DIRECTORY_HEADER_LENGTH; 168 169 final StringBuilder result = new StringBuilder(); 170 for (final TiffField entry : entries) { 171 result.append(String.format("\t[%d]: %s (%d, 0x%x), %s, %d: %s%n", entryOffset, entry.getTagInfo().name, entry.getTag(), entry.getTag(), 172 entry.getFieldType().getName(), entry.getBytesLength(), entry.getValueDescription())); 173 174 entryOffset += TiffConstants.ENTRY_LENGTH; 175 } 176 return result.toString(); 177 } 178 179 public Object getFieldValue(final TagInfo tag) throws ImagingException { 180 final TiffField field = findField(tag); 181 if (field == null) { 182 return null; 183 } 184 return field.getValue(); 185 } 186 187 public String[] getFieldValue(final TagInfoAscii tag, final boolean mustExist) throws ImagingException { 188 final TiffField field = findField(tag); 189 if (field == null) { 190 if (mustExist) { 191 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 192 } 193 return null; 194 } 195 if (!tag.dataTypes.contains(field.getFieldType())) { 196 if (mustExist) { 197 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 198 } 199 return null; 200 } 201 final byte[] bytes = field.getByteArrayValue(); 202 return tag.getValue(field.getByteOrder(), bytes); 203 } 204 205 public byte getFieldValue(final TagInfoByte tag) throws ImagingException { 206 final TiffField field = findField(tag); 207 if (field == null) { 208 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 209 } 210 if (!tag.dataTypes.contains(field.getFieldType())) { 211 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 212 } 213 if (field.getCount() != 1) { 214 throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount()); 215 } 216 return field.getByteArrayValue()[0]; 217 } 218 219 public byte[] getFieldValue(final TagInfoBytes tag, final boolean mustExist) throws ImagingException { 220 final TiffField field = findField(tag); 221 if (field == null) { 222 if (mustExist) { 223 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 224 } 225 return null; 226 } 227 if (!tag.dataTypes.contains(field.getFieldType())) { 228 if (mustExist) { 229 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 230 } 231 return null; 232 } 233 return field.getByteArrayValue(); 234 } 235 236 public double getFieldValue(final TagInfoDouble tag) throws ImagingException { 237 final TiffField field = findField(tag); 238 if (field == null) { 239 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 240 } 241 if (!tag.dataTypes.contains(field.getFieldType())) { 242 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 243 } 244 if (field.getCount() != 1) { 245 throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount()); 246 } 247 final byte[] bytes = field.getByteArrayValue(); 248 return tag.getValue(field.getByteOrder(), bytes); 249 } 250 251 public double[] getFieldValue(final TagInfoDoubles tag, final boolean mustExist) throws ImagingException { 252 final TiffField field = findField(tag); 253 if (field == null) { 254 if (mustExist) { 255 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 256 } 257 return null; 258 } 259 if (!tag.dataTypes.contains(field.getFieldType())) { 260 if (mustExist) { 261 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 262 } 263 return null; 264 } 265 final byte[] bytes = field.getByteArrayValue(); 266 return tag.getValue(field.getByteOrder(), bytes); 267 } 268 269 public float getFieldValue(final TagInfoFloat tag) throws ImagingException { 270 final TiffField field = findField(tag); 271 if (field == null) { 272 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 273 } 274 if (!tag.dataTypes.contains(field.getFieldType())) { 275 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 276 } 277 if (field.getCount() != 1) { 278 throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount()); 279 } 280 final byte[] bytes = field.getByteArrayValue(); 281 return tag.getValue(field.getByteOrder(), bytes); 282 } 283 284 public float[] getFieldValue(final TagInfoFloats tag, final boolean mustExist) throws ImagingException { 285 final TiffField field = findField(tag); 286 if (field == null) { 287 if (mustExist) { 288 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 289 } 290 return null; 291 } 292 if (!tag.dataTypes.contains(field.getFieldType())) { 293 if (mustExist) { 294 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 295 } 296 return null; 297 } 298 final byte[] bytes = field.getByteArrayValue(); 299 return tag.getValue(field.getByteOrder(), bytes); 300 } 301 302 public String getFieldValue(final TagInfoGpsText tag, final boolean mustExist) throws ImagingException { 303 final TiffField field = findField(tag); 304 if (field == null) { 305 if (mustExist) { 306 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 307 } 308 return null; 309 } 310 return tag.getValue(field); 311 } 312 313 public int getFieldValue(final TagInfoLong tag) throws ImagingException { 314 final TiffField field = findField(tag); 315 if (field == null) { 316 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 317 } 318 if (!tag.dataTypes.contains(field.getFieldType())) { 319 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 320 } 321 if (field.getCount() != 1) { 322 throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount()); 323 } 324 final byte[] bytes = field.getByteArrayValue(); 325 return tag.getValue(field.getByteOrder(), bytes); 326 } 327 328 public int[] getFieldValue(final TagInfoLongs tag, final boolean mustExist) throws ImagingException { 329 final TiffField field = findField(tag); 330 if (field == null) { 331 if (mustExist) { 332 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 333 } 334 return null; 335 } 336 if (!tag.dataTypes.contains(field.getFieldType())) { 337 if (mustExist) { 338 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 339 } 340 return null; 341 } 342 final byte[] bytes = field.getByteArrayValue(); 343 return tag.getValue(field.getByteOrder(), bytes); 344 } 345 346 public RationalNumber getFieldValue(final TagInfoRational tag) throws ImagingException { 347 final TiffField field = findField(tag); 348 if (field == null) { 349 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 350 } 351 if (!tag.dataTypes.contains(field.getFieldType())) { 352 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 353 } 354 if (field.getCount() != 1) { 355 throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount()); 356 } 357 final byte[] bytes = field.getByteArrayValue(); 358 return tag.getValue(field.getByteOrder(), bytes); 359 } 360 361 public RationalNumber[] getFieldValue(final TagInfoRationals tag, final boolean mustExist) throws ImagingException { 362 final TiffField field = findField(tag); 363 if (field == null) { 364 if (mustExist) { 365 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 366 } 367 return null; 368 } 369 if (!tag.dataTypes.contains(field.getFieldType())) { 370 if (mustExist) { 371 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 372 } 373 return null; 374 } 375 final byte[] bytes = field.getByteArrayValue(); 376 return tag.getValue(field.getByteOrder(), bytes); 377 } 378 379 public byte getFieldValue(final TagInfoSByte tag) throws ImagingException { 380 final TiffField field = findField(tag); 381 if (field == null) { 382 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 383 } 384 if (!tag.dataTypes.contains(field.getFieldType())) { 385 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 386 } 387 if (field.getCount() != 1) { 388 throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount()); 389 } 390 return field.getByteArrayValue()[0]; 391 } 392 393 public byte[] getFieldValue(final TagInfoSBytes tag, final boolean mustExist) throws ImagingException { 394 final TiffField field = findField(tag); 395 if (field == null) { 396 if (mustExist) { 397 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 398 } 399 return null; 400 } 401 if (!tag.dataTypes.contains(field.getFieldType())) { 402 if (mustExist) { 403 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 404 } 405 return null; 406 } 407 return field.getByteArrayValue(); 408 } 409 410 public short getFieldValue(final TagInfoShort tag) throws ImagingException { 411 final TiffField field = findField(tag); 412 if (field == null) { 413 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 414 } 415 if (!tag.dataTypes.contains(field.getFieldType())) { 416 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 417 } 418 if (field.getCount() != 1) { 419 throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount()); 420 } 421 final byte[] bytes = field.getByteArrayValue(); 422 return tag.getValue(field.getByteOrder(), bytes); 423 } 424 425 public int[] getFieldValue(final TagInfoShortOrLong tag, final boolean mustExist) throws ImagingException { 426 final TiffField field = findField(tag); 427 if (field == null) { 428 if (mustExist) { 429 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 430 } 431 return null; 432 } 433 if (!tag.dataTypes.contains(field.getFieldType())) { 434 if (mustExist) { 435 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 436 } 437 return null; 438 } 439 final byte[] bytes = field.getByteArrayValue(); 440 if (field.getFieldType() == AbstractFieldType.SHORT) { 441 return ByteConversions.toUInt16s(bytes, field.getByteOrder()); 442 } 443 return ByteConversions.toInts(bytes, field.getByteOrder()); 444 } 445 446 public short[] getFieldValue(final TagInfoShorts tag, final boolean mustExist) throws ImagingException { 447 final TiffField field = findField(tag); 448 if (field == null) { 449 if (mustExist) { 450 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 451 } 452 return null; 453 } 454 if (!tag.dataTypes.contains(field.getFieldType())) { 455 if (mustExist) { 456 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 457 } 458 return null; 459 } 460 final byte[] bytes = field.getByteArrayValue(); 461 return tag.getValue(field.getByteOrder(), bytes); 462 } 463 464 public int getFieldValue(final TagInfoSLong tag) throws ImagingException { 465 final TiffField field = findField(tag); 466 if (field == null) { 467 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 468 } 469 if (!tag.dataTypes.contains(field.getFieldType())) { 470 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 471 } 472 if (field.getCount() != 1) { 473 throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount()); 474 } 475 final byte[] bytes = field.getByteArrayValue(); 476 return tag.getValue(field.getByteOrder(), bytes); 477 } 478 479 public int[] getFieldValue(final TagInfoSLongs tag, final boolean mustExist) throws ImagingException { 480 final TiffField field = findField(tag); 481 if (field == null) { 482 if (mustExist) { 483 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 484 } 485 return null; 486 } 487 if (!tag.dataTypes.contains(field.getFieldType())) { 488 if (mustExist) { 489 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 490 } 491 return null; 492 } 493 final byte[] bytes = field.getByteArrayValue(); 494 return tag.getValue(field.getByteOrder(), bytes); 495 } 496 497 public RationalNumber getFieldValue(final TagInfoSRational tag) throws ImagingException { 498 final TiffField field = findField(tag); 499 if (field == null) { 500 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 501 } 502 if (!tag.dataTypes.contains(field.getFieldType())) { 503 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 504 } 505 if (field.getCount() != 1) { 506 throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount()); 507 } 508 final byte[] bytes = field.getByteArrayValue(); 509 return tag.getValue(field.getByteOrder(), bytes); 510 } 511 512 public RationalNumber[] getFieldValue(final TagInfoSRationals tag, final boolean mustExist) throws ImagingException { 513 final TiffField field = findField(tag); 514 if (field == null) { 515 if (mustExist) { 516 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 517 } 518 return null; 519 } 520 if (!tag.dataTypes.contains(field.getFieldType())) { 521 if (mustExist) { 522 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 523 } 524 return null; 525 } 526 final byte[] bytes = field.getByteArrayValue(); 527 return tag.getValue(field.getByteOrder(), bytes); 528 } 529 530 public short getFieldValue(final TagInfoSShort tag) throws ImagingException { 531 final TiffField field = findField(tag); 532 if (field == null) { 533 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 534 } 535 if (!tag.dataTypes.contains(field.getFieldType())) { 536 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 537 } 538 if (field.getCount() != 1) { 539 throw new ImagingException("Field \"" + tag.name + "\" has wrong count " + field.getCount()); 540 } 541 final byte[] bytes = field.getByteArrayValue(); 542 return tag.getValue(field.getByteOrder(), bytes); 543 } 544 545 public short[] getFieldValue(final TagInfoSShorts tag, final boolean mustExist) throws ImagingException { 546 final TiffField field = findField(tag); 547 if (field == null) { 548 if (mustExist) { 549 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 550 } 551 return null; 552 } 553 if (!tag.dataTypes.contains(field.getFieldType())) { 554 if (mustExist) { 555 throw new ImagingException("Required field \"" + tag.name + "\" has incorrect type " + field.getFieldType().getName()); 556 } 557 return null; 558 } 559 final byte[] bytes = field.getByteArrayValue(); 560 return tag.getValue(field.getByteOrder(), bytes); 561 } 562 563 public String getFieldValue(final TagInfoXpString tag, final boolean mustExist) throws ImagingException { 564 final TiffField field = findField(tag); 565 if (field == null) { 566 if (mustExist) { 567 throw new ImagingException("Required field \"" + tag.name + "\" is missing"); 568 } 569 return null; 570 } 571 return tag.getValue(field); 572 } 573 574 public JpegImageData getJpegImageData() { 575 return jpegImageData; 576 } 577 578 public ImageDataElement getJpegRawImageDataElement() throws ImagingException { 579 final TiffField jpegInterchangeFormat = findField(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT); 580 final TiffField jpegInterchangeFormatLength = findField(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); 581 582 if (jpegInterchangeFormat != null && jpegInterchangeFormatLength != null) { 583 final int offSet = jpegInterchangeFormat.getIntArrayValue()[0]; 584 final int byteCount = jpegInterchangeFormatLength.getIntArrayValue()[0]; 585 586 return new ImageDataElement(offSet, byteCount); 587 } 588 throw new ImagingException("Couldn't find image data."); 589 } 590 591 public long getNextDirectoryOffset() { 592 return nextDirectoryOffset; 593 } 594 595 /** 596 * Reads the numerical data stored in this TIFF directory, if available. Note that this method is defined only for TIFF directories that contain 597 * floating-point data or two-byte signed integer data. 598 * <p> 599 * TIFF directories that provide numerical data do not directly specify images, though it is possible to interpret the data as an image using this library. 600 * TIFF files may contain multiple directories which are allowed to have different formats. Thus it is possible for a TIFF file to contain a mix of image 601 * and floating-point raster data. 602 * <p> 603 * If desired, sub-image data can be read from the file by using a Java Map instance to specify the subsection of the image that is required. The following 604 * code illustrates the approach: 605 * 606 * <pre> 607 * int x; // coordinate (column) of corner of sub-image 608 * int y; // coordinate (row) of corner of sub-image 609 * int width; // width of sub-image 610 * int height; // height of sub-image 611 * 612 * Map<String, Object> params = new HashMap<>(); 613 * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_X, x); 614 * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_Y, y); 615 * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_WIDTH, width); 616 * params.put(TiffConstants.PARAM_KEY_SUBIMAGE_HEIGHT, height); 617 * TiffRasterData raster = directory.readFloatingPointRasterData(params); 618 * </pre> 619 * 620 * @param params an optional parameter map instance 621 * @return a valid instance 622 * @throws ImagingException in the event of incompatible or malformed data 623 * @throws IOException in the event of an I/O error 624 */ 625 public AbstractTiffRasterData getRasterData(final TiffImagingParameters params) throws ImagingException, IOException { 626 627 final TiffImageParser parser = new TiffImageParser(); 628 return parser.getRasterData(this, headerByteOrder, params); 629 } 630 631 private List<ImageDataElement> getRawImageDataElements(final TiffField offsetsField, final TiffField byteCountsField) throws ImagingException { 632 final long[] offsets = offsetsField.getLongArrayValue(); 633 final int[] byteCounts = byteCountsField.getIntArrayValue(); 634 635 if (offsets.length != byteCounts.length) { 636 throw new ImagingException("offsets.length(" + offsets.length + ") != byteCounts.length(" + byteCounts.length + ")"); 637 } 638 639 final List<ImageDataElement> result = Allocator.arrayList(offsets.length); 640 for (int i = 0; i < offsets.length; i++) { 641 result.add(new ImageDataElement(offsets[i], byteCounts[i])); 642 } 643 return result; 644 } 645 646 public String getSingleFieldValue(final TagInfoAscii tag) throws ImagingException { 647 final String[] result = getFieldValue(tag, true); 648 if (result.length != 1) { 649 throw new ImagingException("Field \"" + tag.name + "\" has incorrect length " + result.length); 650 } 651 return result[0]; 652 } 653 654 public int getSingleFieldValue(final TagInfoShortOrLong tag) throws ImagingException { 655 final int[] result = getFieldValue(tag, true); 656 if (result.length != 1) { 657 throw new ImagingException("Field \"" + tag.name + "\" has incorrect length " + result.length); 658 } 659 return result[0]; 660 } 661 662 /** 663 * Gets the image associated with the directory, if any. Note that not all directories contain images. 664 * 665 * @return if successful, a valid BufferedImage instance. 666 * @throws ImagingException in the event of an invalid or incompatible data format. 667 * @throws IOException in the event of an I/O error. 668 */ 669 public BufferedImage getTiffImage() throws ImagingException, IOException { 670 if (null == abstractTiffImageData) { 671 return null; 672 } 673 674 return new TiffImageParser().getBufferedImage(this, headerByteOrder, null); 675 } 676 677 /** 678 * Gets the image associated with the directory, if any. Note that not all directories contain images. 679 * <p> 680 * This method comes from an older version of this class in which byte order was required from an external source. Developers are encouraged to use the 681 * simpler version of getTiffImage that does not require the byte-order argument. 682 * 683 * @param byteOrder byte-order obtained from the containing TIFF file 684 * @return if successful, a valid BufferedImage instance. 685 * @throws ImagingException in the event of an invalid or incompatible data format. 686 * @throws IOException in the event of an I/O error. 687 */ 688 public BufferedImage getTiffImage(final ByteOrder byteOrder) throws ImagingException, IOException { 689 return getTiffImage(byteOrder, new TiffImagingParameters()); 690 } 691 692 /** 693 * Gets the image associated with the directory, if any. Note that not all directories contain images. 694 * <p> 695 * This method comes from an older version of this class in which byte order was required from an external source. Developers are encouraged to use the 696 * simpler version of getTiffImage that does not require the byte-order argument. 697 * 698 * @param byteOrder byte-order obtained from the containing TIFF file 699 * @param params an object containing optional parameters to be applied to the read operation. 700 * @return if successful, a valid BufferedImage instance. 701 * @throws ImagingException in the event of an invalid or incompatible data format. 702 * @throws IOException in the event of an I/O error. 703 */ 704 public BufferedImage getTiffImage(final ByteOrder byteOrder, final TiffImagingParameters params) throws ImagingException, IOException { 705 if (null == abstractTiffImageData) { 706 return null; 707 } 708 709 return new TiffImageParser().getBufferedImage(this, byteOrder, params); 710 } 711 712 /** 713 * Gets the image associated with the directory, if any. Note that not all directories contain images. 714 * <p> 715 * The optional parameters object can be used to specify image access or rendering options such as reading only a part of the overall image (i.e. reading a 716 * sub-image) or applying a custom photometric interpreter. 717 * 718 * @param params an object containing optional parameters to be applied to the read operation. 719 * @return if successful, a valid BufferedImage instance. 720 * @throws ImagingException in the event of an invalid or incompatible data format. 721 * @throws IOException in the event of an I/O error. 722 */ 723 public BufferedImage getTiffImage(final TiffImagingParameters params) throws ImagingException, IOException { 724 if (null == abstractTiffImageData) { 725 return null; 726 } 727 728 return new TiffImageParser().getBufferedImage(this, headerByteOrder, params); 729 } 730 731 public AbstractTiffImageData getTiffImageData() { 732 return abstractTiffImageData; 733 } 734 735 public List<ImageDataElement> getTiffRawImageDataElements() throws ImagingException { 736 final TiffField tileOffsets = findField(TiffTagConstants.TIFF_TAG_TILE_OFFSETS); 737 final TiffField tileByteCounts = findField(TiffTagConstants.TIFF_TAG_TILE_BYTE_COUNTS); 738 final TiffField stripOffsets = findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS); 739 final TiffField stripByteCounts = findField(TiffTagConstants.TIFF_TAG_STRIP_BYTE_COUNTS); 740 741 if (tileOffsets != null && tileByteCounts != null) { 742 return getRawImageDataElements(tileOffsets, tileByteCounts); 743 } 744 if (stripOffsets != null && stripByteCounts != null) { 745 return getRawImageDataElements(stripOffsets, stripByteCounts); 746 } 747 throw new ImagingException("Couldn't find image data."); 748 } 749 750 public boolean hasJpegImageData() throws ImagingException { 751 return null != findField(TiffTagConstants.TIFF_TAG_JPEG_INTERCHANGE_FORMAT); 752 } 753 754 /** 755 * Indicates whether the directory definition specifies a float-point data format. 756 * 757 * @return {@code true} if the directory contains floating point data; otherwise, {@code false} 758 * @throws ImagingException in the event of an invalid or malformed specification. 759 */ 760 public boolean hasTiffFloatingPointRasterData() throws ImagingException { 761 if (!hasTiffImageData()) { 762 return false; 763 } 764 final short[] s = getFieldValue(TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, false); 765 return s != null && s.length > 0 && s[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT; 766 767 } 768 769 public boolean hasTiffImageData() throws ImagingException { 770 if (null != findField(TiffTagConstants.TIFF_TAG_TILE_OFFSETS)) { 771 return true; 772 } 773 774 return null != findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS); 775 } 776 777 /** 778 * Indicates whether the content associated with the directory is given in a supported numerical-data format. If this method returns {@code true}, the 779 * Imaging API will be able to extract a TiffRasterData instance from the associated TIFF file using this directory. 780 * 781 * @return {@code true} if the directory contains a supported raster data format; otherwise, {@code false}. 782 * @throws ImagingException in the event of an invalid or malformed specification. 783 */ 784 public boolean hasTiffRasterData() throws ImagingException { 785 if (!hasTiffImageData()) { 786 return false; 787 } 788 final short[] s = getFieldValue(TiffTagConstants.TIFF_TAG_SAMPLE_FORMAT, false); 789 return s != null && s.length > 0 && (s[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_IEEE_FLOATING_POINT 790 || s[0] == TiffTagConstants.SAMPLE_FORMAT_VALUE_TWOS_COMPLEMENT_SIGNED_INTEGER); 791 } 792 793 public boolean imageDataInStrips() throws ImagingException { 794 final TiffField tileOffsets = findField(TiffTagConstants.TIFF_TAG_TILE_OFFSETS); 795 final TiffField tileByteCounts = findField(TiffTagConstants.TIFF_TAG_TILE_BYTE_COUNTS); 796 final TiffField stripOffsets = findField(TiffTagConstants.TIFF_TAG_STRIP_OFFSETS); 797 final TiffField stripByteCounts = findField(TiffTagConstants.TIFF_TAG_STRIP_BYTE_COUNTS); 798 799 if (tileOffsets != null && tileByteCounts != null) { 800 return false; 801 } 802 if (stripOffsets != null && stripByteCounts != null) { 803 return true; 804 } 805 throw new ImagingException("Couldn't find image data."); 806 } 807 808 @Override 809 public Iterator<TiffField> iterator() { 810 return entries.iterator(); 811 } 812 813 public void setJpegImageData(final JpegImageData value) { 814 this.jpegImageData = value; 815 } 816 817 public void setTiffImageData(final AbstractTiffImageData rawImageData) { 818 this.abstractTiffImageData = rawImageData; 819 } 820 821 public int size() { 822 return entries.size(); 823 } 824}