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