de.mpg.imeji.logic.storage.util.ImageUtils.java Source code

Java tutorial

Introduction

Here is the source code for de.mpg.imeji.logic.storage.util.ImageUtils.java

Source

/**
 * License: src/main/resources/license/escidoc.license
 */
package de.mpg.imeji.logic.storage.util;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.Arrays;

import javax.imageio.ImageIO;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

import com.sun.media.jai.codec.FileSeekableStream;
import com.sun.media.jai.codec.ImageCodec;
import com.sun.media.jai.codec.ImageDecoder;
import com.sun.media.jai.codec.ImageEncoder;
import com.sun.media.jai.codec.JPEGEncodeParam;
import com.sun.media.jai.codec.PNGDecodeParam;
import com.sun.media.jai.codec.SeekableStream;
import com.sun.media.jai.codec.TIFFDecodeParam;

import de.mpg.imeji.logic.storage.Storage.FileResolution;
import de.mpg.imeji.presentation.util.PropertyReader;

/**
 * Mehtods to help wotk with images
 * 
 * @author saquet (initial creation)
 * @author $Author$ (last modification)
 * @version $Revision$ $LastChangedDate$
 */
public class ImageUtils {
    private static Logger logger = Logger.getLogger(ImageUtils.class);
    /**
     * If true, the rescale will keep the better quality of the images
     */
    private static boolean RESCALE_HIGH_QUALITY = true;

    /**
     * Prepare the image for the upload: <br/>
     * if it is original image upload, do nothing <br/>
     * if it is another resolution, resize it <br/>
     * if it is a tiff to be resized, transformed it to jpeg and resize it
     * 
     * @param stream
     * @param contentCategory
     * @param format
     * @return
     * @throws IOException
     * @throws Exception
     */
    public static byte[] transformImage(byte[] bytes, FileResolution resolution, String mimeType)
            throws IOException, Exception {
        // If it is orginal resolution, don't touch the file, otherwise transform (compress and/or )
        if (!FileResolution.ORIGINAL.equals(resolution)) {
            if (FileResolution.THUMBNAIL.equals(resolution) || StorageUtils.getMimeType("tif").equals(mimeType)) {
                // If it is the thumbnail, compress the images (in jpeg), if it is a tif compress even for WEb
                // resolution, since resizing not possible with tif
                byte[] compressed = compressImage(bytes, mimeType);
                if (!Arrays.equals(compressed, bytes)) {
                    mimeType = StorageUtils.getMimeType("jpg");
                }
                bytes = compressed;
            }
            // Read the bytes as BufferedImage
            BufferedImage image;
            if (StorageUtils.getMimeType("jpg").equals(mimeType)) {
                image = JpegUtils.readJpeg(bytes);
            } else {
                image = ImageIO.read(new ByteArrayInputStream(bytes));
            }
            if (image == null) {
                // The image couldn't be read
                return null;
            }
            // Resize image
            if (StorageUtils.getMimeType("gif").equals(mimeType) && GifUtils.isAnimatedGif(bytes)) {
                // If it is an animated gif, resize all frame and build a new gif with this resized frames
                bytes = GifUtils.resizeAnimatedGif(bytes, resolution);
            } else {
                bytes = toBytes(scaleImage(image, resolution), mimeType);
            }
        }
        return bytes;
    }

    /**
     * Scale a {@link BufferedImage} to new size. Is faster than the basic {@link ImageUtils}.scaleImage method, has the
     * same quality. If it is a thumbnail, cut the images to fit into the raster
     * 
     * @param image original image
     * @param size the size to be resized to
     * @param resolution the type of the image. Might be thumb or web
     * @return the resized images
     * @throws Exception
     */
    public static BufferedImage scaleImageFast(BufferedImage image, int size, FileResolution resolution)
            throws Exception {
        int width = image.getWidth(null);
        int height = image.getHeight(null);
        BufferedImage newImg = null;
        Image rescaledImage;
        if (width > height) {
            if (FileResolution.THUMBNAIL.equals(resolution)) {
                newImg = new BufferedImage(height, height, BufferedImage.TYPE_INT_RGB);
                Graphics g1 = newImg.createGraphics();
                g1.drawImage(image, (height - width) / 2, 0, null);
                if (height > size)
                    rescaledImage = getScaledInstance(newImg, size, size,
                            RenderingHints.VALUE_INTERPOLATION_BILINEAR, RESCALE_HIGH_QUALITY);
                else
                    rescaledImage = newImg;
            } else
                rescaledImage = getScaledInstance(image, size, height * size / width,
                        RenderingHints.VALUE_INTERPOLATION_BILINEAR, RESCALE_HIGH_QUALITY);
        } else {
            if (FileResolution.THUMBNAIL.equals(resolution)) {
                newImg = new BufferedImage(width, width, BufferedImage.TYPE_INT_RGB);
                Graphics g1 = newImg.createGraphics();
                g1.drawImage(image, 0, (width - height) / 2, null);
                if (width > size)
                    rescaledImage = getScaledInstance(newImg, size, size,
                            RenderingHints.VALUE_INTERPOLATION_BILINEAR, RESCALE_HIGH_QUALITY);
                else
                    rescaledImage = newImg;
            } else
                rescaledImage = getScaledInstance(image, width * size / height, size,
                        RenderingHints.VALUE_INTERPOLATION_BILINEAR, RESCALE_HIGH_QUALITY);
        }
        BufferedImage rescaledBufferedImage = new BufferedImage(rescaledImage.getWidth(null),
                rescaledImage.getHeight(null), BufferedImage.TYPE_INT_RGB);
        Graphics g2 = rescaledBufferedImage.getGraphics();
        g2.drawImage(rescaledImage, 0, 0, null);
        return rescaledBufferedImage;
    }

