/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package org.apache.commons.imaging.formats.tiff; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.Charset; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Hashtable; import java.util.List; import java.util.Map; import org.apache.commons.imaging.ImageReadException; import org.apache.commons.imaging.common.BinaryFunctions; import org.apache.commons.imaging.common.ByteOrder; import org.apache.commons.imaging.formats.tiff.constants.AllTagConstants; import org.apache.commons.imaging.formats.tiff.constants.TiffConstants; import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryType; import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants; import org.apache.commons.imaging.formats.tiff.fieldtypes.FieldType; import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo; /** * A TIFF field in a TIFF directory. Immutable. */ public class TiffField { private final TagInfo tagInfo; private final int tag; private final int directoryType; private final FieldType fieldType; private final long count; private final long offset; private final byte[] value; private final ByteOrder byteOrder; private final int sortHint; public TiffField(final int tag, final int directoryType, final FieldType fieldType, final long count, final long offset, final byte[] value, final ByteOrder byteOrder, final int sortHint) throws ImageReadException{ this.tag = tag; this.directoryType = directoryType; this.fieldType = fieldType; this.count = count; this.offset = offset; this.value = value; this.byteOrder = byteOrder; this.sortHint = sortHint; tagInfo = getTag(directoryType, tag); } public int getDirectoryType() { return directoryType; } public TagInfo getTagInfo() { return tagInfo; } /** * Returns the field's tag, derived from bytes 0-1. * @return the tag, as an <code>int</code> in which only the lowest 2 bytes are set */ public int getTag() { return tag; } /** * Returns the field's type, derived from bytes 2-3. * @return the field's type, as a {@code FieldType} object. */ public FieldType getFieldType() { return fieldType; } /** * Returns the field's count, derived from bytes 4-7. * @return the count */ public long getCount() { return count; } /** * Returns the TIFF field's offset/value field, derived from bytes 8-11. * @return the field's offset in a <code>long</code> of 4 packed bytes, * or its inlined value <= 4 bytes long encoded in the field's byte order. */ public int getOffset() { return (int)offset; } /** * Returns the field's byte order. * @return the byte order */ public ByteOrder getByteOrder() { return byteOrder; } public int getSortHint() { return sortHint; } /** * Indicates whether the field's value is inlined into the offset field. * @return true if the value is inlined */ public boolean isLocalValue() { return (count * fieldType.getSize()) <= TiffConstants.TIFF_ENTRY_MAX_VALUE_LENGTH; } /** * The length of the field's value. * @return the length, in bytes. */ public int getBytesLength() { return (int) count * fieldType.getSize(); } /** * Returns a copy of the raw value of the field. * @return the value of the field, in the byte order of the field. */ public byte[] getByteArrayValue() { return BinaryFunctions.head(value, getBytesLength()); } public final class OversizeValueElement extends TiffElement { public OversizeValueElement(final int offset, final int length) { super(offset, length); } @Override public String getElementDescription(final boolean verbose) { if (verbose) { return null; } return "OversizeValueElement, tag: " + getTagInfo().name + ", fieldType: " + getFieldType().getName(); } } public TiffElement getOversizeValueElement() { if (isLocalValue()) { return null; } return new OversizeValueElement(getOffset(), value.length); } private static TagInfo getTag(final int directoryType, final int tag, final List<TagInfo> possibleMatches) { // Please keep this method in sync with TiffImageMetadata's findField() if (possibleMatches.size() < 1) { return null; } // else if (possibleMatches.size() == 1) // { // TagInfo tagInfo = (TagInfo) possibleMatches.get(0); // return tagInfo; // } // first search for exact match. for (int i = 0; i < possibleMatches.size(); i++) { final TagInfo tagInfo = possibleMatches.get(i); if (tagInfo.directoryType == TiffDirectoryType.EXIF_DIRECTORY_UNKNOWN) { // pass continue; } else if (directoryType == tagInfo.directoryType.directoryType) { return tagInfo; } } // accept an inexact match. for (int i = 0; i < possibleMatches.size(); i++) { final TagInfo tagInfo = possibleMatches.get(i); if (tagInfo.directoryType == TiffDirectoryType.EXIF_DIRECTORY_UNKNOWN) { // pass continue; } else if (directoryType >= 0 && tagInfo.directoryType.isImageDirectory()) { return tagInfo; } else if (directoryType < 0 && !tagInfo.directoryType.isImageDirectory()) { return tagInfo; } } // accept a wildcard match. for (int i = 0; i < possibleMatches.size(); i++) { final TagInfo tagInfo = possibleMatches.get(i); if (tagInfo.directoryType == TiffDirectoryType.EXIF_DIRECTORY_UNKNOWN) { return tagInfo; } } // // accept a very rough match. // for (int i = 0; i < possibleMatches.size(); i++) // { // TagInfo tagInfo = (TagInfo) possibleMatches.get(i); // if (tagInfo.exifDirectory == EXIF_DIRECTORY_UNKNOWN) // return tagInfo; // else if (directoryType == DIRECTORY_TYPE_EXIF // && tagInfo.exifDirectory == EXIF_DIRECTORY_EXIF_IFD) // return tagInfo; // else if (directoryType == DIRECTORY_TYPE_INTEROPERABILITY // && tagInfo.exifDirectory == EXIF_DIRECTORY_INTEROP_IFD) // return tagInfo; // else if (directoryType == DIRECTORY_TYPE_GPS // && tagInfo.exifDirectory == EXIF_DIRECTORY_GPS) // return tagInfo; // else if (directoryType == DIRECTORY_TYPE_MAKER_NOTES // && tagInfo.exifDirectory == EXIF_DIRECTORY_MAKER_NOTES) // return tagInfo; // else if (directoryType >= 0 // && tagInfo.exifDirectory.isImageDirectory()) // return tagInfo; // else if (directoryType < 0 // && !tagInfo.exifDirectory.isImageDirectory()) // return tagInfo; // } return TiffTagConstants.TIFF_TAG_UNKNOWN; // if (true) // throw new Error("Why didn't this algorithm work?"); // // { // TagInfo tagInfo = (TagInfo) possibleMatches.get(0); // return tagInfo; // } // Object key = new Integer(tag); // // if (directoryType == DIRECTORY_TYPE_EXIF // || directoryType == DIRECTORY_TYPE_INTEROPERABILITY) // { // if (EXIF_TAG_MAP.containsKey(key)) // return (TagInfo) EXIF_TAG_MAP.get(key); // } // else if (directoryType == DIRECTORY_TYPE_GPS) // { // if (GPS_TAG_MAP.containsKey(key)) // return (TagInfo) GPS_TAG_MAP.get(key); // } // else // { // if (TIFF_TAG_MAP.containsKey(key)) // return (TagInfo) TIFF_TAG_MAP.get(key); // } // // if (ALL_TAG_MAP.containsKey(key)) // return (TagInfo) ALL_TAG_MAP.get(key); // public static final int DIRECTORY_TYPE_EXIF = -2; // // public static final int DIRECTORY_TYPE_SUB = 5; // public static final int DIRECTORY_TYPE_GPS = -3; // public static final int DIRECTORY_TYPE_INTEROPERABILITY = -4; // // private static final Map GPS_TAG_MAP = makeTagMap(ALL_GPS_TAGS, // false); // private static final Map TIFF_TAG_MAP = makeTagMap(ALL_TIFF_TAGS, // false); // private static final Map EXIF_TAG_MAP = makeTagMap(ALL_EXIF_TAGS, // false); // private static final Map ALL_TAG_MAP = makeTagMap(ALL_TAGS, true); // // for (int i = 0; i < ALL_TAGS.length; i++) // { // TagInfo2 tag = ALL_TAGS[i]; // if (tag.tag == value) // return tag; // } // return TIFF_TAG_UNKNOWN; } private static TagInfo getTag(final int directoryType, final int tag) { final List<TagInfo> possibleMatches = ALL_TAG_MAP.get(tag); if (null == possibleMatches) { return TiffTagConstants.TIFF_TAG_UNKNOWN; } final TagInfo result = getTag(directoryType, tag, possibleMatches); return result; } public String getValueDescription() { try { return getValueDescription(getValue()); } catch (final ImageReadException e) { return "Invalid value: " + e.getMessage(); } } private String getValueDescription(final Object o) { if (o == null) { return null; } if (o instanceof Number) { return o.toString(); } else if (o instanceof String) { return "'" + o.toString().trim() + "'"; } else if (o instanceof Date) { final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); return df.format((Date) o); } else if (o instanceof Object[]) { final Object objects[] = (Object[]) o; final StringBuilder result = new StringBuilder(); for (int i = 0; i < objects.length; i++) { final Object object = objects[i]; if (i > 50) { result.append("... (" + objects.length + ")"); break; } if (i > 0) { result.append(", "); } result.append("" + object); } return result.toString(); // } else if (o instanceof Number[]) // { // Number numbers[] = (Number[]) o; // StringBuffer result = new StringBuffer(); // // for (int i = 0; i < numbers.length; i++) // { // Number number = numbers[i]; // // if (i > 0) // result.append(", "); // result.append("" + number); // } // return result.toString(); // } } else if (o instanceof short[]) { final short values[] = (short[]) o; final StringBuffer result = new StringBuffer(); for (int i = 0; i < values.length; i++) { final short value = values[i]; if (i > 50) { result.append("... (" + values.length + ")"); break; } if (i > 0) { result.append(", "); } result.append("" + value); } return result.toString(); } else if (o instanceof int[]) { final int values[] = (int[]) o; final StringBuffer result = new StringBuffer(); for (int i = 0; i < values.length; i++) { final int value = values[i]; if (i > 50) { result.append("... (" + values.length + ")"); break; } if (i > 0) { result.append(", "); } result.append("" + value); } return result.toString(); } else if (o instanceof long[]) { final long values[] = (long[]) o; final StringBuffer result = new StringBuffer(); for (int i = 0; i < values.length; i++) { final long value = values[i]; if (i > 50) { result.append("... (" + values.length + ")"); break; } if (i > 0) { result.append(", "); } result.append("" + value); } return result.toString(); } else if (o instanceof double[]) { final double values[] = (double[]) o; final StringBuffer result = new StringBuffer(); for (int i = 0; i < values.length; i++) { final double value = values[i]; if (i > 50) { result.append("... (" + values.length + ")"); break; } if (i > 0) { result.append(", "); } result.append("" + value); } return result.toString(); } else if (o instanceof byte[]) { final byte values[] = (byte[]) o; final StringBuffer result = new StringBuffer(); for (int i = 0; i < values.length; i++) { final byte value = values[i]; if (i > 50) { result.append("... (" + values.length + ")"); break; } if (i > 0) { result.append(", "); } result.append("" + value); } return result.toString(); } else if (o instanceof char[]) { final char values[] = (char[]) o; final StringBuffer result = new StringBuffer(); for (int i = 0; i < values.length; i++) { final char value = values[i]; if (i > 50) { result.append("... (" + values.length + ")"); break; } if (i > 0) { result.append(", "); } result.append("" + value); } return result.toString(); } else if (o instanceof float[]) { final float values[] = (float[]) o; final StringBuffer result = new StringBuffer(); for (int i = 0; i < values.length; i++) { final float value = values[i]; if (i > 50) { result.append("... (" + values.length + ")"); break; } if (i > 0) { result.append(", "); } result.append("" + value); } return result.toString(); } // else if (o instanceof short[]) // { // short numbers[] = (short[]) o; // StringBuffer result = new StringBuffer(); // // for (int i = 0; i < numbers.length; i++) // { // short number = numbers[i]; // // if (i > 0) // result.append(", "); // result.append("" + number); // } // return result.toString(); // } return "Unknown: " + o.getClass().getName(); } public void dump() { final PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out, Charset.defaultCharset())); dump(pw); pw.flush(); } public void dump(final PrintWriter pw) { dump(pw, null); } public void dump(final PrintWriter pw, final String prefix) { if (prefix != null) { pw.print(prefix + ": "); } pw.println(toString()); pw.flush(); } public String getDescriptionWithoutValue() { return getTag() + " (0x" + Integer.toHexString(getTag()) + ": " + getTagInfo().name + "): "; } @Override public String toString() { final StringBuilder result = new StringBuilder(); result.append(getTag() + " (0x" + Integer.toHexString(getTag()) + ": " + getTagInfo().name + "): "); result.append(getValueDescription() + " (" + getCount() + " " + getFieldType().getName() + ")"); return result.toString(); } public String getTagName() { if (getTagInfo() == TiffTagConstants.TIFF_TAG_UNKNOWN) { return getTagInfo().name + " (0x" + Integer.toHexString(getTag()) + ")"; } return getTagInfo().name; } public String getFieldTypeName() { return getFieldType().getName(); } public static final String Attribute_Tag = "Tag"; public Object getValue() throws ImageReadException { // System.out.print("getValue"); return getTagInfo().getValue(this); } public String getStringValue() throws ImageReadException { final Object o = getValue(); if (o == null) { return null; } if (!(o instanceof String)) { throw new ImageReadException("Expected String value(" + getTagInfo().getDescription() + "): " + o); } return (String) o; } private static final Map<Object, List<TagInfo>> makeTagMap( final List<TagInfo> tags, final boolean ignoreDuplicates, final String name) { // make sure to use the thread-safe version; this is shared state. final Map<Object, List<TagInfo>> map = new Hashtable<Object, List<TagInfo>>(); for (int i = 0; i < tags.size(); i++) { final TagInfo tag = tags.get(i); List<TagInfo> tagList = map.get(tag.tag); if (tagList == null) { tagList = new ArrayList<TagInfo>(); map.put(tag.tag, tagList); } tagList.add(tag); } return map; } private static final Map<Object, List<TagInfo>> ALL_TAG_MAP = makeTagMap( AllTagConstants.ALL_TAGS, true, "All"); public int[] getIntArrayValue() throws ImageReadException { final Object o = getValue(); // if (o == null) // return null; if (o instanceof Number) { return new int[] { ((Number) o).intValue() }; } else if (o instanceof Number[]) { final Number numbers[] = (Number[]) o; final int result[] = new int[numbers.length]; for (int i = 0; i < numbers.length; i++) { result[i] = numbers[i].intValue(); } return result; } else if (o instanceof short[]) { final short numbers[] = (short[]) o; final int result[] = new int[numbers.length]; for (int i = 0; i < numbers.length; i++) { result[i] = 0xffff & numbers[i]; } return result; } else if (o instanceof int[]) { final int numbers[] = (int[]) o; final int result[] = new int[numbers.length]; for (int i = 0; i < numbers.length; i++) { result[i] = numbers[i]; } return result; } throw new ImageReadException("Unknown value: " + o + " for: " + getTagInfo().getDescription()); // return null; } public double[] getDoubleArrayValue() throws ImageReadException { final Object o = getValue(); // if (o == null) // return null; if (o instanceof Number) { return new double[] { ((Number) o).doubleValue() }; } else if (o instanceof Number[]) { final Number numbers[] = (Number[]) o; final double result[] = new double[numbers.length]; for (int i = 0; i < numbers.length; i++) { result[i] = numbers[i].doubleValue(); } return result; } else if (o instanceof short[]) { final short numbers[] = (short[]) o; final double result[] = new double[numbers.length]; for (int i = 0; i < numbers.length; i++) { result[i] = numbers[i]; } return result; } else if (o instanceof int[]) { final int numbers[] = (int[]) o; final double result[] = new double[numbers.length]; for (int i = 0; i < numbers.length; i++) { result[i] = numbers[i]; } return result; } else if (o instanceof float[]) { final float numbers[] = (float[]) o; final double result[] = new double[numbers.length]; for (int i = 0; i < numbers.length; i++) { result[i] = numbers[i]; } return result; } else if (o instanceof double[]) { final double numbers[] = (double[]) o; final double result[] = new double[numbers.length]; for (int i = 0; i < numbers.length; i++) { result[i] = numbers[i]; } return result; } throw new ImageReadException("Unknown value: " + o + " for: " + getTagInfo().getDescription()); // return null; } public int getIntValueOrArraySum() throws ImageReadException { final Object o = getValue(); // if (o == null) // return -1; if (o instanceof Number) { return ((Number) o).intValue(); } else if (o instanceof Number[]) { final Number numbers[] = (Number[]) o; int sum = 0; for (final Number number : numbers) { sum += number.intValue(); } return sum; } else if (o instanceof short[]) { final short[] numbers = (short[]) o; int sum = 0; for (final short number : numbers) { sum += number; } return sum; } else if (o instanceof int[]) { final int numbers[] = (int[]) o; int sum = 0; for (final int number : numbers) { sum += number; } return sum; } throw new ImageReadException("Unknown value: " + o + " for: " + getTagInfo().getDescription()); // return -1; } public int getIntValue() throws ImageReadException { final Object o = getValue(); if (o == null) { throw new ImageReadException("Missing value: " + getTagInfo().getDescription()); } return ((Number) o).intValue(); } public double getDoubleValue() throws ImageReadException { final Object o = getValue(); if (o == null) { throw new ImageReadException("Missing value: " + getTagInfo().getDescription()); } return ((Number) o).doubleValue(); } }