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}