001 package railo.runtime.tag; 002 003 import java.awt.Color; 004 import java.io.IOException; 005 006 import javax.servlet.jsp.JspException; 007 008 import railo.commons.color.ColorCaster; 009 import railo.commons.io.res.Resource; 010 import railo.commons.io.res.util.ResourceUtil; 011 import railo.commons.lang.StringUtil; 012 import railo.runtime.exp.ApplicationException; 013 import railo.runtime.exp.ExpressionException; 014 import railo.runtime.exp.PageException; 015 import railo.runtime.ext.tag.TagImpl; 016 import railo.runtime.functions.other.CreateUUID; 017 import railo.runtime.functions.system.ContractPath; 018 import railo.runtime.img.ImageUtil; 019 import railo.runtime.img.MarpleCaptcha; 020 import railo.runtime.op.Caster; 021 import railo.runtime.type.List; 022 import railo.runtime.type.Struct; 023 import railo.runtime.type.util.ArrayUtil; 024 025 // GetWriteableImageFormats 026 // GetReadableImageFormats 027 028 029 /** 030 * Lets you resize and add labels to GIF and JPEG format images. 031 * 032 * 033 * 034 **/ 035 public final class Image extends TagImpl { 036 037 private static int ACTION_BORDER=0; 038 private static int ACTION_CAPTCHA=1; 039 private static int ACTION_CONVERT=2; 040 private static int ACTION_INFO=3; 041 private static int ACTION_READ=4; 042 private static int ACTION_RESIZE=5; 043 private static int ACTION_ROTATE=6; 044 private static int ACTION_WRITE=7; 045 private static int ACTION_WRITE_TO_BROWSER=8; 046 047 048 private int action=ACTION_READ; 049 private String strAction="read"; 050 private int angle=-1; 051 private Color color=Color.BLACK; 052 private Resource destination; 053 private int difficulty=MarpleCaptcha.DIFFICULTY_LOW; 054 private String[] fonts=new String[]{"arial"}; 055 private int fontsize=24; 056 private String format="png"; 057 private String height; 058 private String width; 059 private boolean isbase64; 060 private String name; 061 private boolean overwrite; 062 private float quality=.75F; 063 private Object oSource; 064 private railo.runtime.img.Image source; 065 private String structName; 066 private String text; 067 private int thickness=1; 068 private String passthrough; 069 private boolean base64; 070 071 072 073 074 /** 075 * 076 * @see railo.runtime.ext.tag.TagImpl#release() 077 */ 078 public void release() { 079 super.release(); 080 action=ACTION_READ; 081 strAction="read"; 082 angle=-1; 083 color=Color.BLACK; 084 destination=null; 085 difficulty=MarpleCaptcha.DIFFICULTY_LOW; 086 fonts=new String[]{"arial"}; 087 fontsize=24; 088 format="png"; 089 height=null; 090 width=null; 091 isbase64=false; 092 name=null; 093 overwrite=false; 094 quality=0.75F; 095 source=null; 096 oSource=null; 097 structName=null; 098 text=null; 099 thickness=1; 100 passthrough=null; 101 base64=false; 102 } 103 104 105 /** 106 * @param action the action to set 107 * @throws ApplicationException 108 */ 109 public void setAction(String strAction) throws ApplicationException { 110 this.strAction = strAction; 111 strAction=strAction.trim().toLowerCase(); 112 if(StringUtil.isEmpty(strAction))action=ACTION_READ; 113 else if("border".equals(strAction))action=ACTION_BORDER; 114 else if("captcha".equals(strAction))action=ACTION_CAPTCHA; 115 else if("convert".equals(strAction))action=ACTION_CONVERT; 116 else if("info".equals(strAction))action=ACTION_INFO; 117 else if("read".equals(strAction))action=ACTION_READ; 118 else if("resize".equals(strAction))action=ACTION_RESIZE; 119 else if("rotate".equals(strAction))action=ACTION_ROTATE; 120 else if("write".equals(strAction))action=ACTION_WRITE; 121 else if("writetobrowser".equals(strAction))action=ACTION_WRITE_TO_BROWSER; 122 else if("write-to-browser".equals(strAction))action=ACTION_WRITE_TO_BROWSER; 123 else if("write_to_browser".equals(strAction))action=ACTION_WRITE_TO_BROWSER; 124 else throw new ApplicationException("invalid action ["+this.strAction+"], " + 125 "valid actions are [border,captcha,convert,info,read,resize,rotate,write,writeToBrowser]"); 126 } 127 128 129 /** 130 * @param base64 the base64 to set 131 */ 132 public void setBase64(boolean base64) { 133 this.base64 = base64; 134 } 135 136 137 /** 138 * @param angle the angle to set 139 */ 140 public void setAngle(double angle) { 141 this.angle = (int) angle; 142 } 143 144 145 /** 146 * @param color the color to set 147 * @throws ExpressionException 148 */ 149 public void setColor(String strColor) throws ExpressionException { 150 this.color = ColorCaster.toColor(strColor); 151 } 152 153 154 /** 155 * @param destination the destination to set 156 */ 157 public void setDestination(String destination) { 158 this.destination = ResourceUtil.toResourceNotExisting(pageContext, destination); 159 } 160 161 162 /** 163 * @param difficulty the difficulty to set 164 * @throws ApplicationException 165 */ 166 public void setDifficulty(String strDifficulty) throws ApplicationException { 167 strDifficulty=strDifficulty.trim().toLowerCase(); 168 if(StringUtil.isEmpty(strDifficulty)) difficulty=MarpleCaptcha.DIFFICULTY_LOW; 169 else if("low".equals(strDifficulty)) difficulty=MarpleCaptcha.DIFFICULTY_LOW; 170 else if("medium".equals(strDifficulty)) difficulty=MarpleCaptcha.DIFFICULTY_MEDIUM; 171 else if("high".equals(strDifficulty)) difficulty=MarpleCaptcha.DIFFICULTY_HIGH; 172 else throw new ApplicationException("invalid difficulty level ["+strDifficulty+"], " + 173 "valid difficulty level are [low,medium,high]"); 174 175 } 176 177 178 /** 179 * @param fonts the fonts to set 180 * @throws PageException 181 */ 182 public void setFonts(String fontList) throws PageException { 183 fonts=ArrayUtil.trim(List.toStringArray(List.listToArray(fontList, ','))); 184 } 185 186 187 188 /** 189 * @param passthrough the passthrough to set 190 */ 191 public void setPassthrough(String passthrough) { 192 this.passthrough = passthrough; 193 } 194 195 /** 196 * @param fontsize the fontsize to set 197 */ 198 public void setFontsize(double fontsize) { 199 this.fontsize = (int) fontsize; 200 } 201 202 203 /** 204 * @param format the format to set 205 * @throws ApplicationException 206 */ 207 public void setFormat(String format) throws ApplicationException { 208 format=format.trim().toLowerCase(); 209 if("gif".equalsIgnoreCase(format)) this.format = "gif"; 210 else if("jpg".equalsIgnoreCase(format)) this.format = "jpg"; 211 else if("jpe".equalsIgnoreCase(format)) this.format = "jpg"; 212 else if("jpeg".equalsIgnoreCase(format))this.format = "jpg"; 213 else if("png".equalsIgnoreCase(format)) this.format = "png"; 214 else if("tiff".equalsIgnoreCase(format))this.format = "tiff"; 215 else if("bmp".equalsIgnoreCase(format)) this.format = "bmp"; 216 else throw new ApplicationException("invalid format ["+format+"], " + 217 "valid formats are [gif,jpg,png,tiff,bmp]"); 218 } 219 220 221 /** 222 * @param height the height to set 223 */ 224 public void setHeight(String height) { 225 this.height = height; 226 } 227 228 229 /** 230 * @param width the width to set 231 */ 232 public void setWidth(String width) { 233 this.width = width; 234 } 235 236 237 /** 238 * @param isbase64 the isbase64 to set 239 */ 240 public void setIsbase64(boolean isbase64) { 241 this.isbase64 = isbase64; 242 } 243 244 245 /** 246 * @param name the name to set 247 */ 248 public void setName(String name) { 249 this.name = name; 250 } 251 252 253 /** 254 * @param overwrite the overwrite to set 255 */ 256 public void setOverwrite(boolean overwrite) { 257 this.overwrite = overwrite; 258 } 259 260 261 /** 262 * @param quality the quality to set 263 * @throws ApplicationException 264 */ 265 public void setQuality(double quality) throws ApplicationException { 266 this.quality = (float) quality; 267 if(quality<0 || quality>1) 268 throw new ApplicationException("quality ("+Caster.toString(quality)+") has to be a value between 0 and 1"); 269 } 270 271 272 /** 273 * @param source the source to set 274 * @throws PageException 275 */ 276 public void setSource(Object source) { 277 this.oSource=source; 278 // this.source=railo.runtime.img.Image.createImage(pageContext, source, false, false); 279 } 280 281 282 /** 283 * @param structName the structName to set 284 */ 285 public void setStructname(String structName) { 286 this.structName = structName; 287 } 288 289 290 /** 291 * @param structName the structName to set 292 */ 293 public void setResult(String structName) { 294 this.structName = structName; 295 } 296 297 298 /** 299 * @param text the text to set 300 */ 301 public void setText(String text) { 302 this.text = text; 303 } 304 305 306 /** 307 * @param thickness the thickness to set 308 */ 309 public void setThickness(double thickness) { 310 this.thickness = (int) thickness; 311 } 312 313 314 /** 315 * 316 * @see railo.runtime.ext.tag.TagImpl#doStartTag() 317 */ 318 public int doStartTag() throws JspException { 319 try { 320 if(this.oSource!=null){ 321 if(isbase64) this.source=new railo.runtime.img.Image(Caster.toString(oSource)); 322 else this.source=railo.runtime.img.Image.createImage(pageContext, oSource, false, false,true); 323 } 324 325 if(action==ACTION_BORDER) doActionBorder(); 326 else if(action==ACTION_CAPTCHA) doActionCaptcha(); 327 else if(action==ACTION_CONVERT) doActionConvert(); 328 else if(action==ACTION_INFO) doActionInfo(); 329 else if(action==ACTION_READ) doActionRead(); 330 else if(action==ACTION_RESIZE) doActionResize(); 331 else if(action==ACTION_ROTATE) doActionRotate(); 332 else if(action==ACTION_WRITE) doActionWrite(); 333 else if(action==ACTION_WRITE_TO_BROWSER) 334 doActionWriteToBrowser(); 335 } 336 catch(Throwable t) { 337 throw Caster.toPageException(t); 338 } 339 return SKIP_BODY; 340 } 341 342 // Add a border to an image 343 private void doActionBorder() throws PageException, IOException { 344 required("source", source); 345 346 source.addBorder(thickness,color,railo.runtime.img.Image.BORDER_TYPE_CONSTANT); 347 write(); 348 349 } 350 351 352 // Create a CAPTCHA image 353 private void doActionCaptcha() throws PageException, IOException { 354 required("height", height); 355 required("width", width); 356 required("text", text); 357 358 String path=null; 359 360 // create destination 361 if(StringUtil.isEmpty(name))path=touchDestination(); 362 363 MarpleCaptcha c=new MarpleCaptcha(); 364 source=new railo.runtime.img.Image(c.generate(text, Caster.toIntValue(width),Caster.toIntValue(height), fonts, true, 365 Color.BLACK,fontsize, 366 difficulty)); 367 368 // link destination 369 if(StringUtil.isEmpty(name))writeLink(path); 370 371 // write out 372 write(); 373 } 374 375 private void writeLink(String path) throws IOException, PageException { 376 String add=""; 377 if(passthrough!=null) { 378 add=" "+passthrough; 379 } 380 381 if(base64) { 382 String b64 = source.getBase64String(format); 383 pageContext.write("<img src=\"data:image/"+ImageUtil.getMimeTypeFromFormat(format)+";base64,"+b64+"\" width=\""+source.getWidth()+"\" height=\""+source.getHeight()+"\""+add+" />"); 384 return; 385 } 386 pageContext.write("<img src=\""+path+"\" width=\""+source.getWidth()+"\" height=\""+source.getHeight()+"\""+add+" />"); 387 388 389 } 390 391 392 private String touchDestination() throws IOException { 393 if(destination==null) { 394 String name=CreateUUID.call(pageContext)+"."+format; 395 Resource folder = pageContext.getConfig().getTempDirectory().getRealResource("graph"); 396 if(!folder.exists())folder.createDirectory(true); 397 destination = folder.getRealResource(name); 398 cleanOld(folder); 399 400 // create path 401 String cp = pageContext.getHttpServletRequest().getContextPath(); 402 if(StringUtil.isEmpty(cp)) cp=""; 403 return cp+"/railo-context/graph.cfm?img="+name+"&type="+(List.last(ImageUtil.getMimeTypeFromFormat(format),'/').trim()); 404 } 405 return ContractPath.call(pageContext, destination.getAbsolutePath()); 406 } 407 408 409 private static void cleanOld(Resource folder) throws IOException { 410 if(!folder.exists())folder.createDirectory(true); 411 else if(folder.isDirectory() && ResourceUtil.getRealSize(folder)>(1024*1024)) { 412 413 Resource[] children = folder.listResources(); 414 long maxAge=System.currentTimeMillis()-(1000*60); 415 for(int i=0;i<children.length;i++) { 416 if(children[i].lastModified()<maxAge) 417 children[i].delete(); 418 } 419 } 420 } 421 422 // Convert an image file format 423 private void doActionConvert() throws PageException, IOException { 424 required("source", source); 425 required("destination", destination); 426 427 source.convert(ImageUtil.getFormat(destination)); 428 write(); 429 } 430 431 // Retrieve information about an image 432 private void doActionInfo() throws PageException { 433 required("source",source); 434 required("structname",structName); 435 436 pageContext.setVariable(structName, source.info()); 437 } 438 439 private Struct doActionInfo(railo.runtime.img.Image source) throws PageException { 440 return source.info(); 441 } 442 443 444 // Read an image into memory 445 private void doActionRead() throws PageException { 446 required("source",source); 447 required("name",name); 448 449 pageContext.setVariable(name, source); 450 } 451 452 453 // Resize an image 454 private void doActionResize() throws PageException, IOException { 455 required("source", source); 456 457 Info i = new Info(source); 458 /*int w = toDimension("width",width,i); 459 int h = toDimension("height",height,i); 460 if(w==-1 && h==-1) 461 throw new ApplicationException("Missing attribute [width or height]. The action ["+strAction+"] requires the attribute [width or height]."); 462 */ 463 464 source.resize(width,height,"highestquality",1D); 465 466 write(); 467 } 468 469 // Rotate an image 470 private void doActionRotate() throws PageException, IOException { 471 required("source",source); 472 required("angle",angle,-1); 473 474 source.rotate(-1, -1, angle, railo.runtime.img.Image.INTERPOLATION_NONE); 475 write(); 476 477 } 478 479 // Write an image to a file 480 private void doActionWrite() throws ApplicationException, IOException, ExpressionException { 481 required("source", source); 482 required("destination", destination); 483 484 source.writeOut(destination,overwrite,quality); 485 } 486 487 // Write an image to the browser 488 private void doActionWriteToBrowser() throws IOException, PageException { 489 required("source", source); 490 491 String path=null; 492 493 // create destination 494 if(!base64 || !StringUtil.isEmpty(name)) { 495 path=touchDestination(); 496 write(); 497 } 498 // link destination 499 if(StringUtil.isEmpty(name))writeLink(path); 500 501 502 503 504 } 505 506 507 508 private void required(String label, Object value) throws ApplicationException { 509 if(value==null) 510 throw new ApplicationException("Missing attribute ["+label+"]. The action ["+strAction+"] requires the attribute [" + label + "]."); 511 //throw new ApplicationException("missing attribute ["+label+"], for the action ["+strAction+"] this attribute is required but was not passed in"); 512 } 513 private void required(String label, int value,int nullValue) throws ApplicationException { 514 if(value==nullValue) 515 throw new ApplicationException("Missing attribute ["+label+"]. The action ["+strAction+"] requires the attribute [" + label + "]."); 516 //throw new ApplicationException("missing attribute ["+label+"], for the action ["+strAction+"] this attribute is required but was not passed in"); 517 } 518 519 520 private void write() throws IOException, PageException { 521 if(destination!=null) { 522 doActionWrite(); 523 } 524 if(!StringUtil.isEmpty(name)) { 525 required("source", source); 526 pageContext.setVariable(name, source);// TODO ist das so gut 527 } 528 //if(writeToResponseWhenNoOtherTarget) doActionWriteToBrowser(); 529 } 530 531 532 class Info { 533 Struct struct; 534 private railo.runtime.img.Image img; 535 536 public Info(railo.runtime.img.Image img) { 537 this.img=img; 538 } 539 540 /** 541 * @return the sct 542 * @throws PageException 543 */ 544 public Struct getStruct() throws PageException { 545 if(struct==null) 546 struct=doActionInfo(img); 547 return struct; 548 } 549 } 550 551 552 private int toDimension(String label, String dimension, Info info) throws PageException { 553 if(StringUtil.isEmpty(dimension)) return -1; 554 dimension=dimension.trim(); 555 // int value 556 int i=Caster.toIntValue(dimension,-1); 557 if(i>-1) return i; 558 559 // percent value 560 if(StringUtil.endsWith(dimension, '%')) { 561 float pro=Caster.toIntValue( 562 dimension.substring(0,dimension.length()-1).trim(), 563 -1); 564 if(pro<0 || pro>100) 565 throw new ExpressionException("attribute ["+label+"] value has a invalid percent definition ["+dimension+"]"); 566 pro/=100F; 567 return (int)(Caster.toFloatValue(info.getStruct().get(label))*pro); 568 } 569 throw new ExpressionException("attribute ["+label+"] value has a invalid definition ["+dimension+"]"); 570 571 } 572 573 }