All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sourceforge.openutils.mgnlmedia.media.utils.ImageUtils Maven / Gradle / Ivy

/**
 *
 * SimpleMedia Module for Magnolia CMS (http://www.openmindlab.com/lab/products/media.html)
 * Copyright(C) 2008-2012, Openmind S.r.l. http://www.openmindonline.it
 *
 *  This program 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.
 *
 *  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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see .
 */

package net.sourceforge.openutils.mgnlmedia.media.utils;

import info.magnolia.cms.beans.runtime.FileProperties;
import info.magnolia.cms.core.Content;
import info.magnolia.cms.core.HierarchyManager;
import info.magnolia.cms.core.NodeData;
import info.magnolia.context.Context;
import info.magnolia.context.MgnlContext;
import info.magnolia.context.MgnlContext.SystemContextOperation;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.color.CMMException;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TimeZone;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.ImageOutputStream;
import javax.jcr.Node;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;

import net.sourceforge.openutils.mgnlmedia.media.configuration.ImageProcessorsManager;
import net.sourceforge.openutils.mgnlmedia.media.configuration.MediaConfigurationManager;
import net.sourceforge.openutils.mgnlmedia.media.configuration.MediaTypeConfiguration;
import net.sourceforge.openutils.mgnlmedia.media.lifecycle.MediaModule;
import net.sourceforge.openutils.mgnlmedia.media.processors.ImagePostProcessor;
import net.sourceforge.openutils.mgnlmedia.media.tags.el.MediaEl;
import net.sourceforge.openutils.mgnlmedia.media.types.impl.BaseTypeHandler;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.jackrabbit.util.Locked;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Main utility class that works with images and media nodes
 * @author molaschi
 * @version $Id: ImageUtils.java 3958 2012-05-06 12:20:23Z fgiust $
 */
public final class ImageUtils
{

    /**
     * Size for the "preview" resolution.
     */
    private static final String RESOLUTION_PREVIEW_SIZE = "l450x350";

    /**
     * Size for the "thumbnail" resolution.
     */
    private static final String RESOLUTION_THUMBNAIL_SIZE = "l100x100";

    /**
     * Name for the "preview" resolution.
     */
    private static final String RESOLUTION_PREVIEW = "preview";

    /**
     * Name for the "thumbnail" resolution.
     */
    private static final String RESOLUTION_THUMBNAIL = "thumbnail";

    /**
     * Logger.
     */
    private static Logger log = LoggerFactory.getLogger(ImageUtils.class);

    private static SimpleDateFormat sdf;

    /**
     * Nodedata name where resolution is saved
     */
    public static String RESOLUTION_PROPERTY = "resolution";

    private static final String[] extensions = new String[]{"jpg", "jpeg", "gif", "png", "ico" };

    private static int currentWorkingThreads = 0;

    static
    {
        sdf = new SimpleDateFormat();
        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
    }

    private ImageUtils()
    {
    }

    private static int getType(ColorModel cm)
    {
        if (cm.getTransparency() == ColorModel.BITMASK)
        {
            if (cm.isAlphaPremultiplied())
            {
                return BufferedImage.TYPE_4BYTE_ABGR_PRE;
            }
            else
            {
                return BufferedImage.TYPE_4BYTE_ABGR;
            }
        }
        if (cm.getTransparency() == ColorModel.TRANSLUCENT)
        {
            if (cm.isAlphaPremultiplied())
            {
                return BufferedImage.TYPE_INT_ARGB_PRE;
            }
            else
            {
                return BufferedImage.TYPE_INT_ARGB;
            }
        }

        if (cm.getPixelSize() == 8)
        {
            return BufferedImage.TYPE_3BYTE_BGR;
        }
        return BufferedImage.TYPE_INT_RGB;
    }

    /**
     * Resize an image to x,y
     * @param original original image
     * @param x new width
     * @param y new height
     * @return resized image
     */
    public static BufferedImage resizeImage(BufferedImage original, int x, int y)
    {
        return resizeImage(original, x, y, false);
    }

    public static BufferedImage resizeImage(BufferedImage original, int x, int y, boolean skipRendering)
    {
        return resizeImage(original, x, y, x, y, null, skipRendering);
    }

    /**
     * Resize an image to x,y
     * @param original original image
     * @param x new width
     * @param y new height
     * @param canvasX canvas width
     * @param canvasY canvas height
     * @param background background color
     * @return resized image
     */
    public static BufferedImage resizeImage(BufferedImage original, int x, int y, int canvasX, int canvasY,
        Color background)
    {
        return resizeImage(original, x, y, canvasX, canvasY, background, false);
    }

