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