source: josm/trunk/src/com/drew/metadata/exif/ExifDescriptor.java@ 4258

Last change on this file since 4258 was 4231, checked in by stoecker, 13 years ago

add signpost and metadata extractor code to repository directly

File size: 46.3 KB
Line 
1/*
2 * ExifDescriptor.java
3 *
4 * This is public domain software - that is, you can do whatever you want
5 * with it, and include it software that is licensed under the GNU or the
6 * BSD license, or whatever other licence you choose, including proprietary
7 * closed source licenses. I do ask that you leave this header in tact.
8 *
9 * If you make modifications to this code that you think would benefit the
10 * wider community, please send me a copy and I'll post it on my site.
11 *
12 * If you make use of this code, I'd appreciate hearing about it.
13 * drew@drewnoakes.com
14 * Latest version of this software kept at
15 * http://drewnoakes.com/
16 *
17 * Created by dnoakes on 12-Nov-2002 22:27:15 using IntelliJ IDEA.
18 */
19package com.drew.metadata.exif;
20
21import com.drew.imaging.PhotographicConversions;
22import com.drew.lang.Rational;
23import com.drew.metadata.Directory;
24import com.drew.metadata.MetadataException;
25import com.drew.metadata.TagDescriptor;
26
27import java.io.UnsupportedEncodingException;
28import java.text.DecimalFormat;
29
30/**
31 * Contains all logic for the presentation of raw Exif data, as stored in ExifDirectory. Use
32 * this class to provide human-readable descriptions of tag values.
33 */
34public class ExifDescriptor extends TagDescriptor
35{
36 /**
37 * Dictates whether rational values will be represented in decimal format in instances
38 * where decimal notation is elegant (such as 1/2 -> 0.5, but not 1/3).
39 */
40 private boolean _allowDecimalRepresentationOfRationals = true;
41
42 private static final java.text.DecimalFormat SimpleDecimalFormatter = new DecimalFormat("0.#");
43
44 public ExifDescriptor(Directory directory)
45 {
46 super(directory);
47 }
48
49 // Note for the potential addition of brightness presentation in eV:
50 // Brightness of taken subject. To calculate Exposure(Ev) from BrigtnessValue(Bv),
51 // you must add SensitivityValue(Sv).
52 // Ev=BV+Sv Sv=log2(ISOSpeedRating/3.125)
53 // ISO100:Sv=5, ISO200:Sv=6, ISO400:Sv=7, ISO125:Sv=5.32.
54
55 /**
56 * Returns a descriptive value of the the specified tag for this image.
57 * Where possible, known values will be substituted here in place of the raw
58 * tokens actually kept in the Exif segment. If no substitution is
59 * available, the value provided by getString(int) will be returned.
60 * @param tagType the tag to find a description for
61 * @return a description of the image's value for the specified tag, or
62 * <code>null</code> if the tag hasn't been defined.
63 */
64 public String getDescription(int tagType) throws MetadataException
65 {
66 switch (tagType) {
67 case ExifDirectory.TAG_ORIENTATION:
68 return getOrientationDescription();
69 case ExifDirectory.TAG_NEW_SUBFILE_TYPE:
70 return getNewSubfileTypeDescription();
71 case ExifDirectory.TAG_SUBFILE_TYPE:
72 return getSubfileTypeDescription();
73 case ExifDirectory.TAG_THRESHOLDING:
74 return getThresholdingDescription();
75 case ExifDirectory.TAG_FILL_ORDER:
76 return getFillOrderDescription();
77 case ExifDirectory.TAG_RESOLUTION_UNIT:
78 return getResolutionDescription();
79 case ExifDirectory.TAG_YCBCR_POSITIONING:
80 return getYCbCrPositioningDescription();
81 case ExifDirectory.TAG_EXPOSURE_TIME:
82 return getExposureTimeDescription();
83 case ExifDirectory.TAG_SHUTTER_SPEED:
84 return getShutterSpeedDescription();
85 case ExifDirectory.TAG_FNUMBER:
86 return getFNumberDescription();
87 case ExifDirectory.TAG_X_RESOLUTION:
88 return getXResolutionDescription();
89 case ExifDirectory.TAG_Y_RESOLUTION:
90 return getYResolutionDescription();
91 case ExifDirectory.TAG_THUMBNAIL_OFFSET:
92 return getThumbnailOffsetDescription();
93 case ExifDirectory.TAG_THUMBNAIL_LENGTH:
94 return getThumbnailLengthDescription();
95 case ExifDirectory.TAG_COMPRESSION_LEVEL:
96 return getCompressionLevelDescription();
97 case ExifDirectory.TAG_SUBJECT_DISTANCE:
98 return getSubjectDistanceDescription();
99 case ExifDirectory.TAG_METERING_MODE:
100 return getMeteringModeDescription();
101 case ExifDirectory.TAG_WHITE_BALANCE:
102 return getWhiteBalanceDescription();
103 case ExifDirectory.TAG_FLASH:
104 return getFlashDescription();
105 case ExifDirectory.TAG_FOCAL_LENGTH:
106 return getFocalLengthDescription();
107 case ExifDirectory.TAG_COLOR_SPACE:
108 return getColorSpaceDescription();
109 case ExifDirectory.TAG_EXIF_IMAGE_WIDTH:
110 return getExifImageWidthDescription();
111 case ExifDirectory.TAG_EXIF_IMAGE_HEIGHT:
112 return getExifImageHeightDescription();
113 case ExifDirectory.TAG_FOCAL_PLANE_UNIT:
114 return getFocalPlaneResolutionUnitDescription();
115 case ExifDirectory.TAG_FOCAL_PLANE_X_RES:
116 return getFocalPlaneXResolutionDescription();
117 case ExifDirectory.TAG_FOCAL_PLANE_Y_RES:
118 return getFocalPlaneYResolutionDescription();
119 case ExifDirectory.TAG_THUMBNAIL_IMAGE_WIDTH:
120 return getThumbnailImageWidthDescription();
121 case ExifDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT:
122 return getThumbnailImageHeightDescription();
123 case ExifDirectory.TAG_BITS_PER_SAMPLE:
124 return getBitsPerSampleDescription();
125 case ExifDirectory.TAG_COMPRESSION:
126 return getCompressionDescription();
127 case ExifDirectory.TAG_PHOTOMETRIC_INTERPRETATION:
128 return getPhotometricInterpretationDescription();
129 case ExifDirectory.TAG_ROWS_PER_STRIP:
130 return getRowsPerStripDescription();
131 case ExifDirectory.TAG_STRIP_BYTE_COUNTS:
132 return getStripByteCountsDescription();
133 case ExifDirectory.TAG_SAMPLES_PER_PIXEL:
134 return getSamplesPerPixelDescription();
135 case ExifDirectory.TAG_PLANAR_CONFIGURATION:
136 return getPlanarConfigurationDescription();
137 case ExifDirectory.TAG_YCBCR_SUBSAMPLING:
138 return getYCbCrSubsamplingDescription();
139 case ExifDirectory.TAG_EXPOSURE_PROGRAM:
140 return getExposureProgramDescription();
141 case ExifDirectory.TAG_APERTURE:
142 return getApertureValueDescription();
143 case ExifDirectory.TAG_MAX_APERTURE:
144 return getMaxApertureValueDescription();
145 case ExifDirectory.TAG_SENSING_METHOD:
146 return getSensingMethodDescription();
147 case ExifDirectory.TAG_EXPOSURE_BIAS:
148 return getExposureBiasDescription();
149 case ExifDirectory.TAG_FILE_SOURCE:
150 return getFileSourceDescription();
151 case ExifDirectory.TAG_SCENE_TYPE:
152 return getSceneTypeDescription();
153 case ExifDirectory.TAG_COMPONENTS_CONFIGURATION:
154 return getComponentConfigurationDescription();
155 case ExifDirectory.TAG_EXIF_VERSION:
156 return getExifVersionDescription();
157 case ExifDirectory.TAG_FLASHPIX_VERSION:
158 return getFlashPixVersionDescription();
159 case ExifDirectory.TAG_REFERENCE_BLACK_WHITE:
160 return getReferenceBlackWhiteDescription();
161 case ExifDirectory.TAG_ISO_EQUIVALENT:
162 return getIsoEquivalentDescription();
163 case ExifDirectory.TAG_THUMBNAIL_DATA:
164 return getThumbnailDescription();
165 case ExifDirectory.TAG_USER_COMMENT:
166 return getUserCommentDescription();
167 case ExifDirectory.TAG_CUSTOM_RENDERED:
168 return getCustomRenderedDescription();
169 case ExifDirectory.TAG_EXPOSURE_MODE:
170 return getExposureModeDescription();
171 case ExifDirectory.TAG_WHITE_BALANCE_MODE:
172 return getWhiteBalanceModeDescription();
173 case ExifDirectory.TAG_DIGITAL_ZOOM_RATIO:
174 return getDigitalZoomRatioDescription();
175 case ExifDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH:
176 return get35mmFilmEquivFocalLengthDescription();
177 case ExifDirectory.TAG_SCENE_CAPTURE_TYPE:
178 return getSceneCaptureTypeDescription();
179 case ExifDirectory.TAG_GAIN_CONTROL:
180 return getGainControlDescription();
181 case ExifDirectory.TAG_CONTRAST:
182 return getContrastDescription();
183 case ExifDirectory.TAG_SATURATION:
184 return getSaturationDescription();
185 case ExifDirectory.TAG_SHARPNESS:
186 return getSharpnessDescription();
187 case ExifDirectory.TAG_SUBJECT_DISTANCE_RANGE:
188 return getSubjectDistanceRangeDescription();
189
190 case ExifDirectory.TAG_WIN_AUTHOR:
191 return getWindowsAuthorDescription();
192 case ExifDirectory.TAG_WIN_COMMENT:
193 return getWindowsCommentDescription();
194 case ExifDirectory.TAG_WIN_KEYWORDS:
195 return getWindowsKeywordsDescription();
196 case ExifDirectory.TAG_WIN_SUBJECT:
197 return getWindowsSubjectDescription();
198 case ExifDirectory.TAG_WIN_TITLE:
199 return getWindowsTitleDescription();
200 default:
201 return _directory.getString(tagType);
202 }
203 }
204
205 public String getNewSubfileTypeDescription() throws MetadataException
206 {
207 if (!_directory.containsTag(ExifDirectory.TAG_NEW_SUBFILE_TYPE)) return null;
208 switch (_directory.getInt(ExifDirectory.TAG_NEW_SUBFILE_TYPE)) {
209 case 1: return "Full-resolution image";
210 case 2: return "Reduced-resolution image";
211 case 3: return "Single page of multi-page reduced-resolution image";
212 case 4: return "Transparency mask";
213 case 5: return "Transparency mask of reduced-resolution image";
214 case 6: return "Transparency mask of multi-page image";
215 case 7: return "Transparency mask of reduced-resolution multi-page image";
216 default:
217 return "Unknown (" + _directory.getInt(ExifDirectory.TAG_NEW_SUBFILE_TYPE) + ")";
218 }
219 }
220
221 public String getSubfileTypeDescription() throws MetadataException
222 {
223 if (!_directory.containsTag(ExifDirectory.TAG_SUBFILE_TYPE)) return null;
224 switch (_directory.getInt(ExifDirectory.TAG_SUBFILE_TYPE)) {
225 case 1: return "Full-resolution image";
226 case 2: return "Reduced-resolution image";
227 case 3: return "Single page of multi-page image";
228 default:
229 return "Unknown (" + _directory.getInt(ExifDirectory.TAG_SUBFILE_TYPE) + ")";
230 }
231 }
232
233 public String getThresholdingDescription() throws MetadataException
234 {
235 if (!_directory.containsTag(ExifDirectory.TAG_THRESHOLDING)) return null;
236 switch (_directory.getInt(ExifDirectory.TAG_THRESHOLDING)) {
237 case 1: return "No dithering or halftoning";
238 case 2: return "Ordered dither or halftone";
239 case 3: return "Randomized dither";
240 default:
241 return "Unknown (" + _directory.getInt(ExifDirectory.TAG_THRESHOLDING) + ")";
242 }
243 }
244
245 public String getFillOrderDescription() throws MetadataException
246 {
247 if (!_directory.containsTag(ExifDirectory.TAG_FILL_ORDER)) return null;
248 switch (_directory.getInt(ExifDirectory.TAG_FILL_ORDER)) {
249 case 1: return "Normal";
250 case 2: return "Reversed";
251 default:
252 return "Unknown (" + _directory.getInt(ExifDirectory.TAG_FILL_ORDER) + ")";
253 }
254 }
255
256 public String getSubjectDistanceRangeDescription() throws MetadataException
257 {
258 if (!_directory.containsTag(ExifDirectory.TAG_SUBJECT_DISTANCE_RANGE)) return null;
259 switch (_directory.getInt(ExifDirectory.TAG_SUBJECT_DISTANCE_RANGE)) {
260 case 0:
261 return "Unknown";
262 case 1:
263 return "Macro";
264 case 2:
265 return "Close view";
266 case 3:
267 return "Distant view";
268 default:
269 return "Unknown (" + _directory.getInt(ExifDirectory.TAG_SUBJECT_DISTANCE_RANGE) + ")";
270 }
271 }
272
273 public String getSharpnessDescription() throws MetadataException
274 {
275 if (!_directory.containsTag(ExifDirectory.TAG_SHARPNESS)) return null;
276 switch (_directory.getInt(ExifDirectory.TAG_SHARPNESS)) {
277 case 0:
278 return "None";
279 case 1:
280 return "Low";
281 case 2:
282 return "Hard";
283 default:
284 return "Unknown (" + _directory.getInt(ExifDirectory.TAG_SHARPNESS) + ")";
285 }
286 }
287
288 public String getSaturationDescription() throws MetadataException
289 {
290 if (!_directory.containsTag(ExifDirectory.TAG_SATURATION)) return null;
291 switch (_directory.getInt(ExifDirectory.TAG_SATURATION)) {
292 case 0:
293 return "None";
294 case 1:
295 return "Low saturation";
296 case 2:
297 return "High saturation";
298 default:
299 return "Unknown (" + _directory.getInt(ExifDirectory.TAG_SATURATION) + ")";
300 }
301 }
302
303 public String getContrastDescription() throws MetadataException
304 {
305 if (!_directory.containsTag(ExifDirectory.TAG_CONTRAST)) return null;
306 switch (_directory.getInt(ExifDirectory.TAG_CONTRAST)) {
307 case 0:
308 return "None";
309 case 1:
310 return "Soft";
311 case 2:
312 return "Hard";
313 default:
314 return "Unknown (" + _directory.getInt(ExifDirectory.TAG_CONTRAST) + ")";
315 }
316 }
317
318 public String getGainControlDescription() throws MetadataException
319 {
320 if (!_directory.containsTag(ExifDirectory.TAG_GAIN_CONTROL)) return null;
321 switch (_directory.getInt(ExifDirectory.TAG_GAIN_CONTROL)) {
322 case 0:
323 return "None";
324 case 1:
325 return "Low gain up";
326 case 2:
327 return "Low gain down";
328 case 3:
329 return "High gain up";
330 case 4:
331 return "High gain down";
332 default:
333 return "Unknown (" + _directory.getInt(ExifDirectory.TAG_GAIN_CONTROL) + ")";
334 }
335 }
336
337 public String getSceneCaptureTypeDescription() throws MetadataException
338 {
339 if (!_directory.containsTag(ExifDirectory.TAG_SCENE_CAPTURE_TYPE)) return null;
340 switch (_directory.getInt(ExifDirectory.TAG_SCENE_CAPTURE_TYPE)) {
341 case 0:
342 return "Standard";
343 case 1:
344 return "Landscape";
345 case 2:
346 return "Portrait";
347 case 3:
348 return "Night scene";
349 default:
350 return "Unknown (" + _directory.getInt(ExifDirectory.TAG_SCENE_CAPTURE_TYPE) + ")";
351 }
352 }
353
354 public String get35mmFilmEquivFocalLengthDescription() throws MetadataException
355 {
356 if (!_directory.containsTag(ExifDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH)) return null;
357 int equivalentFocalLength = _directory.getInt(ExifDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH);
358
359 if (equivalentFocalLength==0)
360 return "Unknown";
361 else
362 return SimpleDecimalFormatter.format(equivalentFocalLength) + "mm";
363 }
364
365 public String getDigitalZoomRatioDescription() throws MetadataException
366 {
367 if (!_directory.containsTag(ExifDirectory.TAG_DIGITAL_ZOOM_RATIO)) return null;
368 Rational rational = _directory.getRational(ExifDirectory.TAG_DIGITAL_ZOOM_RATIO);
369 if (rational.getNumerator()==0)
370 return "Digital zoom not used.";
371
372 return SimpleDecimalFormatter.format(rational.doubleValue());
373 }
374
375 public String getWhiteBalanceModeDescription() throws MetadataException
376 {
377 if (!_directory.containsTag(ExifDirectory.TAG_WHITE_BALANCE_MODE)) return null;
378 switch (_directory.getInt(ExifDirectory.TAG_WHITE_BALANCE_MODE)) {
379 case 0:
380 return "Auto white balance";
381 case 1:
382 return "Manual white balance";
383 default:
384 return "Unknown (" + _directory.getInt(ExifDirectory.TAG_WHITE_BALANCE_MODE) + ")";
385 }
386 }
387
388 public String getExposureModeDescription() throws MetadataException
389 {
390 if (!_directory.containsTag(ExifDirectory.TAG_EXPOSURE_MODE)) return null;
391 switch (_directory.getInt(ExifDirectory.TAG_EXPOSURE_MODE)) {
392 case 0:
393 return "Auto exposure";
394 case 1:
395 return "Manual exposure";
396 case 2:
397 return "Auto bracket";
398 default:
399 return "Unknown (" + _directory.getInt(ExifDirectory.TAG_EXPOSURE_MODE) + ")";
400 }
401 }
402
403 public String getCustomRenderedDescription() throws MetadataException
404 {
405 if (!_directory.containsTag(ExifDirectory.TAG_CUSTOM_RENDERED)) return null;
406 switch (_directory.getInt(ExifDirectory.TAG_CUSTOM_RENDERED)) {
407 case 0:
408 return "Normal process";
409 case 1:
410 return "Custom process";
411 default:
412 return "Unknown (" + _directory.getInt(ExifDirectory.TAG_CUSTOM_RENDERED) + ")";
413 }
414 }
415
416 public String getUserCommentDescription() throws MetadataException
417 {
418 if (!_directory.containsTag(ExifDirectory.TAG_USER_COMMENT)) return null;
419
420 byte[] commentBytes = _directory.getByteArray(ExifDirectory.TAG_USER_COMMENT);
421
422 if (commentBytes.length==0)
423 return "";
424
425 final String[] encodingNames = new String[] { "ASCII", "UNICODE", "JIS" };
426
427 if (commentBytes.length>=10)
428 {
429 String encodingRegion = new String(commentBytes, 0, 10);
430
431 // try each encoding name
432 for (int i = 0; i<encodingNames.length; i++) {
433 String encodingName = encodingNames[i];
434 if (encodingRegion.startsWith(encodingName))
435 {
436 // remove the null characters (and any spaces) commonly present after the encoding name
437 for (int j = encodingName.length(); j<10; j++) {
438 byte b = commentBytes[j];
439 if (b!='\0' && b!=' ') {
440 if (encodingName.equals("UNICODE")) {
441 try {
442 return new String(commentBytes, j, commentBytes.length - j, "UTF-16LE").trim();
443 }
444 catch (UnsupportedEncodingException ex) {
445 return null;
446 }
447 }
448 return new String(commentBytes, j, commentBytes.length - j).trim();
449 }
450 }
451 return new String(commentBytes, 10, commentBytes.length - 10).trim();
452 }
453 }
454 }
455
456 // special handling fell through, return a plain string representation
457 return new String(commentBytes).trim();
458 }
459
460 public String getThumbnailDescription() throws MetadataException
461 {
462 if (!_directory.containsTag(ExifDirectory.TAG_THUMBNAIL_DATA)) return null;
463 int[] thumbnailBytes = _directory.getIntArray(ExifDirectory.TAG_THUMBNAIL_DATA);
464 return "[" + thumbnailBytes.length + " bytes of thumbnail data]";
465 }
466
467 public String getIsoEquivalentDescription() throws MetadataException
468 {
469 if (!_directory.containsTag(ExifDirectory.TAG_ISO_EQUIVALENT)) return null;
470 int isoEquiv = _directory.getInt(ExifDirectory.TAG_ISO_EQUIVALENT);
471 if (isoEquiv < 50) {
472 isoEquiv *= 200;
473 }
474 return Integer.toString(isoEquiv);
475 }
476
477 public String getReferenceBlackWhiteDescription() throws MetadataException
478 {
479 if (!_directory.containsTag(ExifDirectory.TAG_REFERENCE_BLACK_WHITE)) return null;
480 int[] ints = _directory.getIntArray(ExifDirectory.TAG_REFERENCE_BLACK_WHITE);
481 int blackR = ints[0];
482 int whiteR = ints[1];
483 int blackG = ints[2];
484 int whiteG = ints[3];
485 int blackB = ints[4];
486 int whiteB = ints[5];
487 String pos = "[" + blackR + "," + blackG + "," + blackB + "] " +
488 "[" + whiteR + "," + whiteG + "," + whiteB + "]";
489 return pos;
490 }
491
492 public String getExifVersionDescription() throws MetadataException
493 {
494 if (!_directory.containsTag(ExifDirectory.TAG_EXIF_VERSION)) return null;
495 int[] ints = _directory.getIntArray(ExifDirectory.TAG_EXIF_VERSION);
496 return ExifDescriptor.convertBytesToVersionString(ints);
497 }
498
499 public String getFlashPixVersionDescription() throws MetadataException
500 {
501 if (!_directory.containsTag(ExifDirectory.TAG_FLASHPIX_VERSION)) return null;
502 int[] ints = _directory.getIntArray(ExifDirectory.TAG_FLASHPIX_VERSION);
503 return ExifDescriptor.convertBytesToVersionString(ints);
504 }
505
506 public String getSceneTypeDescription() throws MetadataException
507 {
508 if (!_directory.containsTag(ExifDirectory.TAG_SCENE_TYPE)) return null;
509 int sceneType = _directory.getInt(ExifDirectory.TAG_SCENE_TYPE);
510 if (sceneType == 1) {
511 return "Directly photographed image";
512 } else {
513 return "Unknown (" + sceneType + ")";
514 }
515 }
516
517 public String getFileSourceDescription() throws MetadataException
518 {
519 if (!_directory.containsTag(ExifDirectory.TAG_FILE_SOURCE)) return null;
520 int fileSource = _directory.getInt(ExifDirectory.TAG_FILE_SOURCE);
521 if (fileSource == 3) {
522 return "Digital Still Camera (DSC)";
523 } else {
524 return "Unknown (" + fileSource + ")";
525 }
526 }
527
528 public String getExposureBiasDescription() throws MetadataException
529 {
530 if (!_directory.containsTag(ExifDirectory.TAG_EXPOSURE_BIAS)) return null;
531 Rational exposureBias = _directory.getRational(ExifDirectory.TAG_EXPOSURE_BIAS);
532 return exposureBias.toSimpleString(true) + " EV";
533 }
534
535 public String getMaxApertureValueDescription() throws MetadataException
536 {
537 if (!_directory.containsTag(ExifDirectory.TAG_MAX_APERTURE)) return null;
538 double aperture = _directory.getDouble(ExifDirectory.TAG_MAX_APERTURE);
539 double fStop = PhotographicConversions.apertureToFStop(aperture);
540 return "F" + SimpleDecimalFormatter.format(fStop);
541 }
542
543 public String getApertureValueDescription() throws MetadataException
544 {
545 if (!_directory.containsTag(ExifDirectory.TAG_APERTURE)) return null;
546 double aperture = _directory.getDouble(ExifDirectory.TAG_APERTURE);
547 double fStop = PhotographicConversions.apertureToFStop(aperture);
548 return "F" + SimpleDecimalFormatter.format(fStop);
549 }
550
551 public String getExposureProgramDescription() throws MetadataException
552 {
553 if (!_directory.containsTag(ExifDirectory.TAG_EXPOSURE_PROGRAM)) return null;
554 // '1' means manual control, '2' program normal, '3' aperture priority,
555 // '4' shutter priority, '5' program creative (slow program),
556 // '6' program action(high-speed program), '7' portrait mode, '8' landscape mode.
557 switch (_directory.getInt(ExifDirectory.TAG_EXPOSURE_PROGRAM)) {
558 case 1: return "Manual control";
559 case 2: return "Program normal";
560 case 3: return "Aperture priority";
561 case 4: return "Shutter priority";
562 case 5: return "Program creative (slow program)";
563 case 6: return "Program action (high-speed program)";
564 case 7: return "Portrait mode";
565 case 8: return "Landscape mode";
566 default:
567 return "Unknown program (" + _directory.getInt(ExifDirectory.TAG_EXPOSURE_PROGRAM) + ")";
568 }
569 }
570
571 public String getYCbCrSubsamplingDescription() throws MetadataException
572 {
573 if (!_directory.containsTag(ExifDirectory.TAG_YCBCR_SUBSAMPLING)) return null;
574 int[] positions = _directory.getIntArray(ExifDirectory.TAG_YCBCR_SUBSAMPLING);
575 if (positions[0] == 2 && positions[1] == 1) {
576 return "YCbCr4:2:2";
577 } else if (positions[0] == 2 && positions[1] == 2) {
578 return "YCbCr4:2:0";
579 } else {
580 return "(Unknown)";
581 }
582 }
583
584 public String getPlanarConfigurationDescription() throws MetadataException
585 {
586 if (!_directory.containsTag(ExifDirectory.TAG_PLANAR_CONFIGURATION)) return null;
587 // When image format is no compression YCbCr, this value shows byte aligns of YCbCr
588 // data. If value is '1', Y/Cb/Cr value is chunky format, contiguous for each subsampling
589 // pixel. If value is '2', Y/Cb/Cr value is separated and stored to Y plane/Cb plane/Cr
590 // plane format.
591
592 switch (_directory.getInt(ExifDirectory.TAG_PLANAR_CONFIGURATION)) {
593 case 1: return "Chunky (contiguous for each subsampling pixel)";
594 case 2: return "Separate (Y-plane/Cb-plane/Cr-plane format)";
595 default:
596 return "Unknown configuration";
597 }
598 }
599
600 public String getSamplesPerPixelDescription()
601 {
602 if (!_directory.containsTag(ExifDirectory.TAG_SAMPLES_PER_PIXEL)) return null;
603 return _directory.getString(ExifDirectory.TAG_SAMPLES_PER_PIXEL) + " samples/pixel";
604 }
605
606 public String getRowsPerStripDescription()
607 {
608 if (!_directory.containsTag(ExifDirectory.TAG_ROWS_PER_STRIP)) return null;
609 return _directory.getString(ExifDirectory.TAG_ROWS_PER_STRIP) + " rows/strip";
610 }
611
612 public String getStripByteCountsDescription()
613 {
614 if (!_directory.containsTag(ExifDirectory.TAG_STRIP_BYTE_COUNTS)) return null;
615 return _directory.getString(ExifDirectory.TAG_STRIP_BYTE_COUNTS) + " bytes";
616 }
617
618 public String getPhotometricInterpretationDescription() throws MetadataException
619 {
620 if (!_directory.containsTag(ExifDirectory.TAG_PHOTOMETRIC_INTERPRETATION)) return null;
621 // Shows the color space of the image data components
622 switch (_directory.getInt(ExifDirectory.TAG_PHOTOMETRIC_INTERPRETATION)) {
623 case 0: return "WhiteIsZero";
624 case 1: return "BlackIsZero";
625 case 2: return "RGB";
626 case 3: return "RGB Palette";
627 case 4: return "Transparency Mask";
628 case 5: return "CMYK";
629 case 6: return "YCbCr";
630 case 8: return "CIELab";
631 case 9: return "ICCLab";
632 case 10: return "ITULab";
633 case 32803: return "Color Filter Array";
634 case 32844: return "Pixar LogL";
635 case 32845: return "Pixar LogLuv";
636 case 32892: return "Linear Raw";
637 default:
638 return "Unknown colour space";
639 }
640 }
641
642 public String getCompressionDescription() throws MetadataException
643 {
644 if (!_directory.containsTag(ExifDirectory.TAG_COMPRESSION)) return null;
645 switch (_directory.getInt(ExifDirectory.TAG_COMPRESSION)) {
646 case 1: return "Uncompressed";
647 case 2: return "CCITT 1D";
648 case 3: return "T4/Group 3 Fax";
649 case 4: return "T6/Group 4 Fax";
650 case 5: return "LZW";
651 case 6: return "JPEG (old-style)";
652 case 7: return "JPEG";
653 case 8: return "Adobe Deflate";
654 case 9: return "JBIG B&W";
655 case 10: return "JBIG Color";
656 case 32766: return "Next";
657 case 32771: return "CCIRLEW";
658 case 32773: return "PackBits";
659 case 32809: return "Thunderscan";
660 case 32895: return "IT8CTPAD";
661 case 32896: return "IT8LW";
662 case 32897: return "IT8MP";
663 case 32898: return "IT8BL";
664 case 32908: return "PixarFilm";
665 case 32909: return "PixarLog";
666 case 32946: return "Deflate";
667 case 32947: return "DCS";
668 case 32661: return "JBIG";
669 case 32676: return "SGILog";
670 case 32677: return "SGILog24";
671 case 32712: return "JPEG 2000";
672 case 32713: return "Nikon NEF Compressed";
673 default:
674 return "Unknown compression";
675 }
676 }
677
678 public String getBitsPerSampleDescription()
679 {
680 if (!_directory.containsTag(ExifDirectory.TAG_BITS_PER_SAMPLE)) return null;
681 return _directory.getString(ExifDirectory.TAG_BITS_PER_SAMPLE) + " bits/component/pixel";
682 }
683
684 public String getThumbnailImageWidthDescription()
685 {
686 if (!_directory.containsTag(ExifDirectory.TAG_THUMBNAIL_IMAGE_WIDTH)) return null;
687 return _directory.getString(ExifDirectory.TAG_THUMBNAIL_IMAGE_WIDTH) + " pixels";
688 }
689
690 public String getThumbnailImageHeightDescription()
691 {
692 if (!_directory.containsTag(ExifDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT)) return null;
693 return _directory.getString(ExifDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT) + " pixels";
694 }
695
696 public String getFocalPlaneXResolutionDescription() throws MetadataException
697 {
698 if (!_directory.containsTag(ExifDirectory.TAG_FOCAL_PLANE_X_RES)) return null;
699 Rational rational = _directory.getRational(ExifDirectory.TAG_FOCAL_PLANE_X_RES);
700 return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals) + " " +
701 getFocalPlaneResolutionUnitDescription().toLowerCase();
702 }
703
704 public String getFocalPlaneYResolutionDescription() throws MetadataException
705 {
706 if (!_directory.containsTag(ExifDirectory.TAG_FOCAL_PLANE_Y_RES)) return null;
707 Rational rational = _directory.getRational(ExifDirectory.TAG_FOCAL_PLANE_Y_RES);
708 return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals) + " " +
709 getFocalPlaneResolutionUnitDescription().toLowerCase();
710 }
711
712 public String getFocalPlaneResolutionUnitDescription() throws MetadataException
713 {
714 if (!_directory.containsTag(ExifDirectory.TAG_FOCAL_PLANE_UNIT)) return null;
715 // Unit of FocalPlaneXResoluton/FocalPlaneYResolution. '1' means no-unit,
716 // '2' inch, '3' centimeter.
717 switch (_directory.getInt(ExifDirectory.TAG_FOCAL_PLANE_UNIT)) {
718 case 1:
719 return "(No unit)";
720 case 2:
721 return "Inches";
722 case 3:
723 return "cm";
724 default:
725 return "";
726 }
727 }
728
729 public String getExifImageWidthDescription() throws MetadataException
730 {
731 if (!_directory.containsTag(ExifDirectory.TAG_EXIF_IMAGE_WIDTH)) return null;
732 return _directory.getInt(ExifDirectory.TAG_EXIF_IMAGE_WIDTH) + " pixels";
733 }
734
735 public String getExifImageHeightDescription() throws MetadataException
736 {
737 if (!_directory.containsTag(ExifDirectory.TAG_EXIF_IMAGE_HEIGHT)) return null;
738 return _directory.getInt(ExifDirectory.TAG_EXIF_IMAGE_HEIGHT) + " pixels";
739 }
740
741 public String getColorSpaceDescription() throws MetadataException
742 {
743 if (!_directory.containsTag(ExifDirectory.TAG_COLOR_SPACE)) return null;
744 int colorSpace = _directory.getInt(ExifDirectory.TAG_COLOR_SPACE);
745 if (colorSpace == 1) {
746 return "sRGB";
747 } else if (colorSpace == 65535) {
748 return "Undefined";
749 } else {
750 return "Unknown";
751 }
752 }
753
754 public String getFocalLengthDescription() throws MetadataException
755 {
756 if (!_directory.containsTag(ExifDirectory.TAG_FOCAL_LENGTH)) return null;
757 java.text.DecimalFormat formatter = new DecimalFormat("0.0##");
758 Rational focalLength = _directory.getRational(ExifDirectory.TAG_FOCAL_LENGTH);
759 return formatter.format(focalLength.doubleValue()) + " mm";
760 }
761
762 public String getFlashDescription() throws MetadataException
763 {
764 if (!_directory.containsTag(ExifDirectory.TAG_FLASH)) return null;
765
766 /*
767 * This is a bitmask.
768 * 0 = flash fired
769 * 1 = return detected
770 * 2 = return able to be detected
771 * 3 = unknown
772 * 4 = auto used
773 * 5 = unknown
774 * 6 = red eye reduction used
775 */
776
777 int val = _directory.getInt(ExifDirectory.TAG_FLASH);
778
779 StringBuffer sb = new StringBuffer();
780
781 if ((val & 0x1)!=0)
782 sb.append("Flash fired");
783 else
784 sb.append("Flash did not fire");
785
786 // check if we're able to detect a return, before we mention it
787 if ((val & 0x4)!=0)
788 {
789 if ((val & 0x2)!=0)
790 sb.append(", return detected");
791 else
792 sb.append(", return not detected");
793 }
794
795 if ((val & 0x10)!=0)
796 sb.append(", auto");
797
798 if ((val & 0x40)!=0)
799 sb.append(", red-eye reduction");
800
801 return sb.toString();
802 }
803
804 public String getWhiteBalanceDescription() throws MetadataException
805 {
806 if (!_directory.containsTag(ExifDirectory.TAG_WHITE_BALANCE)) return null;
807 // '0' means unknown, '1' daylight, '2' fluorescent, '3' tungsten, '10' flash,
808 // '17' standard light A, '18' standard light B, '19' standard light C, '20' D55,
809 // '21' D65, '22' D75, '255' other.
810 switch (_directory.getInt(ExifDirectory.TAG_WHITE_BALANCE)) {
811 case 0:
812 return "Unknown";
813 case 1:
814 return "Daylight";
815 case 2:
816 return "Flourescent";
817 case 3:
818 return "Tungsten";
819 case 10:
820 return "Flash";
821 case 17:
822 return "Standard light";
823 case 18:
824 return "Standard light (B)";
825 case 19:
826 return "Standard light (C)";
827 case 20:
828 return "D55";
829 case 21:
830 return "D65";
831 case 22:
832 return "D75";
833 case 255:
834 return "(Other)";
835 default:
836 return "Unknown (" + _directory.getInt(ExifDirectory.TAG_WHITE_BALANCE) + ")";
837 }
838 }
839
840 public String getMeteringModeDescription() throws MetadataException
841 {
842 if (!_directory.containsTag(ExifDirectory.TAG_METERING_MODE)) return null;
843 // '0' means unknown, '1' average, '2' center weighted average, '3' spot
844 // '4' multi-spot, '5' multi-segment, '6' partial, '255' other
845 int meteringMode = _directory.getInt(ExifDirectory.TAG_METERING_MODE);
846 switch (meteringMode) {
847 case 0:
848 return "Unknown";
849 case 1:
850 return "Average";
851 case 2:
852 return "Center weighted average";
853 case 3:
854 return "Spot";
855 case 4:
856 return "Multi-spot";
857 case 5:
858 return "Multi-segment";
859 case 6:
860 return "Partial";
861 case 255:
862 return "(Other)";
863 default:
864 return "";
865 }
866 }
867
868 public String getSubjectDistanceDescription() throws MetadataException
869 {
870 if (!_directory.containsTag(ExifDirectory.TAG_SUBJECT_DISTANCE)) return null;
871 Rational distance = _directory.getRational(ExifDirectory.TAG_SUBJECT_DISTANCE);
872 java.text.DecimalFormat formatter = new DecimalFormat("0.0##");
873 return formatter.format(distance.doubleValue()) + " metres";
874 }
875
876 public String getCompressionLevelDescription() throws MetadataException
877 {
878 if (!_directory.containsTag(ExifDirectory.TAG_COMPRESSION_LEVEL)) return null;
879 Rational compressionRatio = _directory.getRational(ExifDirectory.TAG_COMPRESSION_LEVEL);
880 String ratio = compressionRatio.toSimpleString(_allowDecimalRepresentationOfRationals);
881 if (compressionRatio.isInteger() && compressionRatio.intValue() == 1) {
882 return ratio + " bit/pixel";
883 } else {
884 return ratio + " bits/pixel";
885 }
886 }
887
888 public String getThumbnailLengthDescription()
889 {
890 if (!_directory.containsTag(ExifDirectory.TAG_THUMBNAIL_LENGTH)) return null;
891 return _directory.getString(ExifDirectory.TAG_THUMBNAIL_LENGTH) + " bytes";
892 }
893
894 public String getThumbnailOffsetDescription()
895 {
896 if (!_directory.containsTag(ExifDirectory.TAG_THUMBNAIL_OFFSET)) return null;
897 return _directory.getString(ExifDirectory.TAG_THUMBNAIL_OFFSET) + " bytes";
898 }
899
900 public String getYResolutionDescription() throws MetadataException
901 {
902 if (!_directory.containsTag(ExifDirectory.TAG_Y_RESOLUTION)) return null;
903 Rational resolution = _directory.getRational(ExifDirectory.TAG_Y_RESOLUTION);
904 return resolution.toSimpleString(_allowDecimalRepresentationOfRationals) +
905 " dots per " +
906 getResolutionDescription().toLowerCase();
907 }
908
909 public String getXResolutionDescription() throws MetadataException
910 {
911 if (!_directory.containsTag(ExifDirectory.TAG_X_RESOLUTION)) return null;
912 Rational resolution = _directory.getRational(ExifDirectory.TAG_X_RESOLUTION);
913 return resolution.toSimpleString(_allowDecimalRepresentationOfRationals) +
914 " dots per " +
915 getResolutionDescription().toLowerCase();
916 }
917
918 public String getExposureTimeDescription()
919 {
920 if (!_directory.containsTag(ExifDirectory.TAG_EXPOSURE_TIME)) return null;
921 return _directory.getString(ExifDirectory.TAG_EXPOSURE_TIME) + " sec";
922 }
923
924 public String getShutterSpeedDescription() throws MetadataException
925 {
926 // I believe this method to now be stable, but am leaving some alternative snippets of
927 // code in here, to assist anyone who's looking into this (given that I don't have a public CVS).
928
929 if (!_directory.containsTag(ExifDirectory.TAG_SHUTTER_SPEED)) return null;
930// float apexValue = _directory.getFloat(ExifDirectory.TAG_SHUTTER_SPEED);
931// int apexPower = (int)Math.pow(2.0, apexValue);
932// return "1/" + apexPower + " sec";
933 // TODO test this method
934 // thanks to Mark Edwards for spotting and patching a bug in the calculation of this
935 // description (spotted bug using a Canon EOS 300D)
936 // thanks also to Gli Blr for spotting this bug
937 float apexValue = _directory.getFloat(ExifDirectory.TAG_SHUTTER_SPEED);
938 if (apexValue<=1) {
939 float apexPower = (float)(1/(Math.exp(apexValue*Math.log(2))));
940 long apexPower10 = Math.round((double)apexPower * 10.0);
941 float fApexPower = (float) apexPower10 / 10.0f;
942 return fApexPower + " sec";
943 } else {
944 int apexPower = (int)((Math.exp(apexValue*Math.log(2))));
945 return "1/" + apexPower + " sec";
946 }
947
948/*
949 // This alternative implementation offered by Bill Richards
950 // TODO determine which is the correct / more-correct implementation
951 double apexValue = _directory.getDouble(ExifDirectory.TAG_SHUTTER_SPEED);
952 double apexPower = Math.pow(2.0, apexValue);
953
954 StringBuffer sb = new StringBuffer();
955 if (apexPower > 1)
956 apexPower = Math.floor(apexPower);
957
958 if (apexPower < 1) {
959 sb.append((int)Math.round(1/apexPower));
960 } else {
961 sb.append("1/");
962 sb.append((int)apexPower);
963 }
964 sb.append(" sec");
965 return sb.toString();
966*/
967
968 }
969
970 public String getFNumberDescription() throws MetadataException
971 {
972 if (!_directory.containsTag(ExifDirectory.TAG_FNUMBER)) return null;
973 Rational fNumber = _directory.getRational(ExifDirectory.TAG_FNUMBER);
974 return "F" + SimpleDecimalFormatter.format(fNumber.doubleValue());
975 }
976
977 public String getYCbCrPositioningDescription() throws MetadataException
978 {
979 if (!_directory.containsTag(ExifDirectory.TAG_YCBCR_POSITIONING)) return null;
980 int yCbCrPosition = _directory.getInt(ExifDirectory.TAG_YCBCR_POSITIONING);
981 switch (yCbCrPosition) {
982 case 1: return "Center of pixel array";
983 case 2: return "Datum point";
984 default:
985 return String.valueOf(yCbCrPosition);
986 }
987 }
988
989 public String getOrientationDescription() throws MetadataException
990 {
991 if (!_directory.containsTag(ExifDirectory.TAG_ORIENTATION)) return null;
992 int orientation = _directory.getInt(ExifDirectory.TAG_ORIENTATION);
993 switch (orientation) {
994 case 1: return "Top, left side (Horizontal / normal)";
995 case 2: return "Top, right side (Mirror horizontal)";
996 case 3: return "Bottom, right side (Rotate 180)";
997 case 4: return "Bottom, left side (Mirror vertical)";
998 case 5: return "Left side, top (Mirror horizontal and rotate 270 CW)";
999 case 6: return "Right side, top (Rotate 90 CW)";
1000 case 7: return "Right side, bottom (Mirror horizontal and rotate 90 CW)";
1001 case 8: return "Left side, bottom (Rotate 270 CW)";
1002 default:
1003 return String.valueOf(orientation);
1004 }
1005 }
1006
1007 public String getResolutionDescription() throws MetadataException
1008 {
1009 if (!_directory.containsTag(ExifDirectory.TAG_RESOLUTION_UNIT)) return "";
1010 // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch)
1011 int resolutionUnit = _directory.getInt(ExifDirectory.TAG_RESOLUTION_UNIT);
1012 switch (resolutionUnit) {
1013 case 1: return "(No unit)";
1014 case 2: return "Inch";
1015 case 3: return "cm";
1016 default:
1017 return "";
1018 }
1019 }
1020
1021 public String getSensingMethodDescription() throws MetadataException
1022 {
1023 if (!_directory.containsTag(ExifDirectory.TAG_SENSING_METHOD)) return null;
1024 // '1' Not defined, '2' One-chip color area sensor, '3' Two-chip color area sensor
1025 // '4' Three-chip color area sensor, '5' Color sequential area sensor
1026 // '7' Trilinear sensor '8' Color sequential linear sensor, 'Other' reserved
1027 int sensingMethod = _directory.getInt(ExifDirectory.TAG_SENSING_METHOD);
1028 switch (sensingMethod) {
1029 case 1:
1030 return "(Not defined)";
1031 case 2:
1032 return "One-chip color area sensor";
1033 case 3:
1034 return "Two-chip color area sensor";
1035 case 4:
1036 return "Three-chip color area sensor";
1037 case 5:
1038 return "Color sequential area sensor";
1039 case 7:
1040 return "Trilinear sensor";
1041 case 8:
1042 return "Color sequential linear sensor";
1043 default:
1044 return "";
1045 }
1046 }
1047
1048 public String getComponentConfigurationDescription() throws MetadataException
1049 {
1050 int[] components = _directory.getIntArray(ExifDirectory.TAG_COMPONENTS_CONFIGURATION);
1051 String[] componentStrings = {"", "Y", "Cb", "Cr", "R", "G", "B"};
1052 StringBuffer componentConfig = new StringBuffer();
1053 for (int i = 0; i < Math.min(4, components.length); i++) {
1054 int j = components[i];
1055 if (j > 0 && j < componentStrings.length) {
1056 componentConfig.append(componentStrings[j]);
1057 }
1058 }
1059 return componentConfig.toString();
1060 }
1061
1062 /**
1063 * Takes a series of 4 bytes from the specified offset, and converts these to a
1064 * well-known version number, where possible. For example, (hex) 30 32 31 30 == 2.10).
1065 * @param components the four version values
1066 * @return the version as a string of form 2.10
1067 */
1068 public static String convertBytesToVersionString(int[] components)
1069 {
1070 StringBuffer version = new StringBuffer();
1071 for (int i = 0; i < 4 && i < components.length; i++) {
1072 if (i == 2) version.append('.');
1073 String digit = String.valueOf((char)components[i]);
1074 if (i == 0 && "0".equals(digit)) continue;
1075 version.append(digit);
1076 }
1077 return version.toString();
1078 }
1079
1080 /**
1081 * The Windows specific tags uses plain Unicode
1082 */
1083 private String getUnicodeDescription(int tag) throws MetadataException
1084 {
1085 if (!_directory.containsTag(tag)) return null;
1086 byte[] commentBytes = _directory.getByteArray(tag);
1087 try {
1088 // decode the unicode string
1089 // trim it, as i'm seeing a junk character on the end
1090 return new String(commentBytes, "UTF-16LE").trim();
1091 }
1092 catch (UnsupportedEncodingException ex) {
1093 return null;
1094 }
1095 }
1096
1097 public String getWindowsAuthorDescription() throws MetadataException
1098 {
1099 return getUnicodeDescription(ExifDirectory.TAG_WIN_AUTHOR);
1100 }
1101
1102 public String getWindowsCommentDescription() throws MetadataException
1103 {
1104 return getUnicodeDescription(ExifDirectory.TAG_WIN_COMMENT);
1105 }
1106
1107 public String getWindowsKeywordsDescription() throws MetadataException
1108 {
1109 return getUnicodeDescription(ExifDirectory.TAG_WIN_KEYWORDS);
1110 }
1111
1112 public String getWindowsTitleDescription() throws MetadataException
1113 {
1114 return getUnicodeDescription(ExifDirectory.TAG_WIN_TITLE);
1115 }
1116
1117 public String getWindowsSubjectDescription() throws MetadataException
1118 {
1119 return getUnicodeDescription(ExifDirectory.TAG_WIN_SUBJECT);
1120 }
1121}
Note: See TracBrowser for help on using the repository browser.