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