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