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