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