    public static BufferedImage resizeImage(BufferedImage original, int x, int y, int canvasX, int canvasY,
        Color background, boolean skipRendering)
    {
        if (x <= 0)
        {
            x = 1;
            // throw new IllegalArgumentException("x=" + x + " (must be >0)");
        }
        if (y <= 0)
        {
            y = 1;
            // throw new IllegalArgumentException("y=" + y + " (must be >0)");
        }
        if (canvasX <= 0)
        {
            canvasX = 1;
            // throw new IllegalArgumentException("canvasX=" + canvasX + " (must be >0)");
        }
        if (canvasY <= 0)
        {
            canvasY = 1;
            // throw new IllegalArgumentException("canvasY=" + canvasY + " (must be >0)");
        }

        BufferedImage resizedImage;
        try
        {
            resizedImage = new BufferedImage(canvasX, canvasY, getType(original.getColorModel()));
        }
        catch (NegativeArraySizeException e)
        {
            throw new RuntimeException("NegativeArraySizeException caught when resizing image to ["
                + canvasX
                + ", "
                + canvasY
                + "]");
        }

        if (!skipRendering)
        {
            Graphics2D graphics2D = resizedImage.createGraphics();

            graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            if (canvasX > x || canvasY > y)
            {
                graphics2D.clearRect(0, 0, canvasX, canvasY);

                if (background == null && original.getColorModel().getTransparency() == Transparency.OPAQUE)
                {
                    background = Color.WHITE;
                }
                // fill bands
                if (background != null)
                {
                    graphics2D.setColor(background);

                    if (canvasX > x)
                    {
                        graphics2D.fillRect(0, 0, (canvasX - x) / 2, canvasY);
                        graphics2D.fillRect(x + (canvasX - x) / 2, 0, canvasX, canvasY);
                    }
                    if (canvasY > y)
                    {
                        graphics2D.fillRect(0, 0, canvasX, (canvasY - y) / 2);
                        graphics2D.fillRect(0, y + (canvasY - y) / 2, canvasX, canvasY);
                    }
                }
            }

            graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);

            AffineTransform at = new AffineTransform();
            double delta = ((double) x) / original.getWidth();
            if (x > original.getWidth())
            {
                at.scale(delta, delta);
                at.translate((canvasX - x) / (2 * delta), (canvasY - y) / (2 * delta));
            }
            else if (x < original.getWidth())
            {
                original = getScaledInstance(original, x, y, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true, false);
                at.translate((canvasX - x) / 2.0, (canvasY - y) / 2.0);
            }

            graphics2D.drawImage(original, at, null);
            graphics2D.dispose();
        }

