001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.img;
020
021import java.awt.AWTException;
022import java.awt.AlphaComposite;
023import java.awt.BasicStroke;
024import java.awt.Color;
025import java.awt.Composite;
026import java.awt.Font;
027import java.awt.Graphics;
028import java.awt.Graphics2D;
029import java.awt.GraphicsConfiguration;
030import java.awt.GraphicsDevice;
031import java.awt.GraphicsEnvironment;
032import java.awt.HeadlessException;
033import java.awt.Point;
034import java.awt.RenderingHints;
035import java.awt.Stroke;
036import java.awt.Transparency;
037import java.awt.color.ColorSpace;
038import java.awt.font.TextAttribute;
039import java.awt.geom.AffineTransform;
040import java.awt.geom.CubicCurve2D;
041import java.awt.geom.QuadCurve2D;
042import java.awt.image.AffineTransformOp;
043import java.awt.image.BufferedImage;
044import java.awt.image.ColorModel;
045import java.awt.image.ComponentColorModel;
046import java.awt.image.DataBufferInt;
047import java.awt.image.DirectColorModel;
048import java.awt.image.IndexColorModel;
049import java.awt.image.PackedColorModel;
050import java.awt.image.PixelGrabber;
051import java.awt.image.Raster;
052import java.awt.image.SampleModel;
053import java.awt.image.SinglePixelPackedSampleModel;
054import java.awt.image.WritableRaster;
055import java.awt.image.renderable.ParameterBlock;
056import java.io.ByteArrayInputStream;
057import java.io.ByteArrayOutputStream;
058import java.io.File;
059import java.io.IOException;
060import java.io.InputStream;
061import java.io.OutputStream;
062import java.nio.charset.Charset;
063import java.text.AttributedString;
064import java.util.Iterator;
065import java.util.Locale;
066
067import javax.imageio.IIOImage;
068import javax.imageio.ImageIO;
069import javax.imageio.ImageReader;
070import javax.imageio.ImageTypeSpecifier;
071import javax.imageio.ImageWriteParam;
072import javax.imageio.ImageWriter;
073import javax.imageio.metadata.IIOMetadata;
074import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
075import javax.imageio.stream.FileImageInputStream;
076import javax.imageio.stream.ImageOutputStream;
077import javax.imageio.stream.MemoryCacheImageInputStream;
078import javax.media.jai.BorderExtender;
079import javax.media.jai.BorderExtenderConstant;
080import javax.media.jai.Interpolation;
081import javax.media.jai.JAI;
082import javax.media.jai.LookupTableJAI;
083import javax.media.jai.operator.ShearDir;
084import javax.media.jai.operator.TransposeType;
085import javax.swing.ImageIcon;
086
087import lucee.commons.io.IOUtil;
088import lucee.commons.io.res.Resource;
089import lucee.commons.lang.CFTypes;
090import lucee.commons.lang.ExceptionUtil;
091import lucee.commons.lang.StringUtil;
092import lucee.commons.lang.font.FontUtil;
093import lucee.runtime.PageContext;
094import lucee.runtime.dump.DumpData;
095import lucee.runtime.dump.DumpProperties;
096import lucee.runtime.dump.DumpTable;
097import lucee.runtime.engine.ThreadLocalPageContext;
098import lucee.runtime.exp.CasterException;
099import lucee.runtime.exp.ExpressionException;
100import lucee.runtime.exp.PageException;
101import lucee.runtime.exp.PageRuntimeException;
102import lucee.runtime.img.filter.QuantizeFilter;
103import lucee.runtime.img.gif.GifEncoder;
104import lucee.runtime.interpreter.VariableInterpreter;
105import lucee.runtime.op.Caster;
106import lucee.runtime.op.Constants;
107import lucee.runtime.op.Decision;
108import lucee.runtime.text.xml.XMLUtil;
109import lucee.runtime.type.Array;
110import lucee.runtime.type.ArrayImpl;
111import lucee.runtime.type.Collection;
112import lucee.runtime.type.ObjectWrap;
113import lucee.runtime.type.Struct;
114import lucee.runtime.type.StructImpl;
115import lucee.runtime.type.UDFPlus;
116import lucee.runtime.type.dt.DateTime;
117import lucee.runtime.type.util.ArrayUtil;
118import lucee.runtime.type.util.ListUtil;
119import lucee.runtime.type.util.MemberUtil;
120import lucee.runtime.type.util.StructSupport;
121
122import org.apache.commons.codec.binary.Base64;
123import org.w3c.dom.Attr;
124import org.w3c.dom.Element;
125import org.w3c.dom.NamedNodeMap;
126import org.w3c.dom.Node;
127import org.w3c.dom.NodeList;
128
129public class Image extends StructSupport implements Cloneable,Struct {
130        private static final long serialVersionUID = -2370381932689749657L;
131
132
133        public static final int BORDER_TYPE_CONSTANT=-1;
134
135        
136        public static final int INTERPOLATION_NONE=0;
137        public static final int INTERPOLATION_NEAREST=1;
138        public static final int INTERPOLATION_BILINEAR=2;
139        public static final int INTERPOLATION_BICUBIC=3;
140
141        public static final int IP_NONE=0;
142        
143        public static final int IPC_NEAREST=1;
144        public static final int IPC_BILINEAR=2;
145        public static final int IPC_BICUBIC=3;
146        public static final int IPC_MAX=3;
147        
148        public static final int IP_HIGHESTQUALITY=100;
149        public static final int IP_HIGHQUALITY=101;
150        public static final int IP_MEDIUMQUALITY=102;
151        public static final int IP_HIGHESTPERFORMANCE=103;
152        public static final int IP_HIGHPERFORMANCE=104;
153        public static final int IP_MEDIUMPERFORMANCE=105;
154        
155        public static final int IP_BESSEL=109;
156        public static final int IP_BLACKMAN=110;
157        public static final int IP_HAMMING=111;
158        public static final int IP_HANNING=112;
159        public static final int IP_HERMITE=113;
160        public static final int IP_LANCZOS=114;
161        public static final int IP_MITCHELL=115;
162        public static final int IP_QUADRATIC=116;
163        public static final int IP_TRIANGLE=117;
164
165        private static final int ANTI_ALIAS_NONE=0;
166        private static final int ANTI_ALIAS_ON=1;
167        private static final int ANTI_ALIAS_OFF=2;
168
169
170        private static final String FORMAT = "javax_imageio_1.0";
171        
172        private BufferedImage _image;
173        private Resource source=null;
174        private String format;
175
176        private Graphics2D graphics;
177
178        private Color bgColor;
179        private Color fgColor;
180        private Color xmColor;
181
182        private float tranparency=-1;
183        private int antiAlias=ANTI_ALIAS_NONE;
184
185        private Stroke stroke;
186
187        private Struct sctInfo;
188
189
190        private float alpha=1;
191
192
193        private Composite composite;
194        private static Object sync=new Object();
195
196        
197        static {
198                ImageIO.scanForPlugins();
199        }
200
201        public Image(byte[] binary) throws IOException {
202                this(binary, null); 
203        }
204        
205        public Image(byte[] binary, String format) throws IOException {
206                if(StringUtil.isEmpty(format))format=ImageUtil.getFormat(binary,null);
207                checkRestriction();
208                this.format=format;
209                _image=ImageUtil.toBufferedImage(binary,format);
210                if(_image==null) throw new IOException("can not read in image");
211        }
212
213        public Image(Resource res) throws IOException {
214                this(res,null);
215        }
216        public Image(Resource res, String format) throws IOException {
217                if(StringUtil.isEmpty(format))format=ImageUtil.getFormat(res);
218                checkRestriction();
219                this.format=format;
220                _image=ImageUtil.toBufferedImage(res,format);
221                this.source=res;
222                if(_image==null) throw new IOException("can not read in file "+res);
223        }
224
225
226        public Image(BufferedImage image) {
227                checkRestriction();
228                this._image=image;
229        }
230        
231
232        public Image(String b64str) throws IOException {
233                this(b64str,null);
234        }
235        
236        
237        public Image(String b64str, String format) throws IOException {
238                
239                // load binary from base64 string and get format
240                StringBuilder mimetype=new StringBuilder();
241                byte[] binary = ImageUtil.readBase64(b64str,mimetype);
242                if(StringUtil.isEmpty(format) && !StringUtil.isEmpty(mimetype)) {
243                        format=ImageUtil.getFormatFromMimeType(mimetype.toString());
244                }
245
246                if(StringUtil.isEmpty(format))format=ImageUtil.getFormat(binary,null);
247                checkRestriction();
248                this.format=format;
249                _image=ImageUtil.toBufferedImage(binary,format);
250                if(_image==null) throw new IOException("can not read in image");
251        }
252        
253        
254
255        public Image(int width, int height, int imageType, Color canvasColor) throws ExpressionException {
256                checkRestriction();
257                _image = new BufferedImage(width, height, imageType);
258                if(!StringUtil.isEmpty(canvasColor)){
259                        
260                        setBackground(canvasColor);
261                        clearRect(0, 0, width, height);
262                }
263        }
264
265        public Image() {
266                checkRestriction();
267        }
268
269                
270        
271
272        /**
273         * add a border to image
274         * @param thickness
275         * @param color
276         * @param borderType 
277         */
278        public void addBorder(int thickness, Color color, int borderType)  throws ExpressionException{
279                
280                double colorArray[] = {color.getRed(), color.getGreen(), color.getBlue()};
281                BorderExtender borderExtender = new BorderExtenderConstant(colorArray);
282                
283                ParameterBlock params = new ParameterBlock();
284                params.addSource(image());
285                params.add(thickness);
286                params.add(thickness);
287                params.add(thickness);
288                params.add(thickness);
289                if(BORDER_TYPE_CONSTANT==borderType)    params.add(borderExtender);
290                else    params.add(BorderExtender.createInstance(borderType));
291                //else if(BORDER_TYPE_WRAP==borderType)params.add(BorderExtender.createInstance(BorderExtender.BORDER_REFLECT));
292
293                image((JAI.create("border", params)).getAsBufferedImage());
294                
295        }
296        
297        public void blur(int blurFactor)  throws ExpressionException{
298            ParameterBlock params = new ParameterBlock();
299                params.addSource(image());
300                params.add(blurFactor);
301                RenderingHints hint= new RenderingHints(JAI.KEY_BORDER_EXTENDER,BorderExtender.createInstance(1));
302                image(JAI.create("boxfilter", params, hint).getAsBufferedImage());
303        }
304
305        public void clearRect(int x, int y, int width, int height)  throws ExpressionException{
306                getGraphics().clearRect(x, y, width, height);
307        }
308        
309
310        public Struct info()  throws ExpressionException{
311                if(sctInfo!=null) return sctInfo;
312                
313                Struct sctInfo=new StructImpl(),sct;
314                
315                
316                sctInfo.setEL("height",new Double(getHeight()));
317                sctInfo.setEL("width",new Double(getWidth()));
318                sctInfo.setEL("source",source==null?"":source.getAbsolutePath());
319                //sct.setEL("mime_type",getMimeType());
320                
321                ColorModel cm = image().getColorModel();
322                sct=new StructImpl();
323                sctInfo.setEL("colormodel",sct);
324                
325                sct.setEL("alpha_channel_support",Caster.toBoolean(cm.hasAlpha()));
326                sct.setEL("alpha_premultiplied",Caster.toBoolean(cm.isAlphaPremultiplied()));
327                sct.setEL("transparency",toStringTransparency(cm.getTransparency()));
328                sct.setEL("pixel_size",Caster.toDouble(cm.getPixelSize()));
329                sct.setEL("num_components",Caster.toDouble(cm.getNumComponents()));
330                sct.setEL("num_color_components",Caster.toDouble(cm.getNumColorComponents()));
331                sct.setEL("colorspace",toStringColorSpace(cm.getColorSpace()));
332                
333            //bits_component
334                int[] bitspercomponent = cm.getComponentSize();
335                Array arr=new ArrayImpl();
336                Double value;
337            for (int i = 0; i < bitspercomponent.length; i++) {
338                sct.setEL("bits_component_" + (i + 1),value=new Double(bitspercomponent[i]));
339                arr.appendEL(value);
340            }
341                sct.setEL("bits_component",arr);
342                
343            // colormodel_type
344                if (cm instanceof ComponentColorModel)          sct.setEL("colormodel_type", "ComponentColorModel");
345                else if (cm instanceof IndexColorModel)         sct.setEL("colormodel_type", "IndexColorModel");
346                else if (cm instanceof PackedColorModel)        sct.setEL("colormodel_type", "PackedColorModel");
347                else sct.setEL("colormodel_type", ListUtil.last(cm.getClass().getName(), '.'));
348
349                
350                getMetaData(sctInfo);
351                
352                ImageMeta.addInfo(format,source,sctInfo);
353                
354                this.sctInfo=sctInfo;
355                return sctInfo;
356        }
357
358        public IIOMetadata getMetaData(Struct parent) {
359        InputStream is=null;
360        javax.imageio.stream.ImageInputStreamImpl iis=null;
361        try {
362                
363                if(source instanceof File) { 
364                                iis=new FileImageInputStream((File) source);
365                        }
366                        else if(source==null)iis=new MemoryCacheImageInputStream(new ByteArrayInputStream(getImageBytes(format,true)));
367                        else iis=new MemoryCacheImageInputStream(is=source.getInputStream());
368                        
369            Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
370            if (readers.hasNext()) {
371                // pick the first available ImageReader
372                ImageReader reader = readers.next();
373                IIOMetadata meta=null;
374                synchronized (sync) {
375                        // attach source to the reader
376                        reader.setInput(iis, true);
377        
378                        // read metadata of first image
379                        meta = reader.getImageMetadata(0);
380                        meta.setFromTree(FORMAT, meta.getAsTree(FORMAT));
381                        reader.reset();
382                }
383                // generating dump
384                if(parent!=null){
385                        String[] formatNames = meta.getMetadataFormatNames();
386                                        for(int i=0;i<formatNames.length;i++) {
387                                                Node root = meta.getAsTree(formatNames[i]);
388                                                //print.out(XMLCaster.toString(root));
389                                                addMetaddata(parent,"metadata",root);
390                                        }
391                }
392                return meta;
393            }
394        }
395        catch (Throwable t) {
396                        ExceptionUtil.rethrowIfNecessary(t);
397                }
398        finally{
399                ImageUtil.closeEL(iis);
400                        IOUtil.closeEL(is);
401        }
402        return null;
403    }
404
405        private void addMetaddata(Struct parent, String name, Node node) {
406                
407                
408                // attributes
409                NamedNodeMap attrs = node.getAttributes();
410                Attr attr;
411                int len=attrs.getLength();
412                if(len==1 && "value".equals(attrs.item(0).getNodeName())) {
413                        parent.setEL(name, attrs.item(0).getNodeValue());
414                }
415                else {
416                        Struct sct=metaGetChild(parent,name);
417                        for(int i=attrs.getLength()-1;i>=0;i--) {
418                                attr=(Attr) attrs.item(i);
419                                sct.setEL(attr.getName(), attr.getValue());
420                        }
421                }
422                
423                
424                // child nodes
425                NodeList children = XMLUtil.getChildNodes(node, Node.ELEMENT_NODE);
426                Element el;
427                for(int i=children.getLength()-1;i>=0;i--) {
428                        el=(Element) children.item(i);
429                        Struct sct = metaGetChild(parent,name);
430                        addMetaddata(sct, el.getNodeName(),children.item(i));
431                }
432        }
433
434        private Struct metaGetChild(Struct parent, String name) {
435                Object child=parent.get(name,null);
436                if(child instanceof Struct) return (Struct) child;
437                Struct sct=new StructImpl();
438                parent.setEL(name, sct);
439                return sct;
440        }
441
442        public void sharpen(float gain)  throws ExpressionException{
443                ParameterBlock params = new ParameterBlock();
444                params.addSource(image());
445                params.add((Object) null);
446                params.add(new Float(gain));
447                image(JAI.create("unsharpmask", params).getAsBufferedImage());
448        }
449        
450        public void setTranparency(float percent)  throws ExpressionException{
451                if(percent==-1)return;
452                tranparency=percent;
453                AlphaComposite rule = AlphaComposite.getInstance(3, 1.0F-(percent/100.0F));
454                getGraphics().setComposite(rule);
455        }
456
457         public void invert()  throws ExpressionException{
458                ParameterBlock params = new ParameterBlock();
459                params.addSource(image());
460                image(JAI.create("invert", params).getAsBufferedImage());
461        }
462
463    public Image copy(float x, float y, float width, float height)  throws ExpressionException{
464        ParameterBlock params = new ParameterBlock();
465        params.addSource(image());
466        params.add(x);
467        params.add(y);
468        params.add(width);
469        params.add(height);
470        //image(JAI.create("crop", params).getAsBufferedImage());
471        return new Image(JAI.create("crop", params).getAsBufferedImage());
472    }
473    
474    public Image copy(float x, float y, float width, float height, float dx,float dy)  throws ExpressionException{
475        Image img = copy(x, y, width, height);
476                img.getGraphics().copyArea((int)x, (int)y, (int)width, (int)height, (int)(dx-x), (int)(dy-y));
477                return img;
478    }
479
480    public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle, boolean filled)  throws ExpressionException{
481        if (filled)
482                getGraphics().fillArc(x, y, width, height, startAngle, arcAngle);
483        else
484                getGraphics().drawArc(x, y, width, height, startAngle, arcAngle);
485    }
486    
487
488    public void draw3DRect(int x, int y, int width, int height, boolean raised, boolean filled)  throws ExpressionException{
489        if (filled)
490                getGraphics().fill3DRect(x, y, width+1, height+1, raised);
491        else
492                getGraphics().draw3DRect(x, y, width, height, raised);
493    }
494    
495    public void drawCubicCurve(double ctrlx1, double ctrly1, double ctrlx2, double ctrly2,double x1, double y1, double x2, double y2)  throws ExpressionException{
496        CubicCurve2D curve = new CubicCurve2D.Double(x1,y1,ctrlx1,ctrly1,ctrlx2,ctrly2,x2,y2);
497                getGraphics().draw(curve);
498    }
499
500    public void drawPoint(int x, int y) throws ExpressionException {
501        drawLine(x, y, x + 1, y);
502    }
503    
504    public void drawQuadraticCurve(double x1, double y1, double ctrlx, double ctrly, double x2, double y2)  throws ExpressionException {
505        QuadCurve2D curve = new QuadCurve2D.Double(x1, y1, ctrlx, ctrly, x2, y2);
506        getGraphics().draw(curve);
507    }
508    
509    public void drawRect(int x, int y, int width, int height, boolean filled)  throws ExpressionException {
510        if (filled)
511            getGraphics().fillRect(x, y, width + 1, height + 1);
512        else
513                getGraphics().drawRect(x, y, width, height);
514    }
515    
516    public void drawRoundRect(int x, int y, int width, int height,int arcWidth, int arcHeight, boolean filled)  throws ExpressionException{
517        if (filled)
518                getGraphics().fillRoundRect(x, y, width + 1, height + 1, arcWidth,arcHeight);
519        else
520                getGraphics().drawRoundRect(x, y, width, height, arcWidth, arcHeight);
521    }
522    
523
524
525        public void drawLine(int x1, int y1, int x2, int y2)  throws ExpressionException{
526        getGraphics().drawLine(x1, y1, x2, y2);
527    }
528
529        public void drawImage(Image img,int x, int y)  throws ExpressionException{
530        getGraphics().drawImage(img.image(), x, y,null);
531    }
532
533        public void drawImage(Image img,int x, int y, int width, int height)  throws ExpressionException{
534        getGraphics().drawImage(img.image(), x, y,width,height,null);
535    }
536        
537    public void drawLines(int[] xcoords, int[] ycoords, boolean isPolygon,boolean filled)  throws ExpressionException{
538        if (isPolygon) {
539                if (filled)     getGraphics().fillPolygon(xcoords, ycoords, xcoords.length);
540                else            getGraphics().drawPolygon(xcoords, ycoords, xcoords.length);
541        } 
542        else {
543                                        getGraphics().drawPolyline(xcoords, ycoords, xcoords.length);
544        }
545    }
546    public void drawOval(int x, int y, int width, int height, boolean filled) throws ExpressionException {
547        if (filled)     getGraphics().fillOval(x, y, width, height);
548        else getGraphics().drawOval(x, y, width, height);
549        }
550    
551    public void drawString(String text, int x, int y, Struct attr) throws PageException {
552        
553        if (attr != null && attr.size()>0) {
554
555         // font
556                String font=StringUtil.toLowerCase(Caster.toString(attr.get("font",""))).trim();
557            if(!StringUtil.isEmpty(font)) {
558                    font=FontUtil.getFont(font).getFontName();
559            }
560            else font = "Serif";
561            
562         // alpha
563                //float alpha=Caster.toFloatValue(attr.get("alpha",null),1F);
564            
565         // size
566            int size=Caster.toIntValue(attr.get("size", Constants.INTEGER_10));
567
568         // style
569            int style=Font.PLAIN;
570            String strStyle=StringUtil.toLowerCase(Caster.toString(attr.get("style","")));
571            strStyle=StringUtil.removeWhiteSpace(strStyle);
572            if(!StringUtil.isEmpty(strStyle)) {
573                    if("plain".equals(strStyle)) style=Font.PLAIN;
574                    else if("bold".equals(strStyle)) style=Font.BOLD;
575                    else if("italic".equals(strStyle)) style=Font.ITALIC;
576                    else if("bolditalic".equals(strStyle)) style=Font.BOLD+Font.ITALIC;
577                    else if("bold,italic".equals(strStyle)) style=Font.BOLD+Font.ITALIC;
578                    else if("italicbold".equals(strStyle)) style=Font.BOLD+Font.ITALIC;
579                    else if("italic,bold".equals(strStyle)) style=Font.BOLD+Font.ITALIC;
580                    else throw new ExpressionException(
581                                "key style of argument attributeCollection has an invalid value ["+strStyle+"], valid values are [plain,bold,italic,bolditalic]");
582            }
583
584         // strikethrough
585            boolean strikethrough = Caster.toBooleanValue(attr.get("strikethrough",Boolean.FALSE));
586            
587         // underline
588            boolean underline = Caster.toBooleanValue(attr.get("underline",Boolean.FALSE));
589            
590            AttributedString as = new AttributedString(text);
591            as.addAttribute(TextAttribute.FONT, new Font(font, style, size));
592            if(strikethrough)   as.addAttribute(TextAttribute.STRIKETHROUGH,TextAttribute.STRIKETHROUGH_ON);
593            if(underline)               as.addAttribute(TextAttribute.UNDERLINE,TextAttribute.UNDERLINE_ON);
594            Graphics2D g = getGraphics();
595            //if(alpha!=1D) setAlpha(g,alpha);
596            
597            g.drawString(as.getIterator(), x, y);
598        } 
599        else getGraphics().drawString(text, x, y);
600        
601    }
602    
603    
604    /*private void setAlpha(Graphics2D graphics,float alpha) {
605        //Composite originalComposite = graphics.getComposite();
606        
607        AlphaComposite alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
608        
609        graphics.setComposite(alphaComposite);
610        //graphics.setComposite(originalComposite);     
611        }*/
612
613        public void setDrawingStroke(Struct attr) throws PageException {
614        
615        // empty 
616        if(attr==null || attr.size()==0) {
617                setDrawingStroke(new BasicStroke());
618                return;
619        }
620        
621        // width
622        float width=Caster.toFloatValue(attr.get("width",new Float(1F)));
623        if(width<0) throw new ExpressionException("key [width] should be a none negativ number");
624        
625        // endcaps
626        String strEndcaps=Caster.toString(attr.get("endcaps","square"));
627        strEndcaps=strEndcaps.trim().toLowerCase();
628        int endcaps;
629        if("square".equals(strEndcaps))         endcaps = BasicStroke.CAP_SQUARE;
630        else if("butt".equals(strEndcaps))      endcaps = BasicStroke.CAP_BUTT;
631        else if("round".equals(strEndcaps))     endcaps = BasicStroke.CAP_ROUND;
632        else throw new ExpressionException("key [endcaps] has an invalid value ["+strEndcaps+"], valid values are [square,round,butt]");
633        
634        // linejoins
635        String strLinejoins=Caster.toString(attr.get("linejoins","miter"));
636        strLinejoins=strLinejoins.trim().toLowerCase();
637        int linejoins;
638        if("bevel".equals(strLinejoins))                linejoins = BasicStroke.JOIN_BEVEL;
639        else if("miter".equals(strLinejoins))   linejoins = BasicStroke.JOIN_MITER;
640        else if("round".equals(strLinejoins))   linejoins = BasicStroke.JOIN_ROUND;
641        else throw new ExpressionException("key [linejoins] has an invalid value ["+strLinejoins+"], valid values are [bevel,miter,round]");
642        
643        // miterlimit
644        float miterlimit = 10.0F;
645        if(linejoins==BasicStroke.JOIN_MITER) {
646                miterlimit=Caster.toFloatValue(attr.get("miterlimit",new Float(10F)));
647                if(miterlimit<1F) throw new ExpressionException("key [miterlimit] should be greater or equal to 1");
648        }
649        
650        // dashArray
651        Object oDashArray=attr.get("dashArray",null);
652        float[] dashArray=null;
653        if(oDashArray!=null) {
654                dashArray=ArrayUtil.toFloatArray(oDashArray);
655        }
656        
657        // dash_phase
658        float dash_phase=Caster.toFloatValue(attr.get("dash_phase",new Float(0F)));
659        
660        
661        
662        setDrawingStroke(width, endcaps, linejoins, miterlimit, dashArray, dash_phase);
663    }
664        
665    public void setDrawingStroke(float width, int endcaps, int linejoins,float miterlimit, float[] dash,float dash_phase)  throws ExpressionException {
666        setDrawingStroke(new BasicStroke(width, endcaps, linejoins, miterlimit, dash, dash_phase));
667        }
668            
669        public void setDrawingStroke(Stroke stroke) throws ExpressionException {
670                if(stroke==null) return;
671                this.stroke=stroke;
672            getGraphics().setStroke(stroke);
673        }
674    
675    
676    public void flip(TransposeType transpose) throws ExpressionException {
677        ParameterBlock params = new ParameterBlock();
678        params.addSource(image());
679        params.add(transpose);
680        image(JAI.create("transpose", params).getAsBufferedImage());
681    }
682
683    public void grayscale() throws ExpressionException {
684        BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_BYTE_GRAY);
685        Graphics2D graphics = img.createGraphics();
686        graphics.drawImage(image(),new AffineTransformOp(AffineTransform.getTranslateInstance(0.0, 0.0),1),0, 0);
687        graphics.dispose();
688        image(img);
689    }
690
691    public void rgb() throws ExpressionException {
692        BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_BYTE_INDEXED);
693        Graphics2D graphics = img.createGraphics();
694        graphics.drawImage(image(),new AffineTransformOp(AffineTransform.getTranslateInstance(0.0, 0.0),1),0, 0);
695        graphics.dispose();
696        image(img);
697        
698    }
699    public void threeBBger() throws ExpressionException {
700        BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
701        Graphics2D graphics = img.createGraphics();
702        graphics.drawImage(image(),new AffineTransformOp(AffineTransform.getTranslateInstance(0.0, 0.0),1),0, 0);
703        graphics.dispose();
704        image(img);
705    }
706    
707    public void overlay(Image topImage) throws ExpressionException {
708        ParameterBlock params = new ParameterBlock();
709        params.addSource(image());
710        params.addSource(topImage.image());
711        image(JAI.create("overlay", params).getAsBufferedImage());
712    }
713    
714    public void paste(Image topImage, int x, int y) throws ExpressionException {
715        RenderingHints interp = new RenderingHints(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BICUBIC);
716        BorderExtender extender = BorderExtender.createInstance(1);
717        Graphics2D g = getGraphics();
718        g.addRenderingHints(new RenderingHints(JAI.KEY_BORDER_EXTENDER,extender));
719        g.drawImage(topImage.image(), (new AffineTransformOp(AffineTransform.getTranslateInstance(x,y),interp)), 0, 0);
720        
721    }
722    
723    public void setXorMode(Color color) throws ExpressionException {
724        if(color==null) return;
725        xmColor=color;
726        getGraphics().setXORMode(color);
727    }
728    
729
730    public void translate(int xtrans, int ytrans, Object interpolation) throws ExpressionException {
731        
732        RenderingHints hints = new RenderingHints(RenderingHints.KEY_INTERPOLATION,interpolation);
733        if(interpolation!=RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR) {
734                hints.add(new RenderingHints(JAI.KEY_BORDER_EXTENDER, BorderExtender.createInstance(1)));
735        }
736        
737        ParameterBlock pb = new ParameterBlock();
738        pb.addSource(image());
739        BufferedImage img = JAI.create("translate", pb).getAsBufferedImage();
740        Graphics2D graphics = img.createGraphics();
741        graphics.clearRect(0, 0, img.getWidth(), img.getHeight());
742        AffineTransform at = new AffineTransform();
743        at.setToIdentity();
744        graphics.drawImage(image(), new AffineTransformOp(at, hints), xtrans, ytrans);
745        graphics.dispose();
746        image(img);
747    }
748    
749    public void translateAxis(int x, int y) throws ExpressionException {
750        getGraphics().translate(x, y);
751    }
752
753    public void rotateAxis(double angle) throws ExpressionException {
754        getGraphics().rotate(Math.toRadians(angle));
755    }
756        
757    public void rotateAxis(double angle, double x, double y) throws ExpressionException {
758        getGraphics().rotate(Math.toRadians(angle), x, y);
759    }
760        
761    public void shearAxis(double shx, double shy) throws ExpressionException {
762        getGraphics().shear(shx, shy);
763    }
764    
765    public void shear(float shear, ShearDir direction, Object interpolation) throws ExpressionException {
766        ParameterBlock params = new ParameterBlock();
767        params.addSource(image());
768        params.add(shear);
769        params.add(direction);
770        params.add(0.0F);
771        params.add(0.0F);
772        RenderingHints hints = null;
773        
774        if (interpolation==RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)
775            params.add(Interpolation.getInstance(0));
776        else if (interpolation==RenderingHints.VALUE_INTERPOLATION_BILINEAR) {
777            params.add(Interpolation.getInstance(1));
778            BorderExtender extender = BorderExtender.createInstance(1);
779            hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER, extender);
780        } 
781        else if (interpolation==RenderingHints.VALUE_INTERPOLATION_BICUBIC) {
782            params.add(Interpolation.getInstance(2));
783            BorderExtender extender = BorderExtender.createInstance(1);
784            hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER, extender);
785        }
786        // TODO
787        Color bg = getGraphics().getBackground();
788        params.add(new double[]{bg.getRed(),bg.getGreen(),bg.getBlue()});
789        image(JAI.create("shear", params, hints).getAsBufferedImage());
790    }
791    
792        public BufferedImage getBufferedImage() throws ExpressionException {
793                return image();
794        }
795        public BufferedImage image() throws ExpressionException {
796                if(_image==null) throw (new ExpressionException("image is not initalized"));
797                return _image;
798        }
799        public void image(BufferedImage image) {
800                this._image=image;
801                graphics=null;
802                
803                sctInfo=null;
804        }
805        
806    private Graphics2D getGraphics() throws ExpressionException {
807                if(graphics==null) {
808                        graphics=image() .createGraphics();
809                        // reset all properties
810                        if(antiAlias!=ANTI_ALIAS_NONE)  setAntiAliasing(antiAlias==ANTI_ALIAS_ON);
811                        if(bgColor!=null)       setBackground(bgColor);
812                        if(fgColor!=null)       setColor(fgColor);
813                        if(alpha!=1)            setAlpha(alpha);
814                        if(tranparency!=-1)     setTranparency(tranparency);
815                        if(xmColor!=null)       setXorMode(xmColor);
816                        if(stroke!=null)        setDrawingStroke(stroke);
817                }
818        return graphics;
819        }
820    
821    
822        private String toStringColorSpace(ColorSpace colorSpace) {
823                switch (colorSpace.getType()) {
824            case 0: return "Any of the family of XYZ color spaces";
825            case 1: return "Any of the family of Lab color spaces";
826            case 2: return "Any of the family of Luv color spaces";
827            case 3: return "Any of the family of YCbCr color spaces";
828            case 4:return "Any of the family of Yxy color spaces";
829            case 5: return "Any of the family of RGB color spaces";
830            case 6: return "Any of the family of GRAY color spaces";
831            case 7: return "Any of the family of HSV color spaces";
832            case 8: return "Any of the family of HLS color spaces";
833            case 9: return "Any of the family of CMYK color spaces";
834            case 11: return "Any of the family of CMY color spaces";
835            case 12: return "Generic 2 component color space.";
836            case 13: return "Generic 3 component color space.";
837            case 14: return "Generic 4 component color space.";
838            case 15: return "Generic 5 component color space.";
839            case 16: return "Generic 6 component color space.";
840            case 17: return "Generic 7 component color space.";
841            case 18: return "Generic 8 component color space.";
842            case 19: return "Generic 9 component color space.";
843            case 20: return "Generic 10 component color space.";
844            case 21: return "Generic 11 component color space.";
845            case 22: return "Generic 12 component color space.";
846            case 23: return "Generic 13 component color space.";
847            case 24: return "Generic 14 component color space.";
848            case 25: return "Generic 15 component color space.";
849            case 1001: return "CIEXYZ";
850            case 1003: return "GRAY";
851            case 1004: return "LINEAR_RGB";
852            case 1002: return "PYCC";
853            case 1000: return "sRGB";
854            }
855                
856                return "Unknown ColorSpace" + colorSpace;
857        }
858
859        private Object toStringTransparency(int transparency) {
860                if(Transparency.OPAQUE==transparency)           return "OPAQUE";
861                if(Transparency.BITMASK==transparency)          return "BITMASK";
862                if(Transparency.TRANSLUCENT==transparency)      return "TRANSLUCENT";
863                return "Unknown type of transparency";
864        }
865        
866        public String writeBase64(Resource destination, String format, boolean inHTMLFormat) throws PageException, IOException {
867                // destination
868                if(destination==null) {
869                        if(source!=null)destination=source;
870                        else throw new IOException("missing destination file");
871                }
872                
873                String content = getBase64String(format);
874                if(inHTMLFormat) content="data:image/" + format + ";base64,"+content;
875                IOUtil.write(destination, content, (Charset)null, false);
876                return content;
877        }
878        
879        public String getBase64String(String format) throws PageException {
880                byte[] imageBytes = getImageBytes(format);
881                return new String(Base64.encodeBase64(imageBytes));
882        }
883        
884        public void writeOut(Resource destination, boolean overwrite, float quality) throws IOException, ExpressionException {
885                String format = ImageUtil.getFormatFromExtension(destination,null);
886                writeOut(destination, format, overwrite, quality);
887        }
888        
889        public void writeOut(Resource destination, String format,boolean overwrite, float quality) throws IOException, ExpressionException {
890                if(destination==null) {
891                        if(source!=null)destination=source;
892                        else throw new IOException("missing destination file");
893                }
894                
895                if(destination.exists()) {
896                        if(!overwrite)throw new IOException("can't overwrite existing image");
897                }
898
899        if(JAIUtil.isSupportedWriteFormat(format)){
900                JAIUtil.write(getBufferedImage(),destination,format);
901                return;
902        }
903                OutputStream os=null;
904                ImageOutputStream ios = null;
905                try {
906                        os=destination.getOutputStream();
907                        ios = ImageIO.createImageOutputStream(os);
908                        _writeOut(ios, format, quality);
909                }
910                finally {
911                        ImageUtil.closeEL(ios);
912                        IOUtil.closeEL(os);
913                }               
914        }
915
916        
917        public static void writeOutGif(BufferedImage src, OutputStream os) throws IOException {
918                BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_ARGB);
919                QuantizeFilter filter=new QuantizeFilter();
920                filter.setSerpentine(true);
921                filter.setDither(true);
922                //filter.setNumColors(8);
923                filter.filter(src, dst);
924                
925
926                //image(Quantizer.quantize(image(), 8));
927                try {
928                        GifEncoder enc = new GifEncoder(dst);
929                        enc.Write(os);
930                        os.flush();
931                } catch (AWTException e) {
932                        throw new IOException(e.getMessage());
933                }
934        }
935        
936        public void writeOut(OutputStream os, String format,float quality, boolean closeStream) throws IOException, ExpressionException {
937                ImageOutputStream ios = ImageIO.createImageOutputStream(os);
938                try{
939                        _writeOut(ios, format, quality);
940                }
941                finally{
942                        IOUtil.closeEL(ios);
943                }
944        }
945
946        private void _writeOut(ImageOutputStream ios, String format,float quality) throws IOException, ExpressionException {
947                _writeOut(ios, format, quality, false);
948        }
949        
950        private void _writeOut(ImageOutputStream ios, String format,float quality,boolean noMeta) throws IOException, ExpressionException {
951                if(quality<0 || quality>1)
952                        throw new IOException("quality has an invalid value ["+quality+"], value has to be between 0 and 1");
953                if(StringUtil.isEmpty(format))  format=this.format;
954                if(StringUtil.isEmpty(format))  throw new IOException("missing format");
955                
956                BufferedImage im = image();
957                
958                //IIOMetadata meta = noMeta?null:metadata(format);
959                IIOMetadata meta = noMeta?null:getMetaData(null);
960                
961                
962                
963                ImageWriter writer = null;
964        ImageTypeSpecifier type =ImageTypeSpecifier.createFromRenderedImage(im);
965        Iterator<ImageWriter> iter = ImageIO.getImageWriters(type, format);
966        
967        
968        if (iter.hasNext()) {
969                writer = iter.next();
970        }
971        if (writer == null) throw new IOException("no writer for format ["+format+"] available, available writer formats are ["+ListUtil.arrayToList(ImageUtil.getWriterFormatNames(), ",")+"]");
972        
973        
974                ImageWriteParam iwp=null;
975        if("jpg".equalsIgnoreCase(format)) {
976                ColorModel cm = im.getColorModel();
977                if(cm.hasAlpha())im=jpgImage(im);
978                JPEGImageWriteParam jiwp = new JPEGImageWriteParam(Locale.getDefault());
979                jiwp.setOptimizeHuffmanTables(true);
980                iwp=jiwp;
981        }
982        else iwp = writer.getDefaultWriteParam();
983        
984                setCompressionModeEL(iwp,ImageWriteParam.MODE_EXPLICIT);
985        setCompressionQualityEL(iwp,quality);
986        writer.setOutput(ios);
987        try {
988                writer.write(meta, new IIOImage(im, null, meta), iwp);
989                
990        } 
991        finally {
992                writer.dispose();
993                ios.flush();
994        }
995        }
996        
997        private BufferedImage jpgImage(BufferedImage src) {
998        int w = src.getWidth();
999        int h = src.getHeight();
1000        SampleModel srcSM = src.getSampleModel();
1001        WritableRaster srcWR = src.getRaster();
1002        java.awt.image.DataBuffer srcDB = srcWR.getDataBuffer();
1003        
1004        ColorModel rgb = new DirectColorModel(32, 0xff0000, 65280, 255);
1005        int[] bitMasks = new int[]{0xff0000, 65280, 255};
1006        
1007        SampleModel csm = new SinglePixelPackedSampleModel(3, w, h, bitMasks);
1008        int data[] = new int[w * h];
1009        for(int i = 0; i < h; i++) {
1010            for(int j = 0; j < w; j++) {
1011                int pix[] = null;
1012                int sample[] = srcSM.getPixel(j, i, pix, srcDB);
1013                if(sample[3] == 0 && sample[2] == 0 && sample[1] == 0 && sample[0] == 0)
1014                    data[i * w + j] = 0xffffff;
1015                else
1016                    data[i * w + j] = sample[0] << 16 | sample[1] << 8 | sample[2];
1017            }
1018
1019        }
1020
1021        java.awt.image.DataBuffer db = new DataBufferInt(data, w * h * 3);
1022        WritableRaster wr = Raster.createWritableRaster(csm, db, new Point(0, 0));
1023        return new BufferedImage(rgb, wr, false, null);
1024    }
1025
1026        private void setCompressionModeEL(ImageWriteParam iwp, int mode) {
1027                try {
1028                        iwp.setCompressionMode(mode);
1029                }
1030                catch(Throwable t) {
1031                        ExceptionUtil.rethrowIfNecessary(t);
1032                }
1033        }
1034
1035        private void setCompressionQualityEL(ImageWriteParam iwp, float quality) {
1036                try {
1037                        iwp.setCompressionQuality(quality);
1038                }
1039                catch(Throwable t) {
1040                        ExceptionUtil.rethrowIfNecessary(t);
1041                }
1042        }
1043
1044        public void convert(String format) {
1045                this.format=format;
1046        }
1047
1048        public void scaleToFit(String fitWidth, String fitHeight,String interpolation, double blurFactor) throws PageException {
1049                if (StringUtil.isEmpty(fitWidth) || StringUtil.isEmpty(fitHeight))      
1050                        resize(fitWidth, fitHeight, interpolation, blurFactor);
1051                else {
1052                        float width = Caster.toFloatValue(fitWidth) / getWidth();
1053                        float height= Caster.toFloatValue(fitHeight) / getHeight();
1054                        if (width < height)  resize(fitWidth, "", interpolation, blurFactor);
1055                        else                            resize("", fitHeight, interpolation, blurFactor);
1056                }
1057        }
1058        
1059        
1060        
1061        /**
1062     * Convenience method that returns a scaled instance of the
1063     * provided {@code BufferedImage}.
1064     *
1065     * @param img the original image to be scaled
1066     * @param targetWidth the desired width of the scaled instance,
1067     *    in pixels
1068     * @param targetHeight the desired height of the scaled instance,
1069     *    in pixels
1070     * @param hint one of the rendering hints that corresponds to
1071     *    {@code RenderingHints.KEY_INTERPOLATION} (e.g.
1072     *    {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR},
1073     *    {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR},
1074     *    {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC})
1075     * @param higherQuality if true, this method will use a multi-step
1076     *    scaling technique that provides higher quality than the usual
1077     *    one-step technique (only useful in downscaling cases, where
1078     *    {@code targetWidth} or {@code targetHeight} is
1079     *    smaller than the original dimensions, and generally only when
1080     *    the {@code BILINEAR} hint is specified)
1081     * @return a scaled version of the original {@code BufferedImage}
1082     */
1083    private BufferedImage getScaledInstance(BufferedImage img,
1084                                           int targetWidth,
1085                                           int targetHeight,
1086                                           Object hint,
1087                                           boolean higherQuality)
1088    {
1089        // functionality not supported in java 1.4
1090        int transparency=Transparency.OPAQUE;
1091        try {
1092                        transparency=img.getTransparency();
1093                } 
1094        catch (Throwable t) {
1095                        ExceptionUtil.rethrowIfNecessary(t);
1096                }
1097        int type = (transparency == Transparency.OPAQUE) ?BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
1098        
1099        
1100        
1101        BufferedImage ret = img;
1102        int w, h;
1103        if (higherQuality) {
1104            // Use multi-step technique: start with original size, then
1105            // scale down in multiple passes with drawImage()
1106            // until the target size is reached
1107            w = img.getWidth();
1108            h = img.getHeight();
1109        } else {
1110            // Use one-step technique: scale directly from original
1111            // size to target size with a single drawImage() call
1112            w = targetWidth;
1113            h = targetHeight;
1114        }
1115        
1116        do {
1117            if (higherQuality && w > targetWidth) {
1118                w /= 2;
1119                if (w < targetWidth) {
1120                    w = targetWidth;
1121                }
1122            }
1123
1124            if (higherQuality && h > targetHeight) {
1125                h /= 2;
1126                if (h < targetHeight) {
1127                    h = targetHeight;
1128                }
1129            }
1130
1131            BufferedImage tmp = new BufferedImage(w, h, type);
1132            Graphics2D g2 = tmp.createGraphics();
1133            g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
1134            g2.drawImage(ret, 0, 0, w, h, null);
1135            g2.dispose();
1136
1137            ret = tmp;
1138        } while (w != targetWidth || h != targetHeight);
1139
1140        return ret;
1141    }
1142
1143        
1144        
1145        
1146        
1147    public void resize(int scale, String interpolation, double blurFactor) throws PageException {
1148        if (blurFactor <= 0.0 || blurFactor > 10.0)
1149                        throw new ExpressionException("blurFactor must be between 0 and 10");
1150        
1151                float width=getWidth()/100F*scale;
1152                float height=getHeight()/100F*scale;
1153                        
1154                resize((int)width, (int)height, toInterpolation(interpolation), blurFactor);
1155    }
1156        
1157    public void resize(String strWidth, String strHeight, String interpolation, double blurFactor) throws PageException {
1158                if (StringUtil.isEmpty(strWidth,true) && StringUtil.isEmpty(strHeight,true))
1159                        throw new ExpressionException("you have to define width or height");
1160                if (blurFactor <= 0.0 || blurFactor > 10.0)
1161                        throw new ExpressionException("blurFactor must be between 0 and 10");
1162                int w = getWidth();
1163                int h = getHeight();
1164                float height=resizeDimesion("height",strHeight, h);
1165                float width=resizeDimesion("width",strWidth, w);
1166                
1167                if(height==-1)  height=h*(width/w);
1168                if(width==-1)   width=w*(height/h);
1169                        
1170                resize((int)width, (int)height, toInterpolation(interpolation), blurFactor);
1171    }
1172        
1173    public void resizeImage2(int width, int height) throws ExpressionException{
1174        image(getScaledInstance(image(),width,height,RenderingHints.VALUE_INTERPOLATION_BILINEAR,false));
1175    }
1176        
1177    public void resizeImage(int width, int height, int interpolation) throws ExpressionException{
1178        Object ip;
1179        if(interpolation==IPC_NEAREST)          ip=RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
1180        else if(interpolation==IPC_BICUBIC)     ip=RenderingHints.VALUE_INTERPOLATION_BICUBIC;
1181        else if(interpolation==IPC_BILINEAR)    ip=RenderingHints.VALUE_INTERPOLATION_BILINEAR;
1182        else throw new ExpressionException("invalid interpoltion definition");
1183        
1184        BufferedImage dst = new BufferedImage(width,height,image().getType());
1185        Graphics2D graphics = dst.createGraphics();
1186        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,ip); 
1187        graphics.drawImage(image(), 0, 0, width, height, null);
1188        graphics.dispose();
1189        image(dst);
1190        
1191    }
1192        
1193        private float resizeDimesion(String label,String strDimension, float originalDimension) throws PageException {
1194                if (StringUtil.isEmpty(strDimension,true)) return -1;
1195                strDimension=strDimension.trim();
1196                
1197                if (StringUtil.endsWith(strDimension, '%')) {
1198                        float p = Caster.toFloatValue(strDimension.substring(0,(strDimension.length()- 1))) / 100.0F;
1199                        return originalDimension*p;
1200                }
1201                float dimension = Caster.toFloatValue(strDimension);
1202                if (dimension <= 0F)
1203                        throw new ExpressionException(label+" has to be a none negative number");
1204                return dimension;
1205        }
1206        
1207        
1208
1209    public void resize(int width, int height, int interpolation, double blurFactor) throws ExpressionException {
1210        
1211                ColorModel cm = image().getColorModel();
1212                
1213            if (interpolation==IP_HIGHESTPERFORMANCE)   {
1214                interpolation = IPC_BICUBIC;
1215            }
1216            
1217            if (cm.getColorSpace().getType() == ColorSpace.TYPE_GRAY && cm.getComponentSize()[0] == 8) {
1218                if (interpolation==IP_HIGHESTQUALITY || interpolation==IP_HIGHPERFORMANCE || interpolation==IP_HIGHQUALITY || interpolation==IP_MEDIUMPERFORMANCE || interpolation==IP_MEDIUMQUALITY)   {
1219                        interpolation = IPC_BICUBIC;
1220                }
1221                if (interpolation!=IPC_BICUBIC && interpolation!=IPC_BILINEAR && interpolation!=IPC_NEAREST)    {
1222                        throw new ExpressionException("invalid grayscale interpolation");
1223                }
1224            }
1225            
1226            if (interpolation<=IPC_MAX)      {
1227                resizeImage(width, height, interpolation);
1228            }
1229            else {
1230                image(ImageResizer.resize(image(), width, height, interpolation, blurFactor));
1231                        
1232            }
1233        }
1234    
1235    
1236    
1237    /*private BufferedImage resizeImageWithJAI(float scaleWidth, float scaleHeight, int interpolation) throws ExpressionException {
1238        ParameterBlock params = new ParameterBlock();
1239        params.addSource(image());
1240                params.add(scaleWidth);
1241                params.add(scaleHeight);
1242                params.add(0.0F);
1243                params.add(0.0F);
1244                RenderingHints hints = null;
1245                if (interpolation != IP_NONE) {
1246                    if (interpolation==IP_NEAREST)      {
1247                        params.add(Interpolation.getInstance(0));
1248                    }
1249                    else if (interpolation==IP_BILINEAR) {
1250                        params.add(Interpolation.getInstance(1));
1251                        BorderExtender extender = BorderExtender.createInstance(1);
1252                        hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER, extender);
1253                    } 
1254                    else if (interpolation==IP_BICUBIC) {
1255                        params.add(Interpolation.getInstance(2));
1256                        BorderExtender extender = BorderExtender.createInstance(1);
1257                        hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER, extender);
1258                    } 
1259                    else        {
1260                        throw new ExpressionException("invalid interpolation definition");
1261                    }
1262                }
1263                return JAI.create("scale", params, hints).getAsBufferedImage();
1264    }*/
1265        
1266    private double toScale(int src, int dst) {
1267        double tmp = Math.round((int)((Caster.toDoubleValue(dst)/Caster.toDoubleValue(src))*100D));
1268        return tmp/100D;
1269        }
1270
1271        public void rotate(float x, float y, float angle, int interpolation) throws ExpressionException {
1272        if(x==-1)x = (float)getWidth() / 2;
1273        if(y==-1)y = (float)getHeight() / 2;
1274                
1275        angle = (float) Math.toRadians(angle);
1276        ColorModel cmSource = image().getColorModel();
1277        
1278        if (cmSource instanceof IndexColorModel && cmSource.hasAlpha() && !cmSource.isAlphaPremultiplied()) {
1279                image(PaletteToARGB(image()));
1280            cmSource = image().getColorModel();
1281        }
1282        
1283        BufferedImage alpha = null;
1284        if (cmSource.hasAlpha() && !cmSource.isAlphaPremultiplied()) {
1285            alpha = getAlpha(image());
1286            image(removeAlpha(image()));
1287        }
1288        
1289        Interpolation interp = Interpolation.getInstance(0);
1290        if (INTERPOLATION_BICUBIC==interpolation)       interp = Interpolation.getInstance(1);
1291        else if (INTERPOLATION_BILINEAR==interpolation)         interp = Interpolation.getInstance(2);
1292        
1293        if (alpha != null) {
1294            ParameterBlock params = new ParameterBlock();
1295            params.addSource(alpha);
1296            params.add(x);
1297            params.add(y);
1298            params.add(angle);
1299            params.add(interp);
1300            params.add(new double[] { 0.0 });
1301            RenderingHints hints= new RenderingHints(RenderingHints.KEY_INTERPOLATION,(RenderingHints.VALUE_INTERPOLATION_BICUBIC));
1302            hints.add(new RenderingHints(JAI.KEY_BORDER_EXTENDER,new BorderExtenderConstant(new double[] { 255.0 })));
1303            hints.add(new RenderingHints(JAI.KEY_REPLACE_INDEX_COLOR_MODEL,Boolean.TRUE));
1304            alpha = JAI.create("rotate", params, hints).getAsBufferedImage();
1305        }
1306        
1307        ParameterBlock params = new ParameterBlock();
1308        params.addSource(image());
1309        params.add(x);
1310        params.add(y);
1311        params.add(angle);
1312        params.add(interp);
1313        params.add(new double[] { 0.0 });
1314        BorderExtender extender= new BorderExtenderConstant(new double[] { 0.0 });
1315        RenderingHints hints= new RenderingHints(JAI.KEY_BORDER_EXTENDER, extender);
1316        hints.add(new RenderingHints(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.TRUE));
1317        image(JAI.create("rotate", params, hints).getAsBufferedImage());
1318        if (alpha != null)image(addAlpha(image(), alpha, 0, 0));
1319    }
1320    
1321    private static BufferedImage PaletteToARGB(BufferedImage src) {
1322        IndexColorModel icm = (IndexColorModel) src.getColorModel();
1323        int bands = icm.hasAlpha()?4:3;
1324        
1325        byte[][] data = new byte[bands][icm.getMapSize()];
1326        if (icm.hasAlpha()) icm.getAlphas(data[3]);
1327        icm.getReds(data[0]);
1328        icm.getGreens(data[1]);
1329        icm.getBlues(data[2]);
1330        LookupTableJAI rtable = new LookupTableJAI(data);
1331        return JAI.create("lookup", src, rtable).getAsBufferedImage();
1332    }
1333
1334    
1335    private static BufferedImage getAlpha(BufferedImage src) {
1336        return JAI.create("bandselect", src, new int[] { 3 }).getAsBufferedImage();
1337    }
1338    
1339    private static BufferedImage removeAlpha(BufferedImage src) {
1340        return JAI.create("bandselect", src, new int[] { 0, 1, 2 }).getAsBufferedImage();
1341    }
1342    
1343    private static BufferedImage addAlpha(BufferedImage src, BufferedImage alpha, int x, int y) {
1344        int w = src.getWidth();
1345        int h = src.getHeight();
1346        BufferedImage bi = new BufferedImage(w, h, 2);
1347        WritableRaster wr = bi.getWritableTile(0, 0);
1348        WritableRaster wr3 = wr.createWritableChild(0, 0, w, h, 0, 0, new int[] { 0, 1, 2 });
1349        WritableRaster wr1 = wr.createWritableChild(0, 0, w, h, 0, 0, new int[] { 3 });
1350        wr3.setRect(src.getData());
1351        wr1.setRect(alpha.getData());
1352        bi.releaseWritableTile(0, 0);
1353        return bi;
1354    }
1355
1356        
1357
1358        public void _rotate(float x, float y, float angle, String interpolation) throws ExpressionException {
1359
1360                float radiansAngle = (float)Math.toRadians(angle);
1361
1362                // rotation center
1363                float centerX = (float)getWidth() / 2;
1364                float centerY = (float)getHeight() / 2;
1365                
1366                ParameterBlock pb = new ParameterBlock();
1367                pb.addSource(image());
1368                pb.add(centerX);
1369                pb.add(centerY);
1370                pb.add(radiansAngle);
1371                pb.add(new javax.media.jai.InterpolationBicubic(10));
1372                
1373                // create a new, rotated image
1374                image(JAI.create("rotate", pb).getAsBufferedImage());
1375
1376        }
1377
1378        public static Image toImage(Object obj) throws PageException {
1379                return toImage(ThreadLocalPageContext.get(), obj, true);
1380        }
1381        
1382        // used in bytecode
1383        public static Image toImage(Object obj,PageContext pc) throws PageException {
1384                return toImage(pc, obj, true);
1385        }
1386        
1387
1388        public static Image toImage(PageContext pc,Object obj) throws PageException {
1389                return toImage(pc, obj, true);
1390        }
1391        
1392        public static Image toImage(PageContext pc,Object obj, boolean checkForVariables) throws PageException {
1393                if(obj instanceof Image) return (Image) obj;
1394                if(obj instanceof ObjectWrap) return toImage(pc,((ObjectWrap)obj).getEmbededObject(),checkForVariables);
1395                
1396                // try to load from binary
1397                if(Decision.isBinary(obj)) {
1398                        try {
1399                                return new Image(Caster.toBinary(obj),null);
1400                        }
1401                        catch (IOException e) {
1402                                throw Caster.toPageException(e);
1403                        }
1404                }
1405                // try to load from String (base64)
1406                if(Decision.isString(obj)) {
1407                        String str=Caster.toString(obj);
1408                        if(checkForVariables && pc!=null) {
1409                                Object o = VariableInterpreter.getVariableEL(pc, str, null);
1410                                if(o!=null) return toImage(pc, o, false);
1411                        }
1412                        try {
1413                                return new Image(str);
1414                        }
1415                        catch (IOException e) {
1416                                throw Caster.toPageException(e);
1417                        }
1418                }
1419                
1420                throw new CasterException(obj,"Image");
1421        }
1422
1423        public static Image toImage(PageContext pc,Object obj, boolean checkForVariables, Image defaultValue) {
1424                try {
1425                        return toImage(pc, obj, checkForVariables);
1426                }
1427                catch (Throwable t) {
1428                        ExceptionUtil.rethrowIfNecessary(t);
1429                        return defaultValue;
1430                }
1431        }
1432
1433        public static boolean isImage(Object obj) {
1434                if(obj instanceof Image) return true;
1435                if(obj instanceof ObjectWrap) return isImage(((ObjectWrap)obj).getEmbededObject(""));
1436                return false;
1437        }
1438        
1439        @Override
1440        public Object call(PageContext pc, Key methodName, Object[] args) throws PageException {
1441                Object obj = get(methodName,null);
1442                if(obj instanceof UDFPlus) {
1443                        return ((UDFPlus)obj).call(pc,methodName,args,false);
1444                }
1445                return MemberUtil.call(pc, this, methodName, args, CFTypes.TYPE_IMAGE, "image");
1446        }
1447
1448    @Override
1449        public Object callWithNamedValues(PageContext pc, Key methodName, Struct args) throws PageException {
1450                Object obj = get(methodName,null);
1451                if(obj instanceof UDFPlus) {
1452                        return ((UDFPlus)obj).callWithNamedValues(pc,methodName,args,false);
1453                }
1454                return MemberUtil.callWithNamedValues(pc,this,methodName,args, CFTypes.TYPE_IMAGE, "image");
1455        }
1456
1457        public static boolean isCastableToImage(PageContext pc,Object obj) {
1458                if(isImage(obj)) return true;
1459                return toImage(pc, obj, true, null)!=null;
1460        }
1461
1462        public static Image createImage(PageContext pc,Object obj, boolean check4Var, boolean clone, boolean checkAccess, String format) throws PageException {
1463                try {
1464                        if(obj instanceof String || obj instanceof Resource || obj instanceof File) {
1465                                try {
1466                                        Resource res = Caster.toResource(pc,obj,true);
1467                                        pc.getConfig().getSecurityManager().checkFileLocation(res);
1468                                        return new Image(res,format);
1469                                } 
1470                                catch (ExpressionException ee) {
1471                                        if(check4Var && Decision.isVariableName(Caster.toString(obj))) {
1472                                                try {
1473                                                        return createImage(pc, pc.getVariable(Caster.toString(obj)), false,clone,checkAccess,format);
1474                                                }
1475                                                catch (Throwable t) {
1476                                                        ExceptionUtil.rethrowIfNecessary(t);
1477                                                        throw ee;
1478                                                }
1479                                        }
1480                                        try {
1481                                                return new Image(Caster.toString(obj),format);
1482                                        }
1483                                        catch (Throwable t) {
1484                                                ExceptionUtil.rethrowIfNecessary(t);
1485                                                throw ee;
1486                                        }
1487                                }
1488                        }
1489                        if(obj instanceof Image)        {
1490                                if(clone)return (Image) ((Image)obj).clone();
1491                                return (Image)obj;
1492                        }
1493                        if(Decision.isBinary(obj))                      return new Image(Caster.toBinary(obj),format);
1494                        if(obj instanceof BufferedImage)        return new Image(((BufferedImage) obj));
1495                        if(obj instanceof java.awt.Image)       return new Image(toBufferedImage((java.awt.Image) obj));
1496                        
1497                } catch (Throwable t) {
1498                        throw Caster.toPageException(t);
1499                }
1500                throw new CasterException(obj,"Image");
1501        }
1502
1503        @Override
1504        public Collection duplicate(boolean deepCopy) {
1505                try {
1506                        //if(_image!=null) return new Image(getBufferedImage());
1507                        return new Image(getImageBytes(null));
1508                        
1509                } catch (Exception e) {
1510                        throw new PageRuntimeException(e.getMessage());
1511                }
1512        }
1513        
1514        
1515        
1516
1517        public ColorModel getColorModel() throws ExpressionException {
1518                return image().getColorModel();
1519        }
1520    
1521    public void crop(float x, float y, float width, float height) throws ExpressionException {
1522        ParameterBlock params = new ParameterBlock();
1523        params.addSource(image());
1524        params.add(x);
1525        params.add(y);
1526
1527        float w = getWidth();
1528        float h = getHeight();
1529        
1530        if (w < x + width) params.add(w - x);
1531        else params.add(width);
1532        
1533        if (h < y + height) params.add(h - y);
1534        else params.add(height);
1535        
1536        image(JAI.create("crop", params).getAsBufferedImage());
1537    }
1538    
1539    public int getWidth() throws ExpressionException {
1540        return image().getWidth();
1541    }
1542    
1543    public int getHeight() throws ExpressionException {
1544        return image().getHeight();
1545    }
1546
1547        public String getFormat() {
1548                return format;
1549        }
1550
1551        public byte[] getImageBytes(String format) throws PageException{
1552                return getImageBytes(format,false);
1553        }
1554        public byte[] getImageBytes(String format,boolean noMeta) throws PageException {
1555                
1556                
1557                ByteArrayOutputStream baos = new ByteArrayOutputStream();
1558                
1559                if(JAIUtil.isSupportedWriteFormat(format)){
1560                try {
1561                                JAIUtil.write(getBufferedImage(),baos,format);
1562                        }catch (IOException e) {
1563                                throw Caster.toPageException(e);
1564                        }
1565        }
1566                else {
1567                        ImageOutputStream ios = null;
1568                        try {
1569                                ios = ImageIO.createImageOutputStream(baos);
1570                                _writeOut(ios, format, 1,noMeta);
1571                        } catch (IOException e) {
1572                                throw Caster.toPageException(e);
1573                        }
1574                        finally {
1575                                IOUtil.closeEL(ios);
1576                        }
1577                }
1578                return baos.toByteArray();
1579        }
1580
1581        public void setColor(Color color) throws ExpressionException {
1582                if(color==null) return;
1583                fgColor=color;
1584                getGraphics().setColor(color);
1585        }
1586        
1587        public void setAlpha(float alpha) throws ExpressionException {
1588                this.alpha=alpha;
1589                Graphics2D g = getGraphics();
1590                
1591                Composite alphaComposite;
1592                if(composite==null) {
1593                        if(alpha==1) return;
1594                        composite = g.getComposite();
1595                }
1596                if(alpha==1) alphaComposite=composite;
1597                else alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
1598        
1599        g.setComposite(alphaComposite);
1600        //graphics.setComposite(originalComposite);     
1601        }       
1602        
1603        public void setBackground(Color color) throws ExpressionException {
1604                if(color==null) return;
1605                bgColor=color;
1606                getGraphics().setBackground(color);
1607        }
1608        
1609        public void setAntiAliasing(boolean antiAlias) throws ExpressionException {
1610                this.antiAlias=antiAlias?ANTI_ALIAS_ON:ANTI_ALIAS_OFF;
1611                Graphics2D graphics = getGraphics();
1612                if(antiAlias) {
1613                    graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
1614                    graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
1615                }
1616                else {
1617                    graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
1618                    graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
1619                } 
1620        }
1621        
1622        private Struct _info() {
1623                try {
1624                        return info();
1625                } catch (ExpressionException e) {
1626                        throw new PageRuntimeException(e);
1627                }
1628        }
1629
1630        @Override
1631        public void clear() {
1632                throw new RuntimeException("can't clear struct, struct is readonly");
1633        }
1634
1635        @Override
1636        public boolean containsKey(Key key) {
1637                return _info().containsKey(key);
1638        }
1639
1640        @Override
1641        public Object get(Key key) throws PageException {
1642                return info().get(key);
1643        }
1644
1645        @Override
1646        public Object get(Key key, Object defaultValue) {
1647                return _info().get(key, defaultValue);
1648        }
1649
1650        @Override
1651        public Key[] keys() {
1652                return _info().keys();
1653        }
1654
1655        @Override
1656        public Object remove(Key key) throws PageException {
1657                throw new ExpressionException("can't remove key ["+key.getString()+"] from struct, struct is readonly");
1658        }
1659
1660        @Override
1661        public Object removeEL(Key key) {
1662                throw new PageRuntimeException("can't remove key ["+key.getString()+"] from struct, struct is readonly");
1663        }
1664
1665        @Override
1666        public Object set(Key key, Object value) throws PageException {
1667                throw new ExpressionException("can't set key ["+key.getString()+"] to struct, struct is readonly");
1668        }
1669
1670        @Override
1671        public Object setEL(Key key, Object value) {
1672                throw new PageRuntimeException("can't set key ["+key.getString()+"] to struct, struct is readonly");
1673        }
1674
1675        @Override
1676        public int size() {
1677                return _info().size();
1678        }
1679
1680        @Override
1681        public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) {
1682                DumpData dd = _info().toDumpData(pageContext, maxlevel,dp);
1683                if(dd instanceof DumpTable)((DumpTable)dd).setTitle("Struct (Image)");
1684                return dd;
1685        }
1686
1687        @Override
1688        public Iterator<Collection.Key> keyIterator() {
1689                return _info().keyIterator();
1690        }
1691    
1692    @Override
1693        public Iterator<String> keysAsStringIterator() {
1694        return _info().keysAsStringIterator();
1695    }
1696        
1697        @Override
1698        public Iterator<Entry<Key, Object>> entryIterator() {
1699                return _info().entryIterator();
1700        }
1701        
1702        @Override
1703        public Iterator<Object> valueIterator() {
1704                return _info().valueIterator();
1705        }
1706
1707        @Override
1708        public boolean castToBooleanValue() throws PageException {
1709                return info().castToBooleanValue();
1710        }
1711
1712        @Override
1713        public Boolean castToBoolean(Boolean defaultValue) {
1714                try {
1715                        return info().castToBoolean(defaultValue);
1716                } catch (ExpressionException e) {
1717                        return defaultValue;
1718                }
1719        }
1720
1721        @Override
1722        public DateTime castToDateTime() throws PageException {
1723                return info().castToDateTime();
1724        }
1725    
1726    @Override
1727    public DateTime castToDateTime(DateTime defaultValue) {
1728        try {
1729                        return info().castToDateTime(defaultValue);
1730                } catch (ExpressionException e) {
1731                        return defaultValue;
1732                }
1733    }
1734
1735        @Override
1736        public double castToDoubleValue() throws PageException {
1737                return info().castToDoubleValue();
1738        }
1739    
1740    @Override
1741    public double castToDoubleValue(double defaultValue) {
1742        try {
1743                        return info().castToDoubleValue(defaultValue);
1744                } catch (ExpressionException e) {
1745                        return defaultValue;
1746                }
1747    }
1748
1749        @Override
1750        public String castToString() throws PageException {
1751                return info().castToString();
1752        }
1753        @Override
1754        public String castToString(String defaultValue) {
1755                try {
1756                        return info().castToString(defaultValue);
1757                } catch (ExpressionException e) {
1758                        return defaultValue;
1759                }
1760        }
1761
1762        @Override
1763        public int compareTo(String str) throws PageException {
1764                return info().compareTo(str);
1765        }
1766
1767        @Override
1768        public int compareTo(boolean b) throws PageException {
1769                return info().compareTo(b);
1770        }
1771
1772        @Override
1773        public int compareTo(double d) throws PageException {
1774                return info().compareTo(d);
1775        }
1776
1777        @Override
1778        public int compareTo(DateTime dt) throws PageException {
1779                return info().compareTo(dt);
1780        }
1781
1782        private static void checkRestriction()  {
1783                try {
1784                        if(JAI.class==null) return;
1785                }
1786                catch(Throwable t) {
1787                        ExceptionUtil.rethrowIfNecessary(t);
1788                        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");
1789                }       
1790        }
1791        
1792        public static int toInterpolation(String strInterpolation) throws ExpressionException {
1793                if(StringUtil.isEmpty(strInterpolation))
1794                        throw new ExpressionException("interpolation definition is empty");
1795                strInterpolation=strInterpolation.trim().toLowerCase();
1796                
1797                if("highestquality".equals(strInterpolation))                   return IP_HIGHESTQUALITY;
1798                else if("highquality".equals(strInterpolation))                 return IP_HIGHQUALITY;
1799                else if("mediumquality".equals(strInterpolation))               return IP_MEDIUMQUALITY;
1800                else if("highestperformance".equals(strInterpolation))  return IP_HIGHESTPERFORMANCE;
1801                else if("highperformance".equals(strInterpolation))     return IP_HIGHPERFORMANCE;
1802                else if("mediumperformance".equals(strInterpolation))   return IP_MEDIUMPERFORMANCE;
1803                else if("nearest".equals(strInterpolation))                     return IPC_NEAREST;
1804                else if("bilinear".equals(strInterpolation))                    return IPC_BILINEAR;
1805                else if("bicubic".equals(strInterpolation))                     return IPC_BICUBIC;
1806                else if("bessel".equals(strInterpolation))                              return IP_BESSEL;
1807                else if("blackman".equals(strInterpolation))                    return IP_BLACKMAN;
1808                else if("hamming".equals(strInterpolation))                     return IP_HAMMING;
1809                else if("hanning".equals(strInterpolation))                     return IP_HANNING;
1810                else if("hermite".equals(strInterpolation))                     return IP_HERMITE;
1811                else if("lanczos".equals(strInterpolation))                     return IP_LANCZOS;
1812                else if("mitchell".equals(strInterpolation))                    return IP_MITCHELL;
1813                else if("quadratic".equals(strInterpolation))                   return IP_QUADRATIC;
1814
1815                throw new ExpressionException("interpolation definition ["+strInterpolation+"] is invalid");
1816        }
1817
1818        /**
1819         * @return the source
1820         */
1821        public Resource getSource() {
1822                return source;
1823        }
1824
1825        @Override
1826        public boolean containsValue(Object value) {
1827                try {
1828                        return info().containsValue(value);
1829                } 
1830                catch (ExpressionException e) {
1831                        return false;
1832                }
1833        }
1834
1835        @Override
1836        public java.util.Collection values() {
1837                try {
1838                        return info().values();
1839                } catch (ExpressionException e) {
1840                        throw new PageRuntimeException(e);
1841                }
1842        }
1843
1844        /**
1845         * This method returns true if the specified image has transparent pixels
1846         * @param image
1847         * @return
1848         */
1849        public static boolean hasAlpha(java.awt.Image image) {
1850            // If buffered image, the color model is readily available
1851            if (image instanceof BufferedImage) {
1852                BufferedImage bimage = (BufferedImage)image;
1853                return bimage.getColorModel().hasAlpha();
1854            }
1855
1856            // Use a pixel grabber to retrieve the image's color model;
1857            // grabbing a single pixel is usually sufficient
1858             PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
1859            try {
1860                pg.grabPixels();
1861            } catch (InterruptedException e) {
1862            }
1863
1864            // Get the image's color model
1865            ColorModel cm = pg.getColorModel();
1866            return cm.hasAlpha();
1867        }
1868        
1869        // This method returns a buffered image with the contents of an image
1870        public static BufferedImage toBufferedImage(java.awt.Image image) {
1871            if (image instanceof BufferedImage) {
1872                return (BufferedImage)image;
1873            }
1874
1875            // This code ensures that all the pixels in the image are loaded
1876            image = new ImageIcon(image).getImage();
1877
1878            // Determine if the image has transparent pixels; for this method's
1879            boolean hasAlpha = hasAlpha(image);
1880
1881            // Create a buffered image with a format that's compatible with the screen
1882            BufferedImage bimage = null;
1883            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
1884            try {
1885                // Determine the type of transparency of the new buffered image
1886                int transparency = Transparency.OPAQUE;
1887                if (hasAlpha) {
1888                    transparency = Transparency.BITMASK;
1889                }
1890
1891                // Create the buffered image
1892                GraphicsDevice gs = ge.getDefaultScreenDevice();
1893                GraphicsConfiguration gc = gs.getDefaultConfiguration();
1894                bimage = gc.createCompatibleImage(
1895                    image.getWidth(null), image.getHeight(null), transparency);
1896            } catch (HeadlessException e) {
1897                // The system does not have a screen
1898            }
1899
1900            if (bimage == null) {
1901                // Create a buffered image using the default color model
1902                int type = BufferedImage.TYPE_INT_RGB;
1903                if (hasAlpha) {
1904                    type = BufferedImage.TYPE_INT_ARGB;
1905                }
1906                bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
1907            }
1908
1909            // Copy image to buffered image
1910            Graphics g = bimage.createGraphics();
1911
1912            // Paint the image onto the buffered image
1913            g.drawImage(image, 0, 0, null);
1914            g.dispose();
1915
1916            return bimage;
1917        }
1918        
1919}