    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.Arrays;
    026import java.util.Date;
    027import java.util.Locale;
    028import java.util.logging.Level;
    029import java.util.logging.Logger;
    030
    031import org.apache.commons.imaging.ImagingException;
    032import org.apache.commons.imaging.common.Allocator;
    033import org.apache.commons.imaging.common.BinaryFunctions;
    034import org.apache.commons.imaging.formats.tiff.constants.TiffConstants;
    035import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;
    036import org.apache.commons.imaging.formats.tiff.fieldtypes.AbstractFieldType;
    037import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
    038
    039/**
    040 * A TIFF field in a TIFF directory. Immutable.
    041 */
    042public class TiffField {
    043
    044    public final class OversizeValueElement extends AbstractTiffElement {
    045        public OversizeValueElement(final int offset, final int length) {
    046            super(offset, length);
    047        }
    048
    049        @Override
    050        public String getElementDescription() {
    051            return "OversizeValueElement, tag: " + getTagInfo().name + ", fieldType: " + getFieldType().getName();
    052        }
    053    }
    054
    055    private static final Logger LOGGER = Logger.getLogger(TiffField.class.getName());
    056    private final TagInfo tagInfo;
    057    private final int tag;
    058    private final int directoryType;
    059    private final AbstractFieldType abstractFieldType;
    060    private final long count;
    061    private final long offset;
    062    private final byte[] value;
    063    private final ByteOrder byteOrder;
    064
    065    private final int sortHint;
    066
    067    public TiffField(final int tag, final int directoryType, final AbstractFieldType abstractFieldType, final long count, final long offset, final byte[] value,
    068            final ByteOrder byteOrder, final int sortHint) {
    069
    070        this.tag = tag;
    071        this.directoryType = directoryType;
    072        this.abstractFieldType = abstractFieldType;
    073        this.count = count;
    074        this.offset = offset;
    075        this.value = value;
    076        this.byteOrder = byteOrder;
    077        this.sortHint = sortHint;
    078
    079        tagInfo = TiffTags.getTag(directoryType, tag);
    080    }
    081
    082    public void dump() {
    083        try (StringWriter sw = new StringWriter();
    084                PrintWriter pw = new PrintWriter(sw)) {
    085            dump(pw);
    086            pw.flush();
    087            sw.flush();
    088            LOGGER.fine(sw.toString());
    089        } catch (final IOException e) {
    090            LOGGER.log(Level.SEVERE, e.getMessage(), e);
    091        }
    092    }
    093
    094    public void dump(final PrintWriter pw) {
    095        dump(pw, null);
    096    }
    097
    098    public void dump(final PrintWriter pw, final String prefix) {
    099        if (prefix != null) {
    100            pw.print(prefix + ": ");
    101        }
    102
    103        pw.println(toString());
    104        pw.flush();
    105    }
    106
    107    /**
    108     * Returns a copy of the raw value of the field.
    109     *
    110     * @return the value of the field, in the byte order of the field.
    111     */
    112    public byte[] getByteArrayValue() {
    113        return BinaryFunctions.copyOfStart(value, getBytesLength());
    114    }
    115
    116    /**
    117     * Returns the field's byte order.
    118     *
    119     * @return the byte order
    120     */
    121    public ByteOrder getByteOrder() {
    122        return byteOrder;
    123    }
    124
    125    /**
    126     * The length of the field's value.
    127     *
    128     * @return the length, in bytes.
    129     */
    130    public int getBytesLength() {
    131        return (int) count * abstractFieldType.getSize();
    132    }
    133
    134    /**
    135     * Returns the field's count, derived from bytes 4-7.
    136     *
    137     * @return the count
    138     */
    139    public long getCount() {
    140        return count;
    141    }
    142
    143    public String getDescriptionWithoutValue() {
    144        return getTag() + " (0x" + Integer.toHexString(getTag()) + ": " + getTagInfo().name + "): ";
    145    }
    146
    147    public int getDirectoryType() {
    148        return directoryType;
    149    }
    150
    151    public double[] getDoubleArrayValue() throws ImagingException {
    152        final Object o = getValue();
    153        // if (o == null)
    154        // return null;
    155
    156        if (o instanceof Number) {
    157            return new double[] { ((Number) o).doubleValue() };
    158        }
    159        if (o instanceof Number[]) {
    160            final Number[] numbers = (Number[]) o;
    161            final double[] result = Allocator.doubleArray(numbers.length);
    162            Arrays.setAll(result, i -> numbers[i].doubleValue());
    163            return result;
    164        }
    165        if (o instanceof short[]) {
    166            final short[] numbers = (short[]) o;
    167            final double[] result = Allocator.doubleArray(numbers.length);
    168            Arrays.setAll(result, i -> numbers[i]);
    169            return result;
    170        }
    171        if (o instanceof int[]) {
    172            final int[] numbers = (int[]) o;
    173            final double[] result = Allocator.doubleArray(numbers.length);
    174            Arrays.setAll(result, i -> numbers[i]);
    175            return result;
    176        }
    177        if (o instanceof float[]) {
    178            final float[] numbers = (float[]) o;
    179            final double[] result = Allocator.doubleArray(numbers.length);
    180            Arrays.setAll(result, i -> numbers[i]);
    181            return result;
    182        }
    183        if (o instanceof double[]) {
    184            final double[] numbers = (double[]) o;
    185            return Arrays.copyOf(numbers, numbers.length);
    186        }
    187
    188        throw new ImagingException("Unknown value: " + o + " for: " + getTagInfo().getDescription());
    189        // return null;
    190    }
    191
    192    public double getDoubleValue() throws ImagingException {
    193        final Object o = getValue();
    194        if (o == null) {
    195            throw new ImagingException("Missing value: " + getTagInfo().getDescription());
    196        }
    197
    198        return ((Number) o).doubleValue();
    199    }
    200
    201    /**
    202     * Returns the field's type, derived from bytes 2-3.
    203     *
    204     * @return the field's type, as a {@code FieldType} object.
    205     */
    206    public AbstractFieldType getFieldType() {
    207        return abstractFieldType;
    208    }
    209
    210    public String getFieldTypeName() {
    211        return getFieldType().getName();
    212    }
    213
    214    public int[] getIntArrayValue() throws ImagingException {
    215        final Object o = getValue();
    216        // if (o == null)
    217        // return null;
    218
    219        if (o instanceof Number) {
    220            return new int[] { ((Number) o).intValue() };
    221        }
    222        if (o instanceof Number[]) {
    223            final Number[] numbers = (Number[]) o;
    224            final int[] result = Allocator.intArray(numbers.length);
    225            Arrays.setAll(result, i -> numbers[i].intValue());
    226            return result;
    227        }
    228        if (o instanceof short[]) {
    229            final short[] numbers = (short[]) o;
    230            final int[] result = Allocator.intArray(numbers.length);
    231            Arrays.setAll(result, i -> 0xffff & numbers[i]);
    232            return result;
    233        }
    234        if (o instanceof int[]) {
    235            final int[] numbers = (int[]) o;
    236            return Arrays.copyOf(numbers, numbers.length);
    237        }
    238        if (o instanceof long[]) {
    239            final long[] numbers = (long[]) o;
    240            final int[] iNumbers = new int[numbers.length];
    241            for (int i = 0; i < iNumbers.length; i++) {
    242                iNumbers[i] = (int) numbers[i];
    243            }
    244            return iNumbers;
    245        }
    246
    247        throw new ImagingException("Unknown value: " + o + " for: " + getTagInfo().getDescription());
    248        // return null;
    249    }
    250
    251    public int getIntValue() throws ImagingException {
    252        final Object o = getValue();
    253        if (o == null) {
    254            throw new ImagingException("Missing value: " + getTagInfo().getDescription());
    255        }
    256
    257        return ((Number) o).intValue();
    258    }
    259
    260    public int getIntValueOrArraySum() throws ImagingException {
    261        final Object o = getValue();
    262        // if (o == null)
    263        // return -1;
    264
    265        if (o instanceof Number) {
    266            return ((Number) o).intValue();
    267        }
    268        if (o instanceof Number[]) {
    269            final Number[] numbers = (Number[]) o;
    270            int sum = 0;
    271            for (final Number number : numbers) {
    272                sum += number.intValue();
    273            }
    274            return sum;
    275        }
    276        if (o instanceof short[]) {
    277            final short[] numbers = (short[]) o;
    278            int sum = 0;
    279            for (final short number : numbers) {
    280                sum += number;
    281            }
    282            return sum;
    283        }
    284        if (o instanceof int[]) {
    285            final int[] numbers = (int[]) o;
    286            int sum = 0;
    287            for (final int number : numbers) {
    288                sum += number;
    289            }
    290            return sum;
    291        }
    292
    293        throw new ImagingException("Unknown value: " + o + " for: " + getTagInfo().getDescription());
    294        // return -1;
    295    }
    296
    297    /**
    298     * Gets the value of the field in the form of an array of eight-byte (long) integers.
    299     *
    300     * @return an valid array of size zero or larger giving signed long integer values.
    301     * @throws ImagingException if the field instance is of an incompatible type or does not contain a valid data element.
    302     */
    303    public long[] getLongArrayValue() throws ImagingException {
    304        final Object o = getValue();
    305        if (o instanceof Number) {
    306            return new long[] { ((Number) o).longValue() };
    307        }
    308        if (o instanceof Number[]) {
    309            final Number[] numbers = (Number[]) o;
    310            final long[] result = Allocator.longArray(numbers.length);
    311            Arrays.setAll(result, i -> numbers[i].longValue());
    312            return result;
    313        }
    314        if (o instanceof short[]) {
    315            final short[] numbers = (short[]) o;
    316            final long[] result = Allocator.longArray(numbers.length);
    317            Arrays.setAll(result, i -> 0xffff & numbers[i]);
    318            return result;
    319        }
    320        if (o instanceof int[]) {
    321            final int[] numbers = (int[]) o;
    322            final long[] result = Allocator.longArray(numbers.length);
    323            Arrays.setAll(result, i -> 0xFFFFffffL & numbers[i]);
    324            return result;
    325        }
    326        if (o instanceof long[]) {
    327            final long[] numbers = (long[]) o;
    328            return Arrays.copyOf(numbers, numbers.length);
    329        }
    330
    331        throw new ImagingException("Unknown value: " + o + " for: " + getTagInfo().getDescription());
    332    }
    333
    334    /**
    335     * Gets the value of the field in the form of an eight-byte (long) integer.
    336     *
    337     * @return a signed long integer value.
    338     * @throws ImagingException if the field instance is of an incompatible type or does not contain a valid data element.
    339     */
    340    public long getLongValue() throws ImagingException {
    341        final Object o = getValue();
    342        if (o == null) {
    343            throw new ImagingException("Missing value: " + getTagInfo().getDescription());
    344        }
    345        return ((Number) o).longValue();
    346    }
    347
    348    /**
    349     * Returns the TIFF field's offset/value field, derived from bytes 8-11.
    350     *
    351     * @return the field's offset in a {@code long} of 4 packed bytes, or its inlined value &lt;= 4 bytes long encoded in the field's byte order.
    352     */
    353    public int getOffset() {
    354        return (int) offset;
    355    }
    356
    357    public AbstractTiffElement getOversizeValueElement() {
    358        if (isLocalValue()) {
    359            return null;
    360        }
    361
    362        return new OversizeValueElement(getOffset(), value.length);
    363    }
    364
    365    public int getSortHint() {
    366        return sortHint;
    367    }
    368
    369    public String getStringValue() throws ImagingException {
    370        final Object o = getValue();
    371        if (o == null) {
    372            return null;
    373        }
    374        if (!(o instanceof String)) {
    375            throw new ImagingException("Expected String value(" + getTagInfo().getDescription() + "): " + o);
    376        }
    377        return (String) o;
    378    }
    379
    380    /**
    381     * Returns the field's tag, derived from bytes 0-1.
    382     *
    383     * @return the tag, as an {@code int} in which only the lowest 2 bytes are set
    384     */
    385    public int getTag() {
    386        return tag;
    387    }
    388
    389    public TagInfo getTagInfo() {
    390        return tagInfo;
    391    }
    392
    393    public String getTagName() {
    394        if (getTagInfo() == TiffTagConstants.TIFF_TAG_UNKNOWN) {
    395            return getTagInfo().name + " (0x" + Integer.toHexString(getTag()) + ")";
    396        }
    397        return getTagInfo().name;
    398    }
    399
    400    public Object getValue() throws ImagingException {
    401        // System.out.print("getValue");
    402        return getTagInfo().getValue(this);
    403    }
    404
    405    public String getValueDescription() {
    406        try {
    407            return getValueDescription(getValue());
    408        } catch (final ImagingException e) {
    409            return "Invalid value: " + e.getMessage();
    410        }
    411    }
    412
    413    private String getValueDescription(final Object o) {
    414        if (o == null) {
    415            return null;
    416        }
    417
    418        if (o instanceof Number) {
    419            return o.toString();
    420        }
    421        if (o instanceof String) {
    422            return "'" + o.toString().trim() + "'";
    423        }
    424        if (o instanceof Date) {
    425            final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.ROOT);
    426            return df.format((Date) o);
    427        }
    428        if (o instanceof Object[]) {
    429            final Object[] objects = (Object[]) o;
    430            final StringBuilder result = new StringBuilder();
    431
    432            for (int i = 0; i < objects.length; i++) {
    433                final Object object = objects[i];
    434
    435                if (i > 50) {
    436                    result.append("... (").append(objects.length).append(")");
    437                    break;
    438                }
    439                if (i > 0) {
    440                    result.append(", ");
    441                }
    442                result.append(object.toString());
    443            }
    444            return result.toString();
    445            // } else if (o instanceof Number[])
    446            // {
    447            // Number[] numbers = (Number[]) o;
    448            // StringBuilder result = new StringBuilder();
    449            //
    450            // for (int i = 0; i < numbers.length; i++)
    451            // {
    452            // Number number = numbers[i];
    453            //
    454            // if (i > 0)
    455            // result.append(", ");
    456            // result.append("" + number);
    457            // }
    458            // return result.toString();
    459            // }
    460        }
    461        if (o instanceof short[]) {
    462            final short[] values = (short[]) o;
    463            final StringBuilder result = new StringBuilder();
    464
    465            for (int i = 0; i < values.length; i++) {
    466                final short sVal = values[i];
    467
    468                if (i > 50) {
    469                    result.append("... (").append(values.length).append(")");
    470                    break;
    471                }
    472                if (i > 0) {
    473                    result.append(", ");
    474                }
    475                result.append(sVal);
    476            }
    477            return result.toString();
    478        }
    479        if (o instanceof int[]) {
    480            final int[] values = (int[]) o;
    481            final StringBuilder result = new StringBuilder();
    482
    483            for (int i = 0; i < values.length; i++) {
    484                final int iVal = values[i];
    485
    486                if (i > 50) {
    487                    result.append("... (").append(values.length).append(")");
    488                    break;
    489                }
    490                if (i > 0) {
    491                    result.append(", ");
    492                }
    493                result.append(iVal);
    494            }
    495            return result.toString();
    496        }
    497        if (o instanceof long[]) {
    498            final long[] values = (long[]) o;
    499            final StringBuilder result = new StringBuilder();
    500
    501            for (int i = 0; i < values.length; i++) {
    502                final long lVal = values[i];
    503
    504                if (i > 50) {
    505                    result.append("... (").append(values.length).append(")");
    506                    break;
    507                }
    508                if (i > 0) {
    509                    result.append(", ");
    510                }
    511                result.append(lVal);
    512            }
    513            return result.toString();
    514        }
    515        if (o instanceof double[]) {
    516            final double[] values = (double[]) o;
    517            final StringBuilder result = new StringBuilder();
    518
    519            for (int i = 0; i < values.length; i++) {
    520                final double dVal = values[i];
    521
    522                if (i > 50) {
    523                    result.append("... (").append(values.length).append(")");
    524                    break;
    525                }
    526                if (i > 0) {
    527                    result.append(", ");
    528                }
    529                result.append(dVal);
    530            }
    531            return result.toString();
    532        }
    533        if (o instanceof byte[]) {
    534            final byte[] values = (byte[]) o;
    535            final StringBuilder result = new StringBuilder();
    536
    537            for (int i = 0; i < values.length; i++) {
    538                final byte bVal = values[i];
    539
    540                if (i > 50) {
    541                    result.append("... (").append(values.length).append(")");
    542                    break;
    543                }
    544                if (i > 0) {
    545                    result.append(", ");
    546                }
    547                result.append(bVal);
    548            }
    549            return result.toString();
    550        }
    551        if (o instanceof char[]) {
    552            final char[] values = (char[]) o;
    553            final StringBuilder result = new StringBuilder();
    554
    555            for (int i = 0; i < values.length; i++) {
    556                final char cVal = values[i];
    557
    558                if (i > 50) {
    559                    result.append("... (").append(values.length).append(")");
    560                    break;
    561                }
    562                if (i > 0) {
    563                    result.append(", ");
    564                }
    565                result.append(cVal);
    566            }
    567            return result.toString();
    568        }
    569        if (o instanceof float[]) {
    570            final float[] values = (float[]) o;
    571            final StringBuilder result = new StringBuilder();
    572
    573            for (int i = 0; i < values.length; i++) {
    574                final float fVal = values[i];
    575
    576                if (i > 50) {
    577                    result.append("... (").append(values.length).append(")");
    578                    break;
    579                }
    580                if (i > 0) {
    581                    result.append(", ");
    582                }
    583                result.append(fVal);
    584            }
    585            return result.toString();
    586        }
    587        // else if (o instanceof short[])
    588        // {
    589        // short[] numbers = (short[]) o;
    590        // StringBuilder result = new StringBuilder();
    591        //
    592        // for (int i = 0; i < numbers.length; i++)
    593        // {
    594        // short number = numbers[i];
    595        //
    596        // if (i > 0)
    597        // result.append(", ");
    598        // result.append("" + number);
    599        // }
    600        // return result.toString();
    601        // }
    602
    603        return "Unknown: " + o.getClass().getName();
    604    }
    605
    606    /**
    607     * Indicates whether the field's value is inlined into the offset field.
    608     *
    609     * @return true if the value is inlined
    610     */
    611    public boolean isLocalValue() {
    612        return count * abstractFieldType.getSize() <= TiffConstants.ENTRY_MAX_VALUE_LENGTH;
    613    }
    614
    615    @Override
    616    public String toString() {
    617        return getTag() + " (0x" + Integer.toHexString(getTag()) + ": " + getTagInfo().name + "): " + getValueDescription() + " (" + getCount() + " "
    618                + getFieldType().getName() + ")";
    619    }
    620}