/*
* Copyright 2002-2012 Drew Noakes
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* More information about this project is available at:
*
* http://drewnoakes.com/code/exif/
* http://code.google.com/p/metadata-extractor/
*/
package com.drew.metadata.exif;
import com.drew.imaging.PhotographicConversions;
import com.drew.lang.Rational;
import com.drew.lang.annotations.NotNull;
import com.drew.lang.annotations.Nullable;
import com.drew.metadata.TagDescriptor;
import java.io.UnsupportedEncodingException;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
/**
* Provides human-readable string representations of tag values stored in a <code>ExifSubIFDDirectory</code>.
*
* @author Drew Noakes http://drewnoakes.com
*/
public class ExifSubIFDDescriptor extends TagDescriptor<ExifSubIFDDirectory>
{
/**
* 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 final boolean _allowDecimalRepresentationOfRationals = true;
@NotNull
private static final java.text.DecimalFormat SimpleDecimalFormatter = new DecimalFormat("0.#");
public ExifSubIFDDescriptor(@NotNull ExifSubIFDDirectory directory)
{
super(directory);
}
// Note for the potential addition of brightness presentation in eV:
// Brightness of taken subject. To calculate Exposure(Ev) from BrightnessValue(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
* <code>null</code> if the tag hasn't been defined.
*/
@Nullable
public String getDescription(int tagType)
{
switch (tagType) {
case ExifSubIFDDirectory.TAG_NEW_SUBFILE_TYPE:
return getNewSubfileTypeDescription();
case ExifSubIFDDirectory.TAG_SUBFILE_TYPE:
return getSubfileTypeDescription();
case ExifSubIFDDirectory.TAG_THRESHOLDING:
return getThresholdingDescription();
case ExifSubIFDDirectory.TAG_FILL_ORDER:
return getFillOrderDescription();
case ExifSubIFDDirectory.TAG_EXPOSURE_TIME:
return getExposureTimeDescription();
case ExifSubIFDDirectory.TAG_SHUTTER_SPEED:
return getShutterSpeedDescription();
case ExifSubIFDDirectory.TAG_FNUMBER:
return getFNumberDescription();
case ExifSubIFDDirectory.TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL:
return getCompressedAverageBitsPerPixelDescription();
case ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE:
return getSubjectDistanceDescription();
case ExifSubIFDDirectory.TAG_METERING_MODE:
return getMeteringModeDescription();
case ExifSubIFDDirectory.TAG_WHITE_BALANCE:
return getWhiteBalanceDescription();
case ExifSubIFDDirectory.TAG_FLASH:
return getFlashDescription();
case ExifSubIFDDirectory.TAG_FOCAL_LENGTH:
return getFocalLengthDescription();
case ExifSubIFDDirectory.TAG_COLOR_SPACE:
return getColorSpaceDescription();
case ExifSubIFDDirectory.TAG_EXIF_IMAGE_WIDTH:
return getExifImageWidthDescription();
case ExifSubIFDDirectory.TAG_EXIF_IMAGE_HEIGHT:
return getExifImageHeightDescription();
case ExifSubIFDDirectory.TAG_FOCAL_PLANE_UNIT:
return getFocalPlaneResolutionUnitDescription();
case ExifSubIFDDirectory.TAG_FOCAL_PLANE_X_RES:
return getFocalPlaneXResolutionDescription();
case ExifSubIFDDirectory.TAG_FOCAL_PLANE_Y_RES:
return getFocalPlaneYResolutionDescription();
case ExifSubIFDDirectory.TAG_BITS_PER_SAMPLE:
return getBitsPerSampleDescription();
case ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION:
return getPhotometricInterpretationDescription();
case ExifSubIFDDirectory.TAG_ROWS_PER_STRIP:
return getRowsPerStripDescription();
case ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS:
return getStripByteCountsDescription();
case ExifSubIFDDirectory.TAG_SAMPLES_PER_PIXEL:
return getSamplesPerPixelDescription();
case ExifSubIFDDirectory.TAG_PLANAR_CONFIGURATION:
return getPlanarConfigurationDescription();
case ExifSubIFDDirectory.TAG_YCBCR_SUBSAMPLING:
return getYCbCrSubsamplingDescription();
case ExifSubIFDDirectory.TAG_EXPOSURE_PROGRAM:
return getExposureProgramDescription();
case ExifSubIFDDirectory.TAG_APERTURE:
return getApertureValueDescription();
case ExifSubIFDDirectory.TAG_MAX_APERTURE:
return getMaxApertureValueDescription();
case ExifSubIFDDirectory.TAG_SENSING_METHOD:
return getSensingMethodDescription();
case ExifSubIFDDirectory.TAG_EXPOSURE_BIAS:
return getExposureBiasDescription();
case ExifSubIFDDirectory.TAG_FILE_SOURCE:
return getFileSourceDescription();
case ExifSubIFDDirectory.TAG_SCENE_TYPE:
return getSceneTypeDescription();
case ExifSubIFDDirectory.TAG_COMPONENTS_CONFIGURATION:
return getComponentConfigurationDescription();
case ExifSubIFDDirectory.TAG_EXIF_VERSION:
return getExifVersionDescription();
case ExifSubIFDDirectory.TAG_FLASHPIX_VERSION:
return getFlashPixVersionDescription();
case ExifSubIFDDirectory.TAG_ISO_EQUIVALENT:
return getIsoEquivalentDescription();
case ExifSubIFDDirectory.TAG_USER_COMMENT:
return getUserCommentDescription();
case ExifSubIFDDirectory.TAG_CUSTOM_RENDERED:
return getCustomRenderedDescription();
case ExifSubIFDDirectory.TAG_EXPOSURE_MODE:
return getExposureModeDescription();
case ExifSubIFDDirectory.TAG_WHITE_BALANCE_MODE:
return getWhiteBalanceModeDescription();
case ExifSubIFDDirectory.TAG_DIGITAL_ZOOM_RATIO:
return getDigitalZoomRatioDescription();
case ExifSubIFDDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH:
return get35mmFilmEquivFocalLengthDescription();
case ExifSubIFDDirectory.TAG_SCENE_CAPTURE_TYPE:
return getSceneCaptureTypeDescription();
case ExifSubIFDDirectory.TAG_GAIN_CONTROL:
return getGainControlDescription();
case ExifSubIFDDirectory.TAG_CONTRAST:
return getContrastDescription();
case ExifSubIFDDirectory.TAG_SATURATION:
return getSaturationDescription();
case ExifSubIFDDirectory.TAG_SHARPNESS:
return getSharpnessDescription();
case ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE_RANGE:
return getSubjectDistanceRangeDescription();
default:
return super.getDescription(tagType);
}
}
@Nullable
public String getNewSubfileTypeDescription()
{
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_NEW_SUBFILE_TYPE);
if (value==null)
return null;
switch (value) {
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 (" + value + ")";
}
}
@Nullable
public String getSubfileTypeDescription()
{
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SUBFILE_TYPE);
if (value==null)
return null;
switch (value) {
case 1: return "Full-resolution image";
case 2: return "Reduced-resolution image";
case 3: return "Single page of multi-page image";
default:
return "Unknown (" + value + ")";
}
}
@Nullable
public String getThresholdingDescription()
{
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_THRESHOLDING);
if (value==null)
return null;
switch (value) {
case 1: return "No dithering or halftoning";
case 2: return "Ordered dither or halftone";
case 3: return "Randomized dither";
default:
return "Unknown (" + value + ")";
}
}
@Nullable
public String getFillOrderDescription()
{
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_FILL_ORDER);
if (value==null)
return null;
switch (value) {
case 1: return "Normal";
case 2: return "Reversed";
default:
return "Unknown (" + value + ")";
}
}
@Nullable
public String getSubjectDistanceRangeDescription()
{
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE_RANGE);
if (value==null)
return null;
switch (value) {
case 0: return "Unknown";
case 1: return "Macro";
case 2: return "Close view";
case 3: return "Distant view";
default:
return "Unknown (" + value + ")";
}
}
@Nullable
public String getSharpnessDescription()
{
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SHARPNESS);
if (value==null)
return null;
switch (value) {
case 0: return "None";
case 1: return "Low";
case 2: return "Hard";
default:
return "Unknown (" + value + ")";
}
}
@Nullable
public String getSaturationDescription()
{
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SATURATION);
if (value==null)
return null;
switch (value) {
case 0: return "None";
case 1: return "Low saturation";
case 2: return "High saturation";
default:
return "Unknown (" + value + ")";
}
}
@Nullable
public String getContrastDescription()
{
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_CONTRAST);
if (value==null)
return null;
switch (value) {
case 0: return "None";
case 1: return "Soft";
case 2: return "Hard";
default:
return "Unknown (" + value + ")";
}
}
@Nullable
public String getGainControlDescription()
{
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_GAIN_CONTROL);
if (value==null)
return null;
switch (value) {
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 (" + value + ")";
}
}
@Nullable
public String getSceneCaptureTypeDescription()
{
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SCENE_CAPTURE_TYPE);
if (value==null)
return null;
switch (value) {
case 0: return "Standard";
case 1: return "Landscape";
case 2: return "Portrait";
case 3: return "Night scene";
default:
return "Unknown (" + value + ")";
}
}
@Nullable
public String get35mmFilmEquivFocalLengthDescription()
{
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH);
if (value==null)
return null;
if (value==0)
return "Unknown";
else
return SimpleDecimalFormatter.format(value) + "mm";
}
@Nullable
public String getDigitalZoomRatioDescription()
{
Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_DIGITAL_ZOOM_RATIO);
if (value==null)
return null;
if (value.getNumerator()==0)
return "Digital zoom not used.";
return SimpleDecimalFormatter.format(value.doubleValue());
}
@Nullable
public String getWhiteBalanceModeDescription()
{
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_WHITE_BALANCE_MODE);
if (value==null)
return null;
switch (value) {
case 0: return "Auto white balance";
case 1: return "Manual white balance";
default:
return "Unknown (" + value + ")";
}
}
@Nullable
public String getExposureModeDescription()
{
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXPOSURE_MODE);
if (value==null)
return null;
switch (value) {
case 0: return "Auto exposure";
case 1: return "Manual exposure";
case 2: return "Auto bracket";
default:
return "Unknown (" + value + ")";
}
}
@Nullable
public String getCustomRenderedDescription()
{
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_CUSTOM_RENDERED);
if (value==null)
return null;
switch (value) {
case 0: return "Normal process";
case 1: return "Custom process";
default:
return "Unknown (" + value + ")";
}
}
@Nullable
public String getUserCommentDescription()
{
byte[] commentBytes = _directory.getByteArray(ExifSubIFDDirectory.TAG_USER_COMMENT);
if (commentBytes==null)
return null;
if (commentBytes.length == 0)
return "";
final Map<String, String> encodingMap = new HashMap<String, String>();
encodingMap.put("ASCII", System.getProperty("file.encoding")); // Someone suggested "ISO-8859-1".
encodingMap.put("UNICODE", "UTF-16LE");
encodingMap.put("JIS", "Shift-JIS"); // We assume this charset for now. Another suggestion is "JIS".
try {
if (commentBytes.length >= 10) {
String firstTenBytesString = new String(commentBytes, 0, 10);
// try each encoding name
for (Map.Entry<String, String> pair : encodingMap.entrySet()) {
String encodingName = pair.getKey();
String charset = pair.getValue();
if (firstTenBytesString.startsWith(encodingName)) {
// skip any null or blank characters commonly present after the encoding name, up to a limit of 10 from the start
for (int j = encodingName.length(); j < 10; j++) {
byte b = commentBytes[j];
if (b != '\0' && b != ' ')
return new String(commentBytes, j, commentBytes.length - j, charset).trim();
}
return new String(commentBytes, 10, commentBytes.length - 10, charset).trim();
}
}
}
// special handling fell through, return a plain string representation
return new String(commentBytes, System.getProperty("file.encoding")).trim();
} catch (UnsupportedEncodingException ex) {
return null;
}
}
@Nullable
public String getIsoEquivalentDescription()
{
// Have seen an exception here from files produced by ACDSEE that stored an int[] here with two values
Integer isoEquiv = _directory.getInteger(ExifSubIFDDirectory.TAG_ISO_EQUIVALENT);
if (isoEquiv==null)
return null;
// There used to be a check here that multiplied ISO values < 50 by 200.
// Issue 36 shows a smart-phone image from a Samsung Galaxy S2 with ISO-40.
return Integer.toString(isoEquiv);
}
@Nullable
public String getExifVersionDescription()
{
int[] ints = _directory.getIntArray(ExifSubIFDDirectory.TAG_EXIF_VERSION);
if (ints==null)
return null;
return ExifSubIFDDescriptor.convertBytesToVersionString(ints, 2);
}
@Nullable
public String getFlashPixVersionDescription()
{
int[] ints = _directory.getIntArray(ExifSubIFDDirectory.TAG_FLASHPIX_VERSION);
if (ints==null)
return null;
return ExifSubIFDDescriptor.convertBytesToVersionString(ints, 2);
}
@Nullable
public String getSceneTypeDescription()
{
Integer sceneType = _directory.getInteger(ExifSubIFDDirectory.TAG_SCENE_TYPE);
if (sceneType==null)
return null;
return sceneType == 1
? "Directly photographed image"
: "Unknown (" + sceneType + ")";
}
@Nullable
public String getFileSourceDescription()
{
Integer fileSource = _directory.getInteger(ExifSubIFDDirectory.TAG_FILE_SOURCE);
if (fileSource==null)
return null;
return fileSource == 3
? "Digital Still Camera (DSC)"
: "Unknown (" + fileSource + ")";
}
@Nullable
public String getExposureBiasDescription()
{
Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_EXPOSURE_BIAS);
if (value==null)
return null;
return value.toSimpleString(true) + " EV";
}
@Nullable
public String getMaxApertureValueDescription()
{
Double aperture = _directory.getDoubleObject(ExifSubIFDDirectory.TAG_MAX_APERTURE);
if (aperture==null)
return null;
double fStop = PhotographicConversions.apertureToFStop(aperture);
return "F" + SimpleDecimalFormatter.format(fStop);
}
@Nullable
public String getApertureValueDescription()
{
Double aperture = _directory.getDoubleObject(ExifSubIFDDirectory.TAG_APERTURE);
if (aperture==null)
return null;
double fStop = PhotographicConversions.apertureToFStop(aperture);
return "F" + SimpleDecimalFormatter.format(fStop);
}
@Nullable
public String getExposureProgramDescription()
{
// '1' means manual control, '2' program normal, '3' aperture priority,
// '4' shutter priority, '5' program creative (slow program),
// '6' program action(high-speed program), '7' portrait mode, '8' landscape mode.
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXPOSURE_PROGRAM);
if (value==null)
return null;
switch (value) {
case 1: return "Manual control";
case 2: return "Program normal";
case 3: return "Aperture priority";
case 4: return "Shutter priority";
case 5: return "Program creative (slow program)";
case 6: return "Program action (high-speed program)";
case 7: return "Portrait mode";
case 8: return "Landscape mode";
default:
return "Unknown program (" + value + ")";
}
}
@Nullable
public String getYCbCrSubsamplingDescription()
{
int[] positions = _directory.getIntArray(ExifSubIFDDirectory.TAG_YCBCR_SUBSAMPLING);
if (positions==null)
return null;
if (positions[0] == 2 && positions[1] == 1) {
return "YCbCr4:2:2";
} else if (positions[0] == 2 && positions[1] == 2) {
return "YCbCr4:2:0";
} else {
return "(Unknown)";
}
}
@Nullable
public String getPlanarConfigurationDescription()
{
// When image format is no compression YCbCr, this value shows byte aligns of YCbCr
// data. If value is '1', Y/Cb/Cr value is chunky format, contiguous for each subsampling
// pixel. If value is '2', Y/Cb/Cr value is separated and stored to Y plane/Cb plane/Cr
// plane format.
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_PLANAR_CONFIGURATION);
if (value==null)
return null;
switch (value) {
case 1: return "Chunky (contiguous for each subsampling pixel)";
case 2: return "Separate (Y-plane/Cb-plane/Cr-plane format)";
default:
return "Unknown configuration";
}
}
@Nullable
public String getSamplesPerPixelDescription()
{
String value = _directory.getString(ExifSubIFDDirectory.TAG_SAMPLES_PER_PIXEL);
return value==null ? null : value + " samples/pixel";
}
@Nullable
public String getRowsPerStripDescription()
{
final String value = _directory.getString(ExifSubIFDDirectory.TAG_ROWS_PER_STRIP);
return value==null ? null : value + " rows/strip";
}
@Nullable
public String getStripByteCountsDescription()
{
final String value = _directory.getString(ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS);
return value==null ? null : value + " bytes";
}
@Nullable
public String getPhotometricInterpretationDescription()
{
// Shows the color space of the image data components
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION);
if (value==null)
return null;
switch (value) {
case 0: return "WhiteIsZero";
case 1: return "BlackIsZero";
case 2: return "RGB";
case 3: return "RGB Palette";
case 4: return "Transparency Mask";
case 5: return "CMYK";
case 6: return "YCbCr";
case 8: return "CIELab";
case 9: return "ICCLab";
case 10: return "ITULab";
case 32803: return "Color Filter Array";
case 32844: return "Pixar LogL";
case 32845: return "Pixar LogLuv";
case 32892: return "Linear Raw";
default:
return "Unknown colour space";
}
}
@Nullable
public String getBitsPerSampleDescription()
{
String value = _directory.getString(ExifSubIFDDirectory.TAG_BITS_PER_SAMPLE);
return value==null ? null : value + " bits/component/pixel";
}
@Nullable
public String getFocalPlaneXResolutionDescription()
{
Rational rational = _directory.getRational(ExifSubIFDDirectory.TAG_FOCAL_PLANE_X_RES);
if (rational==null)
return null;
final String unit = getFocalPlaneResolutionUnitDescription();
return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals)
+ (unit==null ? "" : " " + unit.toLowerCase());
}
@Nullable
public String getFocalPlaneYResolutionDescription()
{
Rational rational = _directory.getRational(ExifSubIFDDirectory.TAG_FOCAL_PLANE_Y_RES);
if (rational==null)
return null;
final String unit = getFocalPlaneResolutionUnitDescription();
return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals)
+ (unit==null ? "" : " " + unit.toLowerCase());
}
@Nullable
public String getFocalPlaneResolutionUnitDescription()
{
// Unit of FocalPlaneXResolution/FocalPlaneYResolution. '1' means no-unit,
// '2' inch, '3' centimeter.
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_FOCAL_PLANE_UNIT);
if (value==null)
return null;
switch (value) {
case 1: return "(No unit)";
case 2: return "Inches";
case 3: return "cm";
default:
return "";
}
}
@Nullable
public String getExifImageWidthDescription()
{
final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXIF_IMAGE_WIDTH);
if (value==null)
return null;
return value + " pixels";
}
@Nullable
public String getExifImageHeightDescription()
{
final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXIF_IMAGE_HEIGHT);
if (value==null)
return null;
return value + " pixels";
}
@Nullable
public String getColorSpaceDescription()
{
final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_COLOR_SPACE);
if (value==null)
return null;
if (value == 1)
return "sRGB";
if (value == 65535)
return "Undefined";
return "Unknown";
}
@Nullable
public String getFocalLengthDescription()
{
Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_FOCAL_LENGTH);
if (value==null)
return null;
java.text.DecimalFormat formatter = new DecimalFormat("0.0##");
return formatter.format(value.doubleValue()) + " mm";
}
@Nullable
public String getFlashDescription()
{
/*
* This is a bitmask.
* 0 = flash fired
* 1 = return detected
* 2 = return able to be detected
* 3 = unknown
* 4 = auto used
* 5 = unknown
* 6 = red eye reduction used
*/
final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_FLASH);
if (value==null)
return null;
StringBuilder sb = new StringBuilder();
if ((value & 0x1)!=0)
sb.append("Flash fired");
else
sb.append("Flash did not fire");
// check if we're able to detect a return, before we mention it
if ((value & 0x4)!=0)
{
if ((value & 0x2)!=0)
sb.append(", return detected");
else
sb.append(", return not detected");
}
if ((value & 0x10)!=0)
sb.append(", auto");
if ((value & 0x40)!=0)
sb.append(", red-eye reduction");
return sb.toString();
}
@Nullable
public String getWhiteBalanceDescription()
{
// '0' means unknown, '1' daylight, '2' fluorescent, '3' tungsten, '10' flash,
// '17' standard light A, '18' standard light B, '19' standard light C, '20' D55,
// '21' D65, '22' D75, '255' other.
final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_WHITE_BALANCE);
if (value==null)
return null;
switch (value) {
case 0: return "Unknown";
case 1: return "Daylight";
case 2: return "Florescent";
case 3: return "Tungsten";
case 10: return "Flash";
case 17: return "Standard light";
case 18: return "Standard light (B)";
case 19: return "Standard light (C)";
case 20: return "D55";
case 21: return "D65";
case 22: return "D75";
case 255: return "(Other)";
default:
return "Unknown (" + value + ")";
}
}
@Nullable
public String getMeteringModeDescription()
{
// '0' means unknown, '1' average, '2' center weighted average, '3' spot
// '4' multi-spot, '5' multi-segment, '6' partial, '255' other
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_METERING_MODE);
if (value==null)
return null;
switch (value) {
case 0: return "Unknown";
case 1: return "Average";
case 2: return "Center weighted average";
case 3: return "Spot";
case 4: return "Multi-spot";
case 5: return "Multi-segment";
case 6: return "Partial";
case 255: return "(Other)";
default:
return "";
}
}
@Nullable
public String getSubjectDistanceDescription()
{
Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE);
if (value==null)
return null;
java.text.DecimalFormat formatter = new DecimalFormat("0.0##");
return formatter.format(value.doubleValue()) + " metres";
}
@Nullable
public String getCompressedAverageBitsPerPixelDescription()
{
Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL);
if (value==null)
return null;
String ratio = value.toSimpleString(_allowDecimalRepresentationOfRationals);
if (value.isInteger() && value.intValue() == 1) {
return ratio + " bit/pixel";
} else {
return ratio + " bits/pixel";
}
}
@Nullable
public String getExposureTimeDescription()
{
String value = _directory.getString(ExifSubIFDDirectory.TAG_EXPOSURE_TIME);
return value==null ? null : value + " sec";
}
@Nullable
public String getShutterSpeedDescription()
{
// I believe this method to now be stable, but am leaving some alternative snippets of
// code in here, to assist anyone who's looking into this (given that I don't have a public CVS).
// float apexValue = _directory.getFloat(ExifSubIFDDirectory.TAG_SHUTTER_SPEED);
// int apexPower = (int)Math.pow(2.0, apexValue);
// return "1/" + apexPower + " sec";
// TODO test this method
// thanks to Mark Edwards for spotting and patching a bug in the calculation of this
// description (spotted bug using a Canon EOS 300D)
// thanks also to Gli Blr for spotting this bug
Float apexValue = _directory.getFloatObject(ExifSubIFDDirectory.TAG_SHUTTER_SPEED);
if (apexValue==null)
return null;
if (apexValue<=1) {
float apexPower = (float)(1/(Math.exp(apexValue*Math.log(2))));
long apexPower10 = Math.round((double)apexPower * 10.0);
float fApexPower = (float) apexPower10 / 10.0f;
return fApexPower + " sec";
} else {
int apexPower = (int)((Math.exp(apexValue*Math.log(2))));
return "1/" + apexPower + " sec";
}
/*
// This alternative implementation offered by Bill Richards
// TODO determine which is the correct / more-correct implementation
double apexValue = _directory.getDouble(ExifSubIFDDirectory.TAG_SHUTTER_SPEED);
double apexPower = Math.pow(2.0, apexValue);
StringBuffer sb = new StringBuffer();
if (apexPower > 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();
*/
}
@Nullable
public String getFNumberDescription()
{
Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_FNUMBER);
if (value==null)
return null;
return "F" + SimpleDecimalFormatter.format(value.doubleValue());
}
@Nullable
public String getSensingMethodDescription()
{
// '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
Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SENSING_METHOD);
if (value==null)
return null;
switch (value) {
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 "";
}
}
@Nullable
public String getComponentConfigurationDescription()
{
int[] components = _directory.getIntArray(ExifSubIFDDirectory.TAG_COMPONENTS_CONFIGURATION);
if (components==null)
return null;
String[] componentStrings = {"", "Y", "Cb", "Cr", "R", "G", "B"};
StringBuilder componentConfig = new StringBuilder();
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();
}
}