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