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