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