001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.plugins.streetside.io.export;
003
004import java.awt.image.BufferedImage;
005import java.io.BufferedOutputStream;
006import java.io.ByteArrayOutputStream;
007import java.io.FileOutputStream;
008import java.io.IOException;
009import java.io.OutputStream;
010import java.util.concurrent.ArrayBlockingQueue;
011
012import javax.imageio.ImageIO;
013
014import org.apache.commons.imaging.ImageReadException;
015import org.apache.commons.imaging.ImageWriteException;
016import org.apache.commons.imaging.Imaging;
017import org.apache.commons.imaging.common.ImageMetadata;
018import org.apache.commons.imaging.common.RationalNumber;
019import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
020import org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter;
021import org.apache.commons.imaging.formats.tiff.TiffImageMetadata;
022import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants;
023import org.apache.commons.imaging.formats.tiff.constants.GpsTagConstants;
024import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory;
025import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
026
027import org.openstreetmap.josm.gui.progress.ProgressMonitor;
028import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
029import org.openstreetmap.josm.plugins.streetside.StreetsideAbstractImage;
030import org.openstreetmap.josm.plugins.streetside.StreetsideImage;
031import org.openstreetmap.josm.plugins.streetside.StreetsideImportedImage;
032import org.openstreetmap.josm.tools.Logging;
033
034/**
035 * Writes the images from the queue in the file system.
036 *
037 * @author nokutu
038 * @see StreetsideExportManager
039 */
040public class StreetsideExportWriterThread extends Thread {
041
042  private final String path;
043  private final ArrayBlockingQueue<BufferedImage> queue;
044  private final ArrayBlockingQueue<StreetsideAbstractImage> queueImages;
045  private final int amount;
046  private final ProgressMonitor monitor;
047
048  /**
049   * Main constructor.
050   *
051   * @param path
052   *          Path to write the pictures.
053   * @param queue
054   *          Queue of {@link StreetsideAbstractImage} objects.
055   * @param queueImages
056   *          Queue of {@link BufferedImage} objects.
057   * @param amount
058   *          Amount of images that are going to be exported.
059   * @param monitor
060   *          Progress monitor.
061   */
062  public StreetsideExportWriterThread(String path,
063      ArrayBlockingQueue<BufferedImage> queue,
064      ArrayBlockingQueue<StreetsideAbstractImage> queueImages, int amount,
065      ProgressMonitor monitor) {
066    this.path = path;
067    this.queue = queue;
068    this.queueImages = queueImages;
069    this.amount = amount;
070    this.monitor = monitor;
071  }
072
073  @Override
074  public void run() {
075    monitor.setCustomText("Downloaded 0/" + amount);
076    BufferedImage img;
077    StreetsideAbstractImage mimg;
078    String finalPath = "";
079    for (int i = 0; i < amount; i++) {
080      try {
081        img = queue.take();
082        mimg = queueImages.take();
083        if (path == null && mimg instanceof StreetsideImportedImage) {
084          String path = ((StreetsideImportedImage) mimg).getFile().getPath();
085          finalPath = path.substring(0, path.lastIndexOf('.'));
086        } else if (mimg instanceof StreetsideImage) {
087          finalPath = path + '/' + ((StreetsideImage) mimg).getId();
088        } else if (mimg instanceof StreetsideImportedImage) {
089          finalPath = path + '/' + ((StreetsideImportedImage) mimg).getFile().getName();
090        }
091
092        // Transforms the image into a byte array.
093        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
094        ImageIO.write(img, "jpg", outputStream);
095        byte[] imageBytes = outputStream.toByteArray();
096
097        // Write EXIF tags
098        TiffOutputSet outputSet = null;
099        TiffOutputDirectory exifDirectory;
100        TiffOutputDirectory gpsDirectory;
101        // If the image is imported, loads the rest of the EXIF data.
102        if (mimg instanceof StreetsideImportedImage) {
103          final ImageMetadata metadata = Imaging
104              .getMetadata(((StreetsideImportedImage) mimg).getFile());
105          final JpegImageMetadata jpegMetadata = (JpegImageMetadata) metadata;
106          if (null != jpegMetadata) {
107            final TiffImageMetadata exif = jpegMetadata.getExif();
108            if (null != exif) {
109              outputSet = exif.getOutputSet();
110            }
111          }
112        }
113        if (null == outputSet) {
114          outputSet = new TiffOutputSet();
115        }
116        exifDirectory = outputSet.getOrCreateExifDirectory();
117        gpsDirectory = outputSet.getOrCreateGPSDirectory();
118
119        gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF);
120        gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF,
121            GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION_REF_VALUE_TRUE_NORTH);
122
123        gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION);
124        gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_IMG_DIRECTION,
125            RationalNumber.valueOf(mimg.getMovingHe()));
126
127        exifDirectory.removeField(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL);
128        if (mimg instanceof StreetsideImportedImage) {
129          exifDirectory.add(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL,
130              mimg.getDate("yyyy/MM/dd HH:mm:ss"));
131        } else if (mimg instanceof StreetsideImage) {
132          exifDirectory.add(ExifTagConstants.EXIF_TAG_DATE_TIME_ORIGINAL,
133              mimg.getDate("yyyy/MM/dd HH/mm/ss"));
134        }
135        outputSet.setGPSInDegrees(mimg.getMovingLatLon().lon(), mimg.getMovingLatLon().lat());
136        OutputStream os = new BufferedOutputStream(new FileOutputStream(finalPath + ".jpg"));
137        new ExifRewriter().updateExifMetadataLossless(imageBytes, os, outputSet);
138
139        os.close();
140      } catch (InterruptedException e) {
141        Logging.info("Streetside export cancelled");
142        return;
143      } catch (IOException | ImageReadException | ImageWriteException e) {
144        Logging.error(e);
145      }
146
147      // Increases the progress bar.
148      monitor.worked(PleaseWaitProgressMonitor.PROGRESS_BAR_MAX / amount);
149      monitor.setCustomText("Downloaded " + (i + 1) + "/" + amount);
150    }
151  }
152}