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

Last change on this file since 11277 was 10862, checked in by Don-vip, 9 years ago

update to metadata-extractor 2.9.1

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