Changeset 8132 in josm for trunk/src/com/drew/metadata/exif
- Timestamp:
- 2015-03-10T01:17:39+01:00 (9 years ago)
- Location:
- trunk/src/com/drew/metadata/exif
- Files:
-
- 38 added
- 29 deleted
- 11 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/com/drew/metadata/exif/ExifIFD0Descriptor.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 … … 29 29 import java.io.UnsupportedEncodingException; 30 30 31 import static com.drew.metadata.exif.ExifIFD0Directory.*; 32 31 33 /** 32 * Provides human-readable string representations of tag values stored in a <code>ExifIFD0Directory</code>.33 * 34 * @author Drew Noakes http ://drewnoakes.com34 * Provides human-readable string representations of tag values stored in a {@link ExifIFD0Directory}. 35 * 36 * @author Drew Noakes https://drewnoakes.com 35 37 */ 36 38 public class ExifIFD0Descriptor extends TagDescriptor<ExifIFD0Directory> … … 54 56 55 57 /** 56 * Returns a descriptive value of the thespecified tag for this image.58 * Returns a descriptive value of the specified tag for this image. 57 59 * Where possible, known values will be substituted here in place of the raw 58 60 * tokens actually kept in the Exif segment. If no substitution is … … 62 64 * <code>null</code> if the tag hasn't been defined. 63 65 */ 66 @Override 64 67 @Nullable 65 68 public String getDescription(int tagType) 66 69 { 67 70 switch (tagType) { 68 case ExifIFD0Directory.TAG_RESOLUTION_UNIT:71 case TAG_RESOLUTION_UNIT: 69 72 return getResolutionDescription(); 70 case ExifIFD0Directory.TAG_YCBCR_POSITIONING:73 case TAG_YCBCR_POSITIONING: 71 74 return getYCbCrPositioningDescription(); 72 case ExifIFD0Directory.TAG_X_RESOLUTION:75 case TAG_X_RESOLUTION: 73 76 return getXResolutionDescription(); 74 case ExifIFD0Directory.TAG_Y_RESOLUTION:77 case TAG_Y_RESOLUTION: 75 78 return getYResolutionDescription(); 76 case ExifIFD0Directory.TAG_REFERENCE_BLACK_WHITE:79 case TAG_REFERENCE_BLACK_WHITE: 77 80 return getReferenceBlackWhiteDescription(); 78 case ExifIFD0Directory.TAG_ORIENTATION:81 case TAG_ORIENTATION: 79 82 return getOrientationDescription(); 80 83 81 case ExifIFD0Directory.TAG_WIN_AUTHOR:84 case TAG_WIN_AUTHOR: 82 85 return getWindowsAuthorDescription(); 83 case ExifIFD0Directory.TAG_WIN_COMMENT:86 case TAG_WIN_COMMENT: 84 87 return getWindowsCommentDescription(); 85 case ExifIFD0Directory.TAG_WIN_KEYWORDS:88 case TAG_WIN_KEYWORDS: 86 89 return getWindowsKeywordsDescription(); 87 case ExifIFD0Directory.TAG_WIN_SUBJECT:90 case TAG_WIN_SUBJECT: 88 91 return getWindowsSubjectDescription(); 89 case ExifIFD0Directory.TAG_WIN_TITLE:92 case TAG_WIN_TITLE: 90 93 return getWindowsTitleDescription(); 91 94 … … 98 101 public String getReferenceBlackWhiteDescription() 99 102 { 100 int[] ints = _directory.getIntArray( ExifIFD0Directory.TAG_REFERENCE_BLACK_WHITE);101 if (ints==null )103 int[] ints = _directory.getIntArray(TAG_REFERENCE_BLACK_WHITE); 104 if (ints==null || ints.length < 6) 102 105 return null; 103 106 int blackR = ints[0]; … … 107 110 int blackB = ints[4]; 108 111 int whiteB = ints[5]; 109 return "[" + blackR + "," + blackG + "," + blackB + "] " + 110 "[" + whiteR + "," + whiteG + "," + whiteB + "]"; 112 return String.format("[%d,%d,%d] [%d,%d,%d]", blackR, blackG, blackB, whiteR, whiteG, whiteB); 111 113 } 112 114 … … 114 116 public String getYResolutionDescription() 115 117 { 116 Rational value = _directory.getRational( ExifIFD0Directory.TAG_Y_RESOLUTION);118 Rational value = _directory.getRational(TAG_Y_RESOLUTION); 117 119 if (value==null) 118 120 return null; 119 121 final String unit = getResolutionDescription(); 120 return value.toSimpleString(_allowDecimalRepresentationOfRationals) +121 " dots per " +122 (unit==null ? "unit" : unit.toLowerCase());122 return String.format("%s dots per %s", 123 value.toSimpleString(_allowDecimalRepresentationOfRationals), 124 unit == null ? "unit" : unit.toLowerCase()); 123 125 } 124 126 … … 126 128 public String getXResolutionDescription() 127 129 { 128 Rational value = _directory.getRational( ExifIFD0Directory.TAG_X_RESOLUTION);130 Rational value = _directory.getRational(TAG_X_RESOLUTION); 129 131 if (value==null) 130 132 return null; 131 133 final String unit = getResolutionDescription(); 132 return value.toSimpleString(_allowDecimalRepresentationOfRationals) +133 " dots per " +134 (unit==null ? "unit" : unit.toLowerCase());134 return String.format("%s dots per %s", 135 value.toSimpleString(_allowDecimalRepresentationOfRationals), 136 unit == null ? "unit" : unit.toLowerCase()); 135 137 } 136 138 … … 138 140 public String getYCbCrPositioningDescription() 139 141 { 140 Integer value = _directory.getInteger(ExifIFD0Directory.TAG_YCBCR_POSITIONING); 141 if (value==null) 142 return null; 143 switch (value) { 144 case 1: return "Center of pixel array"; 145 case 2: return "Datum point"; 146 default: 147 return String.valueOf(value); 142 return getIndexedDescription(TAG_YCBCR_POSITIONING, 1, "Center of pixel array", "Datum point"); 143 } 144 145 @Nullable 146 public String getOrientationDescription() 147 { 148 return getIndexedDescription(TAG_ORIENTATION, 1, 149 "Top, left side (Horizontal / normal)", 150 "Top, right side (Mirror horizontal)", 151 "Bottom, right side (Rotate 180)", 152 "Bottom, left side (Mirror vertical)", 153 "Left side, top (Mirror horizontal and rotate 270 CW)", 154 "Right side, top (Rotate 90 CW)", 155 "Right side, bottom (Mirror horizontal and rotate 90 CW)", 156 "Left side, bottom (Rotate 270 CW)"); 157 } 158 159 @Nullable 160 public String getResolutionDescription() 161 { 162 // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch) 163 return getIndexedDescription(TAG_RESOLUTION_UNIT, 1, "(No unit)", "Inch", "cm"); 164 } 165 166 /** The Windows specific tags uses plain Unicode. */ 167 @Nullable 168 private String getUnicodeDescription(int tag) 169 { 170 byte[] bytes = _directory.getByteArray(tag); 171 if (bytes == null) 172 return null; 173 try { 174 // Decode the unicode string and trim the unicode zero "\0" from the end. 175 return new String(bytes, "UTF-16LE").trim(); 176 } catch (UnsupportedEncodingException ex) { 177 return null; 148 178 } 149 179 } 150 180 151 181 @Nullable 152 public String getOrientationDescription()153 {154 Integer value = _directory.getInteger(ExifIFD0Directory.TAG_ORIENTATION);155 if (value==null)156 return null;157 switch (value) {158 case 1: return "Top, left side (Horizontal / normal)";159 case 2: return "Top, right side (Mirror horizontal)";160 case 3: return "Bottom, right side (Rotate 180)";161 case 4: return "Bottom, left side (Mirror vertical)";162 case 5: return "Left side, top (Mirror horizontal and rotate 270 CW)";163 case 6: return "Right side, top (Rotate 90 CW)";164 case 7: return "Right side, bottom (Mirror horizontal and rotate 90 CW)";165 case 8: return "Left side, bottom (Rotate 270 CW)";166 default:167 return String.valueOf(value);168 }169 }170 171 @Nullable172 public String getResolutionDescription()173 {174 // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch)175 Integer value = _directory.getInteger(ExifIFD0Directory.TAG_RESOLUTION_UNIT);176 if (value==null)177 return null;178 switch (value) {179 case 1: return "(No unit)";180 case 2: return "Inch";181 case 3: return "cm";182 default:183 return "";184 }185 }186 187 /** The Windows specific tags uses plain Unicode. */188 @Nullable189 private String getUnicodeDescription(int tag)190 {191 byte[] commentBytes = _directory.getByteArray(tag);192 if (commentBytes==null)193 return null;194 try {195 // Decode the unicode string and trim the unicode zero "\0" from the end.196 return new String(commentBytes, "UTF-16LE").trim();197 }198 catch (UnsupportedEncodingException ex) {199 return null;200 }201 }202 203 @Nullable204 182 public String getWindowsAuthorDescription() 205 183 { 206 return getUnicodeDescription( ExifIFD0Directory.TAG_WIN_AUTHOR);184 return getUnicodeDescription(TAG_WIN_AUTHOR); 207 185 } 208 186 … … 210 188 public String getWindowsCommentDescription() 211 189 { 212 return getUnicodeDescription( ExifIFD0Directory.TAG_WIN_COMMENT);190 return getUnicodeDescription(TAG_WIN_COMMENT); 213 191 } 214 192 … … 216 194 public String getWindowsKeywordsDescription() 217 195 { 218 return getUnicodeDescription( ExifIFD0Directory.TAG_WIN_KEYWORDS);196 return getUnicodeDescription(TAG_WIN_KEYWORDS); 219 197 } 220 198 … … 222 200 public String getWindowsTitleDescription() 223 201 { 224 return getUnicodeDescription( ExifIFD0Directory.TAG_WIN_TITLE);202 return getUnicodeDescription(TAG_WIN_TITLE); 225 203 } 226 204 … … 228 206 public String getWindowsSubjectDescription() 229 207 { 230 return getUnicodeDescription( ExifIFD0Directory.TAG_WIN_SUBJECT);208 return getUnicodeDescription(TAG_WIN_SUBJECT); 231 209 } 232 210 } -
trunk/src/com/drew/metadata/exif/ExifIFD0Directory.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 … … 30 30 * Describes Exif tags from the IFD0 directory. 31 31 * 32 * @author Drew Noakes http ://drewnoakes.com32 * @author Drew Noakes https://drewnoakes.com 33 33 */ 34 34 public class ExifIFD0Directory extends Directory … … 46 46 public static final int TAG_WHITE_POINT = 0x013E; 47 47 public static final int TAG_PRIMARY_CHROMATICITIES = 0x013F; 48 48 49 public static final int TAG_YCBCR_COEFFICIENTS = 0x0211; 49 50 public static final int TAG_YCBCR_POSITIONING = 0x0213; 50 51 public static final int TAG_REFERENCE_BLACK_WHITE = 0x0214; 52 53 54 /** This tag is a pointer to the Exif SubIFD. */ 55 public static final int TAG_EXIF_SUB_IFD_OFFSET = 0x8769; 56 57 /** This tag is a pointer to the Exif GPS IFD. */ 58 public static final int TAG_GPS_INFO_OFFSET = 0x8825; 59 51 60 public static final int TAG_COPYRIGHT = 0x8298; 61 62 /** Non-standard, but in use. */ 63 public static final int TAG_TIME_ZONE_OFFSET = 0x882a; 52 64 53 65 /** The image title, as used by Windows XP. */ … … 82 94 _tagNameMap.put(TAG_YCBCR_POSITIONING, "YCbCr Positioning"); 83 95 _tagNameMap.put(TAG_REFERENCE_BLACK_WHITE, "Reference Black/White"); 96 84 97 _tagNameMap.put(TAG_COPYRIGHT, "Copyright"); 98 99 _tagNameMap.put(TAG_TIME_ZONE_OFFSET, "Time Zone Offset"); 85 100 86 101 _tagNameMap.put(TAG_WIN_AUTHOR, "Windows XP Author"); … … 96 111 } 97 112 113 @Override 98 114 @NotNull 99 115 public String getName() … … 102 118 } 103 119 120 @Override 104 121 @NotNull 105 122 protected HashMap<Integer, String> getTagNameMap() -
trunk/src/com/drew/metadata/exif/ExifInteropDescriptor.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.exif; … … 25 25 import com.drew.metadata.TagDescriptor; 26 26 27 import static com.drew.metadata.exif.ExifInteropDirectory.*; 28 27 29 /** 28 * Provides human-readable string representations of tag values stored in a <code>ExifInteropDirectory</code>.30 * Provides human-readable string representations of tag values stored in a {@link ExifInteropDirectory}. 29 31 * 30 * @author Drew Noakes http ://drewnoakes.com32 * @author Drew Noakes https://drewnoakes.com 31 33 */ 32 34 public class ExifInteropDescriptor extends TagDescriptor<ExifInteropDirectory> … … 37 39 } 38 40 41 @Override 39 42 @Nullable 40 43 public String getDescription(int tagType) 41 44 { 42 45 switch (tagType) { 43 case ExifInteropDirectory.TAG_INTEROP_INDEX:46 case TAG_INTEROP_INDEX: 44 47 return getInteropIndexDescription(); 45 case ExifInteropDirectory.TAG_INTEROP_VERSION:48 case TAG_INTEROP_VERSION: 46 49 return getInteropVersionDescription(); 47 50 default: … … 53 56 public String getInteropVersionDescription() 54 57 { 55 int[] ints = _directory.getIntArray(ExifInteropDirectory.TAG_INTEROP_VERSION); 56 return convertBytesToVersionString(ints, 2); 58 return getVersionBytesDescription(TAG_INTEROP_VERSION, 2); 57 59 } 58 60 … … 60 62 public String getInteropIndexDescription() 61 63 { 62 String value = _directory.getString( ExifInteropDirectory.TAG_INTEROP_INDEX);64 String value = _directory.getString(TAG_INTEROP_INDEX); 63 65 64 if (value ==null)66 if (value == null) 65 67 return null; 66 68 -
trunk/src/com/drew/metadata/exif/ExifInteropDirectory.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.exif; … … 29 29 * Describes Exif interoperability tags. 30 30 * 31 * @author Drew Noakes http ://drewnoakes.com31 * @author Drew Noakes https://drewnoakes.com 32 32 */ 33 33 public class ExifInteropDirectory extends Directory … … 56 56 } 57 57 58 @Override 58 59 @NotNull 59 60 public String getName() … … 62 63 } 63 64 65 @Override 64 66 @NotNull 65 67 protected HashMap<Integer, String> getTagNameMap() -
trunk/src/com/drew/metadata/exif/ExifReader.java
r6209 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.exif; 22 22 23 import java.util.HashSet; 24 import java.util.Set; 23 import com.drew.imaging.jpeg.JpegSegmentMetadataReader; 24 import com.drew.imaging.jpeg.JpegSegmentType; 25 import com.drew.imaging.tiff.TiffProcessingException; 26 import com.drew.imaging.tiff.TiffReader; 27 import com.drew.lang.ByteArrayReader; 28 import com.drew.lang.annotations.NotNull; 29 import com.drew.metadata.Metadata; 25 30 26 import com.drew.lang.BufferBoundsException; 27 import com.drew.lang.BufferReader; 28 import com.drew.lang.Rational; 29 import com.drew.lang.annotations.NotNull; 30 import com.drew.metadata.Directory; 31 import com.drew.metadata.Metadata; 32 import com.drew.metadata.MetadataReader; 31 import java.io.IOException; 32 import java.util.Arrays; 33 33 34 34 /** 35 35 * Decodes Exif binary data, populating a {@link Metadata} object with tag values in {@link ExifSubIFDDirectory}, 36 * {@link ExifThumbnailDirectory}, {@link ExifInteropDirectory}, {@link GpsDirectory} and one of the many camera makernote directories. 36 * {@link ExifThumbnailDirectory}, {@link ExifInteropDirectory}, {@link GpsDirectory} and one of the many camera 37 * makernote directories. 37 38 * 38 * @author Drew Noakes http ://drewnoakes.com39 * @author Drew Noakes https://drewnoakes.com 39 40 */ 40 public class ExifReader implements MetadataReader41 public class ExifReader implements JpegSegmentMetadataReader 41 42 { 42 // TODO extract a reusable TiffReader from this class with hooks for special tag handling and subdir following 43 44 /** The number of bytes used per format descriptor. */ 43 /** 44 * The offset at which the TIFF data actually starts. This may be necessary when, for example, processing 45 * JPEG Exif data from APP0 which has a 6-byte preamble before starting the TIFF data. 46 */ 47 private static final String JPEG_EXIF_SEGMENT_PREAMBLE = "Exif\0\0"; 48 49 private boolean _storeThumbnailBytes = true; 50 51 public boolean isStoreThumbnailBytes() 52 { 53 return _storeThumbnailBytes; 54 } 55 56 public void setStoreThumbnailBytes(boolean storeThumbnailBytes) 57 { 58 _storeThumbnailBytes = storeThumbnailBytes; 59 } 60 45 61 @NotNull 46 private static final int[] BYTES_PER_FORMAT = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 }; 62 public Iterable<JpegSegmentType> getSegmentTypes() 63 { 64 return Arrays.asList(JpegSegmentType.APP1); 65 } 47 66 48 /** The number of formats known. */ 49 private static final int MAX_FORMAT_CODE = 12; 67 public boolean canProcess(@NotNull final byte[] segmentBytes, @NotNull final JpegSegmentType segmentType) 68 { 69 return segmentBytes.length >= JPEG_EXIF_SEGMENT_PREAMBLE.length() && new String(segmentBytes, 0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equalsIgnoreCase(JPEG_EXIF_SEGMENT_PREAMBLE); 70 } 50 71 51 // Format types 52 // TODO use an enum for these? 53 /** An 8-bit unsigned integer. */ 54 private static final int FMT_BYTE = 1; 55 /** A fixed-length character string. */ 56 private static final int FMT_STRING = 2; 57 /** An unsigned 16-bit integer. */ 58 private static final int FMT_USHORT = 3; 59 /** An unsigned 32-bit integer. */ 60 private static final int FMT_ULONG = 4; 61 private static final int FMT_URATIONAL = 5; 62 /** An 8-bit signed integer. */ 63 private static final int FMT_SBYTE = 6; 64 private static final int FMT_UNDEFINED = 7; 65 /** A signed 16-bit integer. */ 66 private static final int FMT_SSHORT = 8; 67 /** A signed 32-bit integer. */ 68 private static final int FMT_SLONG = 9; 69 private static final int FMT_SRATIONAL = 10; 70 /** A 32-bit floating point number. */ 71 private static final int FMT_SINGLE = 11; 72 /** A 64-bit floating point number. */ 73 private static final int FMT_DOUBLE = 12; 72 public void extract(@NotNull final byte[] segmentBytes, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType) 73 { 74 if (segmentBytes == null) 75 throw new NullPointerException("segmentBytes cannot be null"); 76 if (metadata == null) 77 throw new NullPointerException("metadata cannot be null"); 78 if (segmentType == null) 79 throw new NullPointerException("segmentType cannot be null"); 74 80 75 /** This tag is a pointer to the Exif SubIFD. */ 76 public static final int TAG_EXIF_SUB_IFD_OFFSET = 0x8769; 77 /** This tag is a pointer to the Exif Interop IFD. */ 78 public static final int TAG_INTEROP_OFFSET = 0xA005; 79 /** This tag is a pointer to the Exif GPS IFD. */ 80 public static final int TAG_GPS_INFO_OFFSET = 0x8825; 81 /** This tag is a pointer to the Exif Makernote IFD. */ 82 public static final int TAG_MAKER_NOTE_OFFSET = 0x927C; 81 try { 82 ByteArrayReader reader = new ByteArrayReader(segmentBytes); 83 83 84 public static final int TIFF_HEADER_START_OFFSET = 6; 85 86 /** 87 * Performs the Exif data extraction, adding found values to the specified 88 * instance of <code>Metadata</code>. 89 * 90 * @param reader The buffer reader from which Exif data should be read. 91 * @param metadata The Metadata object into which extracted values should be merged. 92 */ 93 public void extract(@NotNull final BufferReader reader, @NotNull Metadata metadata) 94 { 95 final ExifSubIFDDirectory directory = metadata.getOrCreateDirectory(ExifSubIFDDirectory.class); 96 97 // check for the header length 98 if (reader.getLength() <= 14) { 99 directory.addError("Exif data segment must contain at least 14 bytes"); 100 return; 101 } 102 103 // check for the header preamble 104 try { 105 if (!reader.getString(0, 6).equals("Exif\0\0")) { 106 directory.addError("Exif data segment doesn't begin with 'Exif'"); 84 // 85 // Check for the header preamble 86 // 87 try { 88 if (!reader.getString(0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equals(JPEG_EXIF_SEGMENT_PREAMBLE)) { 89 // TODO what do to with this error state? 90 System.err.println("Invalid JPEG Exif segment preamble"); 91 return; 92 } 93 } catch (IOException e) { 94 // TODO what do to with this error state? 95 e.printStackTrace(System.err); 107 96 return; 108 97 } 109 98 110 extractIFD(metadata, metadata.getOrCreateDirectory(ExifIFD0Directory.class), TIFF_HEADER_START_OFFSET, reader); 111 } catch (BufferBoundsException e) { 112 directory.addError("Exif data segment ended prematurely"); 99 // 100 // Read the TIFF-formatted Exif data 101 // 102 new TiffReader().processTiff( 103 reader, 104 new ExifTiffHandler(metadata, _storeThumbnailBytes), 105 JPEG_EXIF_SEGMENT_PREAMBLE.length() 106 ); 107 108 } catch (TiffProcessingException e) { 109 // TODO what do to with this error state? 110 e.printStackTrace(System.err); 111 } catch (IOException e) { 112 // TODO what do to with this error state? 113 e.printStackTrace(System.err); 113 114 } 114 115 } 115 116 /**117 * Performs the Exif data extraction on a TIFF/RAW, adding found values to the specified118 * instance of <code>Metadata</code>.119 *120 * @param reader The BufferReader from which TIFF data should be read.121 * @param metadata The Metadata object into which extracted values should be merged.122 */123 public void extractTiff(@NotNull BufferReader reader, @NotNull Metadata metadata)124 {125 final ExifIFD0Directory directory = metadata.getOrCreateDirectory(ExifIFD0Directory.class);126 127 try {128 extractIFD(metadata, directory, 0, reader);129 } catch (BufferBoundsException e) {130 directory.addError("Exif data segment ended prematurely");131 }132 }133 134 private void extractIFD(@NotNull Metadata metadata, @NotNull final ExifIFD0Directory directory, int tiffHeaderOffset, @NotNull BufferReader reader) throws BufferBoundsException135 {136 // this should be either "MM" or "II"137 String byteOrderIdentifier = reader.getString(tiffHeaderOffset, 2);138 139 if ("MM".equals(byteOrderIdentifier)) {140 reader.setMotorolaByteOrder(true);141 } else if ("II".equals(byteOrderIdentifier)) {142 reader.setMotorolaByteOrder(false);143 } else {144 directory.addError("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier);145 return;146 }147 148 // Check the next two values for correctness.149 final int tiffMarker = reader.getUInt16(2 + tiffHeaderOffset);150 151 final int standardTiffMarker = 0x002A;152 final int olympusRawTiffMarker = 0x4F52; // for ORF files153 final int panasonicRawTiffMarker = 0x0055; // for RW2 files154 155 if (tiffMarker != standardTiffMarker && tiffMarker != olympusRawTiffMarker && tiffMarker != panasonicRawTiffMarker) {156 directory.addError("Unexpected TIFF marker after byte order identifier: 0x" + Integer.toHexString(tiffMarker));157 return;158 }159 160 int firstDirectoryOffset = reader.getInt32(4 + tiffHeaderOffset) + tiffHeaderOffset;161 162 // David Ekholm sent a digital camera image that has this problem163 if (firstDirectoryOffset >= reader.getLength() - 1) {164 directory.addError("First exif directory offset is beyond end of Exif data segment");165 // First directory normally starts 14 bytes in -- try it here and catch another error in the worst case166 firstDirectoryOffset = 14;167 }168 169 Set<Integer> processedDirectoryOffsets = new HashSet<Integer>();170 171 processDirectory(directory, processedDirectoryOffsets, firstDirectoryOffset, tiffHeaderOffset, metadata, reader);172 173 // after the extraction process, if we have the correct tags, we may be able to store thumbnail information174 ExifThumbnailDirectory thumbnailDirectory = metadata.getDirectory(ExifThumbnailDirectory.class);175 if (thumbnailDirectory!=null && thumbnailDirectory.containsTag(ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION)) {176 Integer offset = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);177 Integer length = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);178 if (offset != null && length != null) {179 try {180 byte[] thumbnailData = reader.getBytes(tiffHeaderOffset + offset, length);181 thumbnailDirectory.setThumbnailData(thumbnailData);182 } catch (BufferBoundsException ex) {183 directory.addError("Invalid thumbnail data specification: " + ex.getMessage());184 }185 }186 }187 }188 189 /**190 * Process one of the nested Tiff IFD directories.191 * <p/>192 * Header193 * 2 bytes: number of tags194 * <p/>195 * Then for each tag196 * 2 bytes: tag type197 * 2 bytes: format code198 * 4 bytes: component count199 */200 private void processDirectory(@NotNull Directory directory, @NotNull Set<Integer> processedDirectoryOffsets, int dirStartOffset, int tiffHeaderOffset, @NotNull final Metadata metadata, @NotNull final BufferReader reader) throws BufferBoundsException201 {202 // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist203 if (processedDirectoryOffsets.contains(Integer.valueOf(dirStartOffset)))204 return;205 206 // remember that we've visited this directory so that we don't visit it again later207 processedDirectoryOffsets.add(dirStartOffset);208 209 if (dirStartOffset >= reader.getLength() || dirStartOffset < 0) {210 directory.addError("Ignored directory marked to start outside data segment");211 return;212 }213 214 // First two bytes in the IFD are the number of tags in this directory215 int dirTagCount = reader.getUInt16(dirStartOffset);216 217 int dirLength = (2 + (12 * dirTagCount) + 4);218 if (dirLength + dirStartOffset > reader.getLength()) {219 directory.addError("Illegally sized directory");220 return;221 }222 223 // Handle each tag in this directory224 for (int tagNumber = 0; tagNumber < dirTagCount; tagNumber++) {225 final int tagOffset = calculateTagOffset(dirStartOffset, tagNumber);226 227 // 2 bytes for the tag type228 final int tagType = reader.getUInt16(tagOffset);229 230 // 2 bytes for the format code231 final int formatCode = reader.getUInt16(tagOffset + 2);232 if (formatCode < 1 || formatCode > MAX_FORMAT_CODE) {233 // This error suggests that we are processing at an incorrect index and will generate234 // rubbish until we go out of bounds (which may be a while). Exit now.235 directory.addError("Invalid TIFF tag format code: " + formatCode);236 continue; // JOSM patch to fix #9030237 }238 239 // 4 bytes dictate the number of components in this tag's data240 final int componentCount = reader.getInt32(tagOffset + 4);241 if (componentCount < 0) {242 directory.addError("Negative TIFF tag component count");243 continue;244 }245 // each component may have more than one byte... calculate the total number of bytes246 final int byteCount = componentCount * BYTES_PER_FORMAT[formatCode];247 final int tagValueOffset;248 if (byteCount > 4) {249 // If it's bigger than 4 bytes, the dir entry contains an offset.250 // dirEntryOffset must be passed, as some makernote implementations (e.g. FujiFilm) incorrectly use an251 // offset relative to the start of the makernote itself, not the TIFF segment.252 final int offsetVal = reader.getInt32(tagOffset + 8);253 if (offsetVal + byteCount > reader.getLength()) {254 // Bogus pointer offset and / or byteCount value255 directory.addError("Illegal TIFF tag pointer offset");256 continue;257 }258 tagValueOffset = tiffHeaderOffset + offsetVal;259 } else {260 // 4 bytes or less and value is in the dir entry itself261 tagValueOffset = tagOffset + 8;262 }263 264 if (tagValueOffset < 0 || tagValueOffset > reader.getLength()) {265 directory.addError("Illegal TIFF tag pointer offset");266 continue;267 }268 269 // Check that this tag isn't going to allocate outside the bounds of the data array.270 // This addresses an uncommon OutOfMemoryError.271 if (byteCount < 0 || tagValueOffset + byteCount > reader.getLength()) {272 directory.addError("Illegal number of bytes: " + byteCount);273 continue;274 }275 276 switch (tagType) {277 case TAG_EXIF_SUB_IFD_OFFSET: {278 final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);279 processDirectory(metadata.getOrCreateDirectory(ExifSubIFDDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);280 continue;281 }282 case TAG_INTEROP_OFFSET: {283 final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);284 processDirectory(metadata.getOrCreateDirectory(ExifInteropDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);285 continue;286 }287 case TAG_GPS_INFO_OFFSET: {288 final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);289 processDirectory(metadata.getOrCreateDirectory(GpsDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);290 continue;291 }292 case TAG_MAKER_NOTE_OFFSET: {293 processMakerNote(tagValueOffset, processedDirectoryOffsets, tiffHeaderOffset, metadata, reader);294 continue;295 }296 default: {297 processTag(directory, tagType, tagValueOffset, componentCount, formatCode, reader);298 break;299 }300 }301 }302 303 // at the end of each IFD is an optional link to the next IFD304 final int finalTagOffset = calculateTagOffset(dirStartOffset, dirTagCount);305 int nextDirectoryOffset = reader.getInt32(finalTagOffset);306 if (nextDirectoryOffset != 0) {307 nextDirectoryOffset += tiffHeaderOffset;308 if (nextDirectoryOffset >= reader.getLength()) {309 // Last 4 bytes of IFD reference another IFD with an address that is out of bounds310 // Note this could have been caused by jhead 1.3 cropping too much311 return;312 } else if (nextDirectoryOffset < dirStartOffset) {313 // Last 4 bytes of IFD reference another IFD with an address that is before the start of this directory314 return;315 }316 // TODO in Exif, the only known 'follower' IFD is the thumbnail one, however this may not be the case317 final ExifThumbnailDirectory nextDirectory = metadata.getOrCreateDirectory(ExifThumbnailDirectory.class);318 processDirectory(nextDirectory, processedDirectoryOffsets, nextDirectoryOffset, tiffHeaderOffset, metadata, reader);319 }320 }321 322 private void processMakerNote(int subdirOffset, @NotNull Set<Integer> processedDirectoryOffsets, int tiffHeaderOffset, @NotNull final Metadata metadata, @NotNull BufferReader reader) throws BufferBoundsException323 {324 // Determine the camera model and makernote format325 Directory ifd0Directory = metadata.getDirectory(ExifIFD0Directory.class);326 327 if (ifd0Directory==null)328 return;329 330 String cameraModel = ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE);331 332 //final String firstTwoChars = reader.getString(subdirOffset, 2);333 final String firstThreeChars = reader.getString(subdirOffset, 3);334 final String firstFourChars = reader.getString(subdirOffset, 4);335 final String firstFiveChars = reader.getString(subdirOffset, 5);336 final String firstSixChars = reader.getString(subdirOffset, 6);337 final String firstSevenChars = reader.getString(subdirOffset, 7);338 final String firstEightChars = reader.getString(subdirOffset, 8);339 final String firstTwelveChars = reader.getString(subdirOffset, 12);340 341 if ("OLYMP".equals(firstFiveChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars)) {342 // Olympus Makernote343 // Epson and Agfa use Olympus maker note standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/344 processDirectory(metadata.getOrCreateDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset, metadata, reader);345 } else if (cameraModel != null && cameraModel.trim().toUpperCase().startsWith("NIKON")) {346 if ("Nikon".equals(firstFiveChars)) {347 /* There are two scenarios here:348 * Type 1: **349 * :0000: 4E 69 6B 6F 6E 00 01 00-05 00 02 00 02 00 06 00 Nikon...........350 * :0010: 00 00 EC 02 00 00 03 00-03 00 01 00 00 00 06 00 ................351 * Type 3: **352 * :0000: 4E 69 6B 6F 6E 00 02 00-00 00 4D 4D 00 2A 00 00 Nikon....MM.*...353 * :0010: 00 08 00 1E 00 01 00 07-00 00 00 04 30 32 30 30 ............0200354 */355 switch (reader.getUInt8(subdirOffset + 6)) {356 case 1:357 processDirectory(metadata.getOrCreateDirectory(NikonType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset, metadata, reader);358 break;359 case 2:360 processDirectory(metadata.getOrCreateDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 18, subdirOffset + 10, metadata, reader);361 break;362 default:363 ifd0Directory.addError("Unsupported Nikon makernote data ignored.");364 break;365 }366 } else {367 // The IFD begins with the first MakerNote byte (no ASCII name). This occurs with CoolPix 775, E990 and D1 models.368 processDirectory(metadata.getOrCreateDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);369 }370 } else if ("SONY CAM".equals(firstEightChars) || "SONY DSC".equals(firstEightChars)) {371 processDirectory(metadata.getOrCreateDirectory(SonyType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset, metadata, reader);372 } else if ("SIGMA\u0000\u0000\u0000".equals(firstEightChars) || "FOVEON\u0000\u0000".equals(firstEightChars)) {373 processDirectory(metadata.getOrCreateDirectory(SigmaMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 10, tiffHeaderOffset, metadata, reader);374 } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) {375 // force MM for this directory376 boolean isMotorola = reader.isMotorolaByteOrder();377 reader.setMotorolaByteOrder(true);378 // skip 12 byte header + 2 for "MM" + 6379 processDirectory(metadata.getOrCreateDirectory(SonyType6MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 20, tiffHeaderOffset, metadata, reader);380 reader.setMotorolaByteOrder(isMotorola);381 } else if ("KDK".equals(firstThreeChars)) {382 processDirectory(metadata.getOrCreateDirectory(KodakMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 20, tiffHeaderOffset, metadata, reader);383 } else if ("Canon".equalsIgnoreCase(cameraModel)) {384 processDirectory(metadata.getOrCreateDirectory(CanonMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);385 } else if (cameraModel != null && cameraModel.toUpperCase().startsWith("CASIO")) {386 if ("QVC\u0000\u0000\u0000".equals(firstSixChars))387 processDirectory(metadata.getOrCreateDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, tiffHeaderOffset, metadata, reader);388 else389 processDirectory(metadata.getOrCreateDirectory(CasioType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);390 } else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraModel)) {391 boolean byteOrderBefore = reader.isMotorolaByteOrder();392 // bug in fujifilm makernote ifd means we temporarily use Intel byte ordering393 reader.setMotorolaByteOrder(false);394 // the 4 bytes after "FUJIFILM" in the makernote point to the start of the makernote395 // IFD, though the offset is relative to the start of the makernote, not the TIFF396 // header (like everywhere else)397 int ifdStart = subdirOffset + reader.getInt32(subdirOffset + 8);398 processDirectory(metadata.getOrCreateDirectory(FujifilmMakernoteDirectory.class), processedDirectoryOffsets, ifdStart, tiffHeaderOffset, metadata, reader);399 reader.setMotorolaByteOrder(byteOrderBefore);400 } else if (cameraModel != null && cameraModel.toUpperCase().startsWith("MINOLTA")) {401 // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote402 // area that commences immediately.403 processDirectory(metadata.getOrCreateDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);404 } else if ("KYOCERA".equals(firstSevenChars)) {405 // http://www.ozhiker.com/electronics/pjmt/jpeg_info/kyocera_mn.html406 processDirectory(metadata.getOrCreateDirectory(KyoceraMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 22, tiffHeaderOffset, metadata, reader);407 } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(subdirOffset, 12))) {408 // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD409 // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment410 // more information here: http://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html411 processDirectory(metadata.getOrCreateDirectory(PanasonicMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset, metadata, reader);412 } else if ("AOC\u0000".equals(firstFourChars)) {413 // NON-Standard TIFF IFD Data using Casio Type 2 Tags414 // IFD has no Next-IFD pointer at end of IFD, and415 // Offsets are relative to the start of the current IFD tag, not the TIFF header416 // Observed for:417 // - Pentax ist D418 processDirectory(metadata.getOrCreateDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, subdirOffset, metadata, reader);419 } else if (cameraModel != null && (cameraModel.toUpperCase().startsWith("PENTAX") || cameraModel.toUpperCase().startsWith("ASAHI"))) {420 // NON-Standard TIFF IFD Data using Pentax Tags421 // IFD has no Next-IFD pointer at end of IFD, and422 // Offsets are relative to the start of the current IFD tag, not the TIFF header423 // Observed for:424 // - PENTAX Optio 330425 // - PENTAX Optio 430426 processDirectory(metadata.getOrCreateDirectory(PentaxMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, subdirOffset, metadata, reader);427 // } else if ("KC".equals(firstTwoChars) || "MINOL".equals(firstFiveChars) || "MLY".equals(firstThreeChars) || "+M+M+M+M".equals(firstEightChars)) {428 // // This Konica data is not understood. Header identified in accordance with information at this site:429 // // http://www.ozhiker.com/electronics/pjmt/jpeg_info/minolta_mn.html430 // // TODO add support for minolta/konica cameras431 // exifDirectory.addError("Unsupported Konica/Minolta data ignored.");432 } else {433 // TODO how to store makernote data when it's not from a supported camera model?434 // this is difficult as the starting offset is not known. we could look for it...435 }436 }437 438 private void processTag(@NotNull Directory directory, int tagType, int tagValueOffset, int componentCount, int formatCode, @NotNull final BufferReader reader) throws BufferBoundsException439 {440 // Directory simply stores raw values441 // The display side uses a Descriptor class per directory to turn the raw values into 'pretty' descriptions442 switch (formatCode) {443 case FMT_UNDEFINED:444 // this includes exif user comments445 directory.setByteArray(tagType, reader.getBytes(tagValueOffset, componentCount));446 break;447 case FMT_STRING:448 String string = reader.getNullTerminatedString(tagValueOffset, componentCount);449 directory.setString(tagType, string);450 /*451 // special handling for certain known tags, proposed by Yuri Binev but left out for now,452 // as it gives the false impression that the image was captured in the same timezone453 // in which the string is parsed454 if (tagType==ExifSubIFDDirectory.TAG_DATETIME ||455 tagType==ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL ||456 tagType==ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED) {457 String[] datePatterns = {458 "yyyy:MM:dd HH:mm:ss",459 "yyyy:MM:dd HH:mm",460 "yyyy-MM-dd HH:mm:ss",461 "yyyy-MM-dd HH:mm"};462 for (String datePattern : datePatterns) {463 try {464 DateFormat parser = new SimpleDateFormat(datePattern);465 Date date = parser.parse(string);466 directory.setDate(tagType, date);467 break;468 } catch (ParseException ex) {469 // simply try the next pattern470 }471 }472 }473 */474 break;475 case FMT_SRATIONAL:476 if (componentCount == 1) {477 directory.setRational(tagType, new Rational(reader.getInt32(tagValueOffset), reader.getInt32(tagValueOffset + 4)));478 } else if (componentCount > 1) {479 Rational[] rationals = new Rational[componentCount];480 for (int i = 0; i < componentCount; i++)481 rationals[i] = new Rational(reader.getInt32(tagValueOffset + (8 * i)), reader.getInt32(tagValueOffset + 4 + (8 * i)));482 directory.setRationalArray(tagType, rationals);483 }484 break;485 case FMT_URATIONAL:486 if (componentCount == 1) {487 directory.setRational(tagType, new Rational(reader.getUInt32(tagValueOffset), reader.getUInt32(tagValueOffset + 4)));488 } else if (componentCount > 1) {489 Rational[] rationals = new Rational[componentCount];490 for (int i = 0; i < componentCount; i++)491 rationals[i] = new Rational(reader.getUInt32(tagValueOffset + (8 * i)), reader.getUInt32(tagValueOffset + 4 + (8 * i)));492 directory.setRationalArray(tagType, rationals);493 }494 break;495 case FMT_SINGLE:496 if (componentCount == 1) {497 directory.setFloat(tagType, reader.getFloat32(tagValueOffset));498 } else {499 float[] floats = new float[componentCount];500 for (int i = 0; i < componentCount; i++)501 floats[i] = reader.getFloat32(tagValueOffset + (i * 4));502 directory.setFloatArray(tagType, floats);503 }504 break;505 case FMT_DOUBLE:506 if (componentCount == 1) {507 directory.setDouble(tagType, reader.getDouble64(tagValueOffset));508 } else {509 double[] doubles = new double[componentCount];510 for (int i = 0; i < componentCount; i++)511 doubles[i] = reader.getDouble64(tagValueOffset + (i * 4));512 directory.setDoubleArray(tagType, doubles);513 }514 break;515 516 //517 // Note that all integral types are stored as int32 internally (the largest supported by TIFF)518 //519 520 case FMT_SBYTE:521 if (componentCount == 1) {522 directory.setInt(tagType, reader.getInt8(tagValueOffset));523 } else {524 int[] bytes = new int[componentCount];525 for (int i = 0; i < componentCount; i++)526 bytes[i] = reader.getInt8(tagValueOffset + i);527 directory.setIntArray(tagType, bytes);528 }529 break;530 case FMT_BYTE:531 if (componentCount == 1) {532 directory.setInt(tagType, reader.getUInt8(tagValueOffset));533 } else {534 int[] bytes = new int[componentCount];535 for (int i = 0; i < componentCount; i++)536 bytes[i] = reader.getUInt8(tagValueOffset + i);537 directory.setIntArray(tagType, bytes);538 }539 break;540 case FMT_USHORT:541 if (componentCount == 1) {542 int i = reader.getUInt16(tagValueOffset);543 directory.setInt(tagType, i);544 } else {545 int[] ints = new int[componentCount];546 for (int i = 0; i < componentCount; i++)547 ints[i] = reader.getUInt16(tagValueOffset + (i * 2));548 directory.setIntArray(tagType, ints);549 }550 break;551 case FMT_SSHORT:552 if (componentCount == 1) {553 int i = reader.getInt16(tagValueOffset);554 directory.setInt(tagType, i);555 } else {556 int[] ints = new int[componentCount];557 for (int i = 0; i < componentCount; i++)558 ints[i] = reader.getInt16(tagValueOffset + (i * 2));559 directory.setIntArray(tagType, ints);560 }561 break;562 case FMT_SLONG:563 case FMT_ULONG:564 // NOTE 'long' in this case means 32 bit, not 64565 if (componentCount == 1) {566 int i = reader.getInt32(tagValueOffset);567 directory.setInt(tagType, i);568 } else {569 int[] ints = new int[componentCount];570 for (int i = 0; i < componentCount; i++)571 ints[i] = reader.getInt32(tagValueOffset + (i * 4));572 directory.setIntArray(tagType, ints);573 }574 break;575 default:576 directory.addError("Unknown format code " + formatCode + " for tag " + tagType);577 }578 }579 580 /**581 * Determine the offset at which a given InteropArray entry begins within the specified IFD.582 *583 * @param dirStartOffset the offset at which the IFD starts584 * @param entryNumber the zero-based entry number585 */586 private int calculateTagOffset(int dirStartOffset, int entryNumber)587 {588 // add 2 bytes for the tag count589 // each entry is 12 bytes, so we skip 12 * the number seen so far590 return dirStartOffset + 2 + (12 * entryNumber);591 }592 116 } -
trunk/src/com/drew/metadata/exif/ExifSubIFDDescriptor.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.exif; … … 32 32 import java.util.Map; 33 33 34 import static com.drew.metadata.exif.ExifSubIFDDirectory.*; 35 34 36 /** 35 * Provides human-readable string representations of tag values stored in a <code>ExifSubIFDDirectory</code>.37 * Provides human-readable string representations of tag values stored in a {@link ExifSubIFDDirectory}. 36 38 * 37 * @author Drew Noakes http ://drewnoakes.com39 * @author Drew Noakes https://drewnoakes.com 38 40 */ 39 41 public class ExifSubIFDDescriptor extends TagDescriptor<ExifSubIFDDirectory> … … 60 62 61 63 /** 62 * Returns a descriptive value of the thespecified tag for this image.64 * Returns a descriptive value of the specified tag for this image. 63 65 * Where possible, known values will be substituted here in place of the raw 64 66 * tokens actually kept in the Exif segment. If no substitution is 65 67 * available, the value provided by getString(int) will be returned. 68 * 66 69 * @param tagType the tag to find a description for 67 70 * @return a description of the image's value for the specified tag, or 68 71 * <code>null</code> if the tag hasn't been defined. 69 72 */ 73 @Override 70 74 @Nullable 71 75 public String getDescription(int tagType) 72 76 { 73 77 switch (tagType) { 74 case ExifSubIFDDirectory.TAG_NEW_SUBFILE_TYPE:78 case TAG_NEW_SUBFILE_TYPE: 75 79 return getNewSubfileTypeDescription(); 76 case ExifSubIFDDirectory.TAG_SUBFILE_TYPE:80 case TAG_SUBFILE_TYPE: 77 81 return getSubfileTypeDescription(); 78 case ExifSubIFDDirectory.TAG_THRESHOLDING:82 case TAG_THRESHOLDING: 79 83 return getThresholdingDescription(); 80 case ExifSubIFDDirectory.TAG_FILL_ORDER:84 case TAG_FILL_ORDER: 81 85 return getFillOrderDescription(); 82 case ExifSubIFDDirectory.TAG_EXPOSURE_TIME:86 case TAG_EXPOSURE_TIME: 83 87 return getExposureTimeDescription(); 84 case ExifSubIFDDirectory.TAG_SHUTTER_SPEED:88 case TAG_SHUTTER_SPEED: 85 89 return getShutterSpeedDescription(); 86 case ExifSubIFDDirectory.TAG_FNUMBER:90 case TAG_FNUMBER: 87 91 return getFNumberDescription(); 88 case ExifSubIFDDirectory.TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL:92 case TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL: 89 93 return getCompressedAverageBitsPerPixelDescription(); 90 case ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE:94 case TAG_SUBJECT_DISTANCE: 91 95 return getSubjectDistanceDescription(); 92 case ExifSubIFDDirectory.TAG_METERING_MODE:96 case TAG_METERING_MODE: 93 97 return getMeteringModeDescription(); 94 case ExifSubIFDDirectory.TAG_WHITE_BALANCE:98 case TAG_WHITE_BALANCE: 95 99 return getWhiteBalanceDescription(); 96 case ExifSubIFDDirectory.TAG_FLASH:100 case TAG_FLASH: 97 101 return getFlashDescription(); 98 case ExifSubIFDDirectory.TAG_FOCAL_LENGTH:102 case TAG_FOCAL_LENGTH: 99 103 return getFocalLengthDescription(); 100 case ExifSubIFDDirectory.TAG_COLOR_SPACE:104 case TAG_COLOR_SPACE: 101 105 return getColorSpaceDescription(); 102 case ExifSubIFDDirectory.TAG_EXIF_IMAGE_WIDTH:106 case TAG_EXIF_IMAGE_WIDTH: 103 107 return getExifImageWidthDescription(); 104 case ExifSubIFDDirectory.TAG_EXIF_IMAGE_HEIGHT:108 case TAG_EXIF_IMAGE_HEIGHT: 105 109 return getExifImageHeightDescription(); 106 case ExifSubIFDDirectory.TAG_FOCAL_PLANE_UNIT:110 case TAG_FOCAL_PLANE_RESOLUTION_UNIT: 107 111 return getFocalPlaneResolutionUnitDescription(); 108 case ExifSubIFDDirectory.TAG_FOCAL_PLANE_X_RES:112 case TAG_FOCAL_PLANE_X_RESOLUTION: 109 113 return getFocalPlaneXResolutionDescription(); 110 case ExifSubIFDDirectory.TAG_FOCAL_PLANE_Y_RES:114 case TAG_FOCAL_PLANE_Y_RESOLUTION: 111 115 return getFocalPlaneYResolutionDescription(); 112 case ExifSubIFDDirectory.TAG_BITS_PER_SAMPLE:116 case TAG_BITS_PER_SAMPLE: 113 117 return getBitsPerSampleDescription(); 114 case ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION:118 case TAG_PHOTOMETRIC_INTERPRETATION: 115 119 return getPhotometricInterpretationDescription(); 116 case ExifSubIFDDirectory.TAG_ROWS_PER_STRIP:120 case TAG_ROWS_PER_STRIP: 117 121 return getRowsPerStripDescription(); 118 case ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS:122 case TAG_STRIP_BYTE_COUNTS: 119 123 return getStripByteCountsDescription(); 120 case ExifSubIFDDirectory.TAG_SAMPLES_PER_PIXEL:124 case TAG_SAMPLES_PER_PIXEL: 121 125 return getSamplesPerPixelDescription(); 122 case ExifSubIFDDirectory.TAG_PLANAR_CONFIGURATION:126 case TAG_PLANAR_CONFIGURATION: 123 127 return getPlanarConfigurationDescription(); 124 case ExifSubIFDDirectory.TAG_YCBCR_SUBSAMPLING:128 case TAG_YCBCR_SUBSAMPLING: 125 129 return getYCbCrSubsamplingDescription(); 126 case ExifSubIFDDirectory.TAG_EXPOSURE_PROGRAM:130 case TAG_EXPOSURE_PROGRAM: 127 131 return getExposureProgramDescription(); 128 case ExifSubIFDDirectory.TAG_APERTURE:132 case TAG_APERTURE: 129 133 return getApertureValueDescription(); 130 case ExifSubIFDDirectory.TAG_MAX_APERTURE:134 case TAG_MAX_APERTURE: 131 135 return getMaxApertureValueDescription(); 132 case ExifSubIFDDirectory.TAG_SENSING_METHOD:136 case TAG_SENSING_METHOD: 133 137 return getSensingMethodDescription(); 134 case ExifSubIFDDirectory.TAG_EXPOSURE_BIAS:138 case TAG_EXPOSURE_BIAS: 135 139 return getExposureBiasDescription(); 136 case ExifSubIFDDirectory.TAG_FILE_SOURCE:140 case TAG_FILE_SOURCE: 137 141 return getFileSourceDescription(); 138 case ExifSubIFDDirectory.TAG_SCENE_TYPE:142 case TAG_SCENE_TYPE: 139 143 return getSceneTypeDescription(); 140 case ExifSubIFDDirectory.TAG_COMPONENTS_CONFIGURATION:144 case TAG_COMPONENTS_CONFIGURATION: 141 145 return getComponentConfigurationDescription(); 142 case ExifSubIFDDirectory.TAG_EXIF_VERSION:146 case TAG_EXIF_VERSION: 143 147 return getExifVersionDescription(); 144 case ExifSubIFDDirectory.TAG_FLASHPIX_VERSION:148 case TAG_FLASHPIX_VERSION: 145 149 return getFlashPixVersionDescription(); 146 case ExifSubIFDDirectory.TAG_ISO_EQUIVALENT:150 case TAG_ISO_EQUIVALENT: 147 151 return getIsoEquivalentDescription(); 148 case ExifSubIFDDirectory.TAG_USER_COMMENT:152 case TAG_USER_COMMENT: 149 153 return getUserCommentDescription(); 150 case ExifSubIFDDirectory.TAG_CUSTOM_RENDERED:154 case TAG_CUSTOM_RENDERED: 151 155 return getCustomRenderedDescription(); 152 case ExifSubIFDDirectory.TAG_EXPOSURE_MODE:156 case TAG_EXPOSURE_MODE: 153 157 return getExposureModeDescription(); 154 case ExifSubIFDDirectory.TAG_WHITE_BALANCE_MODE:158 case TAG_WHITE_BALANCE_MODE: 155 159 return getWhiteBalanceModeDescription(); 156 case ExifSubIFDDirectory.TAG_DIGITAL_ZOOM_RATIO:160 case TAG_DIGITAL_ZOOM_RATIO: 157 161 return getDigitalZoomRatioDescription(); 158 case ExifSubIFDDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH:162 case TAG_35MM_FILM_EQUIV_FOCAL_LENGTH: 159 163 return get35mmFilmEquivFocalLengthDescription(); 160 case ExifSubIFDDirectory.TAG_SCENE_CAPTURE_TYPE:164 case TAG_SCENE_CAPTURE_TYPE: 161 165 return getSceneCaptureTypeDescription(); 162 case ExifSubIFDDirectory.TAG_GAIN_CONTROL:166 case TAG_GAIN_CONTROL: 163 167 return getGainControlDescription(); 164 case ExifSubIFDDirectory.TAG_CONTRAST:168 case TAG_CONTRAST: 165 169 return getContrastDescription(); 166 case ExifSubIFDDirectory.TAG_SATURATION:170 case TAG_SATURATION: 167 171 return getSaturationDescription(); 168 case ExifSubIFDDirectory.TAG_SHARPNESS:172 case TAG_SHARPNESS: 169 173 return getSharpnessDescription(); 170 case ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE_RANGE:174 case TAG_SUBJECT_DISTANCE_RANGE: 171 175 return getSubjectDistanceRangeDescription(); 172 176 default: … … 178 182 public String getNewSubfileTypeDescription() 179 183 { 180 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_NEW_SUBFILE_TYPE); 181 if (value==null) 182 return null; 183 switch (value) { 184 case 1: return "Full-resolution image"; 185 case 2: return "Reduced-resolution image"; 186 case 3: return "Single page of multi-page reduced-resolution image"; 187 case 4: return "Transparency mask"; 188 case 5: return "Transparency mask of reduced-resolution image"; 189 case 6: return "Transparency mask of multi-page image"; 190 case 7: return "Transparency mask of reduced-resolution multi-page image"; 191 default: 192 return "Unknown (" + value + ")"; 193 } 184 return getIndexedDescription(TAG_NEW_SUBFILE_TYPE, 1, 185 "Full-resolution image", 186 "Reduced-resolution image", 187 "Single page of multi-page reduced-resolution image", 188 "Transparency mask", 189 "Transparency mask of reduced-resolution image", 190 "Transparency mask of multi-page image", 191 "Transparency mask of reduced-resolution multi-page image" 192 ); 194 193 } 195 194 … … 197 196 public String getSubfileTypeDescription() 198 197 { 199 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SUBFILE_TYPE); 200 if (value==null) 201 return null; 202 switch (value) { 203 case 1: return "Full-resolution image"; 204 case 2: return "Reduced-resolution image"; 205 case 3: return "Single page of multi-page image"; 206 default: 207 return "Unknown (" + value + ")"; 208 } 198 return getIndexedDescription(TAG_SUBFILE_TYPE, 1, 199 "Full-resolution image", 200 "Reduced-resolution image", 201 "Single page of multi-page image" 202 ); 209 203 } 210 204 … … 212 206 public String getThresholdingDescription() 213 207 { 214 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_THRESHOLDING); 215 if (value==null) 216 return null; 217 switch (value) { 218 case 1: return "No dithering or halftoning"; 219 case 2: return "Ordered dither or halftone"; 220 case 3: return "Randomized dither"; 221 default: 222 return "Unknown (" + value + ")"; 223 } 208 return getIndexedDescription(TAG_THRESHOLDING, 1, 209 "No dithering or halftoning", 210 "Ordered dither or halftone", 211 "Randomized dither" 212 ); 224 213 } 225 214 … … 227 216 public String getFillOrderDescription() 228 217 { 229 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_FILL_ORDER); 230 if (value==null) 231 return null; 232 switch (value) { 233 case 1: return "Normal"; 234 case 2: return "Reversed"; 235 default: 236 return "Unknown (" + value + ")"; 237 } 218 return getIndexedDescription(TAG_FILL_ORDER, 1, 219 "Normal", 220 "Reversed" 221 ); 238 222 } 239 223 … … 241 225 public String getSubjectDistanceRangeDescription() 242 226 { 243 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE_RANGE); 244 if (value==null) 245 return null; 246 switch (value) { 247 case 0: return "Unknown"; 248 case 1: return "Macro"; 249 case 2: return "Close view"; 250 case 3: return "Distant view"; 251 default: 252 return "Unknown (" + value + ")"; 253 } 227 return getIndexedDescription(TAG_SUBJECT_DISTANCE_RANGE, 228 "Unknown", 229 "Macro", 230 "Close view", 231 "Distant view" 232 ); 254 233 } 255 234 … … 257 236 public String getSharpnessDescription() 258 237 { 259 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SHARPNESS); 260 if (value==null) 261 return null; 262 switch (value) { 263 case 0: return "None"; 264 case 1: return "Low"; 265 case 2: return "Hard"; 266 default: 267 return "Unknown (" + value + ")"; 268 } 238 return getIndexedDescription(TAG_SHARPNESS, 239 "None", 240 "Low", 241 "Hard" 242 ); 269 243 } 270 244 … … 272 246 public String getSaturationDescription() 273 247 { 274 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SATURATION); 275 if (value==null) 276 return null; 277 switch (value) { 278 case 0: return "None"; 279 case 1: return "Low saturation"; 280 case 2: return "High saturation"; 281 default: 282 return "Unknown (" + value + ")"; 283 } 248 return getIndexedDescription(TAG_SATURATION, 249 "None", 250 "Low saturation", 251 "High saturation" 252 ); 284 253 } 285 254 … … 287 256 public String getContrastDescription() 288 257 { 289 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_CONTRAST); 290 if (value==null) 291 return null; 292 switch (value) { 293 case 0: return "None"; 294 case 1: return "Soft"; 295 case 2: return "Hard"; 296 default: 297 return "Unknown (" + value + ")"; 298 } 258 return getIndexedDescription(TAG_CONTRAST, 259 "None", 260 "Soft", 261 "Hard" 262 ); 299 263 } 300 264 … … 302 266 public String getGainControlDescription() 303 267 { 304 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_GAIN_CONTROL); 305 if (value==null) 306 return null; 307 switch (value) { 308 case 0: return "None"; 309 case 1: return "Low gain up"; 310 case 2: return "Low gain down"; 311 case 3: return "High gain up"; 312 case 4: return "High gain down"; 313 default: 314 return "Unknown (" + value + ")"; 315 } 268 return getIndexedDescription(TAG_GAIN_CONTROL, 269 "None", 270 "Low gain up", 271 "Low gain down", 272 "High gain up", 273 "High gain down" 274 ); 316 275 } 317 276 … … 319 278 public String getSceneCaptureTypeDescription() 320 279 { 321 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SCENE_CAPTURE_TYPE); 322 if (value==null) 323 return null; 324 switch (value) { 325 case 0: return "Standard"; 326 case 1: return "Landscape"; 327 case 2: return "Portrait"; 328 case 3: return "Night scene"; 329 default: 330 return "Unknown (" + value + ")"; 331 } 280 return getIndexedDescription(TAG_SCENE_CAPTURE_TYPE, 281 "Standard", 282 "Landscape", 283 "Portrait", 284 "Night scene" 285 ); 332 286 } 333 287 … … 335 289 public String get35mmFilmEquivFocalLengthDescription() 336 290 { 337 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH); 338 if (value==null) 339 return null; 340 if (value==0) 341 return "Unknown"; 342 else 343 return SimpleDecimalFormatter.format(value) + "mm"; 291 Integer value = _directory.getInteger(TAG_35MM_FILM_EQUIV_FOCAL_LENGTH); 292 return value == null 293 ? null 294 : value == 0 295 ? "Unknown" 296 : SimpleDecimalFormatter.format(value) + "mm"; 344 297 } 345 298 … … 347 300 public String getDigitalZoomRatioDescription() 348 301 { 349 Rational value = _directory.getRational( ExifSubIFDDirectory.TAG_DIGITAL_ZOOM_RATIO);350 if (value==null)351 return null;352 if (value.getNumerator()==0)353 return "Digital zoom not used.";354 returnSimpleDecimalFormatter.format(value.doubleValue());302 Rational value = _directory.getRational(TAG_DIGITAL_ZOOM_RATIO); 303 return value == null 304 ? null 305 : value.getNumerator() == 0 306 ? "Digital zoom not used." 307 : SimpleDecimalFormatter.format(value.doubleValue()); 355 308 } 356 309 … … 358 311 public String getWhiteBalanceModeDescription() 359 312 { 360 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_WHITE_BALANCE_MODE); 361 if (value==null) 362 return null; 363 switch (value) { 364 case 0: return "Auto white balance"; 365 case 1: return "Manual white balance"; 366 default: 367 return "Unknown (" + value + ")"; 368 } 313 return getIndexedDescription(TAG_WHITE_BALANCE_MODE, 314 "Auto white balance", 315 "Manual white balance" 316 ); 369 317 } 370 318 … … 372 320 public String getExposureModeDescription() 373 321 { 374 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXPOSURE_MODE); 375 if (value==null) 376 return null; 377 switch (value) { 378 case 0: return "Auto exposure"; 379 case 1: return "Manual exposure"; 380 case 2: return "Auto bracket"; 381 default: 382 return "Unknown (" + value + ")"; 383 } 322 return getIndexedDescription(TAG_EXPOSURE_MODE, 323 "Auto exposure", 324 "Manual exposure", 325 "Auto bracket" 326 ); 384 327 } 385 328 … … 387 330 public String getCustomRenderedDescription() 388 331 { 389 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_CUSTOM_RENDERED); 390 if (value==null) 391 return null; 392 switch (value) { 393 case 0: return "Normal process"; 394 case 1: return "Custom process"; 395 default: 396 return "Unknown (" + value + ")"; 397 } 332 return getIndexedDescription(TAG_CUSTOM_RENDERED, 333 "Normal process", 334 "Custom process" 335 ); 398 336 } 399 337 … … 401 339 public String getUserCommentDescription() 402 340 { 403 byte[] commentBytes = _directory.getByteArray( ExifSubIFDDirectory.TAG_USER_COMMENT);404 if (commentBytes ==null)341 byte[] commentBytes = _directory.getByteArray(TAG_USER_COMMENT); 342 if (commentBytes == null) 405 343 return null; 406 344 if (commentBytes.length == 0) … … 408 346 409 347 final Map<String, String> encodingMap = new HashMap<String, String>(); 410 encodingMap.put("ASCII", 411 encodingMap.put("UNICODE", 412 encodingMap.put("JIS", 348 encodingMap.put("ASCII", System.getProperty("file.encoding")); // Someone suggested "ISO-8859-1". 349 encodingMap.put("UNICODE", "UTF-16LE"); 350 encodingMap.put("JIS", "Shift-JIS"); // We assume this charset for now. Another suggestion is "JIS". 413 351 414 352 try { … … 442 380 { 443 381 // Have seen an exception here from files produced by ACDSEE that stored an int[] here with two values 444 Integer isoEquiv = _directory.getInteger(ExifSubIFDDirectory.TAG_ISO_EQUIVALENT); 445 if (isoEquiv==null) 446 return null; 382 Integer isoEquiv = _directory.getInteger(TAG_ISO_EQUIVALENT); 447 383 // There used to be a check here that multiplied ISO values < 50 by 200. 448 384 // Issue 36 shows a smart-phone image from a Samsung Galaxy S2 with ISO-40. 449 return Integer.toString(isoEquiv); 385 return isoEquiv != null 386 ? Integer.toString(isoEquiv) 387 : null; 450 388 } 451 389 … … 453 391 public String getExifVersionDescription() 454 392 { 455 int[] ints = _directory.getIntArray(ExifSubIFDDirectory.TAG_EXIF_VERSION); 456 if (ints==null) 457 return null; 458 return ExifSubIFDDescriptor.convertBytesToVersionString(ints, 2); 393 return getVersionBytesDescription(TAG_EXIF_VERSION, 2); 459 394 } 460 395 … … 462 397 public String getFlashPixVersionDescription() 463 398 { 464 int[] ints = _directory.getIntArray(ExifSubIFDDirectory.TAG_FLASHPIX_VERSION); 465 if (ints==null) 466 return null; 467 return ExifSubIFDDescriptor.convertBytesToVersionString(ints, 2); 399 return getVersionBytesDescription(TAG_FLASHPIX_VERSION, 2); 468 400 } 469 401 … … 471 403 public String getSceneTypeDescription() 472 404 { 473 Integer sceneType = _directory.getInteger(ExifSubIFDDirectory.TAG_SCENE_TYPE); 474 if (sceneType==null) 475 return null; 476 return sceneType == 1 477 ? "Directly photographed image" 478 : "Unknown (" + sceneType + ")"; 405 return getIndexedDescription(TAG_SCENE_TYPE, 406 1, 407 "Directly photographed image" 408 ); 479 409 } 480 410 … … 482 412 public String getFileSourceDescription() 483 413 { 484 Integer fileSource = _directory.getInteger(ExifSubIFDDirectory.TAG_FILE_SOURCE);485 if (fileSource==null)486 return null;487 return fileSource == 3488 ?"Digital Still Camera (DSC)"489 : "Unknown (" + fileSource + ")";414 return getIndexedDescription(TAG_FILE_SOURCE, 415 1, 416 "Film Scanner", 417 "Reflection Print Scanner", 418 "Digital Still Camera (DSC)" 419 ); 490 420 } 491 421 … … 493 423 public String getExposureBiasDescription() 494 424 { 495 Rational value = _directory.getRational( ExifSubIFDDirectory.TAG_EXPOSURE_BIAS);496 if (value ==null)425 Rational value = _directory.getRational(TAG_EXPOSURE_BIAS); 426 if (value == null) 497 427 return null; 498 428 return value.toSimpleString(true) + " EV"; … … 502 432 public String getMaxApertureValueDescription() 503 433 { 504 Double aperture = _directory.getDoubleObject( ExifSubIFDDirectory.TAG_MAX_APERTURE);505 if (aperture ==null)434 Double aperture = _directory.getDoubleObject(TAG_MAX_APERTURE); 435 if (aperture == null) 506 436 return null; 507 437 double fStop = PhotographicConversions.apertureToFStop(aperture); … … 512 442 public String getApertureValueDescription() 513 443 { 514 Double aperture = _directory.getDoubleObject( ExifSubIFDDirectory.TAG_APERTURE);515 if (aperture ==null)444 Double aperture = _directory.getDoubleObject(TAG_APERTURE); 445 if (aperture == null) 516 446 return null; 517 447 double fStop = PhotographicConversions.apertureToFStop(aperture); … … 522 452 public String getExposureProgramDescription() 523 453 { 524 // '1' means manual control, '2' program normal, '3' aperture priority, 525 // '4' shutter priority, '5' program creative (slow program), 526 // '6' program action(high-speed program), '7' portrait mode, '8' landscape mode. 527 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXPOSURE_PROGRAM); 528 if (value==null) 529 return null; 530 switch (value) { 531 case 1: return "Manual control"; 532 case 2: return "Program normal"; 533 case 3: return "Aperture priority"; 534 case 4: return "Shutter priority"; 535 case 5: return "Program creative (slow program)"; 536 case 6: return "Program action (high-speed program)"; 537 case 7: return "Portrait mode"; 538 case 8: return "Landscape mode"; 539 default: 540 return "Unknown program (" + value + ")"; 541 } 454 return getIndexedDescription(TAG_EXPOSURE_PROGRAM, 455 1, 456 "Manual control", 457 "Program normal", 458 "Aperture priority", 459 "Shutter priority", 460 "Program creative (slow program)", 461 "Program action (high-speed program)", 462 "Portrait mode", 463 "Landscape mode" 464 ); 542 465 } 543 466 … … 545 468 public String getYCbCrSubsamplingDescription() 546 469 { 547 int[] positions = _directory.getIntArray( ExifSubIFDDirectory.TAG_YCBCR_SUBSAMPLING);548 if (positions ==null)470 int[] positions = _directory.getIntArray(TAG_YCBCR_SUBSAMPLING); 471 if (positions == null) 549 472 return null; 550 473 if (positions[0] == 2 && positions[1] == 1) { … … 564 487 // pixel. If value is '2', Y/Cb/Cr value is separated and stored to Y plane/Cb plane/Cr 565 488 // plane format. 566 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_PLANAR_CONFIGURATION); 567 if (value==null) 568 return null; 569 switch (value) { 570 case 1: return "Chunky (contiguous for each subsampling pixel)"; 571 case 2: return "Separate (Y-plane/Cb-plane/Cr-plane format)"; 572 default: 573 return "Unknown configuration"; 574 } 489 return getIndexedDescription(TAG_PLANAR_CONFIGURATION, 490 1, 491 "Chunky (contiguous for each subsampling pixel)", 492 "Separate (Y-plane/Cb-plane/Cr-plane format)" 493 ); 575 494 } 576 495 … … 578 497 public String getSamplesPerPixelDescription() 579 498 { 580 String value = _directory.getString( ExifSubIFDDirectory.TAG_SAMPLES_PER_PIXEL);581 return value ==null ? null : value + " samples/pixel";499 String value = _directory.getString(TAG_SAMPLES_PER_PIXEL); 500 return value == null ? null : value + " samples/pixel"; 582 501 } 583 502 … … 585 504 public String getRowsPerStripDescription() 586 505 { 587 final String value = _directory.getString( ExifSubIFDDirectory.TAG_ROWS_PER_STRIP);588 return value ==null ? null : value + " rows/strip";506 final String value = _directory.getString(TAG_ROWS_PER_STRIP); 507 return value == null ? null : value + " rows/strip"; 589 508 } 590 509 … … 592 511 public String getStripByteCountsDescription() 593 512 { 594 final String value = _directory.getString( ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS);595 return value ==null ? null : value + " bytes";513 final String value = _directory.getString(TAG_STRIP_BYTE_COUNTS); 514 return value == null ? null : value + " bytes"; 596 515 } 597 516 … … 600 519 { 601 520 // Shows the color space of the image data components 602 Integer value = _directory.getInteger( ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION);603 if (value ==null)521 Integer value = _directory.getInteger(TAG_PHOTOMETRIC_INTERPRETATION); 522 if (value == null) 604 523 return null; 605 524 switch (value) { … … 626 545 public String getBitsPerSampleDescription() 627 546 { 628 String value = _directory.getString( ExifSubIFDDirectory.TAG_BITS_PER_SAMPLE);629 return value ==null ? null : value + " bits/component/pixel";547 String value = _directory.getString(TAG_BITS_PER_SAMPLE); 548 return value == null ? null : value + " bits/component/pixel"; 630 549 } 631 550 … … 633 552 public String getFocalPlaneXResolutionDescription() 634 553 { 635 Rational rational = _directory.getRational( ExifSubIFDDirectory.TAG_FOCAL_PLANE_X_RES);636 if (rational ==null)554 Rational rational = _directory.getRational(TAG_FOCAL_PLANE_X_RESOLUTION); 555 if (rational == null) 637 556 return null; 638 557 final String unit = getFocalPlaneResolutionUnitDescription(); 639 558 return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals) 640 + (unit ==null ? "" : " " + unit.toLowerCase());559 + (unit == null ? "" : " " + unit.toLowerCase()); 641 560 } 642 561 … … 644 563 public String getFocalPlaneYResolutionDescription() 645 564 { 646 Rational rational = _directory.getRational( ExifSubIFDDirectory.TAG_FOCAL_PLANE_Y_RES);647 if (rational ==null)565 Rational rational = _directory.getRational(TAG_FOCAL_PLANE_Y_RESOLUTION); 566 if (rational == null) 648 567 return null; 649 568 final String unit = getFocalPlaneResolutionUnitDescription(); 650 569 return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals) 651 + (unit ==null ? "" : " " + unit.toLowerCase());570 + (unit == null ? "" : " " + unit.toLowerCase()); 652 571 } 653 572 … … 655 574 public String getFocalPlaneResolutionUnitDescription() 656 575 { 657 // Unit of FocalPlaneXResolution/FocalPlaneYResolution. '1' means no-unit, 658 // '2' inch, '3' centimeter. 659 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_FOCAL_PLANE_UNIT); 660 if (value==null) 661 return null; 662 switch (value) { 663 case 1: return "(No unit)"; 664 case 2: return "Inches"; 665 case 3: return "cm"; 666 default: 667 return ""; 668 } 576 // Unit of FocalPlaneXResolution/FocalPlaneYResolution. 577 // '1' means no-unit, '2' inch, '3' centimeter. 578 return getIndexedDescription(TAG_FOCAL_PLANE_RESOLUTION_UNIT, 579 1, 580 "(No unit)", 581 "Inches", 582 "cm" 583 ); 669 584 } 670 585 … … 672 587 public String getExifImageWidthDescription() 673 588 { 674 final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXIF_IMAGE_WIDTH); 675 if (value==null) 676 return null; 677 return value + " pixels"; 589 final Integer value = _directory.getInteger(TAG_EXIF_IMAGE_WIDTH); 590 return value == null ? null : value + " pixels"; 678 591 } 679 592 … … 681 594 public String getExifImageHeightDescription() 682 595 { 683 final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXIF_IMAGE_HEIGHT); 684 if (value==null) 685 return null; 686 return value + " pixels"; 596 final Integer value = _directory.getInteger(TAG_EXIF_IMAGE_HEIGHT); 597 return value == null ? null : value + " pixels"; 687 598 } 688 599 … … 690 601 public String getColorSpaceDescription() 691 602 { 692 final Integer value = _directory.getInteger( ExifSubIFDDirectory.TAG_COLOR_SPACE);693 if (value ==null)603 final Integer value = _directory.getInteger(TAG_COLOR_SPACE); 604 if (value == null) 694 605 return null; 695 606 if (value == 1) … … 697 608 if (value == 65535) 698 609 return "Undefined"; 699 return "Unknown ";610 return "Unknown (" + value + ")"; 700 611 } 701 612 … … 703 614 public String getFocalLengthDescription() 704 615 { 705 Rational value = _directory.getRational( ExifSubIFDDirectory.TAG_FOCAL_LENGTH);706 if (value ==null)616 Rational value = _directory.getRational(TAG_FOCAL_LENGTH); 617 if (value == null) 707 618 return null; 708 619 java.text.DecimalFormat formatter = new DecimalFormat("0.0##"); … … 714 625 { 715 626 /* 716 * This is a bit mask.627 * This is a bit mask. 717 628 * 0 = flash fired 718 629 * 1 = return detected … … 724 635 */ 725 636 726 final Integer value = _directory.getInteger( ExifSubIFDDirectory.TAG_FLASH);727 728 if (value ==null)637 final Integer value = _directory.getInteger(TAG_FLASH); 638 639 if (value == null) 729 640 return null; 730 641 731 642 StringBuilder sb = new StringBuilder(); 732 643 733 if ((value & 0x1) !=0)644 if ((value & 0x1) != 0) 734 645 sb.append("Flash fired"); 735 646 else … … 737 648 738 649 // check if we're able to detect a return, before we mention it 739 if ((value & 0x4)!=0) 740 { 741 if ((value & 0x2)!=0) 650 if ((value & 0x4) != 0) { 651 if ((value & 0x2) != 0) 742 652 sb.append(", return detected"); 743 653 else … … 745 655 } 746 656 747 if ((value & 0x10) !=0)657 if ((value & 0x10) != 0) 748 658 sb.append(", auto"); 749 659 750 if ((value & 0x40) !=0)660 if ((value & 0x40) != 0) 751 661 sb.append(", red-eye reduction"); 752 662 … … 760 670 // '17' standard light A, '18' standard light B, '19' standard light C, '20' D55, 761 671 // '21' D65, '22' D75, '255' other. 762 final Integer value = _directory.getInteger( ExifSubIFDDirectory.TAG_WHITE_BALANCE);763 if (value ==null)672 final Integer value = _directory.getInteger(TAG_WHITE_BALANCE); 673 if (value == null) 764 674 return null; 765 675 switch (value) { … … 786 696 // '0' means unknown, '1' average, '2' center weighted average, '3' spot 787 697 // '4' multi-spot, '5' multi-segment, '6' partial, '255' other 788 Integer value = _directory.getInteger( ExifSubIFDDirectory.TAG_METERING_MODE);789 if (value ==null)698 Integer value = _directory.getInteger(TAG_METERING_MODE); 699 if (value == null) 790 700 return null; 791 701 switch (value) { … … 806 716 public String getSubjectDistanceDescription() 807 717 { 808 Rational value = _directory.getRational( ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE);809 if (value ==null)718 Rational value = _directory.getRational(TAG_SUBJECT_DISTANCE); 719 if (value == null) 810 720 return null; 811 721 java.text.DecimalFormat formatter = new DecimalFormat("0.0##"); … … 816 726 public String getCompressedAverageBitsPerPixelDescription() 817 727 { 818 Rational value = _directory.getRational( ExifSubIFDDirectory.TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL);819 if (value ==null)728 Rational value = _directory.getRational(TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL); 729 if (value == null) 820 730 return null; 821 731 String ratio = value.toSimpleString(_allowDecimalRepresentationOfRationals); 822 if (value.isInteger() && value.intValue() == 1) { 823 return ratio + " bit/pixel"; 824 } else { 825 return ratio + " bits/pixel"; 826 } 732 return value.isInteger() && value.intValue() == 1 733 ? ratio + " bit/pixel" 734 : ratio + " bits/pixel"; 827 735 } 828 736 … … 830 738 public String getExposureTimeDescription() 831 739 { 832 String value = _directory.getString( ExifSubIFDDirectory.TAG_EXPOSURE_TIME);833 return value ==null ? null : value + " sec";740 String value = _directory.getString(TAG_EXPOSURE_TIME); 741 return value == null ? null : value + " sec"; 834 742 } 835 743 … … 847 755 // description (spotted bug using a Canon EOS 300D) 848 756 // thanks also to Gli Blr for spotting this bug 849 Float apexValue = _directory.getFloatObject( ExifSubIFDDirectory.TAG_SHUTTER_SPEED);850 if (apexValue ==null)851 return null; 852 if (apexValue <=1) {853 float apexPower = (float)(1 /(Math.exp(apexValue*Math.log(2))));757 Float apexValue = _directory.getFloatObject(TAG_SHUTTER_SPEED); 758 if (apexValue == null) 759 return null; 760 if (apexValue <= 1) { 761 float apexPower = (float)(1 / (Math.exp(apexValue * Math.log(2)))); 854 762 long apexPower10 = Math.round((double)apexPower * 10.0); 855 float fApexPower = (float) 763 float fApexPower = (float)apexPower10 / 10.0f; 856 764 return fApexPower + " sec"; 857 765 } else { 858 int apexPower = (int)((Math.exp(apexValue *Math.log(2))));766 int apexPower = (int)((Math.exp(apexValue * Math.log(2)))); 859 767 return "1/" + apexPower + " sec"; 860 768 } … … 879 787 return sb.toString(); 880 788 */ 881 882 789 } 883 790 … … 885 792 public String getFNumberDescription() 886 793 { 887 Rational value = _directory.getRational( ExifSubIFDDirectory.TAG_FNUMBER);888 if (value ==null)794 Rational value = _directory.getRational(TAG_FNUMBER); 795 if (value == null) 889 796 return null; 890 797 return "F" + SimpleDecimalFormatter.format(value.doubleValue()); … … 897 804 // '4' Three-chip color area sensor, '5' Color sequential area sensor 898 805 // '7' Trilinear sensor '8' Color sequential linear sensor, 'Other' reserved 899 Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SENSING_METHOD); 900 if (value==null) 901 return null; 902 switch (value) { 903 case 1: return "(Not defined)"; 904 case 2: return "One-chip color area sensor"; 905 case 3: return "Two-chip color area sensor"; 906 case 4: return "Three-chip color area sensor"; 907 case 5: return "Color sequential area sensor"; 908 case 7: return "Trilinear sensor"; 909 case 8: return "Color sequential linear sensor"; 910 default: 911 return ""; 912 } 806 return getIndexedDescription(TAG_SENSING_METHOD, 807 1, 808 "(Not defined)", 809 "One-chip color area sensor", 810 "Two-chip color area sensor", 811 "Three-chip color area sensor", 812 "Color sequential area sensor", 813 null, 814 "Trilinear sensor", 815 "Color sequential linear sensor" 816 ); 913 817 } 914 818 … … 916 820 public String getComponentConfigurationDescription() 917 821 { 918 int[] components = _directory.getIntArray( ExifSubIFDDirectory.TAG_COMPONENTS_CONFIGURATION);919 if (components ==null)822 int[] components = _directory.getIntArray(TAG_COMPONENTS_CONFIGURATION); 823 if (components == null) 920 824 return null; 921 825 String[] componentStrings = {"", "Y", "Cb", "Cr", "R", "G", "B"}; -
trunk/src/com/drew/metadata/exif/ExifSubIFDDirectory.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.exif; … … 29 29 * Describes Exif tags from the SubIFD directory. 30 30 * 31 * @author Drew Noakes http ://drewnoakes.com31 * @author Drew Noakes https://drewnoakes.com 32 32 */ 33 33 public class ExifSubIFDDirectory extends Directory … … 133 133 /** 134 134 * Indicates the Opto-Electric Conversion Function (OECF) specified in ISO 14524. 135 * <p />135 * <p> 136 136 * OECF is the relationship between the camera optical input and the image values. 137 * <p />137 * <p> 138 138 * The values are: 139 139 * <ul> … … 260 260 */ 261 261 public static final int TAG_FOCAL_LENGTH = 0x920A; 262 263 /** 264 * This tag holds the Exif Makernote. Makernotes are free to be in any format, though they are often IFDs. 265 * To determine the format, we consider the starting bytes of the makernote itself and sometimes the 266 * camera model and make. 267 * <p> 268 * The component count for this tag includes all of the bytes needed for the makernote. 269 */ 270 public static final int TAG_MAKERNOTE = 0x927C; 271 262 272 public static final int TAG_USER_COMMENT = 0x9286; 273 263 274 public static final int TAG_SUBSECOND_TIME = 0x9290; 264 275 public static final int TAG_SUBSECOND_TIME_ORIGINAL = 0x9291; 265 276 public static final int TAG_SUBSECOND_TIME_DIGITIZED = 0x9292; 277 266 278 public static final int TAG_FLASHPIX_VERSION = 0xA000; 267 279 /** … … 274 286 public static final int TAG_EXIF_IMAGE_HEIGHT = 0xA003; 275 287 public static final int TAG_RELATED_SOUND_FILE = 0xA004; 276 public static final int TAG_FOCAL_PLANE_X_RES = 0xA20E; 277 public static final int TAG_FOCAL_PLANE_Y_RES = 0xA20F; 288 289 /** This tag is a pointer to the Exif Interop IFD. */ 290 public static final int TAG_INTEROP_OFFSET = 0xA005; 291 292 public static final int TAG_FOCAL_PLANE_X_RESOLUTION = 0xA20E; 293 public static final int TAG_FOCAL_PLANE_Y_RESOLUTION = 0xA20F; 278 294 /** 279 295 * Unit of FocalPlaneXResolution/FocalPlaneYResolution. '1' means no-unit, … … 285 301 * been changed to use value '2' but it doesn't match to actual value also. 286 302 */ 287 public static final int TAG_FOCAL_PLANE_ UNIT = 0xA210;303 public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT = 0xA210; 288 304 public static final int TAG_EXPOSURE_INDEX = 0xA215; 289 305 public static final int TAG_SENSING_METHOD = 0xA217; … … 512 528 _tagNameMap.put(0x0200, "JPEG Proc"); 513 529 _tagNameMap.put(TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL, "Compressed Bits Per Pixel"); 514 _tagNameMap.put( 0x927C, "Maker Note");515 _tagNameMap.put( 0xA005, "Interoperability Offset");530 _tagNameMap.put(TAG_MAKERNOTE, "Makernote"); 531 _tagNameMap.put(TAG_INTEROP_OFFSET, "Interoperability Offset"); 516 532 517 533 _tagNameMap.put(TAG_NEW_SUBFILE_TYPE, "New Subfile Type"); … … 586 602 _tagNameMap.put(TAG_SPATIAL_FREQ_RESPONSE_2, "Spatial Frequency Response"); 587 603 // 0x920E in TIFF/EP 588 _tagNameMap.put(TAG_FOCAL_PLANE_X_RES , "Focal Plane X Resolution");604 _tagNameMap.put(TAG_FOCAL_PLANE_X_RESOLUTION, "Focal Plane X Resolution"); 589 605 // 0x920F in TIFF/EP 590 _tagNameMap.put(TAG_FOCAL_PLANE_Y_RES , "Focal Plane Y Resolution");606 _tagNameMap.put(TAG_FOCAL_PLANE_Y_RESOLUTION, "Focal Plane Y Resolution"); 591 607 // 0x9210 in TIFF/EP 592 _tagNameMap.put(TAG_FOCAL_PLANE_ UNIT, "Focal Plane Resolution Unit");608 _tagNameMap.put(TAG_FOCAL_PLANE_RESOLUTION_UNIT, "Focal Plane Resolution Unit"); 593 609 // 0x9214 in TIFF/EP 594 610 _tagNameMap.put(TAG_SUBJECT_LOCATION_2, "Subject Location"); … … 614 630 _tagNameMap.put(TAG_SUBJECT_DISTANCE_RANGE, "Subject Distance Range"); 615 631 _tagNameMap.put(TAG_IMAGE_UNIQUE_ID, "Unique Image ID"); 616 632 617 633 _tagNameMap.put(TAG_CAMERA_OWNER_NAME, "Camera Owner Name"); 618 634 _tagNameMap.put(TAG_BODY_SERIAL_NUMBER, "Body Serial Number"); … … 634 650 } 635 651 652 @Override 636 653 @NotNull 637 654 public String getName() … … 640 657 } 641 658 659 @Override 642 660 @NotNull 643 661 protected HashMap<Integer, String> getTagNameMap() -
trunk/src/com/drew/metadata/exif/ExifThumbnailDescriptor.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 … … 27 27 import com.drew.metadata.TagDescriptor; 28 28 29 import static com.drew.metadata.exif.ExifThumbnailDirectory.*; 30 29 31 /** 30 * Provides human-readable string representations of tag values stored in a <code>ExifThumbnailDirectory</code>.31 * 32 * @author Drew Noakes http ://drewnoakes.com32 * Provides human-readable string representations of tag values stored in a {@link ExifThumbnailDirectory}. 33 * 34 * @author Drew Noakes https://drewnoakes.com 33 35 */ 34 36 public class ExifThumbnailDescriptor extends TagDescriptor<ExifThumbnailDirectory> … … 52 54 53 55 /** 54 * Returns a descriptive value of the thespecified tag for this image.56 * Returns a descriptive value of the specified tag for this image. 55 57 * Where possible, known values will be substituted here in place of the raw 56 58 * tokens actually kept in the Exif segment. If no substitution is 57 59 * available, the value provided by getString(int) will be returned. 60 * 58 61 * @param tagType the tag to find a description for 59 62 * @return a description of the image's value for the specified tag, or 60 63 * <code>null</code> if the tag hasn't been defined. 61 64 */ 65 @Override 62 66 @Nullable 63 67 public String getDescription(int tagType) 64 68 { 65 69 switch (tagType) { 66 case ExifThumbnailDirectory.TAG_ORIENTATION:70 case TAG_ORIENTATION: 67 71 return getOrientationDescription(); 68 case ExifThumbnailDirectory.TAG_RESOLUTION_UNIT:72 case TAG_RESOLUTION_UNIT: 69 73 return getResolutionDescription(); 70 case ExifThumbnailDirectory.TAG_YCBCR_POSITIONING:74 case TAG_YCBCR_POSITIONING: 71 75 return getYCbCrPositioningDescription(); 72 case ExifThumbnailDirectory.TAG_X_RESOLUTION:76 case TAG_X_RESOLUTION: 73 77 return getXResolutionDescription(); 74 case ExifThumbnailDirectory.TAG_Y_RESOLUTION:78 case TAG_Y_RESOLUTION: 75 79 return getYResolutionDescription(); 76 case ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET:80 case TAG_THUMBNAIL_OFFSET: 77 81 return getThumbnailOffsetDescription(); 78 case ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH:82 case TAG_THUMBNAIL_LENGTH: 79 83 return getThumbnailLengthDescription(); 80 case ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_WIDTH:84 case TAG_THUMBNAIL_IMAGE_WIDTH: 81 85 return getThumbnailImageWidthDescription(); 82 case ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT:86 case TAG_THUMBNAIL_IMAGE_HEIGHT: 83 87 return getThumbnailImageHeightDescription(); 84 case ExifThumbnailDirectory.TAG_BITS_PER_SAMPLE:88 case TAG_BITS_PER_SAMPLE: 85 89 return getBitsPerSampleDescription(); 86 case ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION:90 case TAG_THUMBNAIL_COMPRESSION: 87 91 return getCompressionDescription(); 88 case ExifThumbnailDirectory.TAG_PHOTOMETRIC_INTERPRETATION:92 case TAG_PHOTOMETRIC_INTERPRETATION: 89 93 return getPhotometricInterpretationDescription(); 90 case ExifThumbnailDirectory.TAG_ROWS_PER_STRIP:94 case TAG_ROWS_PER_STRIP: 91 95 return getRowsPerStripDescription(); 92 case ExifThumbnailDirectory.TAG_STRIP_BYTE_COUNTS:96 case TAG_STRIP_BYTE_COUNTS: 93 97 return getStripByteCountsDescription(); 94 case ExifThumbnailDirectory.TAG_SAMPLES_PER_PIXEL:98 case TAG_SAMPLES_PER_PIXEL: 95 99 return getSamplesPerPixelDescription(); 96 case ExifThumbnailDirectory.TAG_PLANAR_CONFIGURATION:100 case TAG_PLANAR_CONFIGURATION: 97 101 return getPlanarConfigurationDescription(); 98 case ExifThumbnailDirectory.TAG_YCBCR_SUBSAMPLING:102 case TAG_YCBCR_SUBSAMPLING: 99 103 return getYCbCrSubsamplingDescription(); 100 case ExifThumbnailDirectory.TAG_REFERENCE_BLACK_WHITE:104 case TAG_REFERENCE_BLACK_WHITE: 101 105 return getReferenceBlackWhiteDescription(); 102 106 default: … … 108 112 public String getReferenceBlackWhiteDescription() 109 113 { 110 int[] ints = _directory.getIntArray( ExifThumbnailDirectory.TAG_REFERENCE_BLACK_WHITE);111 if (ints ==null)114 int[] ints = _directory.getIntArray(TAG_REFERENCE_BLACK_WHITE); 115 if (ints == null || ints.length < 6) 112 116 return null; 113 117 int blackR = ints[0]; … … 117 121 int blackB = ints[4]; 118 122 int whiteB = ints[5]; 119 return "[" + blackR + "," + blackG + "," + blackB + "] " + 120 "[" + whiteR + "," + whiteG + "," + whiteB + "]"; 123 return String.format("[%d,%d,%d] [%d,%d,%d]", blackR, blackG, blackB, whiteR, whiteG, whiteB); 121 124 } 122 125 … … 124 127 public String getYCbCrSubsamplingDescription() 125 128 { 126 int[] positions = _directory.getIntArray( ExifThumbnailDirectory.TAG_YCBCR_SUBSAMPLING);127 if (positions ==null || positions.length < 2)129 int[] positions = _directory.getIntArray(TAG_YCBCR_SUBSAMPLING); 130 if (positions == null || positions.length < 2) 128 131 return null; 129 132 if (positions[0] == 2 && positions[1] == 1) { … … 143 146 // pixel. If value is '2', Y/Cb/Cr value is separated and stored to Y plane/Cb plane/Cr 144 147 // plane format. 145 Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_PLANAR_CONFIGURATION); 146 if (value==null) 147 return null; 148 switch (value) { 149 case 1: return "Chunky (contiguous for each subsampling pixel)"; 150 case 2: return "Separate (Y-plane/Cb-plane/Cr-plane format)"; 151 default: 152 return "Unknown configuration"; 153 } 148 return getIndexedDescription(TAG_PLANAR_CONFIGURATION, 149 1, 150 "Chunky (contiguous for each subsampling pixel)", 151 "Separate (Y-plane/Cb-plane/Cr-plane format)" 152 ); 154 153 } 155 154 … … 157 156 public String getSamplesPerPixelDescription() 158 157 { 159 String value = _directory.getString( ExifThumbnailDirectory.TAG_SAMPLES_PER_PIXEL);160 return value ==null ? null : value + " samples/pixel";158 String value = _directory.getString(TAG_SAMPLES_PER_PIXEL); 159 return value == null ? null : value + " samples/pixel"; 161 160 } 162 161 … … 164 163 public String getRowsPerStripDescription() 165 164 { 166 final String value = _directory.getString( ExifThumbnailDirectory.TAG_ROWS_PER_STRIP);167 return value ==null ? null : value + " rows/strip";165 final String value = _directory.getString(TAG_ROWS_PER_STRIP); 166 return value == null ? null : value + " rows/strip"; 168 167 } 169 168 … … 171 170 public String getStripByteCountsDescription() 172 171 { 173 final String value = _directory.getString( ExifThumbnailDirectory.TAG_STRIP_BYTE_COUNTS);174 return value ==null ? null : value + " bytes";172 final String value = _directory.getString(TAG_STRIP_BYTE_COUNTS); 173 return value == null ? null : value + " bytes"; 175 174 } 176 175 … … 179 178 { 180 179 // Shows the color space of the image data components 181 Integer value = _directory.getInteger( ExifThumbnailDirectory.TAG_PHOTOMETRIC_INTERPRETATION);182 if (value ==null)180 Integer value = _directory.getInteger(TAG_PHOTOMETRIC_INTERPRETATION); 181 if (value == null) 183 182 return null; 184 183 switch (value) { … … 205 204 public String getCompressionDescription() 206 205 { 207 Integer value = _directory.getInteger( ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION);208 if (value ==null)206 Integer value = _directory.getInteger(TAG_THUMBNAIL_COMPRESSION); 207 if (value == null) 209 208 return null; 210 209 switch (value) { … … 244 243 public String getBitsPerSampleDescription() 245 244 { 246 String value = _directory.getString( ExifThumbnailDirectory.TAG_BITS_PER_SAMPLE);247 return value ==null ? null : value + " bits/component/pixel";245 String value = _directory.getString(TAG_BITS_PER_SAMPLE); 246 return value == null ? null : value + " bits/component/pixel"; 248 247 } 249 248 … … 251 250 public String getThumbnailImageWidthDescription() 252 251 { 253 String value = _directory.getString( ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_WIDTH);254 return value ==null ? null : value + " pixels";252 String value = _directory.getString(TAG_THUMBNAIL_IMAGE_WIDTH); 253 return value == null ? null : value + " pixels"; 255 254 } 256 255 … … 258 257 public String getThumbnailImageHeightDescription() 259 258 { 260 String value = _directory.getString( ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT);261 return value ==null ? null : value + " pixels";259 String value = _directory.getString(TAG_THUMBNAIL_IMAGE_HEIGHT); 260 return value == null ? null : value + " pixels"; 262 261 } 263 262 … … 265 264 public String getThumbnailLengthDescription() 266 265 { 267 String value = _directory.getString( ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);268 return value ==null ? null : value + " bytes";266 String value = _directory.getString(TAG_THUMBNAIL_LENGTH); 267 return value == null ? null : value + " bytes"; 269 268 } 270 269 … … 272 271 public String getThumbnailOffsetDescription() 273 272 { 274 String value = _directory.getString( ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);275 return value ==null ? null : value + " bytes";273 String value = _directory.getString(TAG_THUMBNAIL_OFFSET); 274 return value == null ? null : value + " bytes"; 276 275 } 277 276 … … 279 278 public String getYResolutionDescription() 280 279 { 281 Rational value = _directory.getRational( ExifThumbnailDirectory.TAG_Y_RESOLUTION);282 if (value ==null)280 Rational value = _directory.getRational(TAG_Y_RESOLUTION); 281 if (value == null) 283 282 return null; 284 283 final String unit = getResolutionDescription(); 285 284 return value.toSimpleString(_allowDecimalRepresentationOfRationals) + 286 287 (unit==null ? "unit" : unit.toLowerCase());285 " dots per " + 286 (unit == null ? "unit" : unit.toLowerCase()); 288 287 } 289 288 … … 291 290 public String getXResolutionDescription() 292 291 { 293 Rational value = _directory.getRational( ExifThumbnailDirectory.TAG_X_RESOLUTION);294 if (value ==null)292 Rational value = _directory.getRational(TAG_X_RESOLUTION); 293 if (value == null) 295 294 return null; 296 295 final String unit = getResolutionDescription(); 297 296 return value.toSimpleString(_allowDecimalRepresentationOfRationals) + 298 299 (unit==null ? "unit" : unit.toLowerCase());297 " dots per " + 298 (unit == null ? "unit" : unit.toLowerCase()); 300 299 } 301 300 … … 303 302 public String getYCbCrPositioningDescription() 304 303 { 305 Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_YCBCR_POSITIONING); 306 if (value==null) 307 return null; 308 switch (value) { 309 case 1: return "Center of pixel array"; 310 case 2: return "Datum point"; 311 default: 312 return String.valueOf(value); 313 } 304 return getIndexedDescription(TAG_YCBCR_POSITIONING, 1, "Center of pixel array", "Datum point"); 314 305 } 315 306 … … 317 308 public String getOrientationDescription() 318 309 { 319 Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_ORIENTATION); 320 if (value==null) 321 return null; 322 switch (value) { 323 case 1: return "Top, left side (Horizontal / normal)"; 324 case 2: return "Top, right side (Mirror horizontal)"; 325 case 3: return "Bottom, right side (Rotate 180)"; 326 case 4: return "Bottom, left side (Mirror vertical)"; 327 case 5: return "Left side, top (Mirror horizontal and rotate 270 CW)"; 328 case 6: return "Right side, top (Rotate 90 CW)"; 329 case 7: return "Right side, bottom (Mirror horizontal and rotate 90 CW)"; 330 case 8: return "Left side, bottom (Rotate 270 CW)"; 331 default: 332 return String.valueOf(value); 333 } 310 return getIndexedDescription(TAG_ORIENTATION, 1, 311 "Top, left side (Horizontal / normal)", 312 "Top, right side (Mirror horizontal)", 313 "Bottom, right side (Rotate 180)", 314 "Bottom, left side (Mirror vertical)", 315 "Left side, top (Mirror horizontal and rotate 270 CW)", 316 "Right side, top (Rotate 90 CW)", 317 "Right side, bottom (Mirror horizontal and rotate 90 CW)", 318 "Left side, bottom (Rotate 270 CW)"); 334 319 } 335 320 … … 338 323 { 339 324 // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch) 340 Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_RESOLUTION_UNIT); 341 if (value==null) 342 return null; 343 switch (value) { 344 case 1: return "(No unit)"; 345 case 2: return "Inch"; 346 case 3: return "cm"; 347 default: 348 return ""; 349 } 325 return getIndexedDescription(TAG_RESOLUTION_UNIT, 1, "(No unit)", "Inch", "cm"); 350 326 } 351 327 } -
trunk/src/com/drew/metadata/exif/ExifThumbnailDirectory.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 … … 34 34 * One of several Exif directories. Otherwise known as IFD1, this directory holds information about an embedded thumbnail image. 35 35 * 36 * @author Drew Noakes http ://drewnoakes.com36 * @author Drew Noakes https://drewnoakes.com 37 37 */ 38 38 public class ExifThumbnailDirectory extends Directory … … 57 57 * 7 = JPEG 58 58 * 8 = Adobe Deflate 59 * 9 = JBIG B& W59 * 9 = JBIG B&W 60 60 * 10 = JBIG Color 61 61 * 32766 = Next … … 98 98 public static final int TAG_PHOTOMETRIC_INTERPRETATION = 0x0106; 99 99 100 /** The position in the file of raster data. */ 100 /** 101 * The position in the file of raster data. 102 */ 101 103 public static final int TAG_STRIP_OFFSETS = 0x0111; 102 104 public static final int TAG_ORIENTATION = 0x0112; 103 /** Each pixel is composed of this many samples. */ 105 /** 106 * Each pixel is composed of this many samples. 107 */ 104 108 public static final int TAG_SAMPLES_PER_PIXEL = 0x0115; 105 /** The raster is codified by a single block of data holding this many rows. */ 109 /** 110 * The raster is codified by a single block of data holding this many rows. 111 */ 106 112 public static final int TAG_ROWS_PER_STRIP = 0x116; 107 /** The size of the raster data in bytes. */ 113 /** 114 * The size of the raster data in bytes. 115 */ 108 116 public static final int TAG_STRIP_BYTE_COUNTS = 0x0117; 109 117 /** … … 117 125 public static final int TAG_PLANAR_CONFIGURATION = 0x011C; 118 126 public static final int TAG_RESOLUTION_UNIT = 0x0128; 119 /** The offset to thumbnail image bytes. */ 127 /** 128 * The offset to thumbnail image bytes. 129 */ 120 130 public static final int TAG_THUMBNAIL_OFFSET = 0x0201; 121 /** The size of the thumbnail image data in bytes. */ 131 /** 132 * The size of the thumbnail image data in bytes. 133 */ 122 134 public static final int TAG_THUMBNAIL_LENGTH = 0x0202; 123 135 public static final int TAG_YCBCR_COEFFICIENTS = 0x0211; … … 129 141 protected static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>(); 130 142 131 static 132 { 143 static { 133 144 _tagNameMap.put(TAG_THUMBNAIL_IMAGE_WIDTH, "Thumbnail Image Width"); 134 145 _tagNameMap.put(TAG_THUMBNAIL_IMAGE_HEIGHT, "Thumbnail Image Height"); … … 161 172 } 162 173 174 @Override 163 175 @NotNull 164 176 public String getName() … … 167 179 } 168 180 181 @Override 169 182 @NotNull 170 183 protected HashMap<Integer, String> getTagNameMap() … … 193 206 byte[] data = _thumbnailData; 194 207 195 if (data ==null)208 if (data == null) 196 209 throw new MetadataException("No thumbnail data exists."); 197 210 … … 201 214 stream.write(data); 202 215 } finally { 203 if (stream !=null)216 if (stream != null) 204 217 stream.close(); 205 218 } -
trunk/src/com/drew/metadata/exif/GpsDescriptor.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.exif; … … 29 29 import java.text.DecimalFormat; 30 30 31 import static com.drew.metadata.exif.GpsDirectory.*; 32 31 33 /** 32 * Provides human-readable string representations of tag values stored in a <code>GpsDirectory</code>.33 * 34 * @author Drew Noakes http ://drewnoakes.com34 * Provides human-readable string representations of tag values stored in a {@link GpsDirectory}. 35 * 36 * @author Drew Noakes https://drewnoakes.com 35 37 */ 36 38 public class GpsDescriptor extends TagDescriptor<GpsDirectory> … … 41 43 } 42 44 45 @Override 43 46 @Nullable 44 47 public String getDescription(int tagType) 45 48 { 46 49 switch (tagType) { 47 case GpsDirectory.TAG_GPS_VERSION_ID:50 case TAG_VERSION_ID: 48 51 return getGpsVersionIdDescription(); 49 case GpsDirectory.TAG_GPS_ALTITUDE:52 case TAG_ALTITUDE: 50 53 return getGpsAltitudeDescription(); 51 case GpsDirectory.TAG_GPS_ALTITUDE_REF:54 case TAG_ALTITUDE_REF: 52 55 return getGpsAltitudeRefDescription(); 53 case GpsDirectory.TAG_GPS_STATUS:56 case TAG_STATUS: 54 57 return getGpsStatusDescription(); 55 case GpsDirectory.TAG_GPS_MEASURE_MODE:58 case TAG_MEASURE_MODE: 56 59 return getGpsMeasureModeDescription(); 57 case GpsDirectory.TAG_GPS_SPEED_REF:60 case TAG_SPEED_REF: 58 61 return getGpsSpeedRefDescription(); 59 case GpsDirectory.TAG_GPS_TRACK_REF:60 case GpsDirectory.TAG_GPS_IMG_DIRECTION_REF:61 case GpsDirectory.TAG_GPS_DEST_BEARING_REF:62 case TAG_TRACK_REF: 63 case TAG_IMG_DIRECTION_REF: 64 case TAG_DEST_BEARING_REF: 62 65 return getGpsDirectionReferenceDescription(tagType); 63 case GpsDirectory.TAG_GPS_TRACK:64 case GpsDirectory.TAG_GPS_IMG_DIRECTION:65 case GpsDirectory.TAG_GPS_DEST_BEARING:66 case TAG_TRACK: 67 case TAG_IMG_DIRECTION: 68 case TAG_DEST_BEARING: 66 69 return getGpsDirectionDescription(tagType); 67 case GpsDirectory.TAG_GPS_DEST_DISTANCE_REF:70 case TAG_DEST_DISTANCE_REF: 68 71 return getGpsDestinationReferenceDescription(); 69 case GpsDirectory.TAG_GPS_TIME_STAMP:72 case TAG_TIME_STAMP: 70 73 return getGpsTimeStampDescription(); 71 case GpsDirectory.TAG_GPS_LONGITUDE:74 case TAG_LONGITUDE: 72 75 // three rational numbers -- displayed in HH"MM"SS.ss 73 76 return getGpsLongitudeDescription(); 74 case GpsDirectory.TAG_GPS_LATITUDE:77 case TAG_LATITUDE: 75 78 // three rational numbers -- displayed in HH"MM"SS.ss 76 79 return getGpsLatitudeDescription(); 77 case GpsDirectory.TAG_GPS_DIFFERENTIAL:80 case TAG_DIFFERENTIAL: 78 81 return getGpsDifferentialDescription(); 79 82 default: … … 85 88 private String getGpsVersionIdDescription() 86 89 { 87 return convertBytesToVersionString(_directory.getIntArray(GpsDirectory.TAG_GPS_VERSION_ID), 1);90 return getVersionBytesDescription(TAG_VERSION_ID, 1); 88 91 } 89 92 … … 92 95 { 93 96 GeoLocation location = _directory.getGeoLocation(); 94 95 if (location == null) 96 return null; 97 98 return GeoLocation.decimalToDegreesMinutesSecondsString(location.getLatitude()); 97 return location == null ? null : GeoLocation.decimalToDegreesMinutesSecondsString(location.getLatitude()); 99 98 } 100 99 … … 103 102 { 104 103 GeoLocation location = _directory.getGeoLocation(); 105 106 if (location == null) 107 return null; 108 109 return GeoLocation.decimalToDegreesMinutesSecondsString(location.getLongitude()); 104 return location == null ? null : GeoLocation.decimalToDegreesMinutesSecondsString(location.getLongitude()); 110 105 } 111 106 … … 114 109 { 115 110 // time in hour, min, sec 116 int[] timeComponents = _directory.getIntArray(GpsDirectory.TAG_GPS_TIME_STAMP); 117 if (timeComponents==null) 118 return null; 119 StringBuilder description = new StringBuilder(); 120 description.append(timeComponents[0]); 121 description.append(":"); 122 description.append(timeComponents[1]); 123 description.append(":"); 124 description.append(timeComponents[2]); 125 description.append(" UTC"); 126 return description.toString(); 111 int[] timeComponents = _directory.getIntArray(TAG_TIME_STAMP); 112 return timeComponents == null ? null : String.format("%d:%d:%d UTC", timeComponents[0], timeComponents[1], timeComponents[2]); 127 113 } 128 114 … … 130 116 public String getGpsDestinationReferenceDescription() 131 117 { 132 final String value = _directory.getString( GpsDirectory.TAG_GPS_DEST_DISTANCE_REF);133 if (value ==null)118 final String value = _directory.getString(TAG_DEST_DISTANCE_REF); 119 if (value == null) 134 120 return null; 135 121 String distanceRef = value.trim(); … … 151 137 // provide a decimal version of rational numbers in the description, to avoid strings like "35334/199 degrees" 152 138 String value = angle != null 153 ? new DecimalFormat("0.##").format(angle.doubleValue()) 154 : _directory.getString(tagType); 155 if (value==null || value.trim().length()==0) 156 return null; 157 return value.trim() + " degrees"; 139 ? new DecimalFormat("0.##").format(angle.doubleValue()) 140 : _directory.getString(tagType); 141 return value == null || value.trim().length() == 0 ? null : value.trim() + " degrees"; 158 142 } 159 143 … … 162 146 { 163 147 final String value = _directory.getString(tagType); 164 if (value ==null)148 if (value == null) 165 149 return null; 166 150 String gpsDistRef = value.trim(); … … 177 161 public String getGpsSpeedRefDescription() 178 162 { 179 final String value = _directory.getString( GpsDirectory.TAG_GPS_SPEED_REF);180 if (value ==null)163 final String value = _directory.getString(TAG_SPEED_REF); 164 if (value == null) 181 165 return null; 182 166 String gpsSpeedRef = value.trim(); … … 195 179 public String getGpsMeasureModeDescription() 196 180 { 197 final String value = _directory.getString( GpsDirectory.TAG_GPS_MEASURE_MODE);198 if (value ==null)181 final String value = _directory.getString(TAG_MEASURE_MODE); 182 if (value == null) 199 183 return null; 200 184 String gpsSpeedMeasureMode = value.trim(); … … 211 195 public String getGpsStatusDescription() 212 196 { 213 final String value = _directory.getString( GpsDirectory.TAG_GPS_STATUS);214 if (value ==null)197 final String value = _directory.getString(TAG_STATUS); 198 if (value == null) 215 199 return null; 216 200 String gpsStatus = value.trim(); … … 227 211 public String getGpsAltitudeRefDescription() 228 212 { 229 Integer value = _directory.getInteger(GpsDirectory.TAG_GPS_ALTITUDE_REF); 230 if (value==null) 231 return null; 232 if (value == 0) 233 return "Sea level"; 234 if (value == 1) 235 return "Below sea level"; 236 return "Unknown (" + value + ")"; 213 return getIndexedDescription(TAG_ALTITUDE_REF, "Sea level", "Below sea level"); 237 214 } 238 215 … … 240 217 public String getGpsAltitudeDescription() 241 218 { 242 final Rational value = _directory.getRational(GpsDirectory.TAG_GPS_ALTITUDE); 243 if (value==null) 244 return null; 245 return value.intValue() + " metres"; 219 final Rational value = _directory.getRational(TAG_ALTITUDE); 220 return value == null ? null : value.intValue() + " metres"; 246 221 } 247 222 … … 249 224 public String getGpsDifferentialDescription() 250 225 { 251 final Integer value = _directory.getInteger(GpsDirectory.TAG_GPS_DIFFERENTIAL); 252 if (value==null) 253 return null; 254 if (value == 0) 255 return "No Correction"; 256 if (value == 1) 257 return "Differential Corrected"; 258 return "Unknown (" + value + ")"; 226 return getIndexedDescription(TAG_DIFFERENTIAL, "No Correction", "Differential Corrected"); 259 227 } 260 228 … … 263 231 { 264 232 GeoLocation location = _directory.getGeoLocation(); 265 266 if (location == null) 267 return null; 268 269 return location.toDMSString(); 233 return location == null ? null : location.toDMSString(); 270 234 } 271 235 } -
trunk/src/com/drew/metadata/exif/GpsDirectory.java
r6127 r8132 1 1 /* 2 * Copyright 2002-201 2Drew Noakes2 * Copyright 2002-2015 Drew Noakes 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 16 16 * More information about this project is available at: 17 17 * 18 * http ://drewnoakes.com/code/exif/19 * http ://code.google.com/p/metadata-extractor/18 * https://drewnoakes.com/code/exif/ 19 * https://github.com/drewnoakes/metadata-extractor 20 20 */ 21 21 package com.drew.metadata.exif; … … 32 32 * Describes Exif tags that contain Global Positioning System (GPS) data. 33 33 * 34 * @author Drew Noakes http ://drewnoakes.com34 * @author Drew Noakes https://drewnoakes.com 35 35 */ 36 36 public class GpsDirectory extends Directory 37 37 { 38 38 /** GPS tag version GPSVersionID 0 0 BYTE 4 */ 39 public static final int TAG_ GPS_VERSION_ID = 0x0000;39 public static final int TAG_VERSION_ID = 0x0000; 40 40 /** North or South Latitude GPSLatitudeRef 1 1 ASCII 2 */ 41 public static final int TAG_ GPS_LATITUDE_REF = 0x0001;41 public static final int TAG_LATITUDE_REF = 0x0001; 42 42 /** Latitude GPSLatitude 2 2 RATIONAL 3 */ 43 public static final int TAG_ GPS_LATITUDE = 0x0002;43 public static final int TAG_LATITUDE = 0x0002; 44 44 /** East or West Longitude GPSLongitudeRef 3 3 ASCII 2 */ 45 public static final int TAG_ GPS_LONGITUDE_REF = 0x0003;45 public static final int TAG_LONGITUDE_REF = 0x0003; 46 46 /** Longitude GPSLongitude 4 4 RATIONAL 3 */ 47 public static final int TAG_ GPS_LONGITUDE = 0x0004;47 public static final int TAG_LONGITUDE = 0x0004; 48 48 /** Altitude reference GPSAltitudeRef 5 5 BYTE 1 */ 49 public static final int TAG_ GPS_ALTITUDE_REF = 0x0005;49 public static final int TAG_ALTITUDE_REF = 0x0005; 50 50 /** Altitude GPSAltitude 6 6 RATIONAL 1 */ 51 public static final int TAG_ GPS_ALTITUDE = 0x0006;51 public static final int TAG_ALTITUDE = 0x0006; 52 52 /** GPS time (atomic clock) GPSTimeStamp 7 7 RATIONAL 3 */ 53 public static final int TAG_ GPS_TIME_STAMP = 0x0007;53 public static final int TAG_TIME_STAMP = 0x0007; 54 54 /** GPS satellites used for measurement GPSSatellites 8 8 ASCII Any */ 55 public static final int TAG_ GPS_SATELLITES = 0x0008;55 public static final int TAG_SATELLITES = 0x0008; 56 56 /** GPS receiver status GPSStatus 9 9 ASCII 2 */ 57 public static final int TAG_ GPS_STATUS = 0x0009;57 public static final int TAG_STATUS = 0x0009; 58 58 /** GPS measurement mode GPSMeasureMode 10 A ASCII 2 */ 59 public static final int TAG_ GPS_MEASURE_MODE = 0x000A;59 public static final int TAG_MEASURE_MODE = 0x000A; 60 60 /** Measurement precision GPSDOP 11 B RATIONAL 1 */ 61 public static final int TAG_ GPS_DOP = 0x000B;61 public static final int TAG_DOP = 0x000B; 62 62 /** Speed unit GPSSpeedRef 12 C ASCII 2 */ 63 public static final int TAG_ GPS_SPEED_REF = 0x000C;63 public static final int TAG_SPEED_REF = 0x000C; 64 64 /** Speed of GPS receiver GPSSpeed 13 D RATIONAL 1 */ 65 public static final int TAG_ GPS_SPEED = 0x000D;65 public static final int TAG_SPEED = 0x000D; 66 66 /** Reference for direction of movement GPSTrackRef 14 E ASCII 2 */ 67 public static final int TAG_ GPS_TRACK_REF = 0x000E;67 public static final int TAG_TRACK_REF = 0x000E; 68 68 /** Direction of movement GPSTrack 15 F RATIONAL 1 */ 69 public static final int TAG_ GPS_TRACK = 0x000F;69 public static final int TAG_TRACK = 0x000F; 70 70 /** Reference for direction of image GPSImgDirectionRef 16 10 ASCII 2 */ 71 public static final int TAG_ GPS_IMG_DIRECTION_REF = 0x0010;71 public static final int TAG_IMG_DIRECTION_REF = 0x0010; 72 72 /** Direction of image GPSImgDirection 17 11 RATIONAL 1 */ 73 public static final int TAG_ GPS_IMG_DIRECTION = 0x0011;73 public static final int TAG_IMG_DIRECTION = 0x0011; 74 74 /** Geodetic survey data used GPSMapDatum 18 12 ASCII Any */ 75 public static final int TAG_ GPS_MAP_DATUM = 0x0012;75 public static final int TAG_MAP_DATUM = 0x0012; 76 76 /** Reference for latitude of destination GPSDestLatitudeRef 19 13 ASCII 2 */ 77 public static final int TAG_ GPS_DEST_LATITUDE_REF = 0x0013;77 public static final int TAG_DEST_LATITUDE_REF = 0x0013; 78 78 /** Latitude of destination GPSDestLatitude 20 14 RATIONAL 3 */ 79 public static final int TAG_ GPS_DEST_LATITUDE = 0x0014;79 public static final int TAG_DEST_LATITUDE = 0x0014; 80 80 /** Reference for longitude of destination GPSDestLongitudeRef 21 15 ASCII 2 */ 81 public static final int TAG_ GPS_DEST_LONGITUDE_REF = 0x0015;81 public static final int TAG_DEST_LONGITUDE_REF = 0x0015; 82 82 /** Longitude of destination GPSDestLongitude 22 16 RATIONAL 3 */ 83 public static final int TAG_ GPS_DEST_LONGITUDE = 0x0016;83 public static final int TAG_DEST_LONGITUDE = 0x0016; 84 84 /** Reference for bearing of destination GPSDestBearingRef 23 17 ASCII 2 */ 85 public static final int TAG_ GPS_DEST_BEARING_REF = 0x0017;85 public static final int TAG_DEST_BEARING_REF = 0x0017; 86 86 /** Bearing of destination GPSDestBearing 24 18 RATIONAL 1 */ 87 public static final int TAG_ GPS_DEST_BEARING = 0x0018;87 public static final int TAG_DEST_BEARING = 0x0018; 88 88 /** Reference for distance to destination GPSDestDistanceRef 25 19 ASCII 2 */ 89 public static final int TAG_ GPS_DEST_DISTANCE_REF = 0x0019;89 public static final int TAG_DEST_DISTANCE_REF = 0x0019; 90 90 /** Distance to destination GPSDestDistance 26 1A RATIONAL 1 */ 91 public static final int TAG_ GPS_DEST_DISTANCE = 0x001A;91 public static final int TAG_DEST_DISTANCE = 0x001A; 92 92 93 93 /** Values of "GPS", "CELLID", "WLAN" or "MANUAL" by the EXIF spec. */ 94 public static final int TAG_ GPS_PROCESSING_METHOD = 0x001B;95 public static final int TAG_ GPS_AREA_INFORMATION = 0x001C;96 public static final int TAG_ GPS_DATE_STAMP = 0x001D;97 public static final int TAG_ GPS_DIFFERENTIAL = 0x001E;94 public static final int TAG_PROCESSING_METHOD = 0x001B; 95 public static final int TAG_AREA_INFORMATION = 0x001C; 96 public static final int TAG_DATE_STAMP = 0x001D; 97 public static final int TAG_DIFFERENTIAL = 0x001E; 98 98 99 99 @NotNull … … 102 102 static 103 103 { 104 _tagNameMap.put(TAG_ GPS_VERSION_ID, "GPS Version ID");105 _tagNameMap.put(TAG_ GPS_LATITUDE_REF, "GPS Latitude Ref");106 _tagNameMap.put(TAG_ GPS_LATITUDE, "GPS Latitude");107 _tagNameMap.put(TAG_ GPS_LONGITUDE_REF, "GPS Longitude Ref");108 _tagNameMap.put(TAG_ GPS_LONGITUDE, "GPS Longitude");109 _tagNameMap.put(TAG_ GPS_ALTITUDE_REF, "GPS Altitude Ref");110 _tagNameMap.put(TAG_ GPS_ALTITUDE, "GPS Altitude");111 _tagNameMap.put(TAG_ GPS_TIME_STAMP, "GPS Time-Stamp");112 _tagNameMap.put(TAG_ GPS_SATELLITES, "GPS Satellites");113 _tagNameMap.put(TAG_ GPS_STATUS, "GPS Status");114 _tagNameMap.put(TAG_ GPS_MEASURE_MODE, "GPS Measure Mode");115 _tagNameMap.put(TAG_ GPS_DOP, "GPS DOP");116 _tagNameMap.put(TAG_ GPS_SPEED_REF, "GPS Speed Ref");117 _tagNameMap.put(TAG_ GPS_SPEED, "GPS Speed");118 _tagNameMap.put(TAG_ GPS_TRACK_REF, "GPS Track Ref");119 _tagNameMap.put(TAG_ GPS_TRACK, "GPS Track");120 _tagNameMap.put(TAG_ GPS_IMG_DIRECTION_REF, "GPS Img Direction Ref");121 _tagNameMap.put(TAG_ GPS_IMG_DIRECTION, "GPS Img Direction");122 _tagNameMap.put(TAG_ GPS_MAP_DATUM, "GPS Map Datum");123 _tagNameMap.put(TAG_ GPS_DEST_LATITUDE_REF, "GPS Dest Latitude Ref");124 _tagNameMap.put(TAG_ GPS_DEST_LATITUDE, "GPS Dest Latitude");125 _tagNameMap.put(TAG_ GPS_DEST_LONGITUDE_REF, "GPS Dest Longitude Ref");126 _tagNameMap.put(TAG_ GPS_DEST_LONGITUDE, "GPS Dest Longitude");127 _tagNameMap.put(TAG_ GPS_DEST_BEARING_REF, "GPS Dest Bearing Ref");128 _tagNameMap.put(TAG_ GPS_DEST_BEARING, "GPS Dest Bearing");129 _tagNameMap.put(TAG_ GPS_DEST_DISTANCE_REF, "GPS Dest Distance Ref");130 _tagNameMap.put(TAG_ GPS_DEST_DISTANCE, "GPS Dest Distance");131 _tagNameMap.put(TAG_ GPS_PROCESSING_METHOD, "GPS Processing Method");132 _tagNameMap.put(TAG_ GPS_AREA_INFORMATION, "GPS Area Information");133 _tagNameMap.put(TAG_ GPS_DATE_STAMP, "GPS Date Stamp");134 _tagNameMap.put(TAG_ GPS_DIFFERENTIAL, "GPS Differential");104 _tagNameMap.put(TAG_VERSION_ID, "GPS Version ID"); 105 _tagNameMap.put(TAG_LATITUDE_REF, "GPS Latitude Ref"); 106 _tagNameMap.put(TAG_LATITUDE, "GPS Latitude"); 107 _tagNameMap.put(TAG_LONGITUDE_REF, "GPS Longitude Ref"); 108 _tagNameMap.put(TAG_LONGITUDE, "GPS Longitude"); 109 _tagNameMap.put(TAG_ALTITUDE_REF, "GPS Altitude Ref"); 110 _tagNameMap.put(TAG_ALTITUDE, "GPS Altitude"); 111 _tagNameMap.put(TAG_TIME_STAMP, "GPS Time-Stamp"); 112 _tagNameMap.put(TAG_SATELLITES, "GPS Satellites"); 113 _tagNameMap.put(TAG_STATUS, "GPS Status"); 114 _tagNameMap.put(TAG_MEASURE_MODE, "GPS Measure Mode"); 115 _tagNameMap.put(TAG_DOP, "GPS DOP"); 116 _tagNameMap.put(TAG_SPEED_REF, "GPS Speed Ref"); 117 _tagNameMap.put(TAG_SPEED, "GPS Speed"); 118 _tagNameMap.put(TAG_TRACK_REF, "GPS Track Ref"); 119 _tagNameMap.put(TAG_TRACK, "GPS Track"); 120 _tagNameMap.put(TAG_IMG_DIRECTION_REF, "GPS Img Direction Ref"); 121 _tagNameMap.put(TAG_IMG_DIRECTION, "GPS Img Direction"); 122 _tagNameMap.put(TAG_MAP_DATUM, "GPS Map Datum"); 123 _tagNameMap.put(TAG_DEST_LATITUDE_REF, "GPS Dest Latitude Ref"); 124 _tagNameMap.put(TAG_DEST_LATITUDE, "GPS Dest Latitude"); 125 _tagNameMap.put(TAG_DEST_LONGITUDE_REF, "GPS Dest Longitude Ref"); 126 _tagNameMap.put(TAG_DEST_LONGITUDE, "GPS Dest Longitude"); 127 _tagNameMap.put(TAG_DEST_BEARING_REF, "GPS Dest Bearing Ref"); 128 _tagNameMap.put(TAG_DEST_BEARING, "GPS Dest Bearing"); 129 _tagNameMap.put(TAG_DEST_DISTANCE_REF, "GPS Dest Distance Ref"); 130 _tagNameMap.put(TAG_DEST_DISTANCE, "GPS Dest Distance"); 131 _tagNameMap.put(TAG_PROCESSING_METHOD, "GPS Processing Method"); 132 _tagNameMap.put(TAG_AREA_INFORMATION, "GPS Area Information"); 133 _tagNameMap.put(TAG_DATE_STAMP, "GPS Date Stamp"); 134 _tagNameMap.put(TAG_DIFFERENTIAL, "GPS Differential"); 135 135 } 136 136 … … 140 140 } 141 141 142 @Override 142 143 @NotNull 143 144 public String getName() … … 146 147 } 147 148 149 @Override 148 150 @NotNull 149 151 protected HashMap<Integer, String> getTagNameMap() … … 161 163 public GeoLocation getGeoLocation() 162 164 { 163 Rational[] latitudes = getRationalArray(GpsDirectory.TAG_ GPS_LATITUDE);164 Rational[] longitudes = getRationalArray(GpsDirectory.TAG_ GPS_LONGITUDE);165 String latitudeRef = getString(GpsDirectory.TAG_ GPS_LATITUDE_REF);166 String longitudeRef = getString(GpsDirectory.TAG_ GPS_LONGITUDE_REF);165 Rational[] latitudes = getRationalArray(GpsDirectory.TAG_LATITUDE); 166 Rational[] longitudes = getRationalArray(GpsDirectory.TAG_LONGITUDE); 167 String latitudeRef = getString(GpsDirectory.TAG_LATITUDE_REF); 168 String longitudeRef = getString(GpsDirectory.TAG_LONGITUDE_REF); 167 169 168 170 // Make sure we have the required values
Note:
See TracChangeset
for help on using the changeset viewer.