/* * ExifDescriptor.java * * This is public domain software - that is, you can do whatever you want * with it, and include it software that is licensed under the GNU or the * BSD license, or whatever other licence you choose, including proprietary * closed source licenses. I do ask that you leave this header in tact. * * If you make modifications to this code that you think would benefit the * wider community, please send me a copy and I'll post it on my site. * * If you make use of this code, I'd appreciate hearing about it. * drew@drewnoakes.com * Latest version of this software kept at * http://drewnoakes.com/ * * Created by dnoakes on 12-Nov-2002 22:27:15 using IntelliJ IDEA. */ package com.drew.metadata.exif; import com.drew.imaging.PhotographicConversions; import com.drew.lang.Rational; import com.drew.metadata.Directory; import com.drew.metadata.MetadataException; import com.drew.metadata.TagDescriptor; import java.io.UnsupportedEncodingException; import java.text.DecimalFormat; /** * Contains all logic for the presentation of raw Exif data, as stored in ExifDirectory. Use * this class to provide human-readable descriptions of tag values. */ public class ExifDescriptor extends TagDescriptor { /** * Dictates whether rational values will be represented in decimal format in instances * where decimal notation is elegant (such as 1/2 -> 0.5, but not 1/3). */ private boolean _allowDecimalRepresentationOfRationals = true; private static final java.text.DecimalFormat SimpleDecimalFormatter = new DecimalFormat("0.#"); public ExifDescriptor(Directory directory) { super(directory); } // Note for the potential addition of brightness presentation in eV: // Brightness of taken subject. To calculate Exposure(Ev) from BrigtnessValue(Bv), // you must add SensitivityValue(Sv). // Ev=BV+Sv Sv=log2(ISOSpeedRating/3.125) // ISO100:Sv=5, ISO200:Sv=6, ISO400:Sv=7, ISO125:Sv=5.32. /** * Returns a descriptive value of the the specified tag for this image. * Where possible, known values will be substituted here in place of the raw * tokens actually kept in the Exif segment. If no substitution is * available, the value provided by getString(int) will be returned. * @param tagType the tag to find a description for * @return a description of the image's value for the specified tag, or * null if the tag hasn't been defined. */ public String getDescription(int tagType) throws MetadataException { switch (tagType) { case ExifDirectory.TAG_ORIENTATION: return getOrientationDescription(); case ExifDirectory.TAG_NEW_SUBFILE_TYPE: return getNewSubfileTypeDescription(); case ExifDirectory.TAG_SUBFILE_TYPE: return getSubfileTypeDescription(); case ExifDirectory.TAG_THRESHOLDING: return getThresholdingDescription(); case ExifDirectory.TAG_FILL_ORDER: return getFillOrderDescription(); case ExifDirectory.TAG_RESOLUTION_UNIT: return getResolutionDescription(); case ExifDirectory.TAG_YCBCR_POSITIONING: return getYCbCrPositioningDescription(); case ExifDirectory.TAG_EXPOSURE_TIME: return getExposureTimeDescription(); case ExifDirectory.TAG_SHUTTER_SPEED: return getShutterSpeedDescription(); case ExifDirectory.TAG_FNUMBER: return getFNumberDescription(); case ExifDirectory.TAG_X_RESOLUTION: return getXResolutionDescription(); case ExifDirectory.TAG_Y_RESOLUTION: return getYResolutionDescription(); case ExifDirectory.TAG_THUMBNAIL_OFFSET: return getThumbnailOffsetDescription(); case ExifDirectory.TAG_THUMBNAIL_LENGTH: return getThumbnailLengthDescription(); case ExifDirectory.TAG_COMPRESSION_LEVEL: return getCompressionLevelDescription(); case ExifDirectory.TAG_SUBJECT_DISTANCE: return getSubjectDistanceDescription(); case ExifDirectory.TAG_METERING_MODE: return getMeteringModeDescription(); case ExifDirectory.TAG_WHITE_BALANCE: return getWhiteBalanceDescription(); case ExifDirectory.TAG_FLASH: return getFlashDescription(); case ExifDirectory.TAG_FOCAL_LENGTH: return getFocalLengthDescription(); case ExifDirectory.TAG_COLOR_SPACE: return getColorSpaceDescription(); case ExifDirectory.TAG_EXIF_IMAGE_WIDTH: return getExifImageWidthDescription(); case ExifDirectory.TAG_EXIF_IMAGE_HEIGHT: return getExifImageHeightDescription(); case ExifDirectory.TAG_FOCAL_PLANE_UNIT: return getFocalPlaneResolutionUnitDescription(); case ExifDirectory.TAG_FOCAL_PLANE_X_RES: return getFocalPlaneXResolutionDescription(); case ExifDirectory.TAG_FOCAL_PLANE_Y_RES: return getFocalPlaneYResolutionDescription(); case ExifDirectory.TAG_THUMBNAIL_IMAGE_WIDTH: return getThumbnailImageWidthDescription(); case ExifDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT: return getThumbnailImageHeightDescription(); case ExifDirectory.TAG_BITS_PER_SAMPLE: return getBitsPerSampleDescription(); case ExifDirectory.TAG_COMPRESSION: return getCompressionDescription(); case ExifDirectory.TAG_PHOTOMETRIC_INTERPRETATION: return getPhotometricInterpretationDescription(); case ExifDirectory.TAG_ROWS_PER_STRIP: return getRowsPerStripDescription(); case ExifDirectory.TAG_STRIP_BYTE_COUNTS: return getStripByteCountsDescription(); case ExifDirectory.TAG_SAMPLES_PER_PIXEL: return getSamplesPerPixelDescription(); case ExifDirectory.TAG_PLANAR_CONFIGURATION: return getPlanarConfigurationDescription(); case ExifDirectory.TAG_YCBCR_SUBSAMPLING: return getYCbCrSubsamplingDescription(); case ExifDirectory.TAG_EXPOSURE_PROGRAM: return getExposureProgramDescription(); case ExifDirectory.TAG_APERTURE: return getApertureValueDescription(); case ExifDirectory.TAG_MAX_APERTURE: return getMaxApertureValueDescription(); case ExifDirectory.TAG_SENSING_METHOD: return getSensingMethodDescription(); case ExifDirectory.TAG_EXPOSURE_BIAS: return getExposureBiasDescription(); case ExifDirectory.TAG_FILE_SOURCE: return getFileSourceDescription(); case ExifDirectory.TAG_SCENE_TYPE: return getSceneTypeDescription(); case ExifDirectory.TAG_COMPONENTS_CONFIGURATION: return getComponentConfigurationDescription(); case ExifDirectory.TAG_EXIF_VERSION: return getExifVersionDescription(); case ExifDirectory.TAG_FLASHPIX_VERSION: return getFlashPixVersionDescription(); case ExifDirectory.TAG_REFERENCE_BLACK_WHITE: return getReferenceBlackWhiteDescription(); case ExifDirectory.TAG_ISO_EQUIVALENT: return getIsoEquivalentDescription(); case ExifDirectory.TAG_THUMBNAIL_DATA: return getThumbnailDescription(); case ExifDirectory.TAG_USER_COMMENT: return getUserCommentDescription(); case ExifDirectory.TAG_CUSTOM_RENDERED: return getCustomRenderedDescription(); case ExifDirectory.TAG_EXPOSURE_MODE: return getExposureModeDescription(); case ExifDirectory.TAG_WHITE_BALANCE_MODE: return getWhiteBalanceModeDescription(); case ExifDirectory.TAG_DIGITAL_ZOOM_RATIO: return getDigitalZoomRatioDescription(); case ExifDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH: return get35mmFilmEquivFocalLengthDescription(); case ExifDirectory.TAG_SCENE_CAPTURE_TYPE: return getSceneCaptureTypeDescription(); case ExifDirectory.TAG_GAIN_CONTROL: return getGainControlDescription(); case ExifDirectory.TAG_CONTRAST: return getContrastDescription(); case ExifDirectory.TAG_SATURATION: return getSaturationDescription(); case ExifDirectory.TAG_SHARPNESS: return getSharpnessDescription(); case ExifDirectory.TAG_SUBJECT_DISTANCE_RANGE: return getSubjectDistanceRangeDescription(); case ExifDirectory.TAG_WIN_AUTHOR: return getWindowsAuthorDescription(); case ExifDirectory.TAG_WIN_COMMENT: return getWindowsCommentDescription(); case ExifDirectory.TAG_WIN_KEYWORDS: return getWindowsKeywordsDescription(); case ExifDirectory.TAG_WIN_SUBJECT: return getWindowsSubjectDescription(); case ExifDirectory.TAG_WIN_TITLE: return getWindowsTitleDescription(); default: return _directory.getString(tagType); } } public String getNewSubfileTypeDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_NEW_SUBFILE_TYPE)) return null; switch (_directory.getInt(ExifDirectory.TAG_NEW_SUBFILE_TYPE)) { case 1: return "Full-resolution image"; case 2: return "Reduced-resolution image"; case 3: return "Single page of multi-page reduced-resolution image"; case 4: return "Transparency mask"; case 5: return "Transparency mask of reduced-resolution image"; case 6: return "Transparency mask of multi-page image"; case 7: return "Transparency mask of reduced-resolution multi-page image"; default: return "Unknown (" + _directory.getInt(ExifDirectory.TAG_NEW_SUBFILE_TYPE) + ")"; } } public String getSubfileTypeDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_SUBFILE_TYPE)) return null; switch (_directory.getInt(ExifDirectory.TAG_SUBFILE_TYPE)) { case 1: return "Full-resolution image"; case 2: return "Reduced-resolution image"; case 3: return "Single page of multi-page image"; default: return "Unknown (" + _directory.getInt(ExifDirectory.TAG_SUBFILE_TYPE) + ")"; } } public String getThresholdingDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_THRESHOLDING)) return null; switch (_directory.getInt(ExifDirectory.TAG_THRESHOLDING)) { case 1: return "No dithering or halftoning"; case 2: return "Ordered dither or halftone"; case 3: return "Randomized dither"; default: return "Unknown (" + _directory.getInt(ExifDirectory.TAG_THRESHOLDING) + ")"; } } public String getFillOrderDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_FILL_ORDER)) return null; switch (_directory.getInt(ExifDirectory.TAG_FILL_ORDER)) { case 1: return "Normal"; case 2: return "Reversed"; default: return "Unknown (" + _directory.getInt(ExifDirectory.TAG_FILL_ORDER) + ")"; } } public String getSubjectDistanceRangeDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_SUBJECT_DISTANCE_RANGE)) return null; switch (_directory.getInt(ExifDirectory.TAG_SUBJECT_DISTANCE_RANGE)) { case 0: return "Unknown"; case 1: return "Macro"; case 2: return "Close view"; case 3: return "Distant view"; default: return "Unknown (" + _directory.getInt(ExifDirectory.TAG_SUBJECT_DISTANCE_RANGE) + ")"; } } public String getSharpnessDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_SHARPNESS)) return null; switch (_directory.getInt(ExifDirectory.TAG_SHARPNESS)) { case 0: return "None"; case 1: return "Low"; case 2: return "Hard"; default: return "Unknown (" + _directory.getInt(ExifDirectory.TAG_SHARPNESS) + ")"; } } public String getSaturationDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_SATURATION)) return null; switch (_directory.getInt(ExifDirectory.TAG_SATURATION)) { case 0: return "None"; case 1: return "Low saturation"; case 2: return "High saturation"; default: return "Unknown (" + _directory.getInt(ExifDirectory.TAG_SATURATION) + ")"; } } public String getContrastDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_CONTRAST)) return null; switch (_directory.getInt(ExifDirectory.TAG_CONTRAST)) { case 0: return "None"; case 1: return "Soft"; case 2: return "Hard"; default: return "Unknown (" + _directory.getInt(ExifDirectory.TAG_CONTRAST) + ")"; } } public String getGainControlDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_GAIN_CONTROL)) return null; switch (_directory.getInt(ExifDirectory.TAG_GAIN_CONTROL)) { case 0: return "None"; case 1: return "Low gain up"; case 2: return "Low gain down"; case 3: return "High gain up"; case 4: return "High gain down"; default: return "Unknown (" + _directory.getInt(ExifDirectory.TAG_GAIN_CONTROL) + ")"; } } public String getSceneCaptureTypeDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_SCENE_CAPTURE_TYPE)) return null; switch (_directory.getInt(ExifDirectory.TAG_SCENE_CAPTURE_TYPE)) { case 0: return "Standard"; case 1: return "Landscape"; case 2: return "Portrait"; case 3: return "Night scene"; default: return "Unknown (" + _directory.getInt(ExifDirectory.TAG_SCENE_CAPTURE_TYPE) + ")"; } } public String get35mmFilmEquivFocalLengthDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH)) return null; int equivalentFocalLength = _directory.getInt(ExifDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH); if (equivalentFocalLength==0) return "Unknown"; else return SimpleDecimalFormatter.format(equivalentFocalLength) + "mm"; } public String getDigitalZoomRatioDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_DIGITAL_ZOOM_RATIO)) return null; Rational rational = _directory.getRational(ExifDirectory.TAG_DIGITAL_ZOOM_RATIO); if (rational.getNumerator()==0) return "Digital zoom not used."; return SimpleDecimalFormatter.format(rational.doubleValue()); } public String getWhiteBalanceModeDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_WHITE_BALANCE_MODE)) return null; switch (_directory.getInt(ExifDirectory.TAG_WHITE_BALANCE_MODE)) { case 0: return "Auto white balance"; case 1: return "Manual white balance"; default: return "Unknown (" + _directory.getInt(ExifDirectory.TAG_WHITE_BALANCE_MODE) + ")"; } } public String getExposureModeDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_EXPOSURE_MODE)) return null; switch (_directory.getInt(ExifDirectory.TAG_EXPOSURE_MODE)) { case 0: return "Auto exposure"; case 1: return "Manual exposure"; case 2: return "Auto bracket"; default: return "Unknown (" + _directory.getInt(ExifDirectory.TAG_EXPOSURE_MODE) + ")"; } } public String getCustomRenderedDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_CUSTOM_RENDERED)) return null; switch (_directory.getInt(ExifDirectory.TAG_CUSTOM_RENDERED)) { case 0: return "Normal process"; case 1: return "Custom process"; default: return "Unknown (" + _directory.getInt(ExifDirectory.TAG_CUSTOM_RENDERED) + ")"; } } public String getUserCommentDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_USER_COMMENT)) return null; byte[] commentBytes = _directory.getByteArray(ExifDirectory.TAG_USER_COMMENT); if (commentBytes.length==0) return ""; final String[] encodingNames = new String[] { "ASCII", "UNICODE", "JIS" }; if (commentBytes.length>=10) { String encodingRegion = new String(commentBytes, 0, 10); // try each encoding name for (int i = 0; i 1) apexPower = Math.floor(apexPower); if (apexPower < 1) { sb.append((int)Math.round(1/apexPower)); } else { sb.append("1/"); sb.append((int)apexPower); } sb.append(" sec"); return sb.toString(); */ } public String getFNumberDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_FNUMBER)) return null; Rational fNumber = _directory.getRational(ExifDirectory.TAG_FNUMBER); return "F" + SimpleDecimalFormatter.format(fNumber.doubleValue()); } public String getYCbCrPositioningDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_YCBCR_POSITIONING)) return null; int yCbCrPosition = _directory.getInt(ExifDirectory.TAG_YCBCR_POSITIONING); switch (yCbCrPosition) { case 1: return "Center of pixel array"; case 2: return "Datum point"; default: return String.valueOf(yCbCrPosition); } } public String getOrientationDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_ORIENTATION)) return null; int orientation = _directory.getInt(ExifDirectory.TAG_ORIENTATION); switch (orientation) { case 1: return "Top, left side (Horizontal / normal)"; case 2: return "Top, right side (Mirror horizontal)"; case 3: return "Bottom, right side (Rotate 180)"; case 4: return "Bottom, left side (Mirror vertical)"; case 5: return "Left side, top (Mirror horizontal and rotate 270 CW)"; case 6: return "Right side, top (Rotate 90 CW)"; case 7: return "Right side, bottom (Mirror horizontal and rotate 90 CW)"; case 8: return "Left side, bottom (Rotate 270 CW)"; default: return String.valueOf(orientation); } } public String getResolutionDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_RESOLUTION_UNIT)) return ""; // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch) int resolutionUnit = _directory.getInt(ExifDirectory.TAG_RESOLUTION_UNIT); switch (resolutionUnit) { case 1: return "(No unit)"; case 2: return "Inch"; case 3: return "cm"; default: return ""; } } public String getSensingMethodDescription() throws MetadataException { if (!_directory.containsTag(ExifDirectory.TAG_SENSING_METHOD)) return null; // '1' Not defined, '2' One-chip color area sensor, '3' Two-chip color area sensor // '4' Three-chip color area sensor, '5' Color sequential area sensor // '7' Trilinear sensor '8' Color sequential linear sensor, 'Other' reserved int sensingMethod = _directory.getInt(ExifDirectory.TAG_SENSING_METHOD); switch (sensingMethod) { case 1: return "(Not defined)"; case 2: return "One-chip color area sensor"; case 3: return "Two-chip color area sensor"; case 4: return "Three-chip color area sensor"; case 5: return "Color sequential area sensor"; case 7: return "Trilinear sensor"; case 8: return "Color sequential linear sensor"; default: return ""; } } public String getComponentConfigurationDescription() throws MetadataException { int[] components = _directory.getIntArray(ExifDirectory.TAG_COMPONENTS_CONFIGURATION); String[] componentStrings = {"", "Y", "Cb", "Cr", "R", "G", "B"}; StringBuffer componentConfig = new StringBuffer(); for (int i = 0; i < Math.min(4, components.length); i++) { int j = components[i]; if (j > 0 && j < componentStrings.length) { componentConfig.append(componentStrings[j]); } } return componentConfig.toString(); } /** * Takes a series of 4 bytes from the specified offset, and converts these to a * well-known version number, where possible. For example, (hex) 30 32 31 30 == 2.10). * @param components the four version values * @return the version as a string of form 2.10 */ public static String convertBytesToVersionString(int[] components) { StringBuffer version = new StringBuffer(); for (int i = 0; i < 4 && i < components.length; i++) { if (i == 2) version.append('.'); String digit = String.valueOf((char)components[i]); if (i == 0 && "0".equals(digit)) continue; version.append(digit); } return version.toString(); } /** * The Windows specific tags uses plain Unicode */ private String getUnicodeDescription(int tag) throws MetadataException { if (!_directory.containsTag(tag)) return null; byte[] commentBytes = _directory.getByteArray(tag); try { // decode the unicode string // trim it, as i'm seeing a junk character on the end return new String(commentBytes, "UTF-16LE").trim(); } catch (UnsupportedEncodingException ex) { return null; } } public String getWindowsAuthorDescription() throws MetadataException { return getUnicodeDescription(ExifDirectory.TAG_WIN_AUTHOR); } public String getWindowsCommentDescription() throws MetadataException { return getUnicodeDescription(ExifDirectory.TAG_WIN_COMMENT); } public String getWindowsKeywordsDescription() throws MetadataException { return getUnicodeDescription(ExifDirectory.TAG_WIN_KEYWORDS); } public String getWindowsTitleDescription() throws MetadataException { return getUnicodeDescription(ExifDirectory.TAG_WIN_TITLE); } public String getWindowsSubjectDescription() throws MetadataException { return getUnicodeDescription(ExifDirectory.TAG_WIN_SUBJECT); } }