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}