ch.entwine.weblounge.preview.jai.JAIPreviewGenerator.java Source code

Java tutorial

Introduction

Here is the source code for ch.entwine.weblounge.preview.jai.JAIPreviewGenerator.java

Source

/*
 *  Weblounge: Web Content Management System
 *  Copyright (c) 2003 - 2011 The Weblounge Team
 *  http://entwinemedia.com/weblounge
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  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; if not, write to the Free Software Foundation
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

package ch.entwine.weblounge.preview.jai;

import ch.entwine.weblounge.common.content.Resource;
import ch.entwine.weblounge.common.content.ResourceContent;
import ch.entwine.weblounge.common.content.image.ImagePreviewGenerator;
import ch.entwine.weblounge.common.content.image.ImageResource;
import ch.entwine.weblounge.common.content.image.ImageStyle;
import ch.entwine.weblounge.common.impl.content.image.ImageStyleUtils;
import ch.entwine.weblounge.common.language.Language;
import ch.entwine.weblounge.common.site.Environment;
import ch.entwine.weblounge.common.site.ImageScalingMode;

import com.sun.media.jai.codec.FileCacheSeekableStream;
import com.sun.media.jai.codec.SeekableStream;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.RenderingHints;
import java.awt.image.renderable.ParameterBlock;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.imageio.ImageIO;
import javax.media.jai.BorderExtender;
import javax.media.jai.Interpolation;
import javax.media.jai.JAI;
import javax.media.jai.RenderedOp;

/**
 * Utility class used for dealing with images and image styles.
 */
public final class JAIPreviewGenerator implements ImagePreviewGenerator {

