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

Last change on this file since 13722 was 13061, checked in by Don-vip, 7 years ago

fix #15505 - update to metadata-extractor 2.10.1

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