de.brazzy.nikki.model.ImageReader.java Source code

Java tutorial

Introduction

Here is the source code for de.brazzy.nikki.model.ImageReader.java

Source

package de.brazzy.nikki.model;

/*   
 *   Copyright 2010 Michael Borgwardt
 *   Part of the Nikki Photo GPS diary:  http://www.brazzy.de/nikki
 *
 *  Nikki is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Nikki is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Nikki.  If not, see <http://www.gnu.org/licenses/>.
 */

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;

import javax.imageio.ImageIO;
import javax.swing.border.EtchedBorder;

import mediautil.gen.Rational;
import mediautil.image.jpeg.Entry;
import mediautil.image.jpeg.Exif;
import mediautil.image.jpeg.LLJTran;
import mediautil.image.jpeg.LLJTranException;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;

import com.mortennobel.imagescaling.ResampleOp;
import com.mortennobel.imagescaling.ThumpnailRescaleOp;

import de.brazzy.nikki.util.ImageDataIO;
import de.brazzy.nikki.util.Texts;

/**
 * Extracts all data from an image file
 * 
 * @author Michael Borgwardt
 */
public class ImageReader extends ImageDataIO {

    private static final int THUMBNAIL_SIZE = 180;
    private static byte[] errorIcon;
    static {
        try {
            errorIcon = IOUtils.toByteArray(ImageReader.class.getResourceAsStream("noimage.jpg"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private DateTimeZone scanZone;
    private Rotation rotation;
    private BufferedImage mainImage;
    private Boolean thumbnailNew;

    /**
     * @param file
     *            to read from
     * @param zone
     *            to use when image does not contain time zone in EXIF
     * @throws LLJTranException
     */
    public ImageReader(File file, DateTimeZone zone) {
        super(file, LLJTran.READ_INFO);
        this.file = file;
        this.scanZone = zone;
    }

    /**
     * @return Image with all fields filled from file data
     */
    public Image createImage() {
        Image image = new Image();
        image.setFileName(file.getName());

        try {
            if (createException != null) {
                throw createException;
            }
            image.setThumbnail(getThumbnail());
            image.setWaypoint(getWaypoint());
            image.setTitle(getTitle());
            image.setDescription(getDescription());
            image.setExport(isExport());
            image.setTime(getTime());
            image.setModified(thumbnailNew.booleanValue());
            llj.freeMemory();
        } catch (Throwable e) {
            Logger.getLogger(getClass()).error("Error reading image " + file.getName(), e);
            image.setDescription(Texts.ERROR_PREFIX + e.getMessage());
            image.setThumbnail(errorIcon);
        }

        return image;
    }

    private Rotation getRotation() {
        if (rotation != null) {
            return rotation;
        }
        if (exifData != null) {
            // see http://sylvana.net/jpegcrop/exif_orientation.html
            switch (exifData.getOrientation()) {
            case 8:
                return Rotation.LEFT;
            case 3:
                return Rotation.ROT180D;
            case 6:
                return Rotation.RIGHT;
            }
        }
        return Rotation.NONE;
    }

    /**
     * @return thumbnail, scaled down from image data if not found in EXIF,
     *         auto-rotated if rotation is known
     */
    public byte[] getThumbnail() throws Exception {
        if (exifData != null) {
            byte[] thumb = exifData.getThumbnailBytes();
            if (thumb != null && thumb.length > 0) {
                thumbnailNew = Boolean.FALSE;
                return adjustForRotation(thumb);
            }
        }
        thumbnailNew = Boolean.TRUE;
        return scale(THUMBNAIL_SIZE, false, true);
    }

    private byte[] adjustForRotation(byte[] result) throws LLJTranException, IOException {
        if (getRotation() != null && getRotation() != Rotation.NONE) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            LLJTran llj = new LLJTran(new ByteArrayInputStream(result));
            llj.read(true);
            llj.transform(out, getRotation().getLLJTranConstant(), 0);
            return out.toByteArray();
        } else {
            return result;
        }
    }

    public DateTime getTime() throws ParseException {
        DateTime time = null;
        if (exifData != null) {
            String date = exifData.getDataTimeOriginalString();
            if (date != null) {
                DateTimeZone zone = getTimeZone();
                if (zone == null) {
                    return null;
                }
                time = TIME_FORMAT.withZone(zone).parseDateTime(date.substring(0, 19));
            }
        }
        return time;
    }

    private String getUTF8(int tagName) {
        if (nikkiIFD == null) {
            return null;
        }
        Entry entry = nikkiIFD.getEntry(tagName, 0);
        if (entry == null) {
            return null;
        }

        Object[] values = entry.getValues();
        byte[] bytes = new byte[values.length];
        for (int i = 0; i < values.length; i++) {
            bytes[i] = (byte) ((Integer) values[i]).intValue();
        }

        try {
            return new String(bytes, "UTF-8");
        } catch (UnsupportedEncodingException ex) {
            throw new IllegalStateException("Can't happen", ex);
        }
    }

    public String getTitle() {
        return getUTF8(ENTRY_TITLE_INDEX);
    }

    public String getDescription() {
        return getUTF8(ENTRY_DESCRIPTION_INDEX);
    }

    public boolean isExport() {
        if (nikkiIFD == null) {
            return false;
        }
        Integer export = (Integer) nikkiIFD.getEntry(ENTRY_EXPORT_INDEX, 0).getValue(0);
        return export != null && export != 0;
    }

    /**
     * @return {@link Boolean#FALSE} when a thumbnail was found in EXIF,
     *         {@link Boolean#FALSE} when it was computed by scaling down image,
     *         null when not yet requested
     */
    public Boolean isThumbnailNew() {
        return thumbnailNew;
    }

    public Waypoint getWaypoint() throws ParseException {
        if (gpsIFD == null) {
            return null;
        }

        Waypoint result = new Waypoint();
        result.setTimestamp(getTime());

        Entry latRef = gpsIFD.getEntry(Exif.GPSLatitudeRef, 0);
        Entry lat = gpsIFD.getEntry(Exif.GPSLatitude, 0);
        Entry lonRef = gpsIFD.getEntry(Exif.GPSLongitudeRef, 0);
        Entry lon = gpsIFD.getEntry(Exif.GPSLongitude, 0);

        if (latRef != null && lat != null) {
            GeoCoordinate c = new GeoCoordinate();
            c.setDirection(Cardinal.parse((String) latRef.getValue(0)));
            c.setMagnitude(readGpsMagnitude(lat));
            result.setLatitude(c);
        }
        if (lonRef != null && lon != null) {
            GeoCoordinate c = new GeoCoordinate();
            c.setDirection(Cardinal.parse((String) lonRef.getValue(0)));
            c.setMagnitude(readGpsMagnitude(lon));
            result.setLongitude(c);
        }

        return result;
    }

    /**
     * Reads a GPS coordinate from EXIF, possible consisting of separate arc
     * degree, minute and second values
     */
    public static float readGpsMagnitude(Entry e) {
        Object[] values = e.getValues();
        float result = ((Rational) values[0]).floatValue();
        if (values.length > 1) {
            result += ((Rational) values[1]).floatValue() / 60.0;
        }
        if (values.length > 2) {
            result += ((Rational) values[2]).floatValue() / (60.0 * 60.0);
        }
        return result;
    }

    /**
     * @return time zone read from EXIF if present, default passed into
     *         constructor otherwise
     */
    public DateTimeZone getTimeZone() {
        if (nikkiIFD == null) {
            return this.scanZone;
        }

        Entry entry = nikkiIFD.getEntry(ENTRY_TIMEZONE_INDEX, 0);
        if (entry != null && entry.getValue(0) != null) {
            String zoneID = (String) entry.getValue(0);
            return DateTimeZone.forID(zoneID);
        } else {
            return this.scanZone;
        }
    }

    public void readMainImage() throws IOException {
        if (mainImage == null) {
            mainImage = ImageIO.read(file);
        }
    }

    /**
     * Scales image to given size, preserving aspec ratio
     * 
     * @param toWidth
     *            width to scale to
     * @param paintBorder
     *            whether to paint an etched border around the image
     * @param isThumbnail
     *            if true, faster low-quality scaling will be used
     */
    public byte[] scale(int toWidth, boolean paintBorder, boolean isThumbnail)
            throws IOException, LLJTranException {
        readMainImage();

        int toHeight = heightForWidth(mainImage, toWidth);

        BufferedImageOp op = isThumbnail ? new ThumpnailRescaleOp(toWidth, toHeight)
                : new ResampleOp(toWidth, toHeight);

        BufferedImage scaledImage = op.filter(mainImage, null);
        if (paintBorder) {
            Graphics2D g = scaledImage.createGraphics();
            g.setPaintMode();
            new EtchedBorder(EtchedBorder.RAISED, Color.LIGHT_GRAY, Color.DARK_GRAY).paintBorder(null, g, 0, 0,
                    toWidth, toHeight);
        }

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ImageIO.write(scaledImage, "jpg", out);
        return adjustForRotation(out.toByteArray());
    }

    private static int heightForWidth(java.awt.Image img, int width) {
        return (int) (img.getHeight(null) / (double) img.getWidth(null) * width);
    }

}