    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}