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

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

see #17848 - update to metadata-extractor 2.12.0

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