    /** The logging facility */
    private static final Logger logger = LoggerFactory.getLogger(JAIPreviewGenerator.class);

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.PreviewGenerator#supports(ch.entwine.weblounge.common.content.Resource)
     */
    public boolean supports(Resource<?> resource) {
        return (resource instanceof ImageResource);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.PreviewGenerator#supports(java.lang.String)
     */
    public boolean supports(String format) {
        if (format == null)
            throw new IllegalArgumentException("Format cannot be null");
        return ImageIO.getImageWritersBySuffix(format.toLowerCase()).hasNext();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.PreviewGenerator#createPreview(ch.entwine.weblounge.common.content.Resource,
     *      ch.entwine.weblounge.common.site.Environment,
     *      ch.entwine.weblounge.common.language.Language,
     *      ch.entwine.weblounge.common.content.image.ImageStyle, String,
     *      java.io.InputStream, java.io.OutputStream)
     */
    public void createPreview(Resource<?> resource, Environment environment, Language language, ImageStyle style,
            String format, InputStream is, OutputStream os) throws IOException {
        if (format == null) {
            if (resource == null)
                throw new IllegalArgumentException("Resource cannot be null");
            if (resource.getContent(language) == null) {
                logger.warn("Skipping creation of preview for {} in language '{}': no content", resource,
                        language.getIdentifier());
                return;
            }
            String mimetype = resource.getContent(language).getMimetype();
            logger.trace("Image preview is generated using the resource's mimetype '{}'", mimetype);
            format = mimetype.substring(mimetype.indexOf("/") + 1);
        }
        style(is, os, format, style);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.image.ImagePreviewGenerator#createPreview(java.io.File,
     *      ch.entwine.weblounge.common.site.Environment,
     *      ch.entwine.weblounge.common.language.Language,
     *      ch.entwine.weblounge.common.content.image.ImageStyle,
     *      java.lang.String, java.io.InputStream, java.io.OutputStream)
     */
    public void createPreview(File imageFile, Environment environment, Language language, ImageStyle style,
            String format, InputStream is, OutputStream os) throws IOException {

        if (format == null) {
            if (imageFile == null)
                throw new IllegalArgumentException("Image file cannot be null");
            format = FilenameUtils.getExtension(imageFile.getName());
            logger.trace("Image preview is generated as '{}'", format);
        }

        style(is, os, format, style);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.PreviewGenerator#getContentType(ch.entwine.weblounge.common.content.Resource,
     *      ch.entwine.weblounge.common.language.Language,
     *      ch.entwine.weblounge.common.content.image.ImageStyle)
     */
    public String getContentType(Resource<?> resource, Language language, ImageStyle style) {
        String mimetype = resource.getContent(language).getMimetype();
        return mimetype;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.PreviewGenerator#getSuffix(ch.entwine.weblounge.common.content.Resource,
     *      ch.entwine.weblounge.common.language.Language,
     *      ch.entwine.weblounge.common.content.image.ImageStyle)
     */
    public String getSuffix(Resource<?> resource, Language language, ImageStyle style) {

        // Load the resource
        ResourceContent content = resource.getContent(language);
        if (content == null) {
            content = resource.getOriginalContent();
            if (content == null) {
                logger.warn("Trying to get filename suffix for {}, which has no content", resource);
                return null;
            }
        }

        // Get the file name
        String filename = content.getFilename();
        if (StringUtils.isBlank(filename)) {
            logger.warn("Trying to get filename suffix for {}, which has no filename", resource);
            return null;
        }

        // Add the file identifier name
        if (StringUtils.isNotBlank(style.getIdentifier())) {
            filename += "-" + style.getIdentifier();
        }

        return FilenameUtils.getExtension(filename);
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.common.content.PreviewGenerator#getPriority()
     */
    public int getPriority() {
        return 0;
    }

    /**
     * Resizes the given image to what is defined by the image style and writes
     * the result to the output stream.
     * 
     * @param is
     *          the input stream
     * @param os
     *          the output stream
     * @param format
     *          the image format
     * @param style
     *          the style
     * @throws IllegalArgumentException
     *           if the image is in an unsupported format
     * @throws IllegalArgumentException
     *           if the input stream is empty
     * @throws IOException
     *           if reading from or writing to the stream fails
     * @throws OutOfMemoryError
     *           if the image is too large to be processed in memory
     */
    private void style(InputStream is, OutputStream os, String format, ImageStyle style)
            throws IllegalArgumentException, IOException, OutOfMemoryError {

        // Does the input stream contain any data?
        if (is.available() == 0)
            throw new IllegalArgumentException("Empty input stream was passed to image styling");

        // Do we need to do any work at all?
        if (style == null || ImageScalingMode.None.equals(style.getScalingMode())) {
            logger.trace("No scaling needed, performing a noop stream copy");
            IOUtils.copy(is, os);
            return;
        }

        SeekableStream seekableInputStream = null;
        RenderedOp image = null;
        try {
            // Load the image from the given input stream
            seekableInputStream = new FileCacheSeekableStream(is);
            image = JAI.create("stream", seekableInputStream);
            if (image == null)
                throw new IOException("Error reading image from input stream");

            // Get the original image size
            int imageWidth = image.getWidth();
            int imageHeight = image.getHeight();

            // Resizing
            float scale = ImageStyleUtils.getScale(imageWidth, imageHeight, style);

            RenderingHints scaleHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            scaleHints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            scaleHints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
            scaleHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

            int scaledWidth = Math.round(scale * image.getWidth());
            int scaledHeight = Math.round(scale * image.getHeight());
            int cropX = 0;
            int cropY = 0;

            // If either one of scaledWidth or scaledHeight is < 1.0, then
            // the scale needs to be adapted to scale to 1.0 exactly and accomplish
            // the rest by cropping.

            if (scaledWidth < 1.0f) {
                scale = 1.0f / imageWidth;
                scaledWidth = 1;
                cropY = imageHeight - scaledHeight;
                scaledHeight = Math.round(imageHeight * scale);
            } else if (scaledHeight < 1.0f) {
                scale = 1.0f / imageHeight;
                scaledHeight = 1;
                cropX = imageWidth - scaledWidth;
                scaledWidth = Math.round(imageWidth * scale);
            }

            if (scale > 1.0) {
                ParameterBlock scaleParams = new ParameterBlock();
                scaleParams.addSource(image);
                scaleParams.add(scale).add(scale).add(0.0f).add(0.0f);
                scaleParams.add(Interpolation.getInstance(Interpolation.INTERP_BICUBIC_2));
                image = JAI.create("scale", scaleParams, scaleHints);
            } else if (scale < 1.0) {
                ParameterBlock subsampleAverageParams = new ParameterBlock();
                subsampleAverageParams.addSource(image);
                subsampleAverageParams.add(Double.valueOf(scale));
                subsampleAverageParams.add(Double.valueOf(scale));
                image = JAI.create("subsampleaverage", subsampleAverageParams, scaleHints);
            }

            // Cropping
            cropX = (int) Math.max(cropX,
                    (float) Math.ceil(ImageStyleUtils.getCropX(scaledWidth, scaledHeight, style)));
            cropY = (int) Math.max(cropY,
                    (float) Math.ceil(ImageStyleUtils.getCropY(scaledWidth, scaledHeight, style)));

            if ((cropX > 0 && Math.floor(cropX / 2.0f) > 0) || (cropY > 0 && Math.floor(cropY / 2.0f) > 0)) {

                ParameterBlock cropTopLeftParams = new ParameterBlock();
                cropTopLeftParams.addSource(image);
                cropTopLeftParams.add(cropX > 0 ? ((float) Math.floor(cropX / 2.0f)) : 0.0f);
                cropTopLeftParams.add(cropY > 0 ? ((float) Math.floor(cropY / 2.0f)) : 0.0f);
                cropTopLeftParams.add(scaledWidth - Math.max(cropX, 0.0f)); // width
                cropTopLeftParams.add(scaledHeight - Math.max(cropY, 0.0f)); // height

                RenderingHints croppingHints = new RenderingHints(JAI.KEY_BORDER_EXTENDER,
                        BorderExtender.createInstance(BorderExtender.BORDER_COPY));

                image = JAI.create("crop", cropTopLeftParams, croppingHints);
            }

            // Write resized/cropped image encoded as JPEG to the output stream
            ParameterBlock encodeParams = new ParameterBlock();
            encodeParams.addSource(image);
            encodeParams.add(os);
            encodeParams.add("jpeg");
            JAI.create("encode", encodeParams);

        } catch (Throwable t) {
            if (t.getClass().getName().contains("ImageFormat")) {
                throw new IllegalArgumentException(t.getMessage());
            }
        } finally {
            IOUtils.closeQuietly(seekableInputStream);
            if (image != null)
                image.dispose();
        }
    }

}