    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.imaging.formats.tiff;
    018
    019import java.io.IOException;
    020import java.io.PrintWriter;
    021import java.io.StringWriter;
    022import java.nio.ByteOrder;
    023import java.text.DateFormat;
    024import java.text.SimpleDateFormat;
    025import java.util.Date;
    026import java.util.Locale;
    027import java.util.logging.Level;
    028import java.util.logging.Logger;
    029
    030import org.apache.commons.imaging.ImageReadException;
    031import org.apache.commons.imaging.common.BinaryFunctions;
    032import org.apache.commons.imaging.formats.tiff.constants.TiffConstants;
    033import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
    034import org.apache.commons.imaging.formats.tiff.fieldtypes.FieldType;
    035import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
    036
    037/**
    038 * A TIFF field in a TIFF directory. Immutable.
    039 */
    040public class TiffField {
    041
    042    private static final Logger LOGGER = Logger.getLogger(TiffField.class.getName());
    043
    044    private final TagInfo tagInfo;
    045    private final int tag;
    046    private final int directoryType;
    047    private final FieldType fieldType;
    048    private final long count;
    049    private final long offset;
    050    private final byte[] value;
    051    private final ByteOrder byteOrder;
    052    private final int sortHint;
    053
    054    public TiffField(final int tag, final int directoryType, final FieldType fieldType,
    055            final long count, final long offset, final byte[] value,
    056            final ByteOrder byteOrder, final int sortHint) {
    057
    058        this.tag = tag;
    059        this.directoryType = directoryType;
    060        this.fieldType = fieldType;
    061        this.count = count;
    062        this.offset = offset;
    063        this.value = value;
    064        this.byteOrder = byteOrder;
    065        this.sortHint = sortHint;
    066
    067        tagInfo = TiffTags.getTag(directoryType, tag);
    068    }
    069
    070    public int getDirectoryType() {
    071        return directoryType;
    072    }
    073
    074    public TagInfo getTagInfo() {
    075        return tagInfo;
    076    }
    077
    078    /**
    079     * Returns the field's tag, derived from bytes 0-1.
    080     * @return the tag, as an {@code int} in which only the lowest 2 bytes are set
    081     */
    082    public int getTag() {
    083        return tag;
    084    }
    085
    086    /**
    087     * Returns the field's type, derived from bytes 2-3.
    088     * @return the field's type, as a {@code FieldType} object.
    089     */
    090    public FieldType getFieldType() {
    091        return fieldType;
    092    }
    093
    094    /**
    095     * Returns the field's count, derived from bytes 4-7.
    096     * @return the count
    097     */
    098    public long getCount() {
    099        return count;
    100    }
    101
    102    /**
    103     * Returns the TIFF field's offset/value field, derived from bytes 8-11.
    104     * @return the field's offset in a {@code long} of 4 packed bytes,
    105     * or its inlined value &lt;= 4 bytes long encoded in the field's byte order.
    106     */
    107    public int getOffset() {
    108        return (int) offset;
    109    }
    110
    111    /**
    112     * Returns the field's byte order.
    113     * @return the byte order
    114     */
    115    public ByteOrder getByteOrder() {
    116        return byteOrder;
    117    }
    118
    119    public int getSortHint() {
    120        return sortHint;
    121    }
    122
    123    /**
    124     * Indicates whether the field's value is inlined into the offset field.
    125     * @return true if the value is inlined
    126     */
    127    public boolean isLocalValue() {
    128        return (count * fieldType.getSize()) <= TiffConstants.TIFF_ENTRY_MAX_VALUE_LENGTH;
    129    }
    130
    131    /**
    132     * The length of the field's value.
    133     * @return the length, in bytes.
    134     */
    135    public int getBytesLength() {
    136        return (int) count * fieldType.getSize();
    137    }
    138
    139    /**
    140     * Returns a copy of the raw value of the field.
    141     * @return the value of the field, in the byte order of the field.
    142     */
    143    public byte[] getByteArrayValue() {
    144        return BinaryFunctions.head(value, getBytesLength());
    145    }
    146
    147    public final class OversizeValueElement extends TiffElement {
    148        public OversizeValueElement(final int offset, final int length) {
    149            super(offset, length);
    150        }
    151
    152        @Override
    153        public String getElementDescription() {
    154            return "OversizeValueElement, tag: " + getTagInfo().name
    155                    + ", fieldType: " + getFieldType().getName();
    156        }
    157    }
    158
    159    public TiffElement getOversizeValueElement() {
    160        if (isLocalValue()) {
    161            return null;
    162        }
    163
    164        return new OversizeValueElement(getOffset(), value.length);
    165    }
    166
    167    public String getValueDescription() {
    168        try {
    169            return getValueDescription(getValue());
    170        } catch (final ImageReadException e) {
    171            return "Invalid value: " + e.getMessage();
    172        }
    173    }
    174
    175    private String getValueDescription(final Object o) {
    176        if (o == null) {
    177            return null;
    178        }
    179
    180        if (o instanceof Number) {
    181            return o.toString();
    182        } else if (o instanceof String) {
    183            return "'" + o.toString().trim() + "'";
    184        } else if (o instanceof Date) {
    185            final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.ENGLISH);
    186            return df.format((Date) o);
    187        } else if (o instanceof Object[]) {
    188            final Object[] objects = (Object[]) o;
    189            final StringBuilder result = new StringBuilder();
    190
    191            for (int i = 0; i < objects.length; i++) {
    192                final Object object = objects[i];
    193
    194                if (i > 50) {
    195                    result.append("... (").append(objects.length).append(")");
    196                    break;
    197                }
    198                if (i > 0) {
    199                    result.append(", ");
    200                }
    201                result.append(object.toString());
    202            }
    203            return result.toString();
    204        // } else if (o instanceof Number[])
    205        // {
    206        // Number numbers[] = (Number[]) o;
    207        // StringBuilder result = new StringBuilder();
    208        //
    209        // for (int i = 0; i < numbers.length; i++)
    210        // {
    211        // Number number = numbers[i];
    212        //
    213        // if (i > 0)
    214        // result.append(", ");
    215        // result.append("" + number);
    216        // }
    217        // return result.toString();
    218        // }
    219        } else if (o instanceof short[]) {
    220            final short[] values = (short[]) o;
    221            final StringBuilder result = new StringBuilder();
    222
    223            for (int i = 0; i < values.length; i++) {
    224                final short sval = values[i];
    225
    226                if (i > 50) {
    227                    result.append("... (").append(values.length).append(")");
    228                    break;
    229                }
    230                if (i > 0) {
    231                    result.append(", ");
    232                }
    233                result.append(sval);
    234            }
    235            return result.toString();
    236        } else if (o instanceof int[]) {
    237            final int[] values = (int[]) o;
    238            final StringBuilder result = new StringBuilder();
    239
    240            for (int i = 0; i < values.length; i++) {
    241                final int iVal = values[i];
    242
    243                if (i > 50) {
    244                    result.append("... (").append(values.length).append(")");
    245                    break;
    246                }
    247                if (i > 0) {
    248                    result.append(", ");
    249                }
    250                result.append(iVal);
    251            }
    252            return result.toString();
    253        } else if (o instanceof long[]) {
    254            final long[] values = (long[]) o;
    255            final StringBuilder result = new StringBuilder();
    256
    257            for (int i = 0; i < values.length; i++) {
    258                final long lVal = values[i];
    259
    260                if (i > 50) {
    261                    result.append("... (").append(values.length).append(")");
    262                    break;
    263                }
    264                if (i > 0) {
    265                    result.append(", ");
    266                }
    267                result.append(lVal);
    268            }
    269            return result.toString();
    270        } else if (o instanceof double[]) {
    271            final double[] values = (double[]) o;
    272            final StringBuilder result = new StringBuilder();
    273
    274            for (int i = 0; i < values.length; i++) {
    275                final double dVal = values[i];
    276
    277                if (i > 50) {
    278                    result.append("... (").append(values.length).append(")");
    279                    break;
    280                }
    281                if (i > 0) {
    282                    result.append(", ");
    283                }
    284                result.append(dVal);
    285            }
    286            return result.toString();
    287        } else if (o instanceof byte[]) {
    288            final byte[] values = (byte[]) o;
    289            final StringBuilder result = new StringBuilder();
    290
    291            for (int i = 0; i < values.length; i++) {
    292                final byte bVal = values[i];
    293
    294                if (i > 50) {
    295                    result.append("... (").append(values.length).append(")");
    296                    break;
    297                }
    298                if (i > 0) {
    299                    result.append(", ");
    300                }
    301                result.append(bVal);
    302            }
    303            return result.toString();
    304        } else if (o instanceof char[]) {
    305            final char[] values = (char[]) o;
    306            final StringBuilder result = new StringBuilder();
    307
    308            for (int i = 0; i < values.length; i++) {
    309                final char cVal = values[i];
    310
    311                if (i > 50) {
    312                    result.append("... (").append(values.length).append(")");
    313                    break;
    314                }
    315                if (i > 0) {
    316                    result.append(", ");
    317                }
    318                result.append(cVal);
    319            }
    320            return result.toString();
    321        } else if (o instanceof float[]) {
    322            final float[] values = (float[]) o;
    323            final StringBuilder result = new StringBuilder();
    324
    325            for (int i = 0; i < values.length; i++) {
    326                final float fVal = values[i];
    327
    328                if (i > 50) {
    329                    result.append("... (").append(values.length).append(")");
    330                    break;
    331                }
    332                if (i > 0) {
    333                    result.append(", ");
    334                }
    335                result.append(fVal);
    336            }
    337            return result.toString();
    338        }
    339        // else if (o instanceof short[])
    340        // {
    341        // short numbers[] = (short[]) o;
    342        // StringBuilder result = new StringBuilder();
    343        //
    344        // for (int i = 0; i < numbers.length; i++)
    345        // {
    346        // short number = numbers[i];
    347        //
    348        // if (i > 0)
    349        // result.append(", ");
    350        // result.append("" + number);
    351        // }
    352        // return result.toString();
    353        // }
    354
    355        return "Unknown: " + o.getClass().getName();
    356    }
    357
    358    public void dump() {
    359        try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
    360            dump(pw);
    361            pw.flush();
    362            sw.flush();
    363            LOGGER.fine(sw.toString());
    364        } catch (final IOException e) {
    365            LOGGER.log(Level.SEVERE, e.getMessage(), e);
    366        }
    367    }
    368
    369    public void dump(final PrintWriter pw) {
    370        dump(pw, null);
    371    }
    372
    373    public void dump(final PrintWriter pw, final String prefix) {
    374        if (prefix != null) {
    375            pw.print(prefix + ": ");
    376        }
    377
    378        pw.println(toString());
    379        pw.flush();
    380    }
    381
    382    public String getDescriptionWithoutValue() {
    383        return getTag() + " (0x" + Integer.toHexString(getTag()) + ": " + getTagInfo().name
    384                + "): ";
    385    }
    386
    387    @Override
    388    public String toString() {
    389        return getTag() +
    390                " (0x" +
    391                Integer.toHexString(getTag()) +
    392                ": " +
    393                getTagInfo().name +
    394                "): " +
    395                getValueDescription() +
    396                " (" +
    397                getCount() +
    398                " " +
    399                getFieldType().getName() +
    400                ")";
    401    }
    402
    403    public String getTagName() {
    404        if (getTagInfo() == TiffTagConstants.TIFF_TAG_UNKNOWN) {
    405            return getTagInfo().name + " (0x" + Integer.toHexString(getTag()) + ")";
    406        }
    407        return getTagInfo().name;
    408    }
    409
    410    public String getFieldTypeName() {
    411        return getFieldType().getName();
    412    }
    413
    414    public Object getValue() throws ImageReadException {
    415        // System.out.print("getValue");
    416        return getTagInfo().getValue(this);
    417    }
    418
    419    public String getStringValue() throws ImageReadException {
    420        final Object o = getValue();
    421        if (o == null) {
    422            return null;
    423        }
    424        if (!(o instanceof String)) {
    425            throw new ImageReadException("Expected String value("
    426                    + getTagInfo().getDescription() + "): " + o);
    427        }
    428        return (String) o;
    429    }
    430
    431    public int[] getIntArrayValue() throws ImageReadException {
    432        final Object o = getValue();
    433        // if (o == null)
    434        // return null;
    435
    436        if (o instanceof Number) {
    437            return new int[] { ((Number) o).intValue() };
    438        } else if (o instanceof Number[]) {
    439            final Number[] numbers = (Number[]) o;
    440            final int[] result = new int[numbers.length];
    441            for (int i = 0; i < numbers.length; i++) {
    442                result[i] = numbers[i].intValue();
    443            }
    444            return result;
    445        } else if (o instanceof short[]) {
    446            final short[] numbers = (short[]) o;
    447            final int[] result = new int[numbers.length];
    448            for (int i = 0; i < numbers.length; i++) {
    449                result[i] = 0xffff & numbers[i];
    450            }
    451            return result;
    452        } else if (o instanceof int[]) {
    453            final int[] numbers = (int[]) o;
    454            final int[] result = new int[numbers.length];
    455            System.arraycopy(numbers, 0, result, 0, numbers.length);
    456            return result;
    457        }
    458
    459        throw new ImageReadException("Unknown value: " + o + " for: "
    460                + getTagInfo().getDescription());
    461        // return null;
    462    }
    463
    464    public double[] getDoubleArrayValue() throws ImageReadException {
    465        final Object o = getValue();
    466        // if (o == null)
    467        // return null;
    468
    469        if (o instanceof Number) {
    470            return new double[] { ((Number) o).doubleValue() };
    471        } else if (o instanceof Number[]) {
    472            final Number[] numbers = (Number[]) o;
    473            final double[] result = new double[numbers.length];
    474            for (int i = 0; i < numbers.length; i++) {
    475                result[i] = numbers[i].doubleValue();
    476            }
    477            return result;
    478        } else if (o instanceof short[]) {
    479            final short[] numbers = (short[]) o;
    480            final double[] result = new double[numbers.length];
    481            for (int i = 0; i < numbers.length; i++) {
    482                result[i] = numbers[i];
    483            }
    484            return result;
    485        } else if (o instanceof int[]) {
    486            final int[] numbers = (int[]) o;
    487            final double[] result = new double[numbers.length];
    488            for (int i = 0; i < numbers.length; i++) {
    489                result[i] = numbers[i];
    490            }
    491            return result;
    492        } else if (o instanceof float[]) {
    493            final float[] numbers = (float[]) o;
    494            final double[] result = new double[numbers.length];
    495            for (int i = 0; i < numbers.length; i++) {
    496                result[i] = numbers[i];
    497            }
    498            return result;
    499        } else if (o instanceof double[]) {
    500            final double[] numbers = (double[]) o;
    501            final double[] result = new double[numbers.length];
    502            System.arraycopy(numbers, 0, result, 0, numbers.length);
    503            return result;
    504        }
    505
    506        throw new ImageReadException("Unknown value: " + o + " for: "
    507                + getTagInfo().getDescription());
    508        // return null;
    509    }
    510
    511    public int getIntValueOrArraySum() throws ImageReadException {
    512        final Object o = getValue();
    513        // if (o == null)
    514        // return -1;
    515
    516        if (o instanceof Number) {
    517            return ((Number) o).intValue();
    518        } else if (o instanceof Number[]) {
    519            final Number[] numbers = (Number[]) o;
    520            int sum = 0;
    521            for (final Number number : numbers) {
    522                sum += number.intValue();
    523            }
    524            return sum;
    525        } else if (o instanceof short[]) {
    526            final short[] numbers = (short[]) o;
    527            int sum = 0;
    528            for (final short number : numbers) {
    529                sum += number;
    530            }
    531            return sum;
    532        } else if (o instanceof int[]) {
    533            final int[] numbers = (int[]) o;
    534            int sum = 0;
    535            for (final int number : numbers) {
    536                sum += number;
    537            }
    538            return sum;
    539        }
    540
    541        throw new ImageReadException("Unknown value: " + o + " for: "
    542                + getTagInfo().getDescription());
    543        // return -1;
    544    }
    545
    546    public int getIntValue() throws ImageReadException {
    547        final Object o = getValue();
    548        if (o == null) {
    549            throw new ImageReadException("Missing value: "
    550                    + getTagInfo().getDescription());
    551        }
    552
    553        return ((Number) o).intValue();
    554    }
    555
    556    public double getDoubleValue() throws ImageReadException {
    557        final Object o = getValue();
    558        if (o == null) {
    559            throw new ImageReadException("Missing value: "
    560                    + getTagInfo().getDescription());
    561        }
    562
    563        return ((Number) o).doubleValue();
    564    }
    565}