lucee.runtime.img.Image.java Source code

Java tutorial

Introduction

Here is the source code for lucee.runtime.img.Image.java

Source

/**
 *
 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
 *
 * This library 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.1 of the License, or (at your option) any later version.
 * 
 * This library 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 library.  If not, see <http://www.gnu.org/licenses/>.
 * 
 **/
package lucee.runtime.img;

import java.awt.AWTException;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.QuadCurve2D;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.PackedColorModel;
import java.awt.image.PixelGrabber;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.ParameterBlock;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.text.AttributedString;
import java.util.Iterator;
import java.util.Locale;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import javax.media.jai.BorderExtender;
import javax.media.jai.BorderExtenderConstant;
import javax.media.jai.Interpolation;
import javax.media.jai.JAI;
import javax.media.jai.LookupTableJAI;
import javax.media.jai.operator.ShearDir;
import javax.media.jai.operator.TransposeType;
import javax.swing.ImageIcon;

import lucee.commons.io.IOUtil;
import lucee.commons.io.res.Resource;
import lucee.commons.lang.CFTypes;
import lucee.commons.lang.StringUtil;
import lucee.commons.lang.font.FontUtil;
import lucee.runtime.PageContext;
import lucee.runtime.dump.DumpData;
import lucee.runtime.dump.DumpProperties;
import lucee.runtime.dump.DumpTable;
import lucee.runtime.engine.ThreadLocalPageContext;
import lucee.runtime.exp.CasterException;
import lucee.runtime.exp.ExpressionException;
import lucee.runtime.exp.PageException;
import lucee.runtime.exp.PageRuntimeException;
import lucee.runtime.img.filter.QuantizeFilter;
import lucee.runtime.img.gif.GifEncoder;
import lucee.runtime.interpreter.VariableInterpreter;
import lucee.runtime.op.Caster;
import lucee.runtime.op.Constants;
import lucee.runtime.op.Decision;
import lucee.runtime.text.xml.XMLUtil;
import lucee.runtime.type.Array;
import lucee.runtime.type.ArrayImpl;
import lucee.runtime.type.Collection;
import lucee.runtime.type.ObjectWrap;
import lucee.runtime.type.Struct;
import lucee.runtime.type.StructImpl;
import lucee.runtime.type.UDFPlus;
import lucee.runtime.type.dt.DateTime;
import lucee.runtime.type.util.ArrayUtil;
import lucee.runtime.type.util.ListUtil;
import lucee.runtime.type.util.MemberUtil;
import lucee.runtime.type.util.StructSupport;

