View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.imaging.formats.tiff;
18  
19  import java.awt.image.BufferedImage;
20  import java.io.IOException;
21  import java.nio.ByteOrder;
22  import java.util.ArrayList;
23  import java.util.List;
24  
25  import org.apache.commons.imaging.ImagingException;
26  import org.apache.commons.imaging.common.GenericImageMetadata;
27  import org.apache.commons.imaging.common.RationalNumber;
28  import org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants;
29  import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryConstants;
30  import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryType;
31  import org.apache.commons.imaging.formats.tiff.fieldtypes.AbstractFieldType;
32  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
33  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoAscii;
34  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoByte;
35  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoDoubles;
36  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoFloats;
37  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoGpsText;
38  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoLongs;
39  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoRationals;
40  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSBytes;
41  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSLongs;
42  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSRationals;
43  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSShorts;
44  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShorts;
45  import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoXpString;
46  import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory;
47  import org.apache.commons.imaging.formats.tiff.write.TiffOutputField;
48  import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
49  
50  public class TiffImageMetadata extends GenericImageMetadata {
51      public static class Directory extends GenericImageMetadata implements ImageMetadataItem {
52          // private BufferedImage thumbnail;
53  
54          public final int type;
55  
56          private final TiffDirectory directory;
57          private final ByteOrder byteOrder;
58  
59          public Directory(final ByteOrder byteOrder, final TiffDirectory directory) {
60              this.type = directory.type;
61              this.directory = directory;
62              this.byteOrder = byteOrder;
63          }
64  
65          public void add(final TiffField entry) {
66              add(new TiffMetadataItem(entry));
67          }
68  
69          public TiffField findField(final TagInfo tagInfo) throws ImagingException {
70              return directory.findField(tagInfo);
71          }
72  
73          public List<TiffField> getAllFields() {
74              return directory.getDirectoryEntries();
75          }
76  
77          public JpegImageData getJpegImageData() {
78              return directory.getJpegImageData();
79          }
80  
81          public TiffOutputDirectory getOutputDirectory(final ByteOrder byteOrder) throws ImagingException {
82              try {
83                  final TiffOutputDirectory dstDir = new TiffOutputDirectory(type, byteOrder);
84  
85                  final List<? extends ImageMetadataItem> entries = getItems();
86                  for (final ImageMetadataItem entry : entries) {
87                      final TiffMetadataItem item = (TiffMetadataItem) entry;
88                      final TiffField srcField = item.getTiffField();
89  
90                      if (null != dstDir.findField(srcField.getTag())) {
91                          // ignore duplicate tags in a directory.
92                          continue;
93                      }
94                      if (srcField.getTagInfo().isOffset()) {
95                          // ignore offset fields.
96                          continue;
97                      }
98  
99                      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 }