source: josm/trunk/src/com/drew/metadata/exif/ExifDescriptorBase.java@ 10411

Last change on this file since 10411 was 8243, checked in by Don-vip, 10 years ago

fix #11359 - update to metadata-extractor 2.8.1

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