    /**
     * Convenience method that returns a scaled instance of the provided {@link BufferedImage}.
     * 
     * @param img the original image to be scaled
     * @param targetWidth the desired width of the scaled instance, in pixels
     * @param targetHeight the desired height of the scaled instance, in pixels
     * @param hint one of the rendering hints that corresponds to {@code RenderingHints.KEY_INTERPOLATION} (e.g.
     *            {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
     *            {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
     *            {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
     * @param higherQuality if true, this method will use a multi-step scaling technique that provides higher quality
     *            than the usual one-step technique (only useful in downscaling cases, where {@code targetWidth} or
     *            {@code targetHeight} is smaller than the original dimensions, and generally only when the
     *            {@code BILINEAR} hint is specified)
     * @return a scaled version of the original {@link BufferedImage}
     */
    public static BufferedImage getScaledInstance(BufferedImage img, int targetWidth, int targetHeight, Object hint,
            boolean higherQuality) {
        int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB
                : BufferedImage.TYPE_INT_ARGB;
        BufferedImage ret = img;
        int w, h;
        if (higherQuality) {
            // Use multi-step technique: start with original size, then
            // scale down in multiple passes with drawImage()
            // until the target size is reached
            w = img.getWidth();
            h = img.getHeight();
        } else {
            // Use one-step technique: scale directly from original
            // size to target size with a single drawImage() call
            w = targetWidth;
            h = targetHeight;
        }
        do {
            if (higherQuality && w > targetWidth) {
                w /= 2;
                if (w < targetWidth) {
                    w = targetWidth;
                }
            }
            if (higherQuality && h > targetHeight) {
                h /= 2;
                if (h < targetHeight) {
                    h = targetHeight;
                }
            }
            BufferedImage tmp = new BufferedImage(w, h, type);
            Graphics2D g2 = tmp.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
            g2.drawImage(ret, 0, 0, w, h, null);
            g2.dispose();
            ret = tmp;
        } while (w != targetWidth || h != targetHeight);
        return ret;
    }

    /**
     * Transform a tiff image into a jpeg image
     * 
     * @param bytes
     * @return
     */
    public static byte[] tiff2Jpeg(byte[] bytes) {
        try {
            File tiffFile = File.createTempFile("upload", "tif.tmp");
            FileUtils.writeByteArrayToFile(tiffFile, bytes);
            SeekableStream s = new FileSeekableStream(tiffFile);
            TIFFDecodeParam param = new TIFFDecodeParam();
            ImageDecoder dec = ImageCodec.createImageDecoder("tiff", s, param);
            return image2Jpeg(tiffFile, dec);
        } catch (Exception e) {
            throw new RuntimeException("Error transforming tiff to jpeg", e);
        }
    }

    /**
     * Transform a png image into a jpeg image
     * 
     * @param bytes
     * @return
     */
    public static byte[] png2Jpeg(byte[] bytes) {
        try {
            File pngFile = File.createTempFile("upload", "png.tmp");
            FileUtils.writeByteArrayToFile(pngFile, bytes);
            SeekableStream s = new FileSeekableStream(pngFile);
            PNGDecodeParam param = new PNGDecodeParam();
            ImageDecoder dec = ImageCodec.createImageDecoder("png", s, param);
            return image2Jpeg(pngFile, dec);
        } catch (Exception e) {
            throw new RuntimeException("Error transforming png to jpeg", e);
        }
    }

    /**
     * Transform a bmp image into a jpeg image
     * 
     * @param bytes
     * @return
     */
    public static byte[] bmp2Jpeg(byte[] bytes) {
        return image2Jpeg(bytes);
    }

    /**
     * Transform a gif image to a jpeg image
     * 
     * @param bytes
     * @return
     */
    public static byte[] gif2Jpeg(byte[] bytes) {
        return image2Jpeg(bytes);
    }

