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.List; 024 025import org.apache.commons.imaging.ImageReadException; 026import org.apache.commons.imaging.ImageWriteException; 027import org.apache.commons.imaging.common.GenericImageMetadata; 028import org.apache.commons.imaging.common.RationalNumber; 029import org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants; 030import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryConstants; 031import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryType; 032import org.apache.commons.imaging.formats.tiff.fieldtypes.FieldType; 033import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo; 034import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoAscii; 035import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoByte; 036import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoDoubles; 037import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoFloats; 038import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoGpsText; 039import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoLongs; 040import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoRationals; 041import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSBytes; 042import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSLongs; 043import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSRationals; 044import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSShorts; 045import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShorts; 046import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoXpString; 047import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory; 048import org.apache.commons.imaging.formats.tiff.write.TiffOutputField; 049import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet; 050 051public class TiffImageMetadata extends GenericImageMetadata { 052 public final TiffContents contents; 053 054 public TiffImageMetadata(final TiffContents contents) { 055 this.contents = contents; 056 } 057 058 public static class Directory extends GenericImageMetadata implements 059 ImageMetadataItem { 060 // private BufferedImage thumbnail = null; 061 062 public final int type; 063 064 private final TiffDirectory directory; 065 private final ByteOrder byteOrder; 066 067 public Directory(final ByteOrder byteOrder, final TiffDirectory directory) { 068 this.type = directory.type; 069 this.directory = directory; 070 this.byteOrder = byteOrder; 071 } 072 073 public void add(final TiffField entry) { 074 add(new TiffMetadataItem(entry)); 075 } 076 077 public BufferedImage getThumbnail() throws ImageReadException, 078 IOException { 079 return directory.getTiffImage(byteOrder); 080 } 081 082 public TiffImageData getTiffImageData() { 083 return directory.getTiffImageData(); 084 } 085 086 public TiffField findField(final TagInfo tagInfo) throws ImageReadException { 087 return directory.findField(tagInfo); 088 } 089 090 public List<TiffField> getAllFields() { 091 return directory.getDirectoryEntries(); 092 } 093 094 public JpegImageData getJpegImageData() { 095 return directory.getJpegImageData(); 096 } 097 098 @Override 099 public String toString(final String prefix) { 100 return (prefix != null ? prefix : "") + directory.description() 101 + ": " // 102 + (getTiffImageData() != null ? " (tiffImageData)" : "") // 103 + (getJpegImageData() != null ? " (jpegImageData)" : "") // 104 + "\n" + super.toString(prefix) + "\n"; 105 } 106 107 public TiffOutputDirectory getOutputDirectory(final ByteOrder byteOrder) 108 throws ImageWriteException { 109 try { 110 final TiffOutputDirectory dstDir = new TiffOutputDirectory(type, 111 byteOrder); 112 113 final List<? extends ImageMetadataItem> entries = getItems(); 114 for (final ImageMetadataItem entry : entries) { 115 final TiffMetadataItem item = (TiffMetadataItem) entry; 116 final TiffField srcField = item.getTiffField(); 117 118 if (null != dstDir.findField(srcField.getTag())) { 119 // ignore duplicate tags in a directory. 120 continue; 121 } 122 if (srcField.getTagInfo().isOffset()) { 123 // ignore offset fields. 124 continue; 125 } 126 127 final TagInfo tagInfo = srcField.getTagInfo(); 128 final FieldType fieldType = srcField.getFieldType(); 129 // byte bytes[] = srcField.fieldType.getRawBytes(srcField); 130 131 // Debug.debug("tagInfo", tagInfo); 132 133 final Object value = srcField.getValue(); 134 135 // Debug.debug("value", Debug.getType(value)); 136 137 final byte[] bytes = tagInfo.encodeValue(fieldType, value, 138 byteOrder); 139 140 // if (tagInfo.isUnknown()) 141 // Debug.debug( 142 // "\t" + "unknown tag(0x" 143 // + Integer.toHexString(srcField.tag) 144 // + ") bytes", bytes); 145 146 final int count = bytes.length / fieldType.getSize(); 147 final TiffOutputField dstField = new TiffOutputField( 148 srcField.getTag(), tagInfo, fieldType, count, bytes); 149 dstField.setSortHint(srcField.getSortHint()); 150 dstDir.add(dstField); 151 } 152 153 dstDir.setTiffImageData(getTiffImageData()); 154 dstDir.setJpegImageData(getJpegImageData()); 155 156 return dstDir; 157 } catch (final ImageReadException e) { 158 throw new ImageWriteException(e.getMessage(), e); 159 } 160 } 161 162 } 163 164 public List<? extends ImageMetadataItem> getDirectories() { 165 return super.getItems(); 166 } 167 168 @Override 169 public List<? extends ImageMetadataItem> getItems() { 170 final List<ImageMetadataItem> result = new ArrayList<>(); 171 172 final List<? extends ImageMetadataItem> items = super.getItems(); 173 for (final ImageMetadataItem item : items) { 174 final Directory dir = (Directory) item; 175 result.addAll(dir.getItems()); 176 } 177 178 return result; 179 } 180 181 public static class TiffMetadataItem extends GenericImageMetadataItem { 182 private final TiffField entry; 183 184 public TiffMetadataItem(final TiffField entry) { 185 // super(entry.getTagName() + " (" + entry.getFieldTypeName() + ")", 186 super(entry.getTagName(), entry.getValueDescription()); 187 this.entry = entry; 188 } 189 190 public TiffField getTiffField() { 191 return entry; 192 } 193 194 } 195 196 public TiffOutputSet getOutputSet() throws ImageWriteException { 197 final ByteOrder byteOrder = contents.header.byteOrder; 198 final TiffOutputSet result = new TiffOutputSet(byteOrder); 199 200 final List<? extends ImageMetadataItem> srcDirs = getDirectories(); 201 for (final ImageMetadataItem srcDir1 : srcDirs) { 202 final Directory srcDir = (Directory) srcDir1; 203 204 if (null != result.findDirectory(srcDir.type)) { 205 // Certain cameras right directories more than once. 206 // This is a bug. 207 // Ignore second directory of a given type. 208 continue; 209 } 210 211 final TiffOutputDirectory outputDirectory = srcDir.getOutputDirectory(byteOrder); 212 result.addDirectory(outputDirectory); 213 } 214 215 return result; 216 } 217 218 public TiffField findField(final TagInfo tagInfo) throws ImageReadException { 219 return findField(tagInfo, false); 220 } 221 222 public TiffField findField(final TagInfo tagInfo, final boolean exactDirectoryMatch) 223 throws ImageReadException { 224 // Please keep this method in sync with TiffField's getTag() 225 final Integer tagCount = TiffTags.getTagCount(tagInfo.tag); 226 final int tagsMatching = tagCount == null ? 0 : tagCount; 227 228 final List<? extends ImageMetadataItem> directories = getDirectories(); 229 if (exactDirectoryMatch 230 || tagInfo.directoryType != TiffDirectoryType.EXIF_DIRECTORY_UNKNOWN) { 231 for (final ImageMetadataItem directory1 : directories) { 232 final Directory directory = (Directory) directory1; 233 if (directory.type == tagInfo.directoryType.directoryType) { 234 final TiffField field = directory.findField(tagInfo); 235 if (field != null) { 236 return field; 237 } 238 } 239 } 240 if (exactDirectoryMatch || tagsMatching > 1) { 241 return null; 242 } 243 for (final ImageMetadataItem directory1 : directories) { 244 final Directory directory = (Directory) directory1; 245 if (tagInfo.directoryType.isImageDirectory() 246 && directory.type >= 0) { 247 final TiffField field = directory.findField(tagInfo); 248 if (field != null) { 249 return field; 250 } 251 } else if (!tagInfo.directoryType.isImageDirectory() 252 && directory.type < 0) { 253 final TiffField field = directory.findField(tagInfo); 254 if (field != null) { 255 return field; 256 } 257 } 258 } 259 } 260 261 for (final ImageMetadataItem directory1 : directories) { 262 final Directory directory = (Directory) directory1; 263 final TiffField field = directory.findField(tagInfo); 264 if (field != null) { 265 return field; 266 } 267 } 268 269 return null; 270 } 271 272 public Object getFieldValue(final TagInfo tag) throws ImageReadException { 273 final TiffField field = findField(tag); 274 if (field == null) { 275 return null; 276 } 277 return field.getValue(); 278 } 279 280 public byte[] getFieldValue(final TagInfoByte tag) throws ImageReadException { 281 final TiffField field = findField(tag); 282 if (field == null) { 283 return null; 284 } 285 if (!tag.dataTypes.contains(field.getFieldType())) { 286 return null; 287 } 288 return field.getByteArrayValue(); 289 } 290 291 public String[] getFieldValue(final TagInfoAscii tag) throws ImageReadException { 292 final TiffField field = findField(tag); 293 if (field == null) { 294 return null; 295 } 296 if (!tag.dataTypes.contains(field.getFieldType())) { 297 return null; 298 } 299 final byte[] bytes = field.getByteArrayValue(); 300 return tag.getValue(field.getByteOrder(), bytes); 301 } 302 303 public short[] getFieldValue(final TagInfoShorts tag) throws ImageReadException { 304 final TiffField field = findField(tag); 305 if (field == null) { 306 return null; 307 } 308 if (!tag.dataTypes.contains(field.getFieldType())) { 309 return null; 310 } 311 final byte[] bytes = field.getByteArrayValue(); 312 return tag.getValue(field.getByteOrder(), bytes); 313 } 314 315 public int[] getFieldValue(final TagInfoLongs tag) throws ImageReadException { 316 final TiffField field = findField(tag); 317 if (field == null) { 318 return null; 319 } 320 if (!tag.dataTypes.contains(field.getFieldType())) { 321 return null; 322 } 323 final byte[] bytes = field.getByteArrayValue(); 324 return tag.getValue(field.getByteOrder(), bytes); 325 } 326 327 public RationalNumber[] getFieldValue(final TagInfoRationals tag) 328 throws ImageReadException { 329 final TiffField field = findField(tag); 330 if (field == null) { 331 return null; 332 } 333 if (!tag.dataTypes.contains(field.getFieldType())) { 334 return null; 335 } 336 final byte[] bytes = field.getByteArrayValue(); 337 return tag.getValue(field.getByteOrder(), bytes); 338 } 339 340 public byte[] getFieldValue(final TagInfoSBytes tag) throws ImageReadException { 341 final TiffField field = findField(tag); 342 if (field == null) { 343 return null; 344 } 345 if (!tag.dataTypes.contains(field.getFieldType())) { 346 return null; 347 } 348 return field.getByteArrayValue(); 349 } 350 351 public short[] getFieldValue(final TagInfoSShorts tag) throws ImageReadException { 352 final TiffField field = findField(tag); 353 if (field == null) { 354 return null; 355 } 356 if (!tag.dataTypes.contains(field.getFieldType())) { 357 return null; 358 } 359 final byte[] bytes = field.getByteArrayValue(); 360 return tag.getValue(field.getByteOrder(), bytes); 361 } 362 363 public int[] getFieldValue(final TagInfoSLongs tag) throws ImageReadException { 364 final TiffField field = findField(tag); 365 if (field == null) { 366 return null; 367 } 368 if (!tag.dataTypes.contains(field.getFieldType())) { 369 return null; 370 } 371 final byte[] bytes = field.getByteArrayValue(); 372 return tag.getValue(field.getByteOrder(), bytes); 373 } 374 375 public RationalNumber[] getFieldValue(final TagInfoSRationals tag) 376 throws ImageReadException { 377 final TiffField field = findField(tag); 378 if (field == null) { 379 return null; 380 } 381 if (!tag.dataTypes.contains(field.getFieldType())) { 382 return null; 383 } 384 final byte[] bytes = field.getByteArrayValue(); 385 return tag.getValue(field.getByteOrder(), bytes); 386 } 387 388 public float[] getFieldValue(final TagInfoFloats tag) throws ImageReadException { 389 final TiffField field = findField(tag); 390 if (field == null) { 391 return null; 392 } 393 if (!tag.dataTypes.contains(field.getFieldType())) { 394 return null; 395 } 396 final byte[] bytes = field.getByteArrayValue(); 397 return tag.getValue(field.getByteOrder(), bytes); 398 } 399 400 public double[] getFieldValue(final TagInfoDoubles tag) throws ImageReadException { 401 final TiffField field = findField(tag); 402 if (field == null) { 403 return null; 404 } 405 if (!tag.dataTypes.contains(field.getFieldType())) { 406 return null; 407 } 408 final byte[] bytes = field.getByteArrayValue(); 409 return tag.getValue(field.getByteOrder(), bytes); 410 } 411 412 public String getFieldValue(final TagInfoGpsText tag) throws ImageReadException { 413 final TiffField field = findField(tag); 414 if (field == null) { 415 return null; 416 } 417 return tag.getValue(field); 418 } 419 420 public String getFieldValue(final TagInfoXpString tag) throws ImageReadException { 421 final TiffField field = findField(tag); 422 if (field == null) { 423 return null; 424 } 425 return tag.getValue(field); 426 } 427 428 public TiffDirectory findDirectory(final int directoryType) { 429 final List<? extends ImageMetadataItem> directories = getDirectories(); 430 for (final ImageMetadataItem directory1 : directories) { 431 final Directory directory = (Directory) directory1; 432 if (directory.type == directoryType) { 433 return directory.directory; 434 } 435 } 436 return null; 437 } 438 439 public List<TiffField> getAllFields() { 440 final List<TiffField> result = new ArrayList<>(); 441 final List<? extends ImageMetadataItem> directories = getDirectories(); 442 for (final ImageMetadataItem directory1 : directories) { 443 final Directory directory = (Directory) directory1; 444 result.addAll(directory.getAllFields()); 445 } 446 return result; 447 } 448 449 public GPSInfo getGPS() throws ImageReadException { 450 final TiffDirectory gpsDirectory = findDirectory(TiffDirectoryConstants.DIRECTORY_TYPE_GPS); 451 if (null == gpsDirectory) { 452 return null; 453 } 454 455 // more specific example of how to access GPS values. 456 final TiffField latitudeRefField = gpsDirectory.findField(GpsTagConstants.GPS_TAG_GPS_LATITUDE_REF); 457 final TiffField latitudeField = gpsDirectory.findField(GpsTagConstants.GPS_TAG_GPS_LATITUDE); 458 final TiffField longitudeRefField = gpsDirectory.findField(GpsTagConstants.GPS_TAG_GPS_LONGITUDE_REF); 459 final TiffField longitudeField = gpsDirectory.findField(GpsTagConstants.GPS_TAG_GPS_LONGITUDE); 460 461 if (latitudeRefField == null || latitudeField == null 462 || longitudeRefField == null || longitudeField == null) { 463 return null; 464 } 465 466 // all of these values are strings. 467 final String latitudeRef = latitudeRefField.getStringValue(); 468 final RationalNumber[] latitude = (RationalNumber[]) latitudeField.getValue(); 469 final String longitudeRef = longitudeRefField.getStringValue(); 470 final RationalNumber[] longitude = (RationalNumber[]) longitudeField.getValue(); 471 472 if (latitude.length != 3 || longitude.length != 3) { 473 throw new ImageReadException("Expected three values for latitude and longitude."); 474 } 475 476 final RationalNumber latitudeDegrees = latitude[0]; 477 final RationalNumber latitudeMinutes = latitude[1]; 478 final RationalNumber latitudeSeconds = latitude[2]; 479 480 final RationalNumber longitudeDegrees = longitude[0]; 481 final RationalNumber longitudeMinutes = longitude[1]; 482 final RationalNumber longitudeSeconds = longitude[2]; 483 484 return new GPSInfo(latitudeRef, longitudeRef, latitudeDegrees, 485 latitudeMinutes, latitudeSeconds, longitudeDegrees, 486 longitudeMinutes, longitudeSeconds); 487 } 488 489 public static class GPSInfo { 490 public final String latitudeRef; 491 public final String longitudeRef; 492 493 public final RationalNumber latitudeDegrees; 494 public final RationalNumber latitudeMinutes; 495 public final RationalNumber latitudeSeconds; 496 public final RationalNumber longitudeDegrees; 497 public final RationalNumber longitudeMinutes; 498 public final RationalNumber longitudeSeconds; 499 500 public GPSInfo(final String latitudeRef, final String longitudeRef, 501 final RationalNumber latitudeDegrees, 502 final RationalNumber latitudeMinutes, 503 final RationalNumber latitudeSeconds, 504 final RationalNumber longitudeDegrees, 505 final RationalNumber longitudeMinutes, 506 final RationalNumber longitudeSeconds) { 507 this.latitudeRef = latitudeRef; 508 this.longitudeRef = longitudeRef; 509 this.latitudeDegrees = latitudeDegrees; 510 this.latitudeMinutes = latitudeMinutes; 511 this.latitudeSeconds = latitudeSeconds; 512 this.longitudeDegrees = longitudeDegrees; 513 this.longitudeMinutes = longitudeMinutes; 514 this.longitudeSeconds = longitudeSeconds; 515 } 516 517 @Override 518 public String toString() { 519 // This will format the gps info like so: 520 // 521 // latitude: 8 degrees, 40 minutes, 42.2 seconds S 522 // longitude: 115 degrees, 26 minutes, 21.8 seconds E 523 524 return "[GPS. Latitude: " + 525 latitudeDegrees.toDisplayString() + 526 " degrees, " + 527 latitudeMinutes.toDisplayString() + 528 " minutes, " + 529 latitudeSeconds.toDisplayString() + 530 " seconds " + 531 latitudeRef + 532 ", Longitude: " + 533 longitudeDegrees.toDisplayString() + 534 " degrees, " + 535 longitudeMinutes.toDisplayString() + 536 " minutes, " + 537 longitudeSeconds.toDisplayString() + 538 " seconds " + 539 longitudeRef + 540 ']'; 541 } 542 543 public double getLongitudeAsDegreesEast() throws ImageReadException { 544 final double result = longitudeDegrees.doubleValue() 545 + (longitudeMinutes.doubleValue() / 60.0) 546 + (longitudeSeconds.doubleValue() / 3600.0); 547 548 if (longitudeRef.trim().equalsIgnoreCase("e")) { 549 return result; 550 } 551 if (longitudeRef.trim().equalsIgnoreCase("w")) { 552 return -result; 553 } 554 throw new ImageReadException("Unknown longitude ref: \"" 555 + longitudeRef + "\""); 556 } 557 558 public double getLatitudeAsDegreesNorth() throws ImageReadException { 559 final double result = latitudeDegrees.doubleValue() 560 + (latitudeMinutes.doubleValue() / 60.0) 561 + (latitudeSeconds.doubleValue() / 3600.0); 562 563 if (latitudeRef.trim().equalsIgnoreCase("n")) { 564 return result; 565 } 566 if (latitudeRef.trim().equalsIgnoreCase("s")) { 567 return -result; 568 } 569 throw new ImageReadException("Unknown latitude ref: \"" 570 + latitudeRef + "\""); 571 } 572 573 } 574 575}