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}