    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.awt.image.BufferedImage;
    020import java.io.IOException;
    021import java.nio.ByteOrder;
    022import java.util.ArrayList;
    023import java.util.List;
    024
    025import org.apache.commons.imaging.ImagingException;
    026import org.apache.commons.imaging.common.GenericImageMetadata;
    027import org.apache.commons.imaging.common.RationalNumber;
    028import org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants;
    029import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryConstants;
    030import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryType;
    031import org.apache.commons.imaging.formats.tiff.fieldtypes.AbstractFieldType;
    032import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
    033import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoAscii;
    034import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoByte;
    035import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoDoubles;
    036import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoFloats;
    037import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoGpsText;
    038import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoLongs;
    039import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoRationals;
    040import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSBytes;
    041import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSLongs;
    042import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSRationals;
    043import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoSShorts;
    044import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoShorts;
    045import org.apache.commons.imaging.formats.tiff.taginfos.TagInfoXpString;
    046import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory;
    047import org.apache.commons.imaging.formats.tiff.write.TiffOutputField;
    048import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
    049
    050public class TiffImageMetadata extends GenericImageMetadata {
    051    public static class Directory extends GenericImageMetadata implements ImageMetadataItem {
    052        // private BufferedImage thumbnail;
    053
    054        public final int type;
    055
    056        private final TiffDirectory directory;
    057        private final ByteOrder byteOrder;
    058
    059        public Directory(final ByteOrder byteOrder, final TiffDirectory directory) {
    060            this.type = directory.type;
    061            this.directory = directory;
    062            this.byteOrder = byteOrder;
    063        }
    064
    065        public void add(final TiffField entry) {
    066            add(new TiffMetadataItem(entry));
    067        }
    068
    069        public TiffField findField(final TagInfo tagInfo) throws ImagingException {
    070            return directory.findField(tagInfo);
    071        }
    072
    073        public List<TiffField> getAllFields() {
    074            return directory.getDirectoryEntries();
    075        }
    076
    077        public JpegImageData getJpegImageData() {
    078            return directory.getJpegImageData();
    079        }
    080
    081        public TiffOutputDirectory getOutputDirectory(final ByteOrder byteOrder) throws ImagingException {
    082            try {
    083                final TiffOutputDirectory dstDir = new TiffOutputDirectory(type, byteOrder);
    084
    085                final List<? extends ImageMetadataItem> entries = getItems();
    086                for (final ImageMetadataItem entry : entries) {
    087                    final TiffMetadataItem item = (TiffMetadataItem) entry;
    088                    final TiffField srcField = item.getTiffField();
    089
    090                    if (null != dstDir.findField(srcField.getTag())) {
    091                        // ignore duplicate tags in a directory.
    092                        continue;
    093                    }
    094                    if (srcField.getTagInfo().isOffset()) {
    095                        // ignore offset fields.
    096                        continue;
    097                    }
    098
    099                    final TagInfo tagInfo = srcField.getTagInfo();
    100                    final AbstractFieldType abstractFieldType = srcField.getFieldType();
    101                    // byte[] bytes = srcField.fieldType.getRawBytes(srcField);
    102
    103                    // Debug.debug("tagInfo", tagInfo);
    104
    105                    final Object value = srcField.getValue();
    106
    107                    // Debug.debug("value", Debug.getType(value));
    108
    109                    final byte[] bytes = tagInfo.encodeValue(abstractFieldType, value, byteOrder);
    110
    111                    // if (tagInfo.isUnknown())
    112                    // Debug.debug(
    113                    // "\t" + "unknown tag(0x"
    114                    // + Integer.toHexString(srcField.tag)
    115                    // + ") bytes", bytes);
    116
    117                    final int count = bytes.length / abstractFieldType.getSize();
    118                    final TiffOutputField dstField = new TiffOutputField(srcField.getTag(), tagInfo, abstractFieldType, count, bytes);
    119                    dstField.setSortHint(srcField.getSortHint());
    120                    dstDir.add(dstField);
    121                }
    122
    123                dstDir.setTiffImageData(getTiffImageData());
    124                dstDir.setJpegImageData(getJpegImageData());
    125
    126                return dstDir;
    127            } catch (final ImagingException e) {
    128                throw new ImagingException(e.getMessage(), e);
    129            }
    130        }
    131
    132        public BufferedImage getThumbnail() throws ImagingException, IOException {
    133            return directory.getTiffImage(byteOrder);
    134        }
    135
    136        public AbstractTiffImageData getTiffImageData() {
    137            return directory.getTiffImageData();
    138        }
    139
    140        @Override
    141        public String toString(final String prefix) {
    142            return (prefix != null ? prefix : "") + directory.description() + ": " //
    143                    + (getTiffImageData() != null ? " (tiffImageData)" : "") //
    144                    + (getJpegImageData() != null ? " (jpegImageData)" : "") //
    145                    + "\n" + super.toString(prefix) + "\n";
    146        }
    147
    148    }
    149
    150    public static class GpsInfo {
    151        public final String latitudeRef;
    152        public final String longitudeRef;
    153
    154        public final RationalNumber latitudeDegrees;
    155        public final RationalNumber latitudeMinutes;
    156        public final RationalNumber latitudeSeconds;
    157        public final RationalNumber longitudeDegrees;
    158        public final RationalNumber longitudeMinutes;
    159        public final RationalNumber longitudeSeconds;
    160
    161        public GpsInfo(final String latitudeRef, final String longitudeRef, final RationalNumber latitudeDegrees, final RationalNumber latitudeMinutes,
    162                final RationalNumber latitudeSeconds, final RationalNumber longitudeDegrees, final RationalNumber longitudeMinutes,
    163                final RationalNumber longitudeSeconds) {
    164            this.latitudeRef = latitudeRef;
    165            this.longitudeRef = longitudeRef;
    166            this.latitudeDegrees = latitudeDegrees;
    167            this.latitudeMinutes = latitudeMinutes;
    168            this.latitudeSeconds = latitudeSeconds;
    169            this.longitudeDegrees = longitudeDegrees;
    170            this.longitudeMinutes = longitudeMinutes;
    171            this.longitudeSeconds = longitudeSeconds;
    172        }
    173
    174        public double getLatitudeAsDegreesNorth() throws ImagingException {
    175            final double result = latitudeDegrees.doubleValue() + latitudeMinutes.doubleValue() / 60.0 + latitudeSeconds.doubleValue() / 3600.0;
    176
    177            if (latitudeRef.trim().equalsIgnoreCase("n")) {
    178                return result;
    179            }
    180            if (latitudeRef.trim().equalsIgnoreCase("s")) {
    181                return -result;
    182            }
    183            throw new ImagingException("Unknown latitude ref: \"" + latitudeRef + "\"");
    184        }
    185
    186        public double getLongitudeAsDegreesEast() throws ImagingException {
    187            final double result = longitudeDegrees.doubleValue() + longitudeMinutes.doubleValue() / 60.0 + longitudeSeconds.doubleValue() / 3600.0;
    188
    189            if (longitudeRef.trim().equalsIgnoreCase("e")) {
    190                return result;
    191            }
    192            if (longitudeRef.trim().equalsIgnoreCase("w")) {
    193                return -result;
    194            }
    195            throw new ImagingException("Unknown longitude ref: \"" + longitudeRef + "\"");
    196        }
    197
    198        @Override
    199        public String toString() {
    200            // This will format the gps info like so:
    201            //
    202            // latitude: 8 degrees, 40 minutes, 42.2 seconds S
    203            // longitude: 115 degrees, 26 minutes, 21.8 seconds E
    204
    205            return "[GPS. Latitude: " + latitudeDegrees.toDisplayString() + " degrees, " + latitudeMinutes.toDisplayString() + " minutes, "
    206                    + latitudeSeconds.toDisplayString() + " seconds " + latitudeRef + ", Longitude: " + longitudeDegrees.toDisplayString() + " degrees, "
    207                    + longitudeMinutes.toDisplayString() + " minutes, " + longitudeSeconds.toDisplayString() + " seconds " + longitudeRef + ']';
    208        }
    209
    210    }
    211
    212    public static class TiffMetadataItem extends GenericImageMetadataItem {
    213        private final TiffField entry;
    214
    215        public TiffMetadataItem(final TiffField entry) {
    216            // super(entry.getTagName() + " (" + entry.getFieldTypeName() + ")",
    217            super(entry.getTagName(), entry.getValueDescription());
    218            this.entry = entry;
    219        }
    220
    221        public TiffField getTiffField() {
    222            return entry;
    223        }
    224
    225    }
    226
    227    public final TiffContents contents;
    228
    229    public TiffImageMetadata(final TiffContents contents) {
    230        this.contents = contents;
    231    }
    232
    233    public TiffDirectory findDirectory(final int directoryType) {
    234        final List<? extends ImageMetadataItem> directories = getDirectories();
    235        for (final ImageMetadataItem directory1 : directories) {
    236            final Directory directory = (Directory) directory1;
    237            if (directory.type == directoryType) {
    238                return directory.directory;
    239            }
    240        }
    241        return null;
    242    }
    243
    244    public TiffField findField(final TagInfo tagInfo) throws ImagingException {
    245        return findField(tagInfo, false);
    246    }
    247
    248    public TiffField findField(final TagInfo tagInfo, final boolean exactDirectoryMatch) throws ImagingException {
    249        // Please keep this method in sync with TiffField's getTag()
    250        final Integer tagCount = TiffTags.getTagCount(tagInfo.tag);
    251        final int tagsMatching = tagCount == null ? 0 : tagCount;
    252
    253        final List<? extends ImageMetadataItem> directories = getDirectories();
    254        if (exactDirectoryMatch || tagInfo.directoryType != TiffDirectoryType.EXIF_DIRECTORY_UNKNOWN) {
    255            for (final ImageMetadataItem directory1 : directories) {
    256                final Directory directory = (Directory) directory1;
    257                if (directory.type == tagInfo.directoryType.directoryType) {
    258                    final TiffField field = directory.findField(tagInfo);
    259                    if (field != null) {
    260                        return field;
    261                    }
    262                }
    263            }
    264            if (exactDirectoryMatch || tagsMatching > 1) {
    265                return null;
    266            }
    267            for (final ImageMetadataItem directory1 : directories) {
    268                final Directory directory = (Directory) directory1;
    269                if (tagInfo.directoryType.isImageDirectory() && directory.type >= 0 || !tagInfo.directoryType.isImageDirectory() && directory.type < 0) {
    270                    final TiffField field = directory.findField(tagInfo);
    271                    if (field != null) {
    272                        return field;
    273                    }
    274                }
    275            }
    276        }
    277
    278        for (final ImageMetadataItem directory1 : directories) {
    279            final Directory directory = (Directory) directory1;
    280            final TiffField field = directory.findField(tagInfo);
    281            if (field != null) {
    282                return field;
    283            }
    284        }
    285
    286        return null;
    287    }
    288
    289    public List<TiffField> getAllFields() {
    290        final List<TiffField> result = new ArrayList<>();
    291        final List<? extends ImageMetadataItem> directories = getDirectories();
    292        for (final ImageMetadataItem directory1 : directories) {
    293            final Directory directory = (Directory) directory1;
    294            result.addAll(directory.getAllFields());
    295        }
    296        return result;
    297    }
    298
    299    public List<? extends ImageMetadataItem> getDirectories() {
    300        return super.getItems();
    301    }
    302
    303    public Object getFieldValue(final TagInfo tag) throws ImagingException {
    304        final TiffField field = findField(tag);
    305        if (field == null) {
    306            return null;
    307        }
    308        return field.getValue();
    309    }
    310
    311    public String[] getFieldValue(final TagInfoAscii tag) throws ImagingException {
    312        final TiffField field = findField(tag);
    313        if (field == null) {
    314            return null;
    315        }
    316        if (!tag.dataTypes.contains(field.getFieldType())) {
    317            return null;
    318        }
    319        final byte[] bytes = field.getByteArrayValue();
    320        return tag.getValue(field.getByteOrder(), bytes);
    321    }
    322
    323    public byte[] getFieldValue(final TagInfoByte tag) throws ImagingException {
    324        final TiffField field = findField(tag);
    325        if (field == null) {
    326            return null;
    327        }
    328        if (!tag.dataTypes.contains(field.getFieldType())) {
    329            return null;
    330        }
    331        return field.getByteArrayValue();
    332    }
    333
    334    public double[] getFieldValue(final TagInfoDoubles tag) throws ImagingException {
    335        final TiffField field = findField(tag);
    336        if (field == null) {
    337            return null;
    338        }
    339        if (!tag.dataTypes.contains(field.getFieldType())) {
    340            return null;
    341        }
    342        final byte[] bytes = field.getByteArrayValue();
    343        return tag.getValue(field.getByteOrder(), bytes);
    344    }
    345
    346    public float[] getFieldValue(final TagInfoFloats tag) throws ImagingException {
    347        final TiffField field = findField(tag);
    348        if (field == null) {
    349            return null;
    350        }
    351        if (!tag.dataTypes.contains(field.getFieldType())) {
    352            return null;
    353        }
    354        final byte[] bytes = field.getByteArrayValue();
    355        return tag.getValue(field.getByteOrder(), bytes);
    356    }
    357
    358    public String getFieldValue(final TagInfoGpsText tag) throws ImagingException {
    359        final TiffField field = findField(tag);
    360        if (field == null) {
    361            return null;
    362        }
    363        return tag.getValue(field);
    364    }
    365
    366    public int[] getFieldValue(final TagInfoLongs tag) throws ImagingException {
    367        final TiffField field = findField(tag);
    368        if (field == null) {
    369            return null;
    370        }
    371        if (!tag.dataTypes.contains(field.getFieldType())) {
    372            return null;
    373        }
    374        final byte[] bytes = field.getByteArrayValue();
    375        return tag.getValue(field.getByteOrder(), bytes);
    376    }
    377
    378    public RationalNumber[] getFieldValue(final TagInfoRationals tag) throws ImagingException {
    379        final TiffField field = findField(tag);
    380        if (field == null) {
    381            return null;
    382        }
    383        if (!tag.dataTypes.contains(field.getFieldType())) {
    384            return null;
    385        }
    386        final byte[] bytes = field.getByteArrayValue();
    387        return tag.getValue(field.getByteOrder(), bytes);
    388    }
    389
    390    public byte[] getFieldValue(final TagInfoSBytes tag) throws ImagingException {
    391        final TiffField field = findField(tag);
    392        if (field == null) {
    393            return null;
    394        }
    395        if (!tag.dataTypes.contains(field.getFieldType())) {
    396            return null;
    397        }
    398        return field.getByteArrayValue();
    399    }
    400
    401    public short[] getFieldValue(final TagInfoShorts tag) throws ImagingException {
    402        final TiffField field = findField(tag);
    403        if (field == null) {
    404            return null;
    405        }
    406        if (!tag.dataTypes.contains(field.getFieldType())) {
    407            return null;
    408        }
    409        final byte[] bytes = field.getByteArrayValue();
    410        return tag.getValue(field.getByteOrder(), bytes);
    411    }
    412
    413    public int[] getFieldValue(final TagInfoSLongs tag) throws ImagingException {
    414        final TiffField field = findField(tag);
    415        if (field == null) {
    416            return null;
    417        }
    418        if (!tag.dataTypes.contains(field.getFieldType())) {
    419            return null;
    420        }
    421        final byte[] bytes = field.getByteArrayValue();
    422        return tag.getValue(field.getByteOrder(), bytes);
    423    }
    424
    425    public RationalNumber[] getFieldValue(final TagInfoSRationals tag) throws ImagingException {
    426        final TiffField field = findField(tag);
    427        if (field == null) {
    428            return null;
    429        }
    430        if (!tag.dataTypes.contains(field.getFieldType())) {
    431            return null;
    432        }
    433        final byte[] bytes = field.getByteArrayValue();
    434        return tag.getValue(field.getByteOrder(), bytes);
    435    }
    436
    437    public short[] getFieldValue(final TagInfoSShorts tag) throws ImagingException {
    438        final TiffField field = findField(tag);
    439        if (field == null) {
    440            return null;
    441        }
    442        if (!tag.dataTypes.contains(field.getFieldType())) {
    443            return null;
    444        }
    445        final byte[] bytes = field.getByteArrayValue();
    446        return tag.getValue(field.getByteOrder(), bytes);
    447    }
    448
    449    public String getFieldValue(final TagInfoXpString tag) throws ImagingException {
    450        final TiffField field = findField(tag);
    451        if (field == null) {
    452            return null;
    453        }
    454        return tag.getValue(field);
    455    }
    456
    457    public GpsInfo getGpsInfo() throws ImagingException {
    458        final TiffDirectory gpsDirectory = findDirectory(TiffDirectoryConstants.DIRECTORY_TYPE_GPS);
    459        if (null == gpsDirectory) {
    460            return null;
    461        }
    462
    463        // more specific example of how to access GPS values.
    464        final TiffField latitudeRefField = gpsDirectory.findField(GpsTagConstants.GPS_TAG_GPS_LATITUDE_REF);
    465        final TiffField latitudeField = gpsDirectory.findField(GpsTagConstants.GPS_TAG_GPS_LATITUDE);
    466        final TiffField longitudeRefField = gpsDirectory.findField(GpsTagConstants.GPS_TAG_GPS_LONGITUDE_REF);
    467        final TiffField longitudeField = gpsDirectory.findField(GpsTagConstants.GPS_TAG_GPS_LONGITUDE);
    468
    469        if (latitudeRefField == null || latitudeField == null || longitudeRefField == null || longitudeField == null) {
    470            return null;
    471        }
    472
    473        // all of these values are strings.
    474        final String latitudeRef = latitudeRefField.getStringValue();
    475        final RationalNumber[] latitude = (RationalNumber[]) latitudeField.getValue();
    476        final String longitudeRef = longitudeRefField.getStringValue();
    477        final RationalNumber[] longitude = (RationalNumber[]) longitudeField.getValue();
    478
    479        if (latitude.length != 3 || longitude.length != 3) {
    480            throw new ImagingException("Expected three values for latitude and longitude.");
    481        }
    482
    483        final RationalNumber latitudeDegrees = latitude[0];
    484        final RationalNumber latitudeMinutes = latitude[1];
    485        final RationalNumber latitudeSeconds = latitude[2];
    486
    487        final RationalNumber longitudeDegrees = longitude[0];
    488        final RationalNumber longitudeMinutes = longitude[1];
    489        final RationalNumber longitudeSeconds = longitude[2];
    490
    491        return new GpsInfo(latitudeRef, longitudeRef, latitudeDegrees, latitudeMinutes, latitudeSeconds, longitudeDegrees, longitudeMinutes, longitudeSeconds);
    492    }
    493
    494    @Override
    495    public List<? extends ImageMetadataItem> getItems() {
    496        final List<ImageMetadataItem> result = new ArrayList<>();
    497
    498        final List<? extends ImageMetadataItem> items = super.getItems();
    499        for (final ImageMetadataItem item : items) {
    500            final Directory dir = (Directory) item;
    501            result.addAll(dir.getItems());
    502        }
    503
    504        return result;
    505    }
    506
    507    public TiffOutputSet getOutputSet() throws ImagingException {
    508        final ByteOrder byteOrder = contents.header.byteOrder;
    509        final TiffOutputSet result = new TiffOutputSet(byteOrder);
    510
    511        final List<? extends ImageMetadataItem> srcDirs = getDirectories();
    512        for (final ImageMetadataItem srcDir1 : srcDirs) {
    513            final Directory srcDir = (Directory) srcDir1;
    514
    515            if (null != result.findDirectory(srcDir.type)) {
    516                // Certain cameras right directories more than once.
    517                // This is a bug.
    518                // Ignore second directory of a given type.
    519                continue;
    520            }
    521
    522            final TiffOutputDirectory outputDirectory = srcDir.getOutputDirectory(byteOrder);
    523            result.addDirectory(outputDirectory);
    524        }
    525
    526        return result;
    527    }
    528
    529}