org.jamwiki.parser.image.ImageProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.jamwiki.parser.image.ImageProcessor.java

Source

/**
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, version 2.1, dated February 1999.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the latest version of the GNU Lesser General
 * Public License as published by the Free Software Foundation;
 *
 * This program 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program (LICENSE.txt); if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package org.jamwiki.parser.image;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.jamwiki.WikiBase;
import org.jamwiki.DataAccessException;
import org.jamwiki.model.ImageData;
import org.jamwiki.utils.WikiLogger;
import org.jamwiki.utils.WikiUtil;

/**
 * Utility methods that wrap native Java image processing functionality to allow
 * functionality such as resizing, reading, saving and otherwise performing
 * common image operations.
 */
public class ImageProcessor {

    private static final WikiLogger logger = WikiLogger.getLogger(ImageProcessor.class.getName());

    static {
        // manually set the ImageIO temp directory so that systems with incorrect defaults won't fail
        // when processing images.
        File directory = WikiUtil.getTempDirectory();
        if (directory.exists()) {
            ImageIO.setCacheDirectory(directory);
        }
    }

    /**
     *
     */
    private ImageProcessor() {
    }

    /**
     * Given a file that corresponds to an existing image, return a
     * BufferedImage object.
     */
    private static BufferedImage loadImage(File file) throws IOException {
        if (!file.exists()) {
            throw new FileNotFoundException("File does not exist: " + file.getAbsolutePath());
        }
        // use a FileInputStream and make sure it gets closed to prevent unclosed file
        // errors on some operating systems
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);
            BufferedImage image = ImageIO.read(fis);
            if (image == null) {
                throw new IOException("JDK is unable to process image file, possibly indicating file corruption: "
                        + file.getAbsolutePath());
            }
            return image;
        } finally {
            IOUtils.closeQuietly(fis);
        }
    }

    /**
     * When storing images in the database, given a fileId return a ImageData object.
     *
     * @param fileId The file identifier for the original image to be loaded.
     * @param fileVersionId The ID of the image revision being loaded, or -1 if
     *  the current image revision is being loaded.
     */
    private static ImageData loadImage(int fileId, int fileRevisionId) throws IOException {
        ImageData imageData = null;
        try {
            if (fileRevisionId != -1) {
                imageData = WikiBase.getDataHandler().getImageVersionData(fileRevisionId, 0);
            } else {
                imageData = WikiBase.getDataHandler().getImageData(fileId, 0);
            }
        } catch (DataAccessException dae) {
            throw new IOException("Failure while retrieving image data for file " + fileId + ": " + dae.toString());
        }
        if (imageData == null) {
            throw new FileNotFoundException("Image does not exist: " + fileId);
        }
        return imageData;
    }

    /**
     * Method for resizing images when images are stored on the filesystem.
     *
     * Convenience method that returns a scaled instance of the provided image.
     * This method never resizes by more than 50% since resizing by more than that
     * amount causes quality issues with the BICUBIC and BILINEAR algorithms.
     *
     * Based on examples from the GraphicsUtilities sample from the book "Filthy
     * Rich Clients" by Chet Haase and Romain Guy (http://filthyrichclients.org/).
     * That source is dual licensed: LGPL (Sun and Romain Guy) and BSD (Romain Guy).
     *
     * @param imageFile The file path for 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.
     * @return a scaled version of the original {@code BufferedImage}
     */
    public static BufferedImage resizeImage(File imageFile, int targetWidth, int targetHeight) throws IOException {
        long start = System.currentTimeMillis();
        BufferedImage resized = ImageProcessor.loadImage(imageFile);
        resized = ImageProcessor.resizeImage(resized, targetWidth, targetHeight);
        if (logger.isDebugEnabled()) {
            long current = System.currentTimeMillis();
            String message = "Image resize time (" + ((current - start) / 1000.000) + " s), dimensions: "
                    + targetWidth + "x" + targetHeight + " for file: " + imageFile.getAbsolutePath();
            logger.debug(message);
        }
        return resized;
    }

    /**
     * Method for resizing images when images are stored in the database.
     *
     * Convenience method that returns a scaled instance of the provided image.
     * This method never resizes by more than 50% since resizing by more than that
     * amount causes quality issues with the BICUBIC and BILINEAR algorithms.
     *
     * Based on examples from the GraphicsUtilities sample from the book "Filthy
     * Rich Clients" by Chet Haase and Romain Guy (http://filthyrichclients.org/).
     * That source is dual licensed: LGPL (Sun and Romain Guy) and BSD (Romain Guy).
     *
     * @param fileId The file identifier for the original image to be scaled.
     * @param fileVersionId The ID of the image revision being resized, or -1 if
     *  the current image revision is being resized.
     * @param targetWidth the desired width of the scaled instance in pixels.
     * @param targetHeight the desired height of the scaled instance in pixels.
     * @return a dimensions of scaled image
     */
    public static Dimension resizeImage(int fileId, int fileVersionId, int targetWidth, int targetHeight)
            throws IOException {
        long start = System.currentTimeMillis();
        ImageData imageData = ImageProcessor.loadImage(fileId, fileVersionId);
        BufferedImage tmp = ImageIO.read(new ByteArrayInputStream(imageData.data));
        if (tmp == null) {
            throw new IOException(
                    "JDK is unable to process image data, possibly indicating data corruption: " + fileId);
        }
        BufferedImage resized = ImageProcessor.resizeImage(tmp, targetWidth, targetHeight);
        int pos = imageData.mimeType.lastIndexOf('/');
        if (pos == -1 || (pos + 1) >= imageData.mimeType.length()) {
            throw new IOException("Unknown image file type " + imageData.mimeType);
        }
        String imageType = imageData.mimeType.substring(pos + 1).toLowerCase();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        boolean result = ImageIO.write(resized, imageType, baos);
        if (!result) {
            throw new IOException("No appropriate writer found when writing image: " + imageData.fileVersionId);
        }
        baos.close();
        imageData.width = resized.getWidth();
        imageData.height = resized.getHeight();
        imageData.data = baos.toByteArray();
        saveImage(imageData);
        if (logger.isDebugEnabled()) {
            long current = System.currentTimeMillis();
            String message = "Image resize time (" + ((current - start) / 1000.000) + " s), dimensions: "
                    + targetWidth + "x" + targetHeight + " for fileId: " + fileId;
            logger.debug(message);
        }
        return new Dimension(imageData.width, imageData.height);
    }

    /**
     *
     */
    private static BufferedImage resizeImage(BufferedImage tmp, int targetWidth, int targetHeight)
            throws IOException {
        int type = (tmp.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB
                : BufferedImage.TYPE_INT_ARGB;
        int width = tmp.getWidth();
        int height = tmp.getHeight();
        BufferedImage resized = tmp;
        do {
            width /= 2;
            if (width < targetWidth) {
                width = targetWidth;
            }
            height /= 2;
            if (height < targetHeight) {
                height = targetHeight;
            }
            tmp = new BufferedImage(width, height, type);
            Graphics2D g2 = tmp.createGraphics();
            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
            g2.drawImage(resized, 0, 0, width, height, null);
            g2.dispose();
            resized = tmp;
        } while (width != targetWidth || height != targetHeight);
        return resized;
    }

    /**
     * Retrieve image dimensions.  This method simply reads headers so it should perform
     * relatively fast.
     */
    protected static Dimension retrieveImageDimensions(File imageFile) throws IOException {
        long start = System.currentTimeMillis();
        if (!imageFile.exists()) {
            logger.info("No file found while determining image dimensions: " + imageFile.getAbsolutePath());
            return null;
        }
        ImageInputStream iis = null;
        Dimension dimensions = null;
        ImageReader reader = null;
        // use a FileInputStream and make sure it gets closed to prevent unclosed file
        // errors on some operating systems
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(imageFile);
            iis = ImageIO.createImageInputStream(fis);
            Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
            if (readers.hasNext()) {
                reader = readers.next();
                reader.setInput(iis, true);
                dimensions = new Dimension(reader.getWidth(0), reader.getHeight(0));
            }
        } finally {
            if (reader != null) {
                reader.dispose();
            }
            if (iis != null) {
                try {
                    iis.close();
                } catch (IOException e) {
                    // ignore
                }
            }
            IOUtils.closeQuietly(fis);
        }
        if (logger.isDebugEnabled()) {
            long execution = (System.currentTimeMillis() - start);
            logger.debug("Image dimension lookup for " + imageFile.getAbsolutePath() + " took "
                    + (execution / 1000.000) + " s");
        }
        return dimensions;
    }

    /**
     * Retrieve image dimensions for an image stored in the database.
     *
     * @param fileId The image file ID.
     * @param fileVersionId The image revision ID, or -1 to retrieve the
     *  current image version.
     * @param resized The size in pixels of the image to retrieve.
     */
    protected static Dimension retrieveImageDimensions(int fileId, int fileVersionId, int resized)
            throws IOException {
        ImageData imageData = null;
        try {
            if (fileVersionId != -1) {
                imageData = WikiBase.getDataHandler().getImageVersionData(fileVersionId, resized);
            } else {
                imageData = WikiBase.getDataHandler().getImageInfo(fileId, resized);
            }
        } catch (DataAccessException dae) {
            throw new IOException("Failure while retrieving image info for file " + fileId + ": " + dae.toString());
        }
        if (imageData == null || imageData.width < 0) {
            return null;
        }
        return new Dimension(imageData.width, imageData.height);
    }

    /**
     * Save an image to a specified file.
     */
    protected static void saveImage(BufferedImage image, File file) throws IOException {
        String filename = file.getName();
        int pos = filename.lastIndexOf('.');
        if (pos == -1 || (pos + 1) >= filename.length()) {
            throw new IOException("Unknown image file type " + filename);
        }
        String imageType = filename.substring(pos + 1);
        File imageFile = new File(file.getParent(), filename);
        // use a FileOutputStream and make sure it gets closed to prevent unclosed file
        // errors on some operating systems
        FileOutputStream fos = null;
        try {
            // use the FileUtils utility method to ensure parent directories are created
            // if necessary
            fos = FileUtils.openOutputStream(imageFile);
            boolean result = ImageIO.write(image, imageType, fos);
            if (!result) {
                throw new IOException("No appropriate writer found when writing image: " + filename);
            }
        } finally {
            IOUtils.closeQuietly(fos);
        }
    }

    /**
     * Save an image.
     */
    private static void saveImage(ImageData imageData) throws IOException {
        try {
            WikiBase.getDataHandler().insertImage(imageData, true);
        } catch (DataAccessException dae) {
            //FIXME
            //throw new IOException(dae);
            logger.warn(dae.toString());
        }
    }
}