    /**
     * Transform a image to a jpeg image. The input image must have a format supported by {@link ImageIO}
     * 
     * @param bytes
     * @return
     */
    private static byte[] image2Jpeg(byte[] bytes) {
        try {
            InputStream ins = new ByteArrayInputStream(bytes);
            BufferedImage image = ImageIO.read(ins);
            ByteArrayOutputStream ous = new ByteArrayOutputStream();
            ImageIO.write(image, "jpg", ous);
            return ous.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException("Error transforming image to jpeg", e);
        }
    }

    /**
     * Transform a image {@link File} to a jpeg file
     * 
     * @param f - the {@link File} where the image is
     * @param dec - The {@link ImageDecoder} needed to decode the passed image
     * @return the image as {@link Byte} array
     */
    private static byte[] image2Jpeg(File f, ImageDecoder dec) {
        try {
            RenderedImage ri = dec.decodeAsRenderedImage(0);
            File jpgFile = File.createTempFile("imeji_upload", "jpg.tmp");
            FileOutputStream fos = new FileOutputStream(jpgFile);
            JPEGEncodeParam jParam = new JPEGEncodeParam();
            jParam.setQuality(1.0f);
            ImageEncoder imageEncoder = ImageCodec.createImageEncoder("JPEG", fos, jParam);
            imageEncoder.encode(ri);
            fos.flush();
            return FileUtils.readFileToByteArray(jpgFile);
        } catch (Exception e) {
            throw new RuntimeException("Error transforming image file to jpeg", e);
        }
    }

    /**
     * Return the format of an image (jpg, tif), according to its mime-type
     * 
     * @param mimeType
     * @return
     */
    public static String getImageFormat(String mimeType) {
        if (mimeType.equals(StorageUtils.getMimeType("tif"))) {
            return "tif";
        }
        return mimeType.toLowerCase().replaceAll("image/", "");
    }

    /**
     * Compress an image in jpeg. Useful to reduce size of thumbnail and web resolution images
     * 
     * @param bytes
     * @param mimeType
     * @return
     */
    public static byte[] compressImage(byte[] bytes, String mimeType) {
        try {
            if (mimeType.equals(StorageUtils.getMimeType("tif"))) {
                bytes = ImageUtils.tiff2Jpeg(bytes);
            } else if (mimeType.equals(StorageUtils.getMimeType("png"))) {
                bytes = ImageUtils.png2Jpeg(bytes);
            } else if (mimeType.equals(StorageUtils.getMimeType("bmp"))) {
                bytes = ImageUtils.bmp2Jpeg(bytes);
            } else if (mimeType.equals(StorageUtils.getMimeType("gif"))) {
                bytes = GifUtils.toJPEG(bytes);
            }
        } catch (Exception e) {
            logger.info("Image could not be compressed: ", e);
        }
        return bytes;
    }

    /**
     * TRansform a {@link BufferedImage} to a {@link Byte} array
     * 
     * @param image
     * @param mimeType
     * @return
     * @throws IOException
     */
    public static byte[] toBytes(BufferedImage image, String mimeType) throws IOException {
        ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
        // use imageIO.write to encode the image back into a byte[]
        ImageIO.write(image, ImageUtils.getImageFormat(mimeType), byteOutput);
        return byteOutput.toByteArray();
    }

    /**
     * cale the image if too big for the size
     * 
     * @param image
     * @param resolution
     * @return
     * @throws Exception
     */
    public static BufferedImage scaleImage(BufferedImage image, FileResolution resolution) throws Exception {
        BufferedImage bufferedImage = null;
        int size = getResolution(resolution);
        if (image.getWidth() > size || image.getHeight() > size) {
            bufferedImage = scaleImageFast(image, size, resolution);
        } else {
            bufferedImage = image;
        }
        return bufferedImage;
    }

    /**
     * Return the maximum size of an image according to its {@link FileResolution}. The values are defined in the
     * properties
     * 
     * @param FileResolution
     * @return
     * @throws IOException
     * @throws URISyntaxException
     */
    private static int getResolution(FileResolution resolution) throws IOException, URISyntaxException {
        switch (resolution) {
        case THUMBNAIL:
            return Integer.parseInt(PropertyReader.getProperty("xsd.resolution.thumbnail"));
        case WEB:
            return Integer.parseInt(PropertyReader.getProperty("xsd.resolution.web"));
        default:
            return 0;
        }
    }

    public static String getThumb() throws IOException, URISyntaxException {
        return PropertyReader.getProperty("xsd.metadata.content-category.thumbnail");
    }

    public static String getWeb() throws IOException, URISyntaxException {
        return PropertyReader.getProperty("xsd.metadata.content-category.web-resolution");
    }

    public static String getOrig() throws IOException, URISyntaxException {
        return PropertyReader.getProperty("xsd.metadata.content-category.original-resolution");
    }
}