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