        return resizedImage;
    }

    /**
     * Convenience method that returns a scaled instance of the provided {@code 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 {@code BufferedImage}
     */
    public static BufferedImage getScaledInstance(BufferedImage img, int targetWidth, int targetHeight, Object hint,
        boolean higherQuality)
    {
        return getScaledInstance(img, targetWidth, targetHeight, hint, higherQuality, false);
    }

    public static BufferedImage getScaledInstance(BufferedImage img, int targetWidth, int targetHeight, Object hint,
        boolean higherQuality, boolean skipRendering)
    {
        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);
            if (!skipRendering)
            {
                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;
    }

    /**
     * Crop an image from left, top for width, height
     * @param original original image
     * @param left start crop point left
     * @param top start crop point top
     * @param width crop width
     * @param height crop height
     * @return resized image
     */
    public static BufferedImage cropImage(BufferedImage original, int left, int top, int width, int height)
    {
        return cropImage(original, left, top, width, height, false);
    }

    public static BufferedImage cropImage(BufferedImage original, int left, int top, int width, int height, boolean skipRendering)
    {
        BufferedImage resizedImage = new BufferedImage(width, height, getType(original.getColorModel()));
        if (!skipRendering)
        {
            Graphics2D graphics2D = resizedImage.createGraphics();
            graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
            graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_DEFAULT);

            AffineTransform at = new AffineTransform();
            at.translate(-left, -top);
            graphics2D.drawImage(original, at, null);
            graphics2D.dispose();
        }

        return resizedImage;
    }

    /**
     * Create rounded corners on image
     * @param original original image
     * @param backgroundColor optional background color
     * @param radius corners radius
     * @return image with rounded corners
     */
    public static BufferedImage addRoundedCorners(BufferedImage original, Color backgroundColor, int radius)
    {
        return addRoundedCorners(original, backgroundColor, radius, false);
    }

    public static BufferedImage addRoundedCorners(BufferedImage original, Color backgroundColor, int radius, boolean skipRendering)
    {
        int originalImageType = getType(original.getColorModel());
        int roundedCornersImageType = BufferedImage.TYPE_4BYTE_ABGR;

        if (originalImageType != BufferedImage.TYPE_4BYTE_ABGR)
        {
            if (originalImageType != BufferedImage.TYPE_4BYTE_ABGR_PRE)
            {
                // the image has not alpha channel so fill background
                if (backgroundColor == null)
                {
                    backgroundColor = Color.WHITE;
                }
            }
            else
            {
                roundedCornersImageType = BufferedImage.TYPE_4BYTE_ABGR_PRE;
            }
        }

        // use a 4 byte image type to create antialiased rounded corners
        BufferedImage roundedImage;
        try
        {
            roundedImage = new BufferedImage(original.getWidth(), original.getHeight(), roundedCornersImageType);
        }
        catch (NegativeArraySizeException e)
        {
            throw new RuntimeException("NegativeArraySizeException caught when adding rounded corners");
        }

        if (!skipRendering)
        {
            Graphics2D roundedGraphics2D = roundedImage.createGraphics();
            roundedGraphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            roundedGraphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

            roundedGraphics2D.setColor(Color.WHITE);
            roundedGraphics2D.fillRoundRect(0, 0, original.getWidth(), original.getHeight(), radius, radius);
            roundedGraphics2D.setComposite(AlphaComposite.SrcIn);
            roundedGraphics2D.drawImage(original, 0, 0, original.getWidth(), original.getHeight(), null);

            if (backgroundColor != null)
            {

                BufferedImage destImage;
                try
                {
                    destImage = new BufferedImage(original.getWidth(), original.getHeight(), originalImageType);
                }
                catch (NegativeArraySizeException e)
                {
                    throw new RuntimeException("NegativeArraySizeException caught when resizing image]");
                }
                // draw new image
                Graphics2D destImageGraphics2D = destImage.createGraphics();
                destImageGraphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                destImageGraphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);

                destImageGraphics2D.setBackground(backgroundColor);
                destImageGraphics2D.clearRect(0, 0, original.getWidth(), original.getHeight());
                // destImageGraphics2D.setComposite(AlphaComposite.DstIn);
                destImageGraphics2D.drawImage(roundedImage, 0, 0, original.getWidth(), original.getHeight(), null);
                return destImage;
            }
        }

        return roundedImage;
    }

    /**
     * Save a resolution for an image to a node (in resolutions/res-[width]x[height]/data.jpg)
     * @param image image to save
     * @param saveTo node to save to
     * @param extension extension
     * @param quality image quality
     * @param forceProgressive true to force progressive mode
     * @throws RepositoryException exception in jcr operations
     * @throws IOException exception converting image to jpg
     */
    public static NodeData saveResolution(BufferedImage image, Content saveTo, String extension, float quality,
        boolean forceProgressive) throws RepositoryException, IOException
    {
        return saveResolution(image, saveTo, null, extension, quality, forceProgressive);
    }

    /**
     * Save a resolution for an image to a node (in resolutions/[name]/data.jpg)
     * @param image image to save
     * @param saveTo node to save to
     * @param name name for this resolution
     * @param extension extension
     * @param quality image quality
     * @param forceProgressive true to force progressive mode
     * @throws RepositoryException exception in jcr operations
     * @throws IOException exception converting image to jpg
     */
    public static NodeData saveResolution(final BufferedImage image, final Content saveTo, final String name,
        final String extension, final float quality, final boolean forceProgressive) throws RepositoryException,
        IOException
    {

        Content resolutions = getResolutionsNode(saveTo);
        if (resolutions == null)
        {
            resolutions = saveTo.createContent("resolutions", MediaConfigurationManager.RESOLUTIONS);
            resolutions.getMetaData().setModificationDate();
            saveTo.save();
        }

        String resolution = name;
        if (resolution == null)
        {
            resolution = "res-" + image.getWidth() + "x" + image.getHeight();
        }

        final String resolutionNodeName = getResolutionPath(resolution);
        final Content resolutionsFinal = resolutions;

        Node resolutionsJcrNode = resolutions.getJCRNode();

        Object ret;
        try
        {
            ret = new Locked()
            {

                @Override
                protected Object run(Node resolutionsJcrNode) throws RepositoryException
                {
                    NodeData ndtemp;
                    boolean existing = false;
                    if (resolutionsFinal.hasNodeData(resolutionNodeName))
                    {
                        ndtemp = resolutionsFinal.getNodeData(resolutionNodeName);
                        existing = true;
                    }
                    else
                    {
                        // don't remove deprecated method call, needed for magnolia 4.0 compatibility
                        ndtemp = resolutionsFinal.createNodeData(resolutionNodeName, PropertyType.BINARY);
                    }
                    log.debug("setting value to {}", ndtemp.getHandle());

                    final NodeData nd = ndtemp;
                    final PipedInputStream stream = new PipedInputStream();
                    PipedOutputStream outputstream = null;
                    Thread t = null;
                    long count = 0;

                    try
                    {
                        outputstream = new PipedOutputStream(stream);
                        t = new Thread(new Runnable()
                        {

                            /**
                             * {@inheritDoc}
                             */
                            public void run()
                            {
                                try
                                {
                                    nd.setValue(stream);
                                }
                                catch (RepositoryException e)
                                {
                                    log.error(e.getMessage(), e);
                                }
                            }

                        });
                        t.start();

                        count = getStream(image, extension, quality, forceProgressive, outputstream);
                    }
                    catch (IOException e)
                    {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                    IOUtils.closeQuietly(outputstream);
                    try
                    {
                        t.join();
                    }
                    catch (InterruptedException e)
                    {
                        log.warn(e.getMessage(), e);
                    }

                    IOUtils.closeQuietly(stream);

                    String mimetype = "image/" + extension;
                    if ("jpg".equals(extension))
                    {
                        mimetype = "image/jpeg";
                    }
                    nd.setAttribute(ImageUtils.RESOLUTION_PROPERTY, resolutionNodeName);
                    nd.setAttribute(FileProperties.PROPERTY_EXTENSION, extension);
                    nd.setAttribute(FileProperties.PROPERTY_FILENAME, saveTo.getName());
                    nd.setAttribute(FileProperties.PROPERTY_CONTENTTYPE, mimetype);
                    nd.setAttribute(
                        FileProperties.PROPERTY_LASTMODIFIED,
                        GregorianCalendar.getInstance(TimeZone.getDefault()));
                    nd.setAttribute(FileProperties.PROPERTY_WIDTH, "" + image.getWidth());
                    nd.setAttribute(FileProperties.PROPERTY_HEIGHT, "" + image.getHeight());

                    nd.setAttribute(FileProperties.PROPERTY_SIZE, "" + count);

                    if (existing)
                    {
                        nd.save();
                    }
                    else
                    {
                        resolutionsFinal.save();
                    }

                    return nd;
                }
            }.with(resolutionsJcrNode, false, 5000);
        }
        catch (InterruptedException e)
        {
            MgnlContext.getHierarchyManager("media").refresh(false);
            ret = Locked.TIMED_OUT;
        }
        if (ret == Locked.TIMED_OUT)
        {
            // do whatever you think is appropriate in this case
            return null;
        }
        else
        {
            // get the value
            return (NodeData) ret;
        }

    }

    /**
     * Get resolution nodedata name for a given resolution string
     * @param resolution resolution string
     * @return resolution nodedata name
     */
    public static String getResolutionPath(String resolution)
    {
        if (resolution.indexOf(';') > 0)
        {
            return StringUtils.substringBefore(resolution, ";")
                + "-p"
                + StringUtils.substringAfter(resolution, ";").hashCode();
        }
        return resolution;
    }

    /**
     * Get an inputstream for an image and the target file extension
     * @param image image to get the inputstream from
     * @param extension target file extension
     * @param quality image quality
     * @param forceProgressive true if image has to be saved as progressive mode
     * @return byte count
     * @throws IOException
     */
    public static long getStream(BufferedImage image, String extension, float quality, boolean forceProgressive,
        OutputStream outputstream) throws IOException
    {

        CountBytesBufferedOutputStream out = new CountBytesBufferedOutputStream(outputstream);

        try
        {
            if (extension.equals("jpg"))
            {
                JPEGImageWriteParam iwparam = new JPEGImageWriteParam(null);

                if (quality != 1.0f)
                {
                    iwparam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                    iwparam.setCompressionType("JPEG");
                    iwparam.setCompressionQuality(quality);
                }
                else
                {
                    iwparam.setCompressionMode(ImageWriteParam.MODE_COPY_FROM_METADATA);
                }

                ImageWriter iw = ImageIO.getImageWritersByFormatName("jpg").next();
                ImageOutputStream ios = ImageIO.createImageOutputStream(out);
                iw.setOutput(ios);
                iw.write(null, new IIOImage(image, null, null), iwparam);
                iw.dispose();
                ios.close();

                image.flush();
            }
            else
            {

                Iterator writers;
                ImageOutputStream imageOutputStream;
                ImageWriteParam params;
                ImageWriter imageWriter;

                String outputextension = extension;
                if (StringUtils.equals(extension, "ico") || StringUtils.equals(extension, "gif"))
                {
                    // gif and ico are remapped to png
                    outputextension = "png";
                }

                // don't use "ico" as output format
                writers = ImageIO.getImageWritersBySuffix(outputextension);

                if (writers != null && writers.hasNext())
                {
                    // Fetch the first writer in the list
                    imageWriter = writers.next();

                    // Specify the parameters according to those the output file will be written

                    // Get Default parameters
                    params = imageWriter.getDefaultWriteParam();

                    try
                    {

                        String[] compressionTypes = params.getCompressionTypes();

                        if (compressionTypes != null && compressionTypes.length > 0)
                        {
                            // Define compression mode
                            params.setCompressionMode(javax.imageio.ImageWriteParam.MODE_EXPLICIT);

                            params.setCompressionType(compressionTypes[0]);

                            // Define compression quality
                            params.setCompressionQuality(quality);
                        }
                        else
                        {
                            params.setCompressionMode(javax.imageio.ImageWriteParam.MODE_COPY_FROM_METADATA);
                        }

                        // Define progressive mode
                        if (forceProgressive)
                        {
                            params.setProgressiveMode(javax.imageio.ImageWriteParam.MODE_DEFAULT);
                        }
                        else
                        {
                            params.setProgressiveMode(javax.imageio.ImageWriteParam.MODE_DISABLED);
                        }
                    }
                    catch (UnsupportedOperationException e)
                    {
                        // go on
                    }

                    // Define destination type - used the ColorModel and SampleModel of the Input Image
                    params.setDestinationType(new ImageTypeSpecifier(image.getColorModel(), image.getSampleModel()));

                    imageOutputStream = ImageIO.createImageOutputStream(out);
                    imageWriter.setOutput(imageOutputStream);

                    IIOImage iioimage = new IIOImage(image, null, null);

                    try
                    {
                        // Write the changed Image
                        imageWriter.write(null, iioimage, params);
                    }
                    catch (NullPointerException e)
                    {
                        // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6287936
                        // GIF writer is buggy for totally transparent gifs

                        // workaround: draw a point!
                        image.createGraphics().drawRect(0, 0, 1, 1);
                        imageWriter.write(null, iioimage, params);

                    }
                    finally
                    {
                        // Close the streams
                        imageOutputStream.close();
                        imageWriter.dispose();
                    }
                }
                else
                {
                    ImageIO.write(image, outputextension, out);
                }
            }
        }
        catch (IOException ex)
        {
            log.error("Error writing image to buffer", ex);
            throw ex;
        }

        return out.count;
    }

    /**
     * Check if the resolution for a media is already present. Otherwise create it
     * @param media media to check the resolutoin on
     * @param resolutionTarget target resolution
     * @return false if resolution doesn't exist and there is a problem in generate it; true otherwise
     */
    public static boolean checkOrCreateResolution(final Content media, final String resolutionTarget)
    {

        return checkOrCreateResolution(media, resolutionTarget, BaseTypeHandler.ORGINAL_NODEDATA_NAME);
    }

    /**
     * Check if the resolution for a media is already present. Otherwise create it
     * @param media media to check the resolution on
     * @param resolutionTarget target resolution
     * @param nodeDataName nodedata where the image to resize is stored
     * @return false if resolution doesn't exist and there is a problem in generate it; true otherwise
     */
    public static boolean checkOrCreateResolution(final Content media, final String resolutionTarget,
        String nodeDataName)
    {
        return checkOrCreateResolution(media, resolutionTarget, nodeDataName, false);
    }

    public static boolean checkOrCreateResolution(final Content media, final String resolutionTarget,
        String nodeDataName, final boolean lazy)
    {

        Content resolutions = getResolutionsNode(media);

        String resolution = resolutionTarget;

        if (!RESOLUTION_THUMBNAIL.equals(resolutionTarget) && !RESOLUTION_PREVIEW.equals(resolutionTarget))
        {
            resolution = "res-" + resolutionTarget;
        }
        try
        {
            if (resolutions != null && resolutions.hasNodeData(getResolutionPath(resolution)))
            {
                if (lazy)
                {
                    return true;
                }
                else if (!StringUtils.isEmpty(resolutions.getNodeData(getResolutionPath(resolution)).getAttribute(
                    "resolutionNotYetCreated")))
                {
                    // continue: replace the fake image with the actual one
                }
                else
                {
                    return true;
                }
            }
        }
        catch (RepositoryException e1)
        {
            // go on with res calculation
        }

        String type = media.getNodeData("type").getString();
        if (StringUtils.equals(type, "other"))
        {
            return false;
        }

        if (nodeDataName == null)
        {

            MediaTypeConfiguration mtc = MediaConfigurationManager.getInstance().getMediaTypeConfigurationFromMedia(
                media);
            if (mtc == null)
            {
                nodeDataName = BaseTypeHandler.ORGINAL_NODEDATA_NAME;
            }
            else
            {
                nodeDataName = mtc.getHandler().getPreviewImageNodeDataName();
            }
        }

        try
        {
            if (!media.hasNodeData(nodeDataName))
            {
                return false;
            }
        }
        catch (RepositoryException e2)
        {
            log.warn(e2.getMessage(), e2);
        }

        final String originalNodeDataName = nodeDataName;

        try
        {
            ImageUtils.doInSystemContext(new MgnlContext.SystemContextOperation()
            {

                /**
                 * {@inheritDoc}
                 */
                public void exec()
                {
                    long timestart = System.currentTimeMillis();

                    HierarchyManager hm = MgnlContext.getHierarchyManager(MediaModule.REPO);
                    String resolutionstring = resolutionTarget;

                    if (RESOLUTION_THUMBNAIL.equals(resolutionstring))
                    {
                        resolutionstring = RESOLUTION_THUMBNAIL_SIZE;
                    }
                    if (RESOLUTION_PREVIEW.equals(resolutionstring))
                    {
                        resolutionstring = RESOLUTION_PREVIEW_SIZE;
                    }

                    String resolutioNodeName = "res-" + resolutionstring;
                    if (RESOLUTION_THUMBNAIL.equals(resolutionTarget) || RESOLUTION_PREVIEW.equals(resolutionTarget))
                    {
                        resolutioNodeName = resolutionTarget;
                    }

                    Content node;
                    try
                    {
                        node = hm.getContent(media.getHandle());
                    }
                    catch (RepositoryException e)
                    {
                        throw new RuntimeException(e);
                    }

                    NodeData image = node.getNodeData(originalNodeDataName);

                    if (image.getContentLength() == 0)
                    {
                        throw new ZeroSizeImageException(image.getHandle());
                    }

                    String outputextension = image.getAttribute(FileProperties.PROPERTY_EXTENSION);
                    if (!Arrays.asList(extensions).contains(outputextension))
                    {
                        outputextension = "jpg";
                    }

                    BufferedImage original = null;
                    BufferedImage img = null;
                    Map params = parseParameters(resolutionstring);

                    if (lazy)
                    {
                        params.put("skipRendering", "true");
                    }

                    synchronized (MediaEl.module().getLocks().nextLock())
                    {
                        currentWorkingThreads++;
                        if (log.isDebugEnabled())
                        {
                            log.debug("Current working resizing thread: {}", currentWorkingThreads);
                        }
                        original = createBufferedImage(image);

                        try
                        {
                            img = ImageUtils.getImageForResolution(original, resolutionstring, params);
                        }
                        catch (IllegalArgumentException e)
                        {
                            throw new RuntimeException(e);
                        }
                        finally
                        {
                            currentWorkingThreads--;
                        }
                    }

                    try
                    {
                        float quality = 0.8F;
                        if (StringUtils.isNotEmpty(params.get("quality")))
                        {
                            try
                            {
                                quality = NumberUtils.toFloat(params.get("quality"));
                                if (quality > 1.0F)
                                {
                                    quality = 1.0F;
                                }
                            }
                            catch (NumberFormatException ex)
                            {
                                log.error("quality parameter must be a float number but was {}", params.get("quality"));
                            }
                        }
                        boolean forceProgressive = false;
                        if (StringUtils.isNotEmpty(params.get("progressive")))
                        {
                            forceProgressive = true;
                        }

                        NodeData nd = ImageUtils.saveResolution(
                            img,
                            node,
                            resolutioNodeName,
                            outputextension,
                            quality,
                            forceProgressive);
                        if (lazy)
                        {
                            nd.setAttribute(
                                "resolutionNotYetCreated",
                                StringUtils.removeStart(resolutioNodeName, "res-"));
                            nd.getParent().save();
                        }
                    }
                    catch (RepositoryException e)
                    {
                        throw new RuntimeException(e);
                    }
                    catch (IOException e)
                    {
                        throw new RuntimeException(e);
                    }

                    try
                    {
                        hm.save();
                    }
                    catch (RepositoryException e)
                    {
                        throw new RuntimeException(e);
                    }

                    log.info("Generated {} for {} in {} milliseconds", new Object[]{
                        resolutioNodeName,
                        node.getHandle(),
                        System.currentTimeMillis() - timestart });
                }

            });
        }
        catch (BadImageFormatException e)
        {
            if (e.getCause() != null)
            {
                log.warn(e.getMessage(), e.getCause());
            }
            else
            {
                log.warn("Unable to extract a valid image from " + media.getHandle() + " (no message)");
            }

            try
            {
                media.setNodeData("bad_image_marker", media.getNodeData("bad_image_marker").getLong() + 1);
                media.save();
            }
            catch (RepositoryException e1)
            {
                // ignore
            }
            return false;
        }
        catch (ZeroSizeImageException ex)
        {
            log.error(ex.getMessage());
            return false;
        }
        catch (RuntimeException ex)
        {
            log.error(ClassUtils.getShortClassName(ex.getClass())
                + " checking resolution for "
                + media.getHandle()
                + ": "
                + ex.getMessage(), ex);
            return false;
        }

        return true;
    }

    /**
     * Returns the "resolutions" node, checking for existence
     * @param media
     * @return
     */
    protected static Content getResolutionsNode(final Content media)
    {
        Content resolutions = null;

        try
        {
            if (media.hasContent("resolutions"))
            {
                resolutions = media.getContent("resolutions");
            }
        }
        catch (RepositoryException e)
        {
            // ignore, try to create it
        }
        return resolutions;
    }

    private static Map parseParameters(String resolution)
    {

        Map params = new HashMap();
        if (StringUtils.contains(resolution, ";"))
        {
            String parameters = StringUtils.substringAfter(resolution, ";");
            resolution = StringUtils.substringBefore(resolution, ";");

            String tokens[] = StringUtils.split(parameters, ',');
            for (String token : tokens)
            {
                if (token.indexOf("=") > 0)
                {
                    String[] keyvalue = StringUtils.split(token, '=');
                    params.put(keyvalue[0], keyvalue[1]);
                }
                else
                {
                    params.put(token, "true");
                }
            }
        }
        return params;
    }

    /**
     * Get image for a resolution
     * @param original original image
     * @param resolution resolution
     * @param params parameters
     * @return new image
     */
    public static BufferedImage getImageForResolution(BufferedImage original, String resolution,
        Map params)
    {

        if (original == null)
        {
            throw new IllegalArgumentException("input image is null");
        }
        if (resolution == null || resolution.length() < 1)
        {
            throw new IllegalArgumentException("Invalid resolution: " + resolution);
        }

        BufferedImage img = null;

        resolution = StringUtils.lowerCase(resolution);

        char controlChar = resolution.charAt(0);

        Point size = parseForSize(resolution);

        if (ImageProcessorsManager.getInstance().isValidControlChar(controlChar))
        {
            img = ImageProcessorsManager
                .getInstance()
                .getImageResolutionProcessor(controlChar)
                .getImageForResolution(original, size.x, size.y, params);
        }
        else if (controlChar < '0' || controlChar > '9')
        {
            throw new IllegalArgumentException("Invalid control char: " + controlChar);
        }
        else
        {
            img = ImageProcessorsManager
                .getInstance()
                .getDefaultImageResolutionProcessor()
                .getImageForResolution(original, size.x, size.y, params);
        }

        for (ImagePostProcessor ipp : ImageProcessorsManager.getInstance().getImagePostProcessorsList())
        {
            img = ipp.processImage(img, size.x, size.y, params);
        }

        return img;
    }

    /**
     * Get file extension for a resolution stored in a media node
     * @param media media
     * @param resolution resolution
     * @return file extension for a resolution stored in a media node
     */
    public static String getExtension(Content media, String resolution)
    {
        try
        {
            Content resolutions = media.getContent("resolutions");
            NodeData res = resolutions.getNodeData(resolution);
            return res.getAttribute(FileProperties.PROPERTY_EXTENSION);
        }
        catch (RepositoryException ex)
        {
            return "jpg";
        }
    }

    /**
     * Create a buffered image from the binary data stored in nodedata
     * @param image nodedata
     * @return buffered image from the binary data stored in nodedata
     */
    public static BufferedImage createBufferedImage(NodeData image)
    {
        InputStream is = image.getStream();

        String ext = image.getAttribute(FileProperties.EXTENSION);

        log.debug("processing {}, extension {}", image.getHandle(), ext);

        try
        {

            if (StringUtils.equalsIgnoreCase(ext, "ico") || StringUtils.equalsIgnoreCase(ext, "bmp"))
            {
                BufferedImage buffered = IcoUtils.createBufferedImage(image);
                if (buffered != null)
                {
                    return buffered;
                }
            }

            BufferedImage result = ImageIO.read(is);

            // yes, ImageIO can return null without throwing any exception
            if (result == null)
            {
                throw new BadImageFormatException("Unable to handle " + image.getHandle() + " (no message)");
            }

            return result;

        }
        catch (IOException e)
        {
            // CMYK jpeg can't be read be ImageIO
            // Caused by: javax.imageio.IIOException: Unsupported Image Type
            BufferedImage result = JpegUtils.processNonStandardImage(image);

            if (result == null)
            {
                // throw the original exception back
                throw new BadImageFormatException("Unable to handle " + image.getHandle(), e);
            }
            return result;

        }
        catch (CMMException e)
        {
            // CMMException is thrown for non-standard jpegs?
            // Invalid image format
            // java.awt.color.CMMException: Invalid image format
            // at sun.awt.color.CMM.checkStatus(CMM.java:131)
            // at sun.awt.color.ICC_Transform.(ICC_Transform.java:89)
            // at java.awt.image.ColorConvertOp.filter(ColorConvertOp.java:516)
            // at com.sun.imageio.plugins.jpeg.JPEGImageReader.acceptPixels(JPEGImageReader.java:1169)
            BufferedImage result = JpegUtils.processNonStandardImage(image);

            if (result == null)
            {
                // throw the original exception back
                throw new BadImageFormatException("Unable to handle " + image.getHandle(), e);
            }
            return result;

        }
        catch (IllegalArgumentException e)
        {
            // java.lang.IllegalArgumentException: Numbers of source Raster bands and source color space components do
            // not match
            // at java.awt.image.ColorConvertOp.filter(ColorConvertOp.java:460)
            // at com.sun.imageio.plugins.jpeg.JPEGImageReader.acceptPixels(JPEGImageReader.java:1169)
            // at com.sun.imageio.plugins.jpeg.JPEGImageReader.readImage(Native Method)
            // at com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(JPEGImageReader.java:1137)
            // at com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:948)
            BufferedImage result = JpegUtils.processNonStandardImage(image);

            if (result == null)
            {
                // throw the original exception back
                throw new BadImageFormatException("Unable to handle " + image.getHandle(), e);
            }
            return result;

        }

        finally
        {
            IOUtils.closeQuietly(is);
        }
    }

    private static String normalizeResolutionString(String res)
    {

        String resolution = StringUtils.lowerCase(res);

        if (RESOLUTION_THUMBNAIL.equals(resolution))
        {
            return RESOLUTION_THUMBNAIL_SIZE;
        }
        if (RESOLUTION_PREVIEW.equals(resolution))
        {
            return RESOLUTION_PREVIEW_SIZE;
        }

        return resolution;
    }

    /**
     * Parse resolution string for required sizesuper.read(b);
     * @param res resolution string
     * @return required size parsed from given resolution string
     */
    public static java.awt.Point parseForSize(String res)
    {
        Point size = new Point();
        String resolution = normalizeResolutionString(res);

        if (StringUtils.contains(resolution, ";"))
        {
            resolution = StringUtils.substringBefore(resolution, ";");
        }

        if (ImageProcessorsManager.getInstance().isValidControlChar(resolution.charAt(0))
            || resolution.charAt(0) < '0'
            || resolution.charAt(0) > '9')
        {
            resolution = resolution.substring(1);
        }

        String[] resXY = StringUtils.split(resolution, "x");

        size.x = NumberUtils.toInt(resXY[0]);
        size.y = NumberUtils.toInt(resXY[1]);
        return size;
    }

    private static void doInSystemContext(SystemContextOperation op)
    {
        final Context originalCtx = MgnlContext.hasInstance() ? MgnlContext.getInstance() : null;
        try
        {
            MgnlContext.setInstance(MgnlContext.getSystemContext());
            op.exec();
        }
        finally
        {
            MgnlContext.setInstance(originalCtx);
        }
    }

    static class CountBytesBufferedOutputStream extends BufferedOutputStream
    {

        private int count = 0;

        /**
         * @param in
         * @param size
         */
        public CountBytesBufferedOutputStream(OutputStream out, int size)
        {
            super(out, size);
            count = 0;
        }

        /**
         * @param in
         */
        public CountBytesBufferedOutputStream(OutputStream in)
        {
            super(in);
            count = 0;
        }

        /**
         * Returns the count.
         * @return the count
         */
        public int getCount()
        {
            return count;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public synchronized void write(byte[] b, int off, int len) throws IOException
        {
            count += len;
            super.write(b, off, len);
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy