source: josm/trunk/src/com/drew/metadata/exif/ExifSubIFDDescriptor.java@ 8132

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

fix #11162 - update to metadata-extractor 2.7.2

  • Property svn:eol-style set to native
  • Property svn:executable set to *
File size: 27.9 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 */
21package com.drew.metadata.exif;
22
23import com.drew.imaging.PhotographicConversions;
24import com.drew.lang.Rational;
25import com.drew.lang.annotations.NotNull;
26import com.drew.lang.annotations.Nullable;
27import com.drew.metadata.TagDescriptor;
28
29import java.io.UnsupportedEncodingException;
30import java.text.DecimalFormat;
31import java.util.HashMap;
32import java.util.Map;
33
34import static com.drew.metadata.exif.ExifSubIFDDirectory.*;
35
36/**
37 * Provides human-readable string representations of tag values stored in a {@link ExifSubIFDDirectory}.
38 *
39 * @author Drew Noakes https://drewnoakes.com
40 */
41public class ExifSubIFDDescriptor extends TagDescriptor<ExifSubIFDDirectory>
42{
43 /**
44 * Dictates whether rational values will be represented in decimal format in instances
45 * where decimal notation is elegant (such as 1/2 -> 0.5, but not 1/3).
46 */
47 private final boolean _allowDecimalRepresentationOfRationals = true;
48
49 @NotNull
50 private static final java.text.DecimalFormat SimpleDecimalFormatter = new DecimalFormat("0.#");
51
52 public ExifSubIFDDescriptor(@NotNull ExifSubIFDDirectory directory)
53 {
54 super(directory);
55 }
56
57 // Note for the potential addition of brightness presentation in eV:
58 // Brightness of taken subject. To calculate Exposure(Ev) from BrightnessValue(Bv),
59 // you must add SensitivityValue(Sv).
60 // Ev=BV+Sv Sv=log2(ISOSpeedRating/3.125)
61 // ISO100:Sv=5, ISO200:Sv=6, ISO400:Sv=7, ISO125:Sv=5.32.
62
63 /**
64 * Returns a descriptive value of the specified tag for this image.
65 * Where possible, known values will be substituted here in place of the raw
66 * tokens actually kept in the Exif segment. If no substitution is
67 * available, the value provided by getString(int) will be returned.
68 *
69 * @param tagType the tag to find a description for
70 * @return a description of the image's value for the specified tag, or
71 * <code>null</code> if the tag hasn't been defined.
72 */
73 @Override
74 @Nullable
75 public String getDescription(int tagType)
76 {
77 switch (tagType) {
78 case TAG_NEW_SUBFILE_TYPE:
79 return getNewSubfileTypeDescription();
80 case TAG_SUBFILE_TYPE:
81 return getSubfileTypeDescription();
82 case TAG_THRESHOLDING:
83 return getThresholdingDescription();
84 case TAG_FILL_ORDER:
85 return getFillOrderDescription();
86 case TAG_EXPOSURE_TIME:
87 return getExposureTimeDescription();
88 case TAG_SHUTTER_SPEED:
89 return getShutterSpeedDescription();
90 case TAG_FNUMBER:
91 return getFNumberDescription();
92 case TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL:
93 return getCompressedAverageBitsPerPixelDescription();
94 case TAG_SUBJECT_DISTANCE:
95 return getSubjectDistanceDescription();
96 case TAG_METERING_MODE:
97 return getMeteringModeDescription();
98 case TAG_WHITE_BALANCE:
99 return getWhiteBalanceDescription();
100 case TAG_FLASH:
101 return getFlashDescription();
102 case TAG_FOCAL_LENGTH:
103 return getFocalLengthDescription();
104 case TAG_COLOR_SPACE:
105 return getColorSpaceDescription();
106 case TAG_EXIF_IMAGE_WIDTH:
107 return getExifImageWidthDescription();
108 case TAG_EXIF_IMAGE_HEIGHT:
109 return getExifImageHeightDescription();
110 case TAG_FOCAL_PLANE_RESOLUTION_UNIT:
111 return getFocalPlaneResolutionUnitDescription();
112 case TAG_FOCAL_PLANE_X_RESOLUTION:
113 return getFocalPlaneXResolutionDescription();
114 case TAG_FOCAL_PLANE_Y_RESOLUTION:
115 return getFocalPlaneYResolutionDescription();
116 case TAG_BITS_PER_SAMPLE:
117 return getBitsPerSampleDescription();
118 case TAG_PHOTOMETRIC_INTERPRETATION:
119 return getPhotometricInterpretationDescription();
120 case TAG_ROWS_PER_STRIP:
121 return getRowsPerStripDescription();
122 case TAG_STRIP_BYTE_COUNTS:
123 return getStripByteCountsDescription();
124 case TAG_SAMPLES_PER_PIXEL:
125 return getSamplesPerPixelDescription();
126 case TAG_PLANAR_CONFIGURATION:
127 return getPlanarConfigurationDescription();
128 case TAG_YCBCR_SUBSAMPLING:
129 return getYCbCrSubsamplingDescription();
130 case TAG_EXPOSURE_PROGRAM:
131 return getExposureProgramDescription();
132 case TAG_APERTURE:
133 return getApertureValueDescription();
134 case TAG_MAX_APERTURE:
135 return getMaxApertureValueDescription();
136 case TAG_SENSING_METHOD:
137 return getSensingMethodDescription();
138 case TAG_EXPOSURE_BIAS:
139 return getExposureBiasDescription();
140 case TAG_FILE_SOURCE:
141 return getFileSourceDescription();
142 case TAG_SCENE_TYPE:
143 return getSceneTypeDescription();
144 case TAG_COMPONENTS_CONFIGURATION:
145 return getComponentConfigurationDescription();
146 case TAG_EXIF_VERSION:
147 return getExifVersionDescription();
148 case TAG_FLASHPIX_VERSION:
149 return getFlashPixVersionDescription();
150 case TAG_ISO_EQUIVALENT:
151 return getIsoEquivalentDescription();
152 case TAG_USER_COMMENT:
153 return getUserCommentDescription();
154 case TAG_CUSTOM_RENDERED:
155 return getCustomRenderedDescription();
156 case TAG_EXPOSURE_MODE:
157 return getExposureModeDescription();
158 case TAG_WHITE_BALANCE_MODE:
159 return getWhiteBalanceModeDescription();
160 case TAG_DIGITAL_ZOOM_RATIO:
161 return getDigitalZoomRatioDescription();
162 case TAG_35MM_FILM_EQUIV_FOCAL_LENGTH:
163 return get35mmFilmEquivFocalLengthDescription();
164 case TAG_SCENE_CAPTURE_TYPE:
165 return getSceneCaptureTypeDescription();
166 case TAG_GAIN_CONTROL:
167 return getGainControlDescription();
168 case TAG_CONTRAST:
169 return getContrastDescription();
170 case TAG_SATURATION:
171 return getSaturationDescription();
172 case TAG_SHARPNESS:
173 return getSharpnessDescription();
174 case TAG_SUBJECT_DISTANCE_RANGE:
175 return getSubjectDistanceRangeDescription();
176 default:
177 return super.getDescription(tagType);
178 }
179 }
180
181 @Nullable
182 public String getNewSubfileTypeDescription()
183 {
184 return getIndexedDescription(TAG_NEW_SUBFILE_TYPE, 1,
185 "Full-resolution image",
186 "Reduced-resolution image",
187 "Single page of multi-page reduced-resolution image",
188 "Transparency mask",
189 "Transparency mask of reduced-resolution image",
190 "Transparency mask of multi-page image",
191 "Transparency mask of reduced-resolution multi-page image"
192 );
193 }
194
195 @Nullable
196 public String getSubfileTypeDescription()
197 {
198 return getIndexedDescription(TAG_SUBFILE_TYPE, 1,
199 "Full-resolution image",
200 "Reduced-resolution image",
201 "Single page of multi-page image"
202 );
203 }
204
205 @Nullable
206 public String getThresholdingDescription()
207 {
208 return getIndexedDescription(TAG_THRESHOLDING, 1,
209 "No dithering or halftoning",
210 "Ordered dither or halftone",
211 "Randomized dither"
212 );
213 }
214
215 @Nullable
216 public String getFillOrderDescription()
217 {
218 return getIndexedDescription(TAG_FILL_ORDER, 1,
219 "Normal",
220 "Reversed"
221 );
222 }
223
224 @Nullable
225 public String getSubjectDistanceRangeDescription()
226 {
227 return getIndexedDescription(TAG_SUBJECT_DISTANCE_RANGE,
228 "Unknown",
229 "Macro",
230 "Close view",
231 "Distant view"
232 );
233 }
234
235 @Nullable
236 public String getSharpnessDescription()
237 {
238 return getIndexedDescription(TAG_SHARPNESS,
239 "None",
240 "Low",
241 "Hard"
242 );
243 }
244
245 @Nullable
246 public String getSaturationDescription()
247 {
248 return getIndexedDescription(TAG_SATURATION,
249 "None",
250 "Low saturation",
251 "High saturation"
252 );
253 }
254
255 @Nullable
256 public String getContrastDescription()
257 {
258 return getIndexedDescription(TAG_CONTRAST,
259 "None",
260 "Soft",
261 "Hard"
262 );
263 }
264
265 @Nullable
266 public String getGainControlDescription()
267 {
268 return getIndexedDescription(TAG_GAIN_CONTROL,
269 "None",
270 "Low gain up",
271 "Low gain down",
272 "High gain up",
273 "High gain down"
274 );
275 }
276
277 @Nullable
278 public String getSceneCaptureTypeDescription()
279 {
280 return getIndexedDescription(TAG_SCENE_CAPTURE_TYPE,
281 "Standard",
282 "Landscape",
283 "Portrait",
284 "Night scene"
285 );
286 }
287
288 @Nullable
289 public String get35mmFilmEquivFocalLengthDescription()
290 {
291 Integer value = _directory.getInteger(TAG_35MM_FILM_EQUIV_FOCAL_LENGTH);
292 return value == null
293 ? null
294 : value == 0
295 ? "Unknown"
296 : SimpleDecimalFormatter.format(value) + "mm";
297 }
298
299 @Nullable
300 public String getDigitalZoomRatioDescription()
301 {
302 Rational value = _directory.getRational(TAG_DIGITAL_ZOOM_RATIO);
303 return value == null
304 ? null
305 : value.getNumerator() == 0
306 ? "Digital zoom not used."
307 : SimpleDecimalFormatter.format(value.doubleValue());
308 }
309
310 @Nullable
311 public String getWhiteBalanceModeDescription()
312 {
313 return getIndexedDescription(TAG_WHITE_BALANCE_MODE,
314 "Auto white balance",
315 "Manual white balance"
316 );
317 }
318
319 @Nullable
320 public String getExposureModeDescription()
321 {
322 return getIndexedDescription(TAG_EXPOSURE_MODE,
323 "Auto exposure",
324 "Manual exposure",
325 "Auto bracket"
326 );
327 }
328
329 @Nullable
330 public String getCustomRenderedDescription()
331 {
332 return getIndexedDescription(TAG_CUSTOM_RENDERED,
333 "Normal process",
334 "Custom process"
335 );
336 }
337
338 @Nullable
339 public String getUserCommentDescription()
340 {
341 byte[] commentBytes = _directory.getByteArray(TAG_USER_COMMENT);
342 if (commentBytes == null)
343 return null;
344 if (commentBytes.length == 0)
345 return "";
346
347 final Map<String, String> encodingMap = new HashMap<String, String>();
348 encodingMap.put("ASCII", System.getProperty("file.encoding")); // Someone suggested "ISO-8859-1".
349 encodingMap.put("UNICODE", "UTF-16LE");
350 encodingMap.put("JIS", "Shift-JIS"); // We assume this charset for now. Another suggestion is "JIS".
351
352 try {
353 if (commentBytes.length >= 10) {
354 String firstTenBytesString = new String(commentBytes, 0, 10);
355
356 // try each encoding name
357 for (Map.Entry<String, String> pair : encodingMap.entrySet()) {
358 String encodingName = pair.getKey();
359 String charset = pair.getValue();
360 if (firstTenBytesString.startsWith(encodingName)) {
361 // skip any null or blank characters commonly present after the encoding name, up to a limit of 10 from the start
362 for (int j = encodingName.length(); j < 10; j++) {
363 byte b = commentBytes[j];
364 if (b != '\0' && b != ' ')
365 return new String(commentBytes, j, commentBytes.length - j, charset).trim();
366 }
367 return new String(commentBytes, 10, commentBytes.length - 10, charset).trim();
368 }
369 }
370 }
371 // special handling fell through, return a plain string representation
372 return new String(commentBytes, System.getProperty("file.encoding")).trim();
373 } catch (UnsupportedEncodingException ex) {
374 return null;
375 }
376 }
377
378 @Nullable
379 public String getIsoEquivalentDescription()
380 {
381 // Have seen an exception here from files produced by ACDSEE that stored an int[] here with two values
382 Integer isoEquiv = _directory.getInteger(TAG_ISO_EQUIVALENT);
383 // There used to be a check here that multiplied ISO values < 50 by 200.
384 // Issue 36 shows a smart-phone image from a Samsung Galaxy S2 with ISO-40.
385 return isoEquiv != null
386 ? Integer.toString(isoEquiv)
387 : null;
388 }
389
390 @Nullable
391 public String getExifVersionDescription()
392 {
393 return getVersionBytesDescription(TAG_EXIF_VERSION, 2);
394 }
395
396 @Nullable
397 public String getFlashPixVersionDescription()
398 {
399 return getVersionBytesDescription(TAG_FLASHPIX_VERSION, 2);
400 }
401
402 @Nullable
403 public String getSceneTypeDescription()
404 {
405 return getIndexedDescription(TAG_SCENE_TYPE,
406 1,
407 "Directly photographed image"
408 );
409 }
410
411 @Nullable
412 public String getFileSourceDescription()
413 {
414 return getIndexedDescription(TAG_FILE_SOURCE,
415 1,
416 "Film Scanner",
417 "Reflection Print Scanner",
418 "Digital Still Camera (DSC)"
419 );
420 }
421
422 @Nullable
423 public String getExposureBiasDescription()
424 {
425 Rational value = _directory.getRational(TAG_EXPOSURE_BIAS);
426 if (value == null)
427 return null;
428 return value.toSimpleString(true) + " EV";
429 }
430
431 @Nullable
432 public String getMaxApertureValueDescription()
433 {
434 Double aperture = _directory.getDoubleObject(TAG_MAX_APERTURE);
435 if (aperture == null)
436 return null;
437 double fStop = PhotographicConversions.apertureToFStop(aperture);
438 return "F" + SimpleDecimalFormatter.format(fStop);
439 }
440
441 @Nullable
442 public String getApertureValueDescription()
443 {
444 Double aperture = _directory.getDoubleObject(TAG_APERTURE);
445 if (aperture == null)
446 return null;
447 double fStop = PhotographicConversions.apertureToFStop(aperture);
448 return "F" + SimpleDecimalFormatter.format(fStop);
449 }
450
451 @Nullable
452 public String getExposureProgramDescription()
453 {
454 return getIndexedDescription(TAG_EXPOSURE_PROGRAM,
455 1,
456 "Manual control",
457 "Program normal",
458 "Aperture priority",
459 "Shutter priority",
460 "Program creative (slow program)",
461 "Program action (high-speed program)",
462 "Portrait mode",
463 "Landscape mode"
464 );
465 }
466
467 @Nullable
468 public String getYCbCrSubsamplingDescription()
469 {
470 int[] positions = _directory.getIntArray(TAG_YCBCR_SUBSAMPLING);
471 if (positions == null)
472 return null;
473 if (positions[0] == 2 && positions[1] == 1) {
474 return "YCbCr4:2:2";
475 } else if (positions[0] == 2 && positions[1] == 2) {
476 return "YCbCr4:2:0";
477 } else {
478 return "(Unknown)";
479 }
480 }
481
482 @Nullable
483 public String getPlanarConfigurationDescription()
484 {
485 // When image format is no compression YCbCr, this value shows byte aligns of YCbCr
486 // data. If value is '1', Y/Cb/Cr value is chunky format, contiguous for each subsampling
487 // pixel. If value is '2', Y/Cb/Cr value is separated and stored to Y plane/Cb plane/Cr
488 // plane format.
489 return getIndexedDescription(TAG_PLANAR_CONFIGURATION,
490 1,
491 "Chunky (contiguous for each subsampling pixel)",
492 "Separate (Y-plane/Cb-plane/Cr-plane format)"
493 );
494 }
495
496 @Nullable
497 public String getSamplesPerPixelDescription()
498 {
499 String value = _directory.getString(TAG_SAMPLES_PER_PIXEL);
500 return value == null ? null : value + " samples/pixel";
501 }
502
503 @Nullable
504 public String getRowsPerStripDescription()
505 {
506 final String value = _directory.getString(TAG_ROWS_PER_STRIP);
507 return value == null ? null : value + " rows/strip";
508 }
509
510 @Nullable
511 public String getStripByteCountsDescription()
512 {
513 final String value = _directory.getString(TAG_STRIP_BYTE_COUNTS);
514 return value == null ? null : value + " bytes";
515 }
516
517 @Nullable
518 public String getPhotometricInterpretationDescription()
519 {
520 // Shows the color space of the image data components
521 Integer value = _directory.getInteger(TAG_PHOTOMETRIC_INTERPRETATION);
522 if (value == null)
523 return null;
524 switch (value) {
525 case 0: return "WhiteIsZero";
526 case 1: return "BlackIsZero";
527 case 2: return "RGB";
528 case 3: return "RGB Palette";
529 case 4: return "Transparency Mask";
530 case 5: return "CMYK";
531 case 6: return "YCbCr";
532 case 8: return "CIELab";
533 case 9: return "ICCLab";
534 case 10: return "ITULab";
535 case 32803: return "Color Filter Array";
536 case 32844: return "Pixar LogL";
537 case 32845: return "Pixar LogLuv";
538 case 32892: return "Linear Raw";
539 default:
540 return "Unknown colour space";
541 }
542 }
543
544 @Nullable
545 public String getBitsPerSampleDescription()
546 {
547 String value = _directory.getString(TAG_BITS_PER_SAMPLE);
548 return value == null ? null : value + " bits/component/pixel";
549 }
550
551 @Nullable
552 public String getFocalPlaneXResolutionDescription()
553 {
554 Rational rational = _directory.getRational(TAG_FOCAL_PLANE_X_RESOLUTION);
555 if (rational == null)
556 return null;
557 final String unit = getFocalPlaneResolutionUnitDescription();
558 return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals)
559 + (unit == null ? "" : " " + unit.toLowerCase());
560 }
561
562 @Nullable
563 public String getFocalPlaneYResolutionDescription()
564 {
565 Rational rational = _directory.getRational(TAG_FOCAL_PLANE_Y_RESOLUTION);
566 if (rational == null)
567 return null;
568 final String unit = getFocalPlaneResolutionUnitDescription();
569 return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals)
570 + (unit == null ? "" : " " + unit.toLowerCase());
571 }
572
573 @Nullable
574 public String getFocalPlaneResolutionUnitDescription()
575 {
576 // Unit of FocalPlaneXResolution/FocalPlaneYResolution.
577 // '1' means no-unit, '2' inch, '3' centimeter.
578 return getIndexedDescription(TAG_FOCAL_PLANE_RESOLUTION_UNIT,
579 1,
580 "(No unit)",
581 "Inches",
582 "cm"
583 );
584 }
585
586 @Nullable
587 public String getExifImageWidthDescription()
588 {
589 final Integer value = _directory.getInteger(TAG_EXIF_IMAGE_WIDTH);
590 return value == null ? null : value + " pixels";
591 }
592
593 @Nullable
594 public String getExifImageHeightDescription()
595 {
596 final Integer value = _directory.getInteger(TAG_EXIF_IMAGE_HEIGHT);
597 return value == null ? null : value + " pixels";
598 }
599
600 @Nullable
601 public String getColorSpaceDescription()
602 {
603 final Integer value = _directory.getInteger(TAG_COLOR_SPACE);
604 if (value == null)
605 return null;
606 if (value == 1)
607 return "sRGB";
608 if (value == 65535)
609 return "Undefined";
610 return "Unknown (" + value + ")";
611 }
612
613 @Nullable
614 public String getFocalLengthDescription()
615 {
616 Rational value = _directory.getRational(TAG_FOCAL_LENGTH);
617 if (value == null)
618 return null;
619 java.text.DecimalFormat formatter = new DecimalFormat("0.0##");
620 return formatter.format(value.doubleValue()) + " mm";
621 }
622
623 @Nullable
624 public String getFlashDescription()
625 {
626 /*
627 * This is a bit mask.
628 * 0 = flash fired
629 * 1 = return detected
630 * 2 = return able to be detected
631 * 3 = unknown
632 * 4 = auto used
633 * 5 = unknown
634 * 6 = red eye reduction used
635 */
636
637 final Integer value = _directory.getInteger(TAG_FLASH);
638
639 if (value == null)
640 return null;
641
642 StringBuilder sb = new StringBuilder();
643
644 if ((value & 0x1) != 0)
645 sb.append("Flash fired");
646 else
647 sb.append("Flash did not fire");
648
649 // check if we're able to detect a return, before we mention it
650 if ((value & 0x4) != 0) {
651 if ((value & 0x2) != 0)
652 sb.append(", return detected");
653 else
654 sb.append(", return not detected");
655 }
656
657 if ((value & 0x10) != 0)
658 sb.append(", auto");
659
660 if ((value & 0x40) != 0)
661 sb.append(", red-eye reduction");
662
663 return sb.toString();
664 }
665
666 @Nullable
667 public String getWhiteBalanceDescription()
668 {
669 // '0' means unknown, '1' daylight, '2' fluorescent, '3' tungsten, '10' flash,
670 // '17' standard light A, '18' standard light B, '19' standard light C, '20' D55,
671 // '21' D65, '22' D75, '255' other.
672 final Integer value = _directory.getInteger(TAG_WHITE_BALANCE);
673 if (value == null)
674 return null;
675 switch (value) {
676 case 0: return "Unknown";
677 case 1: return "Daylight";
678 case 2: return "Florescent";
679 case 3: return "Tungsten";
680 case 10: return "Flash";
681 case 17: return "Standard light";
682 case 18: return "Standard light (B)";
683 case 19: return "Standard light (C)";
684 case 20: return "D55";
685 case 21: return "D65";
686 case 22: return "D75";
687 case 255: return "(Other)";
688 default:
689 return "Unknown (" + value + ")";
690 }
691 }
692
693 @Nullable
694 public String getMeteringModeDescription()
695 {
696 // '0' means unknown, '1' average, '2' center weighted average, '3' spot
697 // '4' multi-spot, '5' multi-segment, '6' partial, '255' other
698 Integer value = _directory.getInteger(TAG_METERING_MODE);
699 if (value == null)
700 return null;
701 switch (value) {
702 case 0: return "Unknown";
703 case 1: return "Average";
704 case 2: return "Center weighted average";
705 case 3: return "Spot";
706 case 4: return "Multi-spot";
707 case 5: return "Multi-segment";
708 case 6: return "Partial";
709 case 255: return "(Other)";
710 default:
711 return "";
712 }
713 }
714
715 @Nullable
716 public String getSubjectDistanceDescription()
717 {
718 Rational value = _directory.getRational(TAG_SUBJECT_DISTANCE);
719 if (value == null)
720 return null;
721 java.text.DecimalFormat formatter = new DecimalFormat("0.0##");
722 return formatter.format(value.doubleValue()) + " metres";
723 }
724
725 @Nullable
726 public String getCompressedAverageBitsPerPixelDescription()
727 {
728 Rational value = _directory.getRational(TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL);
729 if (value == null)
730 return null;
731 String ratio = value.toSimpleString(_allowDecimalRepresentationOfRationals);
732 return value.isInteger() && value.intValue() == 1
733 ? ratio + " bit/pixel"
734 : ratio + " bits/pixel";
735 }
736
737 @Nullable
738 public String getExposureTimeDescription()
739 {
740 String value = _directory.getString(TAG_EXPOSURE_TIME);
741 return value == null ? null : value + " sec";
742 }
743
744 @Nullable
745 public String getShutterSpeedDescription()
746 {
747 // I believe this method to now be stable, but am leaving some alternative snippets of
748 // code in here, to assist anyone who's looking into this (given that I don't have a public CVS).
749
750// float apexValue = _directory.getFloat(ExifSubIFDDirectory.TAG_SHUTTER_SPEED);
751// int apexPower = (int)Math.pow(2.0, apexValue);
752// return "1/" + apexPower + " sec";
753 // TODO test this method
754 // thanks to Mark Edwards for spotting and patching a bug in the calculation of this
755 // description (spotted bug using a Canon EOS 300D)
756 // thanks also to Gli Blr for spotting this bug
757 Float apexValue = _directory.getFloatObject(TAG_SHUTTER_SPEED);
758 if (apexValue == null)
759 return null;
760 if (apexValue <= 1) {
761 float apexPower = (float)(1 / (Math.exp(apexValue * Math.log(2))));
762 long apexPower10 = Math.round((double)apexPower * 10.0);
763 float fApexPower = (float)apexPower10 / 10.0f;
764 return fApexPower + " sec";
765 } else {
766 int apexPower = (int)((Math.exp(apexValue * Math.log(2))));
767 return "1/" + apexPower + " sec";
768 }
769
770/*
771 // This alternative implementation offered by Bill Richards
772 // TODO determine which is the correct / more-correct implementation
773 double apexValue = _directory.getDouble(ExifSubIFDDirectory.TAG_SHUTTER_SPEED);
774 double apexPower = Math.pow(2.0, apexValue);
775
776 StringBuffer sb = new StringBuffer();
777 if (apexPower > 1)
778 apexPower = Math.floor(apexPower);
779
780 if (apexPower < 1) {
781 sb.append((int)Math.round(1/apexPower));
782 } else {
783 sb.append("1/");
784 sb.append((int)apexPower);
785 }
786 sb.append(" sec");
787 return sb.toString();
788*/
789 }
790
791 @Nullable
792 public String getFNumberDescription()
793 {
794 Rational value = _directory.getRational(TAG_FNUMBER);
795 if (value == null)
796 return null;
797 return "F" + SimpleDecimalFormatter.format(value.doubleValue());
798 }
799
800 @Nullable
801 public String getSensingMethodDescription()
802 {
803 // '1' Not defined, '2' One-chip color area sensor, '3' Two-chip color area sensor
804 // '4' Three-chip color area sensor, '5' Color sequential area sensor
805 // '7' Trilinear sensor '8' Color sequential linear sensor, 'Other' reserved
806 return getIndexedDescription(TAG_SENSING_METHOD,
807 1,
808 "(Not defined)",
809 "One-chip color area sensor",
810 "Two-chip color area sensor",
811 "Three-chip color area sensor",
812 "Color sequential area sensor",
813 null,
814 "Trilinear sensor",
815 "Color sequential linear sensor"
816 );
817 }
818
819 @Nullable
820 public String getComponentConfigurationDescription()
821 {
822 int[] components = _directory.getIntArray(TAG_COMPONENTS_CONFIGURATION);
823 if (components == null)
824 return null;
825 String[] componentStrings = {"", "Y", "Cb", "Cr", "R", "G", "B"};
826 StringBuilder componentConfig = new StringBuilder();
827 for (int i = 0; i < Math.min(4, components.length); i++) {
828 int j = components[i];
829 if (j > 0 && j < componentStrings.length) {
830 componentConfig.append(componentStrings[j]);
831 }
832 }
833 return componentConfig.toString();
834 }
835}
Note: See TracBrowser for help on using the repository browser.