Changeset 15217 in josm for trunk/src/com/drew/metadata/exif/ExifDescriptorBase.java
- Timestamp:
- 2019-07-07T01:56:46+02:00 (6 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/com/drew/metadata/exif/ExifDescriptorBase.java
r13061 r15217 1 1 /* 2 * Copyright 2002-201 7Drew Noakes2 * Copyright 2002-2019 Drew Noakes and contributors 3 3 * 4 4 * Licensed under the Apache License, Version 2.0 (the "License"); … … 33 33 import java.io.UnsupportedEncodingException; 34 34 import java.text.DecimalFormat; 35 import java.util.HashMap;36 import java.util.Map;37 35 38 36 import static com.drew.metadata.exif.ExifDirectoryBase.*; … … 67 65 public String getDescription(int tagType) 68 66 { 69 // TODO order case blocks and corresponding methods in the same order as the TAG_* values are defined70 71 67 switch (tagType) { 72 68 case TAG_INTEROP_INDEX: … … 74 70 case TAG_INTEROP_VERSION: 75 71 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(); 72 case TAG_NEW_SUBFILE_TYPE: 73 return getNewSubfileTypeDescription(); 74 case TAG_SUBFILE_TYPE: 75 return getSubfileTypeDescription(); 86 76 case TAG_IMAGE_WIDTH: 87 77 return getImageWidthDescription(); … … 90 80 case TAG_BITS_PER_SAMPLE: 91 81 return getBitsPerSampleDescription(); 82 case TAG_COMPRESSION: 83 return getCompressionDescription(); 92 84 case TAG_PHOTOMETRIC_INTERPRETATION: 93 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 94 case TAG_ROWS_PER_STRIP: 95 95 return getRowsPerStripDescription(); 96 96 case TAG_STRIP_BYTE_COUNTS: 97 97 return getStripByteCountsDescription(); 98 case TAG_SAMPLES_PER_PIXEL: 99 return getSamplesPerPixelDescription(); 98 case TAG_X_RESOLUTION: 99 return getXResolutionDescription(); 100 case TAG_Y_RESOLUTION: 101 return getYResolutionDescription(); 100 102 case TAG_PLANAR_CONFIGURATION: 101 103 return getPlanarConfigurationDescription(); 104 case TAG_RESOLUTION_UNIT: 105 return getResolutionDescription(); 106 case TAG_JPEG_PROC: 107 return getJpegProcDescription(); 102 108 case TAG_YCBCR_SUBSAMPLING: 103 109 return getYCbCrSubsamplingDescription(); 110 case TAG_YCBCR_POSITIONING: 111 return getYCbCrPositioningDescription(); 104 112 case TAG_REFERENCE_BLACK_WHITE: 105 113 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 114 case TAG_CFA_PATTERN_2: 125 115 return getCfaPattern2Description(); 126 116 case TAG_EXPOSURE_TIME: 127 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(); 128 132 case TAG_SHUTTER_SPEED: 129 133 return getShutterSpeedDescription(); 130 case TAG_FNUMBER: 131 return getFNumberDescription(); 132 case TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL: 133 return getCompressedAverageBitsPerPixelDescription(); 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(); 134 142 case TAG_SUBJECT_DISTANCE: 135 143 return getSubjectDistanceDescription(); … … 142 150 case TAG_FOCAL_LENGTH: 143 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(); 144 178 case TAG_COLOR_SPACE: 145 179 return getColorSpaceDescription(); … … 148 182 case TAG_EXIF_IMAGE_HEIGHT: 149 183 return getExifImageHeightDescription(); 150 case TAG_FOCAL_PLANE_RESOLUTION_UNIT:151 return getFocalPlaneResolutionUnitDescription();152 184 case TAG_FOCAL_PLANE_X_RESOLUTION: 153 185 return getFocalPlaneXResolutionDescription(); 154 186 case TAG_FOCAL_PLANE_Y_RESOLUTION: 155 187 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(); 188 case TAG_FOCAL_PLANE_RESOLUTION_UNIT: 189 return getFocalPlaneResolutionUnitDescription(); 162 190 case TAG_SENSING_METHOD: 163 191 return getSensingMethodDescription(); 164 case TAG_EXPOSURE_BIAS:165 return getExposureBiasDescription();166 192 case TAG_FILE_SOURCE: 167 193 return getFileSourceDescription(); … … 170 196 case TAG_CFA_PATTERN: 171 197 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 198 case TAG_CUSTOM_RENDERED: 183 199 return getCustomRenderedDescription(); … … 202 218 case TAG_SUBJECT_DISTANCE_RANGE: 203 219 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 220 case TAG_LENS_SPECIFICATION: 211 221 return getLensSpecificationDescription(); … … 216 226 217 227 @Nullable 218 public String getInteropVersionDescription()219 {220 return getVersionBytesDescription(TAG_INTEROP_VERSION, 2);221 }222 223 @Nullable224 228 public String getInteropIndexDescription() 225 229 { … … 235 239 236 240 @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"; 241 public String getInteropVersionDescription() 242 { 243 return getVersionBytesDescription(TAG_INTEROP_VERSION, 2); 452 244 } 453 245 … … 478 270 479 271 @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); 272 public String getImageWidthDescription() 273 { 274 String value = _directory.getString(TAG_IMAGE_WIDTH); 958 275 return value == null ? null : value + " pixels"; 959 276 } 960 277 961 278 @Nullable 962 public String get ExifImageHeightDescription()963 { 964 final Integervalue = _directory.getInteger(TAG_EXIF_IMAGE_HEIGHT);279 public String getImageHeightDescription() 280 { 281 String value = _directory.getString(TAG_IMAGE_HEIGHT); 965 282 return value == null ? null : value + " pixels"; 966 283 } 967 284 968 285 @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 } 286 public String getBitsPerSampleDescription() 287 { 288 String value = _directory.getString(TAG_BITS_PER_SAMPLE); 289 return value == null ? null : value + " bits/component/pixel"; 1086 290 } 1087 291 … … 1140 344 1141 345 @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"; 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(); 1149 681 } 1150 682 … … 1162 694 1163 695 @Nullable 1164 public String getExposureTimeDescription()1165 {1166 String value = _directory.getString(TAG_EXPOSURE_TIME);1167 return value == null ? null : value + " sec";1168 }1169 1170 @Nullable1171 696 public String getShutterSpeedDescription() 1172 697 { … … 1175 700 1176 701 @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()); 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 ); 1183 1053 } 1184 1054 … … 1203 1073 1204 1074 @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]); 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; 1216 1160 } 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 } 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); 1233 1297 } 1234 1298 }
Note:
See TracChangeset
for help on using the changeset viewer.