source: josm/trunk/src/com/drew/metadata/exif/ExifReader.java@ 6209

Last change on this file since 6209 was 6209, checked in by Don-vip, 12 years ago

fix #8895, fix #9030 - fix regression in geottaged pictures support caused by metadata-extractor upgrade, enhance ExifReader, add JUnit tests + javadoc

File size: 31.9 KB
Line 
1/*
2 * Copyright 2002-2012 Drew Noakes
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 * More information about this project is available at:
17 *
18 * http://drewnoakes.com/code/exif/
19 * http://code.google.com/p/metadata-extractor/
20 */
21package com.drew.metadata.exif;
22
23import java.util.HashSet;
24import java.util.Set;
25
26import com.drew.lang.BufferBoundsException;
27import com.drew.lang.BufferReader;
28import com.drew.lang.Rational;
29import com.drew.lang.annotations.NotNull;
30import com.drew.metadata.Directory;
31import com.drew.metadata.Metadata;
32import com.drew.metadata.MetadataReader;
33
34/**
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.
37 *
38 * @author Drew Noakes http://drewnoakes.com
39 */
40public class ExifReader implements MetadataReader
41{
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. */
45 @NotNull
46 private static final int[] BYTES_PER_FORMAT = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 };
47
48 /** The number of formats known. */
49 private static final int MAX_FORMAT_CODE = 12;
50
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;
74
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;
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'");
107 return;
108 }
109
110 extractIFD(metadata, metadata.getOrCreateDirectory(ExifIFD0Directory.class), TIFF_HEADER_START_OFFSET, reader);
111 } catch (BufferBoundsException e) {
112 directory.addError("Exif data segment ended prematurely");
113 }
114 }
115
116 /**
117 * Performs the Exif data extraction on a TIFF/RAW, adding found values to the specified
118 * 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 BufferBoundsException
135 {
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 files
153 final int panasonicRawTiffMarker = 0x0055; // for RW2 files
154
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 problem
163 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 case
166 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 information
174 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 * Header
193 * 2 bytes: number of tags
194 * <p/>
195 * Then for each tag
196 * 2 bytes: tag type
197 * 2 bytes: format code
198 * 4 bytes: component count
199 */
200 private void processDirectory(@NotNull Directory directory, @NotNull Set<Integer> processedDirectoryOffsets, int dirStartOffset, int tiffHeaderOffset, @NotNull final Metadata metadata, @NotNull final BufferReader reader) throws BufferBoundsException
201 {
202 // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist
203 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 later
207 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 directory
215 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 directory
224 for (int tagNumber = 0; tagNumber < dirTagCount; tagNumber++) {
225 final int tagOffset = calculateTagOffset(dirStartOffset, tagNumber);
226
227 // 2 bytes for the tag type
228 final int tagType = reader.getUInt16(tagOffset);
229
230 // 2 bytes for the format code
231 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 generate
234 // 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 #9030
237 }
238
239 // 4 bytes dictate the number of components in this tag's data
240 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 bytes
246 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 an
251 // 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 value
255 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 itself
261 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 IFD
304 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 bounds
310 // Note this could have been caused by jhead 1.3 cropping too much
311 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 directory
314 return;
315 }
316 // TODO in Exif, the only known 'follower' IFD is the thumbnail one, however this may not be the case
317 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 BufferBoundsException
323 {
324 // Determine the camera model and makernote format
325 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 Makernote
343 // 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 ............0200
354 */
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 directory
376 boolean isMotorola = reader.isMotorolaByteOrder();
377 reader.setMotorolaByteOrder(true);
378 // skip 12 byte header + 2 for "MM" + 6
379 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 else
389 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 ordering
393 reader.setMotorolaByteOrder(false);
394 // the 4 bytes after "FUJIFILM" in the makernote point to the start of the makernote
395 // IFD, though the offset is relative to the start of the makernote, not the TIFF
396 // 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 makernote
402 // 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.html
406 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 IFD
409 // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment
410 // more information here: http://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html
411 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 Tags
414 // IFD has no Next-IFD pointer at end of IFD, and
415 // Offsets are relative to the start of the current IFD tag, not the TIFF header
416 // Observed for:
417 // - Pentax ist D
418 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 Tags
421 // IFD has no Next-IFD pointer at end of IFD, and
422 // Offsets are relative to the start of the current IFD tag, not the TIFF header
423 // Observed for:
424 // - PENTAX Optio 330
425 // - PENTAX Optio 430
426 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.html
430// // TODO add support for minolta/konica cameras
431// 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 BufferBoundsException
439 {
440 // Directory simply stores raw values
441 // The display side uses a Descriptor class per directory to turn the raw values into 'pretty' descriptions
442 switch (formatCode) {
443 case FMT_UNDEFINED:
444 // this includes exif user comments
445 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 timezone
453 // in which the string is parsed
454 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 pattern
470 }
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 64
565 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 starts
584 * @param entryNumber the zero-based entry number
585 */
586 private int calculateTagOffset(int dirStartOffset, int entryNumber)
587 {
588 // add 2 bytes for the tag count
589 // each entry is 12 bytes, so we skip 12 * the number seen so far
590 return dirStartOffset + 2 + (12 * entryNumber);
591 }
592}
Note: See TracBrowser for help on using the repository browser.