import org.apache.commons.codec.binary.Base64;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class Image extends StructSupport implements Cloneable, Struct {
    private static final long serialVersionUID = -2370381932689749657L;

    public static final int BORDER_TYPE_CONSTANT = -1;

    public static final int INTERPOLATION_NONE = 0;
    public static final int INTERPOLATION_NEAREST = 1;
    public static final int INTERPOLATION_BILINEAR = 2;
    public static final int INTERPOLATION_BICUBIC = 3;

    public static final int IP_NONE = 0;

    public static final int IPC_NEAREST = 1;
    public static final int IPC_BILINEAR = 2;
    public static final int IPC_BICUBIC = 3;
    public static final int IPC_MAX = 3;

    public static final int IP_HIGHESTQUALITY = 100;
    public static final int IP_HIGHQUALITY = 101;
    public static final int IP_MEDIUMQUALITY = 102;
    public static final int IP_HIGHESTPERFORMANCE = 103;
    public static final int IP_HIGHPERFORMANCE = 104;
    public static final int IP_MEDIUMPERFORMANCE = 105;

    public static final int IP_BESSEL = 109;
    public static final int IP_BLACKMAN = 110;
    public static final int IP_HAMMING = 111;
    public static final int IP_HANNING = 112;
    public static final int IP_HERMITE = 113;
    public static final int IP_LANCZOS = 114;
    public static final int IP_MITCHELL = 115;
    public static final int IP_QUADRATIC = 116;
    public static final int IP_TRIANGLE = 117;

    private static final int ANTI_ALIAS_NONE = 0;
    private static final int ANTI_ALIAS_ON = 1;
    private static final int ANTI_ALIAS_OFF = 2;

    private static final String FORMAT = "javax_imageio_1.0";

    private BufferedImage _image;
    private Resource source = null;
    private String format;

    private Graphics2D graphics;

    private Color bgColor;
    private Color fgColor;
    private Color xmColor;

    private float tranparency = -1;
    private int antiAlias = ANTI_ALIAS_NONE;

    private Stroke stroke;

    private Struct sctInfo;

    private float alpha = 1;

    private Composite composite;
    private static Object sync = new Object();

    static {
        ImageIO.scanForPlugins();
    }

    public Image(byte[] binary) throws IOException {
        this(binary, null);
    }

    public Image(byte[] binary, String format) throws IOException {
        if (StringUtil.isEmpty(format))
            format = ImageUtil.getFormat(binary, null);
        checkRestriction();
        this.format = format;
        _image = ImageUtil.toBufferedImage(binary, format);
        if (_image == null)
            throw new IOException("can not read in image");
    }

    public Image(Resource res) throws IOException {
        this(res, null);
    }

    public Image(Resource res, String format) throws IOException {
        if (StringUtil.isEmpty(format))
            format = ImageUtil.getFormat(res);
        checkRestriction();
        this.format = format;
        _image = ImageUtil.toBufferedImage(res, format);
        this.source = res;
        if (_image == null)
            throw new IOException("can not read in file " + res);
    }

    public Image(BufferedImage image) {
        checkRestriction();
        this._image = image;
    }

    public Image(String b64str) throws IOException {
        this(b64str, null);
    }

    public Image(String b64str, String format) throws IOException {

        // load binary from base64 string and get format
        StringBuilder mimetype = new StringBuilder();
        byte[] binary = ImageUtil.readBase64(b64str, mimetype);
        if (StringUtil.isEmpty(format) && !StringUtil.isEmpty(mimetype)) {
            format = ImageUtil.getFormatFromMimeType(mimetype.toString());
        }

        if (StringUtil.isEmpty(format))
            format = ImageUtil.getFormat(binary, null);
        checkRestriction();
        this.format = format;
        _image = ImageUtil.toBufferedImage(binary, format);
        if (_image == null)
            throw new IOException("can not read in image");
    }

    public Image(int width, int height, int imageType, Color canvasColor) throws ExpressionException {
        checkRestriction();
        _image = new BufferedImage(width, height, imageType);
        if (!StringUtil.isEmpty(canvasColor)) {

            setBackground(canvasColor);
            clearRect(0, 0, width, height);
        }
    }

    public Image() {
        checkRestriction();
    }

    /**
     * add a border to image
     * @param thickness
     * @param color
     * @param borderType 
     */
    public void addBorder(int thickness, Color color, int borderType) throws ExpressionException {

        double colorArray[] = { color.getRed(), color.getGreen(), color.getBlue() };
        BorderExtender borderExtender = new BorderExtenderConstant(colorArray);

        ParameterBlock params = new ParameterBlock();
        params.addSource(image());
        params.add(thickness);
        params.add(thickness);
        params.add(thickness);
        params.add(thickness);
        if (BORDER_TYPE_CONSTANT == borderType)
            params.add(borderExtender);
        else
            params.add(BorderExtender.createInstance(borderType));
        //else if(BORDER_TYPE_WRAP==borderType)params.add(BorderExtender.createInstance(BorderExtender.BORDER_REFLECT));

        image((JAI.create("border", params)).getAsBufferedImage());

    }

    public void blur(int blurFactor) throws ExpressionException {
        ParameterBlock params = new ParameterBlock();
        params.addSource(image());
        params.add(blurFactor);
        RenderingHints hint = new RenderingHints(JAI.KEY_BORDER_EXTENDER, BorderExtender.createInstance(1));
        image(JAI.create("boxfilter", params, hint).getAsBufferedImage());
    }

    public void clearRect(int x, int y, int width, int height) throws ExpressionException {
        getGraphics().clearRect(x, y, width, height);
    }

    public Struct info() throws ExpressionException {
        if (sctInfo != null)
            return sctInfo;

        Struct sctInfo = new StructImpl(), sct;

        sctInfo.setEL("height", new Double(getHeight()));
        sctInfo.setEL("width", new Double(getWidth()));
        sctInfo.setEL("source", source == null ? "" : source.getAbsolutePath());
        //sct.setEL("mime_type",getMimeType());

        ColorModel cm = image().getColorModel();
        sct = new StructImpl();
        sctInfo.setEL("colormodel", sct);

        sct.setEL("alpha_channel_support", Caster.toBoolean(cm.hasAlpha()));
        sct.setEL("alpha_premultiplied", Caster.toBoolean(cm.isAlphaPremultiplied()));
        sct.setEL("transparency", toStringTransparency(cm.getTransparency()));
        sct.setEL("pixel_size", Caster.toDouble(cm.getPixelSize()));
        sct.setEL("num_components", Caster.toDouble(cm.getNumComponents()));
        sct.setEL("num_color_components", Caster.toDouble(cm.getNumColorComponents()));
        sct.setEL("colorspace", toStringColorSpace(cm.getColorSpace()));

        //bits_component
        int[] bitspercomponent = cm.getComponentSize();
        Array arr = new ArrayImpl();
        Double value;
        for (int i = 0; i < bitspercomponent.length; i++) {
            sct.setEL("bits_component_" + (i + 1), value = new Double(bitspercomponent[i]));
            arr.appendEL(value);
        }
        sct.setEL("bits_component", arr);

        // colormodel_type
        if (cm instanceof ComponentColorModel)
            sct.setEL("colormodel_type", "ComponentColorModel");
        else if (cm instanceof IndexColorModel)
            sct.setEL("colormodel_type", "IndexColorModel");
        else if (cm instanceof PackedColorModel)
            sct.setEL("colormodel_type", "PackedColorModel");
        else
            sct.setEL("colormodel_type", ListUtil.last(cm.getClass().getName(), '.'));

        getMetaData(sctInfo);

        ImageMeta.addInfo(format, source, sctInfo);

        this.sctInfo = sctInfo;
        return sctInfo;
    }

    public IIOMetadata getMetaData(Struct parent) {
        InputStream is = null;
        javax.imageio.stream.ImageInputStreamImpl iis = null;
        try {

            if (source instanceof File) {
                iis = new FileImageInputStream((File) source);
            } else if (source == null)
                iis = new MemoryCacheImageInputStream(new ByteArrayInputStream(getImageBytes(format, true)));
            else
                iis = new MemoryCacheImageInputStream(is = source.getInputStream());

            Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
            if (readers.hasNext()) {
                // pick the first available ImageReader
                ImageReader reader = readers.next();
                IIOMetadata meta = null;
                synchronized (sync) {
                    // attach source to the reader
                    reader.setInput(iis, true);

                    // read metadata of first image
                    meta = reader.getImageMetadata(0);
                    meta.setFromTree(FORMAT, meta.getAsTree(FORMAT));
                    reader.reset();
                }
                // generating dump
                if (parent != null) {
                    String[] formatNames = meta.getMetadataFormatNames();
                    for (int i = 0; i < formatNames.length; i++) {
                        Node root = meta.getAsTree(formatNames[i]);
                        //print.out(XMLCaster.toString(root));
                        addMetaddata(parent, "metadata", root);
                    }
                }
                return meta;
            }
        } catch (Throwable t) {
        } finally {
            ImageUtil.closeEL(iis);
            IOUtil.closeEL(is);
        }
        return null;
    }

    private void addMetaddata(Struct parent, String name, Node node) {

        // attributes
        NamedNodeMap attrs = node.getAttributes();
        Attr attr;
        int len = attrs.getLength();
        if (len == 1 && "value".equals(attrs.item(0).getNodeName())) {
            parent.setEL(name, attrs.item(0).getNodeValue());
        } else {
            Struct sct = metaGetChild(parent, name);
            for (int i = attrs.getLength() - 1; i >= 0; i--) {
                attr = (Attr) attrs.item(i);
                sct.setEL(attr.getName(), attr.getValue());
            }
        }

        // child nodes
        NodeList children = XMLUtil.getChildNodes(node, Node.ELEMENT_NODE);
        Element el;
        for (int i = children.getLength() - 1; i >= 0; i--) {
            el = (Element) children.item(i);
            Struct sct = metaGetChild(parent, name);
            addMetaddata(sct, el.getNodeName(), children.item(i));
        }
    }

    private Struct metaGetChild(Struct parent, String name) {
        Object child = parent.get(name, null);
        if (child instanceof Struct)
            return (Struct) child;
        Struct sct = new StructImpl();
        parent.setEL(name, sct);
        return sct;
    }

    public void sharpen(float gain) throws ExpressionException {
        ParameterBlock params = new ParameterBlock();
        params.addSource(image());
        params.add((Object) null);
        params.add(new Float(gain));
        image(JAI.create("unsharpmask", params).getAsBufferedImage());
    }

    public void setTranparency(float percent) throws ExpressionException {
        if (percent == -1)
            return;
        tranparency = percent;
        AlphaComposite rule = AlphaComposite.getInstance(3, 1.0F - (percent / 100.0F));
        getGraphics().setComposite(rule);
    }

    public void invert() throws ExpressionException {
        ParameterBlock params = new ParameterBlock();
        params.addSource(image());
        image(JAI.create("invert", params).getAsBufferedImage());
    }

    public Image copy(float x, float y, float width, float height) throws ExpressionException {
        ParameterBlock params = new ParameterBlock();
        params.addSource(image());
        params.add(x);
        params.add(y);
        params.add(width);
        params.add(height);
        //image(JAI.create("crop", params).getAsBufferedImage());
        return new Image(JAI.create("crop", params).getAsBufferedImage());
    }

    public Image copy(float x, float y, float width, float height, float dx, float dy) throws ExpressionException {
        Image img = copy(x, y, width, height);
        img.getGraphics().copyArea((int) x, (int) y, (int) width, (int) height, (int) (dx - x), (int) (dy - y));
        return img;
    }

    public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle, boolean filled)
            throws ExpressionException {
        if (filled)
            getGraphics().fillArc(x, y, width, height, startAngle, arcAngle);
        else
            getGraphics().drawArc(x, y, width, height, startAngle, arcAngle);
    }

    public void draw3DRect(int x, int y, int width, int height, boolean raised, boolean filled)
            throws ExpressionException {
        if (filled)
            getGraphics().fill3DRect(x, y, width + 1, height + 1, raised);
        else
            getGraphics().draw3DRect(x, y, width, height, raised);
    }

    public void drawCubicCurve(double ctrlx1, double ctrly1, double ctrlx2, double ctrly2, double x1, double y1,
            double x2, double y2) throws ExpressionException {
        CubicCurve2D curve = new CubicCurve2D.Double(x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2);
        getGraphics().draw(curve);
    }

    public void drawPoint(int x, int y) throws ExpressionException {
        drawLine(x, y, x + 1, y);
    }

    public void drawQuadraticCurve(double x1, double y1, double ctrlx, double ctrly, double x2, double y2)
            throws ExpressionException {
        QuadCurve2D curve = new QuadCurve2D.Double(x1, y1, ctrlx, ctrly, x2, y2);
        getGraphics().draw(curve);
    }

    public void drawRect(int x, int y, int width, int height, boolean filled) throws ExpressionException {
        if (filled)
            getGraphics().fillRect(x, y, width + 1, height + 1);
        else
            getGraphics().drawRect(x, y, width, height);
    }

    public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight, boolean filled)
            throws ExpressionException {
        if (filled)
            getGraphics().fillRoundRect(x, y, width + 1, height + 1, arcWidth, arcHeight);
        else
            getGraphics().drawRoundRect(x, y, width, height, arcWidth, arcHeight);
    }

    public void drawLine(int x1, int y1, int x2, int y2) throws ExpressionException {
        getGraphics().drawLine(x1, y1, x2, y2);
    }

    public void drawImage(Image img, int x, int y) throws ExpressionException {
        getGraphics().drawImage(img.image(), x, y, null);
    }

    public void drawImage(Image img, int x, int y, int width, int height) throws ExpressionException {
        getGraphics().drawImage(img.image(), x, y, width, height, null);
    }

    public void drawLines(int[] xcoords, int[] ycoords, boolean isPolygon, boolean filled)
            throws ExpressionException {
        if (isPolygon) {
            if (filled)
                getGraphics().fillPolygon(xcoords, ycoords, xcoords.length);
            else
                getGraphics().drawPolygon(xcoords, ycoords, xcoords.length);
        } else {
            getGraphics().drawPolyline(xcoords, ycoords, xcoords.length);
        }
    }

    public void drawOval(int x, int y, int width, int height, boolean filled) throws ExpressionException {
        if (filled)
            getGraphics().fillOval(x, y, width, height);
        else
            getGraphics().drawOval(x, y, width, height);
    }

    public void drawString(String text, int x, int y, Struct attr) throws PageException {

        if (attr != null && attr.size() > 0) {

            // font
            String font = StringUtil.toLowerCase(Caster.toString(attr.get("font", ""))).trim();
            if (!StringUtil.isEmpty(font)) {
                font = FontUtil.getFont(font).getFontName();
            } else
                font = "Serif";

            // alpha
            //float alpha=Caster.toFloatValue(attr.get("alpha",null),1F);

            // size
            int size = Caster.toIntValue(attr.get("size", Constants.INTEGER_10));

            // style
            int style = Font.PLAIN;
            String strStyle = StringUtil.toLowerCase(Caster.toString(attr.get("style", "")));
            strStyle = StringUtil.removeWhiteSpace(strStyle);
            if (!StringUtil.isEmpty(strStyle)) {
                if ("plain".equals(strStyle))
                    style = Font.PLAIN;
                else if ("bold".equals(strStyle))
                    style = Font.BOLD;
                else if ("italic".equals(strStyle))
                    style = Font.ITALIC;
                else if ("bolditalic".equals(strStyle))
                    style = Font.BOLD + Font.ITALIC;
                else if ("bold,italic".equals(strStyle))
                    style = Font.BOLD + Font.ITALIC;
                else if ("italicbold".equals(strStyle))
                    style = Font.BOLD + Font.ITALIC;
                else if ("italic,bold".equals(strStyle))
                    style = Font.BOLD + Font.ITALIC;
                else
                    throw new ExpressionException("key style of argument attributeCollection has an invalid value ["
                            + strStyle + "], valid values are [plain,bold,italic,bolditalic]");
            }

            // strikethrough
            boolean strikethrough = Caster.toBooleanValue(attr.get("strikethrough", Boolean.FALSE));

            // underline
            boolean underline = Caster.toBooleanValue(attr.get("underline", Boolean.FALSE));

            AttributedString as = new AttributedString(text);
            as.addAttribute(TextAttribute.FONT, new Font(font, style, size));
            if (strikethrough)
                as.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
            if (underline)
                as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
            Graphics2D g = getGraphics();
            //if(alpha!=1D) setAlpha(g,alpha);

            g.drawString(as.getIterator(), x, y);
        } else
            getGraphics().drawString(text, x, y);

    }

    /*private void setAlpha(Graphics2D graphics,float alpha) {
       //Composite originalComposite = graphics.getComposite();
           
       AlphaComposite alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
        
       graphics.setComposite(alphaComposite);
       //graphics.setComposite(originalComposite);   
    }*/

    public void setDrawingStroke(Struct attr) throws PageException {

        // empty 
        if (attr == null || attr.size() == 0) {
            setDrawingStroke(new BasicStroke());
            return;
        }

        // width
        float width = Caster.toFloatValue(attr.get("width", new Float(1F)));
        if (width < 0)
            throw new ExpressionException("key [width] should be a none negativ number");

        // endcaps
        String strEndcaps = Caster.toString(attr.get("endcaps", "square"));
        strEndcaps = strEndcaps.trim().toLowerCase();
        int endcaps;
        if ("square".equals(strEndcaps))
            endcaps = BasicStroke.CAP_SQUARE;
        else if ("butt".equals(strEndcaps))
            endcaps = BasicStroke.CAP_BUTT;
        else if ("round".equals(strEndcaps))
            endcaps = BasicStroke.CAP_ROUND;
        else
            throw new ExpressionException("key [endcaps] has an invalid value [" + strEndcaps
                    + "], valid values are [square,round,butt]");

        // linejoins
        String strLinejoins = Caster.toString(attr.get("linejoins", "miter"));
        strLinejoins = strLinejoins.trim().toLowerCase();
        int linejoins;
        if ("bevel".equals(strLinejoins))
            linejoins = BasicStroke.JOIN_BEVEL;
        else if ("miter".equals(strLinejoins))
            linejoins = BasicStroke.JOIN_MITER;
        else if ("round".equals(strLinejoins))
            linejoins = BasicStroke.JOIN_ROUND;
        else
            throw new ExpressionException("key [linejoins] has an invalid value [" + strLinejoins
                    + "], valid values are [bevel,miter,round]");

        // miterlimit
        float miterlimit = 10.0F;
        if (linejoins == BasicStroke.JOIN_MITER) {
            miterlimit = Caster.toFloatValue(attr.get("miterlimit", new Float(10F)));
            if (miterlimit < 1F)
                throw new ExpressionException("key [miterlimit] should be greater or equal to 1");
        }

        // dashArray
        Object oDashArray = attr.get("dashArray", null);
        float[] dashArray = null;
        if (oDashArray != null) {
            dashArray = ArrayUtil.toFloatArray(oDashArray);
        }

        // dash_phase
        float dash_phase = Caster.toFloatValue(attr.get("dash_phase", new Float(0F)));

        setDrawingStroke(width, endcaps, linejoins, miterlimit, dashArray, dash_phase);
    }

    public void setDrawingStroke(float width, int endcaps, int linejoins, float miterlimit, float[] dash,
            float dash_phase) throws ExpressionException {
        setDrawingStroke(new BasicStroke(width, endcaps, linejoins, miterlimit, dash, dash_phase));
    }

    public void setDrawingStroke(Stroke stroke) throws ExpressionException {
        if (stroke == null)
            return;
        this.stroke = stroke;
        getGraphics().setStroke(stroke);
    }

    public void flip(TransposeType transpose) throws ExpressionException {
        ParameterBlock params = new ParameterBlock();
        params.addSource(image());
        params.add(transpose);
        image(JAI.create("transpose", params).getAsBufferedImage());
    }

    public void grayscale() throws ExpressionException {
        BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_BYTE_GRAY);
        Graphics2D graphics = img.createGraphics();
        graphics.drawImage(image(), new AffineTransformOp(AffineTransform.getTranslateInstance(0.0, 0.0), 1), 0, 0);
        graphics.dispose();
        image(img);
    }

    public void rgb() throws ExpressionException {
        BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_BYTE_INDEXED);
        Graphics2D graphics = img.createGraphics();
        graphics.drawImage(image(), new AffineTransformOp(AffineTransform.getTranslateInstance(0.0, 0.0), 1), 0, 0);
        graphics.dispose();
        image(img);

    }

    public void threeBBger() throws ExpressionException {
        BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
        Graphics2D graphics = img.createGraphics();
        graphics.drawImage(image(), new AffineTransformOp(AffineTransform.getTranslateInstance(0.0, 0.0), 1), 0, 0);
        graphics.dispose();
        image(img);
    }

    public void overlay(Image topImage) throws ExpressionException {
        ParameterBlock params = new ParameterBlock();
        params.addSource(image());
        params.addSource(topImage.image());
        image(JAI.create("overlay", params).getAsBufferedImage());
    }

    public void paste(Image topImage, int x, int y) throws ExpressionException {
        RenderingHints interp = new RenderingHints(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        BorderExtender extender = BorderExtender.createInstance(1);
        Graphics2D g = getGraphics();
        g.addRenderingHints(new RenderingHints(JAI.KEY_BORDER_EXTENDER, extender));
        g.drawImage(topImage.image(), (new AffineTransformOp(AffineTransform.getTranslateInstance(x, y), interp)),
                0, 0);

    }

    public void setXorMode(Color color) throws ExpressionException {
        if (color == null)
            return;
        xmColor = color;
        getGraphics().setXORMode(color);
    }

    public void translate(int xtrans, int ytrans, Object interpolation) throws ExpressionException {

        RenderingHints hints = new RenderingHints(RenderingHints.KEY_INTERPOLATION, interpolation);
        if (interpolation != RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR) {
            hints.add(new RenderingHints(JAI.KEY_BORDER_EXTENDER, BorderExtender.createInstance(1)));
        }

        ParameterBlock pb = new ParameterBlock();
        pb.addSource(image());
        BufferedImage img = JAI.create("translate", pb).getAsBufferedImage();
        Graphics2D graphics = img.createGraphics();
        graphics.clearRect(0, 0, img.getWidth(), img.getHeight());
        AffineTransform at = new AffineTransform();
        at.setToIdentity();
        graphics.drawImage(image(), new AffineTransformOp(at, hints), xtrans, ytrans);
        graphics.dispose();
        image(img);
    }

    public void translateAxis(int x, int y) throws ExpressionException {
        getGraphics().translate(x, y);
    }

    public void rotateAxis(double angle) throws ExpressionException {
        getGraphics().rotate(Math.toRadians(angle));
    }

    public void rotateAxis(double angle, double x, double y) throws ExpressionException {
        getGraphics().rotate(Math.toRadians(angle), x, y);
    }

    public void shearAxis(double shx, double shy) throws ExpressionException {
        getGraphics().shear(shx, shy);
    }

    public void shear(float shear, ShearDir direction, Object interpolation) throws ExpressionException {
        ParameterBlock params = new ParameterBlock();
        params.addSource(image());
        params.add(shear);
        params.add(direction);
        params.add(0.0F);
        params.add(0.0F);
        RenderingHints hints = null;

        if (interpolation == RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)
            params.add(Interpolation.getInstance(0));
        else if (interpolation == RenderingHints.VALUE_INTERPOLATION_BILINEAR) {
            params.add(Interpolation.getInstance(1));
            BorderExtender extender = BorderExtender.createInstance(1);
            hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER, extender);
        } else if (interpolation == RenderingHints.VALUE_INTERPOLATION_BICUBIC) {
            params.add(Interpolation.getInstance(2));
            BorderExtender extender = BorderExtender.createInstance(1);
            hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER, extender);
        }
        // TODO
        Color bg = getGraphics().getBackground();
        params.add(new double[] { bg.getRed(), bg.getGreen(), bg.getBlue() });
        image(JAI.create("shear", params, hints).getAsBufferedImage());
    }

    public BufferedImage getBufferedImage() throws ExpressionException {
        return image();
    }

    public BufferedImage image() throws ExpressionException {
        if (_image == null)
            throw (new ExpressionException("image is not initalized"));
        return _image;
    }

    public void image(BufferedImage image) {
        this._image = image;
        graphics = null;

        sctInfo = null;
    }

    private Graphics2D getGraphics() throws ExpressionException {
        if (graphics == null) {
            graphics = image().createGraphics();
            // reset all properties
            if (antiAlias != ANTI_ALIAS_NONE)
                setAntiAliasing(antiAlias == ANTI_ALIAS_ON);
            if (bgColor != null)
                setBackground(bgColor);
            if (fgColor != null)
                setColor(fgColor);
            if (alpha != 1)
                setAlpha(alpha);
            if (tranparency != -1)
                setTranparency(tranparency);
            if (xmColor != null)
                setXorMode(xmColor);
            if (stroke != null)
                setDrawingStroke(stroke);
        }
        return graphics;
    }

    private String toStringColorSpace(ColorSpace colorSpace) {
        switch (colorSpace.getType()) {
        case 0:
            return "Any of the family of XYZ color spaces";
        case 1:
            return "Any of the family of Lab color spaces";
        case 2:
            return "Any of the family of Luv color spaces";
        case 3:
            return "Any of the family of YCbCr color spaces";
        case 4:
            return "Any of the family of Yxy color spaces";
        case 5:
            return "Any of the family of RGB color spaces";
        case 6:
            return "Any of the family of GRAY color spaces";
        case 7:
            return "Any of the family of HSV color spaces";
        case 8:
            return "Any of the family of HLS color spaces";
        case 9:
            return "Any of the family of CMYK color spaces";
        case 11:
            return "Any of the family of CMY color spaces";
        case 12:
            return "Generic 2 component color space.";
        case 13:
            return "Generic 3 component color space.";
        case 14:
            return "Generic 4 component color space.";
        case 15:
            return "Generic 5 component color space.";
        case 16:
            return "Generic 6 component color space.";
        case 17:
            return "Generic 7 component color space.";
        case 18:
            return "Generic 8 component color space.";
        case 19:
            return "Generic 9 component color space.";
        case 20:
            return "Generic 10 component color space.";
        case 21:
            return "Generic 11 component color space.";
        case 22:
            return "Generic 12 component color space.";
        case 23:
            return "Generic 13 component color space.";
        case 24:
            return "Generic 14 component color space.";
        case 25:
            return "Generic 15 component color space.";
        case 1001:
            return "CIEXYZ";
        case 1003:
            return "GRAY";
        case 1004:
            return "LINEAR_RGB";
        case 1002:
            return "PYCC";
        case 1000:
            return "sRGB";
        }

        return "Unknown ColorSpace" + colorSpace;
    }

    private Object toStringTransparency(int transparency) {
        if (Transparency.OPAQUE == transparency)
            return "OPAQUE";
        if (Transparency.BITMASK == transparency)
            return "BITMASK";
        if (Transparency.TRANSLUCENT == transparency)
            return "TRANSLUCENT";
        return "Unknown type of transparency";
    }

    public String writeBase64(Resource destination, String format, boolean inHTMLFormat)
            throws PageException, IOException {
        // destination
        if (destination == null) {
            if (source != null)
                destination = source;
            else
                throw new IOException("missing destination file");
        }

        String content = getBase64String(format);
        if (inHTMLFormat)
            content = "data:image/" + format + ";base64," + content;
        IOUtil.write(destination, content, (Charset) null, false);
        return content;
    }

    public String getBase64String(String format) throws PageException {
        byte[] imageBytes = getImageBytes(format);
        return new String(Base64.encodeBase64(imageBytes));
    }

    public void writeOut(Resource destination, boolean overwrite, float quality)
            throws IOException, ExpressionException {
        String format = ImageUtil.getFormatFromExtension(destination, null);
        writeOut(destination, format, overwrite, quality);
    }

    public void writeOut(Resource destination, String format, boolean overwrite, float quality)
            throws IOException, ExpressionException {
        if (destination == null) {
            if (source != null)
                destination = source;
            else
                throw new IOException("missing destination file");
        }

        if (destination.exists()) {
            if (!overwrite)
                throw new IOException("can't overwrite existing image");
        }

        if (JAIUtil.isSupportedWriteFormat(format)) {
            JAIUtil.write(getBufferedImage(), destination, format);
            return;
        }
        OutputStream os = null;
        ImageOutputStream ios = null;
        try {
            os = destination.getOutputStream();
            ios = ImageIO.createImageOutputStream(os);
            _writeOut(ios, format, quality);
        } finally {
            ImageUtil.closeEL(ios);
            IOUtil.closeEL(os);
        }
    }

    public static void writeOutGif(BufferedImage src, OutputStream os) throws IOException {
        BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_ARGB);
        QuantizeFilter filter = new QuantizeFilter();
        filter.setSerpentine(true);
        filter.setDither(true);
        //filter.setNumColors(8);
        filter.filter(src, dst);

        //image(Quantizer.quantize(image(), 8));
        try {
            GifEncoder enc = new GifEncoder(dst);
            enc.Write(os);
            os.flush();
        } catch (AWTException e) {
            throw new IOException(e.getMessage());
        }
    }

    public void writeOut(OutputStream os, String format, float quality, boolean closeStream)
            throws IOException, ExpressionException {
        ImageOutputStream ios = ImageIO.createImageOutputStream(os);
        try {
            _writeOut(ios, format, quality);
        } finally {
            IOUtil.closeEL(ios);
        }
    }

    private void _writeOut(ImageOutputStream ios, String format, float quality)
            throws IOException, ExpressionException {
        _writeOut(ios, format, quality, false);
    }

    private void _writeOut(ImageOutputStream ios, String format, float quality, boolean noMeta)
            throws IOException, ExpressionException {
        if (quality < 0 || quality > 1)
            throw new IOException(
                    "quality has an invalid value [" + quality + "], value has to be between 0 and 1");
        if (StringUtil.isEmpty(format))
            format = this.format;
        if (StringUtil.isEmpty(format))
            throw new IOException("missing format");

        BufferedImage im = image();

        //IIOMetadata meta = noMeta?null:metadata(format);
        IIOMetadata meta = noMeta ? null : getMetaData(null);

        ImageWriter writer = null;
        ImageTypeSpecifier type = ImageTypeSpecifier.createFromRenderedImage(im);
        Iterator<ImageWriter> iter = ImageIO.getImageWriters(type, format);

        if (iter.hasNext()) {
            writer = iter.next();
        }
        if (writer == null)
            throw new IOException("no writer for format [" + format + "] available, available writer formats are ["
                    + ListUtil.arrayToList(ImageUtil.getWriterFormatNames(), ",") + "]");

        ImageWriteParam iwp = null;
        if ("jpg".equalsIgnoreCase(format)) {
            ColorModel cm = im.getColorModel();
            if (cm.hasAlpha())
                im = jpgImage(im);
            JPEGImageWriteParam jiwp = new JPEGImageWriteParam(Locale.getDefault());
            jiwp.setOptimizeHuffmanTables(true);
            iwp = jiwp;
        } else
            iwp = writer.getDefaultWriteParam();

        setCompressionModeEL(iwp, ImageWriteParam.MODE_EXPLICIT);
        setCompressionQualityEL(iwp, quality);
        writer.setOutput(ios);
        try {
            writer.write(meta, new IIOImage(im, null, meta), iwp);

        } finally {
            writer.dispose();
            ios.flush();
        }
    }

    private BufferedImage jpgImage(BufferedImage src) {
        int w = src.getWidth();
        int h = src.getHeight();
        SampleModel srcSM = src.getSampleModel();
        WritableRaster srcWR = src.getRaster();
        java.awt.image.DataBuffer srcDB = srcWR.getDataBuffer();

        ColorModel rgb = new DirectColorModel(32, 0xff0000, 65280, 255);
        int[] bitMasks = new int[] { 0xff0000, 65280, 255 };

        SampleModel csm = new SinglePixelPackedSampleModel(3, w, h, bitMasks);
        int data[] = new int[w * h];
        for (int i = 0; i < h; i++) {
            for (int j = 0; j < w; j++) {
                int pix[] = null;
                int sample[] = srcSM.getPixel(j, i, pix, srcDB);
                if (sample[3] == 0 && sample[2] == 0 && sample[1] == 0 && sample[0] == 0)
                    data[i * w + j] = 0xffffff;
                else
                    data[i * w + j] = sample[0] << 16 | sample[1] << 8 | sample[2];
            }

        }

        java.awt.image.DataBuffer db = new DataBufferInt(data, w * h * 3);
        WritableRaster wr = Raster.createWritableRaster(csm, db, new Point(0, 0));
        return new BufferedImage(rgb, wr, false, null);
    }

    private void setCompressionModeEL(ImageWriteParam iwp, int mode) {
        try {
            iwp.setCompressionMode(mode);
        } catch (Throwable t) {
        }
    }

    private void setCompressionQualityEL(ImageWriteParam iwp, float quality) {
        try {
            iwp.setCompressionQuality(quality);
        } catch (Throwable t) {
        }
    }

    public void convert(String format) {
        this.format = format;
    }

    public void scaleToFit(String fitWidth, String fitHeight, String interpolation, double blurFactor)
            throws PageException {
        if (StringUtil.isEmpty(fitWidth) || StringUtil.isEmpty(fitHeight))
            resize(fitWidth, fitHeight, interpolation, blurFactor);
        else {
            float width = Caster.toFloatValue(fitWidth) / getWidth();
            float height = Caster.toFloatValue(fitHeight) / getHeight();
            if (width < height)
                resize(fitWidth, "", interpolation, blurFactor);
            else
                resize("", fitHeight, interpolation, blurFactor);
        }
    }

    /**
      * 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}
      */
    private BufferedImage getScaledInstance(BufferedImage img, int targetWidth, int targetHeight, Object hint,
            boolean higherQuality) {
        // functionality not supported in java 1.4
        int transparency = Transparency.OPAQUE;
        try {
            transparency = img.getTransparency();
        } catch (Throwable t) {
        }
        int type = (transparency == 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;
    }

    public void resize(int scale, String interpolation, double blurFactor) throws PageException {
        if (blurFactor <= 0.0 || blurFactor > 10.0)
            throw new ExpressionException("blurFactor must be between 0 and 10");

        float width = getWidth() / 100F * scale;
        float height = getHeight() / 100F * scale;

        resize((int) width, (int) height, toInterpolation(interpolation), blurFactor);
    }

    public void resize(String strWidth, String strHeight, String interpolation, double blurFactor)
            throws PageException {
        if (StringUtil.isEmpty(strWidth, true) && StringUtil.isEmpty(strHeight, true))
            throw new ExpressionException("you have to define width or height");
        if (blurFactor <= 0.0 || blurFactor > 10.0)
            throw new ExpressionException("blurFactor must be between 0 and 10");
        int w = getWidth();
        int h = getHeight();
        float height = resizeDimesion("height", strHeight, h);
        float width = resizeDimesion("width", strWidth, w);

        if (height == -1)
            height = h * (width / w);
        if (width == -1)
            width = w * (height / h);

        resize((int) width, (int) height, toInterpolation(interpolation), blurFactor);
    }

    public void resizeImage2(int width, int height) throws ExpressionException {
        image(getScaledInstance(image(), width, height, RenderingHints.VALUE_INTERPOLATION_BILINEAR, false));
    }

    public void resizeImage(int width, int height, int interpolation) throws ExpressionException {
        Object ip;
        if (interpolation == IPC_NEAREST)
            ip = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
        else if (interpolation == IPC_BICUBIC)
            ip = RenderingHints.VALUE_INTERPOLATION_BICUBIC;
        else if (interpolation == IPC_BILINEAR)
            ip = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
        else
            throw new ExpressionException("invalid interpoltion definition");

        BufferedImage dst = new BufferedImage(width, height, image().getType());
        Graphics2D graphics = dst.createGraphics();
        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, ip);
        graphics.drawImage(image(), 0, 0, width, height, null);
        graphics.dispose();
        image(dst);

    }

    private float resizeDimesion(String label, String strDimension, float originalDimension) throws PageException {
        if (StringUtil.isEmpty(strDimension, true))
            return -1;
        strDimension = strDimension.trim();

        if (StringUtil.endsWith(strDimension, '%')) {
            float p = Caster.toFloatValue(strDimension.substring(0, (strDimension.length() - 1))) / 100.0F;
            return originalDimension * p;
        }
        float dimension = Caster.toFloatValue(strDimension);
        if (dimension <= 0F)
            throw new ExpressionException(label + " has to be a none negative number");
        return dimension;
    }

    public void resize(int width, int height, int interpolation, double blurFactor) throws ExpressionException {

        ColorModel cm = image().getColorModel();

        if (interpolation == IP_HIGHESTPERFORMANCE) {
            interpolation = IPC_BICUBIC;
        }

        if (cm.getColorSpace().getType() == ColorSpace.TYPE_GRAY && cm.getComponentSize()[0] == 8) {
            if (interpolation == IP_HIGHESTQUALITY || interpolation == IP_HIGHPERFORMANCE
                    || interpolation == IP_HIGHQUALITY || interpolation == IP_MEDIUMPERFORMANCE
                    || interpolation == IP_MEDIUMQUALITY) {
                interpolation = IPC_BICUBIC;
            }
            if (interpolation != IPC_BICUBIC && interpolation != IPC_BILINEAR && interpolation != IPC_NEAREST) {
                throw new ExpressionException("invalid grayscale interpolation");
            }
        }

        if (interpolation <= IPC_MAX) {
            resizeImage(width, height, interpolation);
        } else {
            image(ImageResizer.resize(image(), width, height, interpolation, blurFactor));

        }
    }

    /*private BufferedImage resizeImageWithJAI(float scaleWidth, float scaleHeight, int interpolation) throws ExpressionException {
       ParameterBlock params = new ParameterBlock();
       params.addSource(image());
      params.add(scaleWidth);
      params.add(scaleHeight);
      params.add(0.0F);
      params.add(0.0F);
      RenderingHints hints = null;
      if (interpolation != IP_NONE) {
      if (interpolation==IP_NEAREST)   {
         params.add(Interpolation.getInstance(0));
      }
      else if (interpolation==IP_BILINEAR) {
         params.add(Interpolation.getInstance(1));
         BorderExtender extender = BorderExtender.createInstance(1);
         hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER, extender);
      } 
      else if (interpolation==IP_BICUBIC) {
         params.add(Interpolation.getInstance(2));
         BorderExtender extender = BorderExtender.createInstance(1);
         hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER, extender);
      } 
      else   {
         throw new ExpressionException("invalid interpolation definition");
      }
      }
      return JAI.create("scale", params, hints).getAsBufferedImage();
    }*/

    private double toScale(int src, int dst) {
        double tmp = Math.round((int) ((Caster.toDoubleValue(dst) / Caster.toDoubleValue(src)) * 100D));
        return tmp / 100D;
    }

    public void rotate(float x, float y, float angle, int interpolation) throws ExpressionException {
        if (x == -1)
            x = (float) getWidth() / 2;
        if (y == -1)
            y = (float) getHeight() / 2;

        angle = (float) Math.toRadians(angle);
        ColorModel cmSource = image().getColorModel();

        if (cmSource instanceof IndexColorModel && cmSource.hasAlpha() && !cmSource.isAlphaPremultiplied()) {
            image(PaletteToARGB(image()));
            cmSource = image().getColorModel();
        }

        BufferedImage alpha = null;
        if (cmSource.hasAlpha() && !cmSource.isAlphaPremultiplied()) {
            alpha = getAlpha(image());
            image(removeAlpha(image()));
        }

        Interpolation interp = Interpolation.getInstance(0);
        if (INTERPOLATION_BICUBIC == interpolation)
            interp = Interpolation.getInstance(1);
        else if (INTERPOLATION_BILINEAR == interpolation)
            interp = Interpolation.getInstance(2);

        if (alpha != null) {
            ParameterBlock params = new ParameterBlock();
            params.addSource(alpha);
            params.add(x);
            params.add(y);
            params.add(angle);
            params.add(interp);
            params.add(new double[] { 0.0 });
            RenderingHints hints = new RenderingHints(RenderingHints.KEY_INTERPOLATION,
                    (RenderingHints.VALUE_INTERPOLATION_BICUBIC));
            hints.add(new RenderingHints(JAI.KEY_BORDER_EXTENDER,
                    new BorderExtenderConstant(new double[] { 255.0 })));
            hints.add(new RenderingHints(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.TRUE));
            alpha = JAI.create("rotate", params, hints).getAsBufferedImage();
        }

        ParameterBlock params = new ParameterBlock();
        params.addSource(image());
        params.add(x);
        params.add(y);
        params.add(angle);
        params.add(interp);
        params.add(new double[] { 0.0 });
        BorderExtender extender = new BorderExtenderConstant(new double[] { 0.0 });
        RenderingHints hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER, extender);
        hints.add(new RenderingHints(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.TRUE));
        image(JAI.create("rotate", params, hints).getAsBufferedImage());
        if (alpha != null)
            image(addAlpha(image(), alpha, 0, 0));
    }

    private static BufferedImage PaletteToARGB(BufferedImage src) {
        IndexColorModel icm = (IndexColorModel) src.getColorModel();
        int bands = icm.hasAlpha() ? 4 : 3;

        byte[][] data = new byte[bands][icm.getMapSize()];
        if (icm.hasAlpha())
            icm.getAlphas(data[3]);
        icm.getReds(data[0]);
        icm.getGreens(data[1]);
        icm.getBlues(data[2]);
        LookupTableJAI rtable = new LookupTableJAI(data);
        return JAI.create("lookup", src, rtable).getAsBufferedImage();
    }

    private static BufferedImage getAlpha(BufferedImage src) {
        return JAI.create("bandselect", src, new int[] { 3 }).getAsBufferedImage();
    }

    private static BufferedImage removeAlpha(BufferedImage src) {
        return JAI.create("bandselect", src, new int[] { 0, 1, 2 }).getAsBufferedImage();
    }

    private static BufferedImage addAlpha(BufferedImage src, BufferedImage alpha, int x, int y) {
        int w = src.getWidth();
        int h = src.getHeight();
        BufferedImage bi = new BufferedImage(w, h, 2);
        WritableRaster wr = bi.getWritableTile(0, 0);
        WritableRaster wr3 = wr.createWritableChild(0, 0, w, h, 0, 0, new int[] { 0, 1, 2 });
        WritableRaster wr1 = wr.createWritableChild(0, 0, w, h, 0, 0, new int[] { 3 });
        wr3.setRect(src.getData());
        wr1.setRect(alpha.getData());
        bi.releaseWritableTile(0, 0);
        return bi;
    }

    public void _rotate(float x, float y, float angle, String interpolation) throws ExpressionException {

        float radiansAngle = (float) Math.toRadians(angle);

        // rotation center
        float centerX = (float) getWidth() / 2;
        float centerY = (float) getHeight() / 2;

        ParameterBlock pb = new ParameterBlock();
        pb.addSource(image());
        pb.add(centerX);
        pb.add(centerY);
        pb.add(radiansAngle);
        pb.add(new javax.media.jai.InterpolationBicubic(10));

        // create a new, rotated image
        image(JAI.create("rotate", pb).getAsBufferedImage());

    }

    public static Image toImage(Object obj) throws PageException {
        return toImage(ThreadLocalPageContext.get(), obj, true);
    }

    // used in bytecode
    public static Image toImage(Object obj, PageContext pc) throws PageException {
        return toImage(pc, obj, true);
    }

    public static Image toImage(PageContext pc, Object obj) throws PageException {
        return toImage(pc, obj, true);
    }

    public static Image toImage(PageContext pc, Object obj, boolean checkForVariables) throws PageException {
        if (obj instanceof Image)
            return (Image) obj;
        if (obj instanceof ObjectWrap)
            return toImage(pc, ((ObjectWrap) obj).getEmbededObject(), checkForVariables);

        // try to load from binary
        if (Decision.isBinary(obj)) {
            try {
                return new Image(Caster.toBinary(obj), null);
            } catch (IOException e) {
                throw Caster.toPageException(e);
            }
        }
        // try to load from String (base64)
        if (Decision.isString(obj)) {
            String str = Caster.toString(obj);
            if (checkForVariables && pc != null) {
                Object o = VariableInterpreter.getVariableEL(pc, str, null);
                if (o != null)
                    return toImage(pc, o, false);
            }
            try {
                return new Image(str);
            } catch (IOException e) {
                throw Caster.toPageException(e);
            }
        }

        throw new CasterException(obj, "Image");
    }

    public static Image toImage(PageContext pc, Object obj, boolean checkForVariables, Image defaultValue) {
        try {
            return toImage(pc, obj, checkForVariables);
        } catch (Throwable t) {
            return defaultValue;
        }
    }

    public static boolean isImage(Object obj) {
        if (obj instanceof Image)
            return true;
        if (obj instanceof ObjectWrap)
            return isImage(((ObjectWrap) obj).getEmbededObject(""));
        return false;
    }

    @Override
    public Object call(PageContext pc, Key methodName, Object[] args) throws PageException {
        Object obj = get(methodName, null);
        if (obj instanceof UDFPlus) {
            return ((UDFPlus) obj).call(pc, methodName, args, false);
        }
        return MemberUtil.call(pc, this, methodName, args, CFTypes.TYPE_IMAGE, "image");
    }

    @Override
    public Object callWithNamedValues(PageContext pc, Key methodName, Struct args) throws PageException {
        Object obj = get(methodName, null);
        if (obj instanceof UDFPlus) {
            return ((UDFPlus) obj).callWithNamedValues(pc, methodName, args, false);
        }
        return MemberUtil.callWithNamedValues(pc, this, methodName, args, CFTypes.TYPE_IMAGE, "image");
    }

    public static boolean isCastableToImage(PageContext pc, Object obj) {
        if (isImage(obj))
            return true;
        return toImage(pc, obj, true, null) != null;
    }

    public static Image createImage(PageContext pc, Object obj, boolean check4Var, boolean clone,
            boolean checkAccess, String format) throws PageException {
        try {
            if (obj instanceof String || obj instanceof Resource || obj instanceof File) {
                try {
                    Resource res = Caster.toResource(pc, obj, true);
                    pc.getConfig().getSecurityManager().checkFileLocation(res);
                    return new Image(res, format);
                } catch (ExpressionException ee) {
                    if (check4Var && Decision.isVariableName(Caster.toString(obj))) {
                        try {
                            return createImage(pc, pc.getVariable(Caster.toString(obj)), false, clone, checkAccess,
                                    format);
                        } catch (Throwable t) {
                            throw ee;
                        }
                    }
                    try {
                        return new Image(Caster.toString(obj), format);
                    } catch (Throwable t) {
                        throw ee;
                    }
                }
            }
            if (obj instanceof Image) {
                if (clone)
                    return (Image) ((Image) obj).clone();
                return (Image) obj;
            }
            if (Decision.isBinary(obj))
                return new Image(Caster.toBinary(obj), format);
            if (obj instanceof BufferedImage)
                return new Image(((BufferedImage) obj));
            if (obj instanceof java.awt.Image)
                return new Image(toBufferedImage((java.awt.Image) obj));

        } catch (Throwable t) {
            throw Caster.toPageException(t);
        }
        throw new CasterException(obj, "Image");
    }

    @Override
    public Collection duplicate(boolean deepCopy) {
        try {
            //if(_image!=null) return new Image(getBufferedImage());
            return new Image(getImageBytes(null));

        } catch (Exception e) {
            throw new PageRuntimeException(e.getMessage());
        }
    }

    public ColorModel getColorModel() throws ExpressionException {
        return image().getColorModel();
    }

    public void crop(float x, float y, float width, float height) throws ExpressionException {
        ParameterBlock params = new ParameterBlock();
        params.addSource(image());
        params.add(x);
        params.add(y);

        float w = getWidth();
        float h = getHeight();

        if (w < x + width)
            params.add(w - x);
        else
            params.add(width);

        if (h < y + height)
            params.add(h - y);
        else
            params.add(height);

        image(JAI.create("crop", params).getAsBufferedImage());
    }

    public int getWidth() throws ExpressionException {
        return image().getWidth();
    }

    public int getHeight() throws ExpressionException {
        return image().getHeight();
    }

    public String getFormat() {
        return format;
    }

    public byte[] getImageBytes(String format) throws PageException {
        return getImageBytes(format, false);
    }

    public byte[] getImageBytes(String format, boolean noMeta) throws PageException {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        if (JAIUtil.isSupportedWriteFormat(format)) {
            try {
                JAIUtil.write(getBufferedImage(), baos, format);
            } catch (IOException e) {
                throw Caster.toPageException(e);
            }
        } else {
            ImageOutputStream ios = null;
            try {
                ios = ImageIO.createImageOutputStream(baos);
                _writeOut(ios, format, 1, noMeta);
            } catch (IOException e) {
                throw Caster.toPageException(e);
            } finally {
                IOUtil.closeEL(ios);
            }
        }
        return baos.toByteArray();
    }

    public void setColor(Color color) throws ExpressionException {
        if (color == null)
            return;
        fgColor = color;
        getGraphics().setColor(color);
    }

    public void setAlpha(float alpha) throws ExpressionException {
        this.alpha = alpha;
        Graphics2D g = getGraphics();

        Composite alphaComposite;
        if (composite == null) {
            if (alpha == 1)
                return;
            composite = g.getComposite();
        }
        if (alpha == 1)
            alphaComposite = composite;
        else
            alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);

        g.setComposite(alphaComposite);
        //graphics.setComposite(originalComposite);   
    }

    public void setBackground(Color color) throws ExpressionException {
        if (color == null)
            return;
        bgColor = color;
        getGraphics().setBackground(color);
    }

    public void setAntiAliasing(boolean antiAlias) throws ExpressionException {
        this.antiAlias = antiAlias ? ANTI_ALIAS_ON : ANTI_ALIAS_OFF;
        Graphics2D graphics = getGraphics();
        if (antiAlias) {
            graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        } else {
            graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                    RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        }
    }

    private Struct _info() {
        try {
            return info();
        } catch (ExpressionException e) {
            throw new PageRuntimeException(e);
        }
    }

    @Override
    public void clear() {
        throw new RuntimeException("can't clear struct, struct is readonly");
    }

    @Override
    public boolean containsKey(Key key) {
        return _info().containsKey(key);
    }

    @Override
    public Object get(Key key) throws PageException {
        return info().get(key);
    }

    @Override
    public Object get(Key key, Object defaultValue) {
        return _info().get(key, defaultValue);
    }

    @Override
    public Key[] keys() {
        return _info().keys();
    }

    @Override
    public Object remove(Key key) throws PageException {
        throw new ExpressionException("can't remove key [" + key.getString() + "] from struct, struct is readonly");
    }

    @Override
    public Object removeEL(Key key) {
        throw new PageRuntimeException(
                "can't remove key [" + key.getString() + "] from struct, struct is readonly");
    }

    @Override
    public Object set(Key key, Object value) throws PageException {
        throw new ExpressionException("can't set key [" + key.getString() + "] to struct, struct is readonly");
    }

    @Override
    public Object setEL(Key key, Object value) {
        throw new PageRuntimeException("can't set key [" + key.getString() + "] to struct, struct is readonly");
    }

    @Override
    public int size() {
        return _info().size();
    }

    @Override
    public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) {
        DumpData dd = _info().toDumpData(pageContext, maxlevel, dp);
        if (dd instanceof DumpTable)
            ((DumpTable) dd).setTitle("Struct (Image)");
        return dd;
    }

    @Override
    public Iterator<Collection.Key> keyIterator() {
        return _info().keyIterator();
    }

    @Override
    public Iterator<String> keysAsStringIterator() {
        return _info().keysAsStringIterator();
    }

    @Override
    public Iterator<Entry<Key, Object>> entryIterator() {
        return _info().entryIterator();
    }

    @Override
    public Iterator<Object> valueIterator() {
        return _info().valueIterator();
    }

    @Override
    public boolean castToBooleanValue() throws PageException {
        return info().castToBooleanValue();
    }

    @Override
    public Boolean castToBoolean(Boolean defaultValue) {
        try {
            return info().castToBoolean(defaultValue);
        } catch (ExpressionException e) {
            return defaultValue;
        }
    }

    @Override
    public DateTime castToDateTime() throws PageException {
        return info().castToDateTime();
    }

    @Override
    public DateTime castToDateTime(DateTime defaultValue) {
        try {
            return info().castToDateTime(defaultValue);
        } catch (ExpressionException e) {
            return defaultValue;
        }
    }

    @Override
    public double castToDoubleValue() throws PageException {
        return info().castToDoubleValue();
    }

    @Override
    public double castToDoubleValue(double defaultValue) {
        try {
            return info().castToDoubleValue(defaultValue);
        } catch (ExpressionException e) {
            return defaultValue;
        }
    }

    @Override
    public String castToString() throws PageException {
        return info().castToString();
    }

    @Override
    public String castToString(String defaultValue) {
        try {
            return info().castToString(defaultValue);
        } catch (ExpressionException e) {
            return defaultValue;
        }
    }

    @Override
    public int compareTo(String str) throws PageException {
        return info().compareTo(str);
    }

    @Override
    public int compareTo(boolean b) throws PageException {
        return info().compareTo(b);
    }

    @Override
    public int compareTo(double d) throws PageException {
        return info().compareTo(d);
    }

    @Override
    public int compareTo(DateTime dt) throws PageException {
        return info().compareTo(dt);
    }

    private static void checkRestriction() {
        try {
            if (JAI.class == null)
                return;
        } catch (Throwable t) {
            throw new PageRuntimeException(
                    "the JAI extension is missing, please download [lucee-x.x.x.xxx-jars.zip] on http://www.lucee-technologies.com/en/download and copy it into the lucee lib directory");
        }
    }

    public static int toInterpolation(String strInterpolation) throws ExpressionException {
        if (StringUtil.isEmpty(strInterpolation))
            throw new ExpressionException("interpolation definition is empty");
        strInterpolation = strInterpolation.trim().toLowerCase();

        if ("highestquality".equals(strInterpolation))
            return IP_HIGHESTQUALITY;
        else if ("highquality".equals(strInterpolation))
            return IP_HIGHQUALITY;
        else if ("mediumquality".equals(strInterpolation))
            return IP_MEDIUMQUALITY;
        else if ("highestperformance".equals(strInterpolation))
            return IP_HIGHESTPERFORMANCE;
        else if ("highperformance".equals(strInterpolation))
            return IP_HIGHPERFORMANCE;
        else if ("mediumperformance".equals(strInterpolation))
            return IP_MEDIUMPERFORMANCE;
        else if ("nearest".equals(strInterpolation))
            return IPC_NEAREST;
        else if ("bilinear".equals(strInterpolation))
            return IPC_BILINEAR;
        else if ("bicubic".equals(strInterpolation))
            return IPC_BICUBIC;
        else if ("bessel".equals(strInterpolation))
            return IP_BESSEL;
        else if ("blackman".equals(strInterpolation))
            return IP_BLACKMAN;
        else if ("hamming".equals(strInterpolation))
            return IP_HAMMING;
        else if ("hanning".equals(strInterpolation))
            return IP_HANNING;
        else if ("hermite".equals(strInterpolation))
            return IP_HERMITE;
        else if ("lanczos".equals(strInterpolation))
            return IP_LANCZOS;
        else if ("mitchell".equals(strInterpolation))
            return IP_MITCHELL;
        else if ("quadratic".equals(strInterpolation))
            return IP_QUADRATIC;

        throw new ExpressionException("interpolation definition [" + strInterpolation + "] is invalid");
    }

    /**
     * @return the source
     */
    public Resource getSource() {
        return source;
    }

    @Override
    public boolean containsValue(Object value) {
        try {
            return info().containsValue(value);
        } catch (ExpressionException e) {
            return false;
        }
    }

    @Override
    public java.util.Collection values() {
        try {
            return info().values();
        } catch (ExpressionException e) {
            throw new PageRuntimeException(e);
        }
    }

    /**
     * This method returns true if the specified image has transparent pixels
     * @param image
     * @return
     */
    public static boolean hasAlpha(java.awt.Image image) {
        // If buffered image, the color model is readily available
        if (image instanceof BufferedImage) {
            BufferedImage bimage = (BufferedImage) image;
            return bimage.getColorModel().hasAlpha();
        }

        // Use a pixel grabber to retrieve the image's color model;
        // grabbing a single pixel is usually sufficient
        PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
        try {
            pg.grabPixels();
        } catch (InterruptedException e) {
        }

        // Get the image's color model
        ColorModel cm = pg.getColorModel();
        return cm.hasAlpha();
    }

    // This method returns a buffered image with the contents of an image
    public static BufferedImage toBufferedImage(java.awt.Image image) {
        if (image instanceof BufferedImage) {
            return (BufferedImage) image;
        }

        // This code ensures that all the pixels in the image are loaded
        image = new ImageIcon(image).getImage();

        // Determine if the image has transparent pixels; for this method's
        boolean hasAlpha = hasAlpha(image);

        // Create a buffered image with a format that's compatible with the screen
        BufferedImage bimage = null;
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        try {
            // Determine the type of transparency of the new buffered image
            int transparency = Transparency.OPAQUE;
            if (hasAlpha) {
                transparency = Transparency.BITMASK;
            }

            // Create the buffered image
            GraphicsDevice gs = ge.getDefaultScreenDevice();
            GraphicsConfiguration gc = gs.getDefaultConfiguration();
            bimage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), transparency);
        } catch (HeadlessException e) {
            // The system does not have a screen
        }

        if (bimage == null) {
            // Create a buffered image using the default color model
            int type = BufferedImage.TYPE_INT_RGB;
            if (hasAlpha) {
                type = BufferedImage.TYPE_INT_ARGB;
            }
            bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
        }

        // Copy image to buffered image
        Graphics g = bimage.createGraphics();

        // Paint the image onto the buffered image
        g.drawImage(image, 0, 0, null);
        g.dispose();

        return bimage;
    }

}