    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.write;
    018
    019import static org.apache.commons.imaging.formats.tiff.constants.TiffConstants.DEFAULT_TIFF_BYTE_ORDER;
    020
    021import java.nio.ByteOrder;
    022import java.util.ArrayList;
    023import java.util.List;
    024
    025import org.apache.commons.imaging.ImageWriteException;
    026import org.apache.commons.imaging.common.RationalNumber;
    027import org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants;
    028import org.apache.commons.imaging.formats.tiff.constants.TiffDirectoryConstants;
    029import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
    030import org.apache.commons.imaging.internal.Debug;
    031
    032public final class TiffOutputSet {
    033    public final ByteOrder byteOrder;
    034    private final List<TiffOutputDirectory> directories = new ArrayList<>();
    035    private static final String NEWLINE = System.getProperty("line.separator");
    036
    037    public TiffOutputSet() {
    038        this(DEFAULT_TIFF_BYTE_ORDER);
    039    }
    040
    041    public TiffOutputSet(final ByteOrder byteOrder) {
    042        this.byteOrder = byteOrder;
    043    }
    044
    045    protected List<TiffOutputItem> getOutputItems(
    046            final TiffOutputSummary outputSummary) throws ImageWriteException {
    047        final List<TiffOutputItem> result = new ArrayList<>();
    048
    049        for (final TiffOutputDirectory directory : directories) {
    050            result.addAll(directory.getOutputItems(outputSummary));
    051        }
    052
    053        return result;
    054    }
    055
    056    public void addDirectory(final TiffOutputDirectory directory)
    057            throws ImageWriteException {
    058        if (null != findDirectory(directory.type)) {
    059            throw new ImageWriteException(
    060                    "Output set already contains a directory of that type.");
    061        }
    062        directories.add(directory);
    063    }
    064
    065    public List<TiffOutputDirectory> getDirectories() {
    066        return new ArrayList<>(directories);
    067    }
    068
    069    public TiffOutputDirectory getRootDirectory() {
    070        return findDirectory(TiffDirectoryConstants.DIRECTORY_TYPE_ROOT);
    071    }
    072
    073    public TiffOutputDirectory getExifDirectory() {
    074        return findDirectory(TiffDirectoryConstants.DIRECTORY_TYPE_EXIF);
    075    }
    076
    077    public TiffOutputDirectory getOrCreateRootDirectory()
    078            throws ImageWriteException {
    079        final TiffOutputDirectory result = findDirectory(TiffDirectoryConstants.DIRECTORY_TYPE_ROOT);
    080        if (null != result) {
    081            return result;
    082        }
    083        return addRootDirectory();
    084    }
    085
    086    public TiffOutputDirectory getOrCreateExifDirectory()
    087            throws ImageWriteException {
    088        // EXIF directory requires root directory.
    089        getOrCreateRootDirectory();
    090
    091        final TiffOutputDirectory result = findDirectory(TiffDirectoryConstants.DIRECTORY_TYPE_EXIF);
    092        if (null != result) {
    093            return result;
    094        }
    095        return addExifDirectory();
    096    }
    097
    098    public TiffOutputDirectory getOrCreateGPSDirectory()
    099            throws ImageWriteException {
    100        // GPS directory requires EXIF directory
    101        getOrCreateExifDirectory();
    102
    103        final TiffOutputDirectory result = findDirectory(TiffDirectoryConstants.DIRECTORY_TYPE_GPS);
    104        if (null != result) {
    105            return result;
    106        }
    107        return addGPSDirectory();
    108    }
    109
    110    public TiffOutputDirectory getGPSDirectory() {
    111        return findDirectory(TiffDirectoryConstants.DIRECTORY_TYPE_GPS);
    112    }
    113
    114    public TiffOutputDirectory getInteroperabilityDirectory() {
    115        return findDirectory(TiffDirectoryConstants.DIRECTORY_TYPE_INTEROPERABILITY);
    116    }
    117
    118    public TiffOutputDirectory findDirectory(final int directoryType) {
    119        for (final TiffOutputDirectory directory : directories) {
    120            if (directory.type == directoryType) {
    121                return directory;
    122            }
    123        }
    124        return null;
    125    }
    126
    127    /**
    128     * A convenience method to update GPS values in EXIF metadata.
    129     *
    130     * @param longitude
    131     *            Longitude in degrees E, negative values are W.
    132     * @param latitude
    133     *            latitude in degrees N, negative values are S.
    134     * @throws ImageWriteException if it fails to write the new data to the GPS directory
    135     */
    136    public void setGPSInDegrees(double longitude, double latitude)
    137            throws ImageWriteException {
    138        final TiffOutputDirectory gpsDirectory = getOrCreateGPSDirectory();
    139
    140        gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_VERSION_ID);
    141        gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_VERSION_ID, GpsTagConstants.gpsVersion());
    142
    143        final String longitudeRef = longitude < 0 ? "W" : "E";
    144        longitude = Math.abs(longitude);
    145        final String latitudeRef = latitude < 0 ? "S" : "N";
    146        latitude = Math.abs(latitude);
    147
    148        gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_LONGITUDE_REF);
    149        gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_LONGITUDE_REF,
    150                longitudeRef);
    151
    152        gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_LATITUDE_REF);
    153        gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_LATITUDE_REF,
    154                latitudeRef);
    155
    156        {
    157            double value = longitude;
    158            final double longitudeDegrees = (long) value;
    159            value %= 1;
    160            value *= 60.0;
    161            final double longitudeMinutes = (long) value;
    162            value %= 1;
    163            value *= 60.0;
    164            final double longitudeSeconds = value;
    165
    166            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_LONGITUDE);
    167            gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_LONGITUDE,
    168                            RationalNumber.valueOf(longitudeDegrees),
    169                            RationalNumber.valueOf(longitudeMinutes),
    170                            RationalNumber.valueOf(longitudeSeconds));
    171        }
    172
    173        {
    174            double value = latitude;
    175            final double latitudeDegrees = (long) value;
    176            value %= 1;
    177            value *= 60.0;
    178            final double latitudeMinutes = (long) value;
    179            value %= 1;
    180            value *= 60.0;
    181            final double latitudeSeconds = value;
    182
    183            gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_LATITUDE);
    184            gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_LATITUDE,
    185                    RationalNumber.valueOf(latitudeDegrees),
    186                    RationalNumber.valueOf(latitudeMinutes),
    187                    RationalNumber.valueOf(latitudeSeconds));
    188        }
    189
    190    }
    191
    192    public void removeField(final TagInfo tagInfo) {
    193        removeField(tagInfo.tag);
    194    }
    195
    196    public void removeField(final int tag) {
    197        for (final TiffOutputDirectory directory : directories) {
    198            directory.removeField(tag);
    199        }
    200    }
    201
    202    public TiffOutputField findField(final TagInfo tagInfo) {
    203        return findField(tagInfo.tag);
    204    }
    205
    206    public TiffOutputField findField(final int tag) {
    207        for (final TiffOutputDirectory directory : directories) {
    208            final TiffOutputField field = directory.findField(tag);
    209            if (null != field) {
    210                return field;
    211            }
    212        }
    213        return null;
    214    }
    215
    216    public TiffOutputDirectory addRootDirectory() throws ImageWriteException {
    217        final TiffOutputDirectory result = new TiffOutputDirectory(
    218                TiffDirectoryConstants.DIRECTORY_TYPE_ROOT, byteOrder);
    219        addDirectory(result);
    220        return result;
    221    }
    222
    223    public TiffOutputDirectory addExifDirectory() throws ImageWriteException {
    224        final TiffOutputDirectory result = new TiffOutputDirectory(
    225                TiffDirectoryConstants.DIRECTORY_TYPE_EXIF, byteOrder);
    226        addDirectory(result);
    227        return result;
    228    }
    229
    230    public TiffOutputDirectory addGPSDirectory() throws ImageWriteException {
    231        final TiffOutputDirectory result = new TiffOutputDirectory(
    232                TiffDirectoryConstants.DIRECTORY_TYPE_GPS, byteOrder);
    233        addDirectory(result);
    234        return result;
    235    }
    236
    237    public TiffOutputDirectory addInteroperabilityDirectory()
    238            throws ImageWriteException {
    239        getOrCreateExifDirectory();
    240
    241        final TiffOutputDirectory result = new TiffOutputDirectory(
    242                TiffDirectoryConstants.DIRECTORY_TYPE_INTEROPERABILITY, byteOrder);
    243        addDirectory(result);
    244        return result;
    245    }
    246
    247    @Override
    248    public String toString() {
    249        return toString(null);
    250    }
    251
    252    public String toString(String prefix) {
    253        if (prefix == null) {
    254            prefix = "";
    255        }
    256
    257        final StringBuilder result = new StringBuilder(39);
    258
    259        result.append(prefix);
    260        result.append("TiffOutputSet {");
    261        result.append(NEWLINE);
    262
    263        result.append(prefix);
    264        result.append("byteOrder: ");
    265        result.append(byteOrder);
    266        result.append(NEWLINE);
    267
    268        for (int i = 0; i < directories.size(); i++) {
    269            final TiffOutputDirectory directory = directories.get(i);
    270            result.append(String.format("%s\tdirectory %d: %s (%d)%n",
    271                    prefix, i, directory.description(), directory.type));
    272
    273            final List<TiffOutputField> fields = directory.getFields();
    274            for (final TiffOutputField field : fields) {
    275                result.append(prefix);
    276                result.append("\t\tfield ").append(i).append(": ").append(field.tagInfo);
    277                result.append(NEWLINE);
    278            }
    279        }
    280        result.append(prefix);
    281
    282        result.append('}');
    283        result.append(NEWLINE);
    284
    285        return result.toString();
    286    }
    287
    288    public void dump() {
    289        Debug.debug(this.toString());
    290    }
    291
    292}