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

Last change on this file since 6127 was 6127, checked in by bastiK, 11 years ago

applied #8895 - Upgrade metadata-extractor to v. 2.6.4 (patch by ebourg)

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