package railo.runtime.tag;
import java.awt.Color;
import java.io.IOException;
import javax.servlet.jsp.JspException;
import railo.commons.color.ColorCaster;
import railo.commons.io.res.Resource;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.StringUtil;
import railo.runtime.exp.ApplicationException;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.ext.tag.TagImpl;
import railo.runtime.functions.other.CreateUUID;
import railo.runtime.functions.system.ContractPath;
import railo.runtime.img.ImageUtil;
import railo.runtime.img.MarpleCaptcha;
import railo.runtime.op.Caster;
import railo.runtime.type.Struct;
import railo.runtime.type.util.ArrayUtil;
import railo.runtime.type.util.ListUtil;
// GetWriteableImageFormats
// GetReadableImageFormats
/**
* Lets you resize and add labels to GIF and JPEG format images.
*
*
*
**/
public final class Image extends TagImpl {
private static int ACTION_BORDER=0;
private static int ACTION_CAPTCHA=1;
private static int ACTION_CONVERT=2;
private static int ACTION_INFO=3;
private static int ACTION_READ=4;
private static int ACTION_RESIZE=5;
private static int ACTION_ROTATE=6;
private static int ACTION_WRITE=7;
private static int ACTION_WRITE_TO_BROWSER=8;
private int action=ACTION_READ;
private String strAction="read";
private int angle=-1;
private Color color=Color.BLACK;
private Resource destination;
private int difficulty=MarpleCaptcha.DIFFICULTY_LOW;
private String[] fonts=new String[]{"arial"};
private int fontsize=24;
private String format="png";
private String height;
private String width;
private boolean isbase64;
private String name;
private boolean overwrite;
private float quality=.75F;
private Object oSource;
private railo.runtime.img.Image source;
private String structName;
private String text;
private int thickness=1;
private String passthrough;
private boolean base64;
@Override
public void release() {
super.release();
action=ACTION_READ;
strAction="read";
angle=-1;
color=Color.BLACK;
destination=null;
difficulty=MarpleCaptcha.DIFFICULTY_LOW;
fonts=new String[]{"arial"};
fontsize=24;
format="png";
height=null;
width=null;
isbase64=false;
name=null;
overwrite=false;
quality=0.75F;
source=null;
oSource=null;
structName=null;
text=null;
thickness=1;
passthrough=null;
base64=false;
}
/**
* @param action the action to set
* @throws ApplicationException
*/
public void setAction(String strAction) throws ApplicationException {
this.strAction = strAction;
strAction=strAction.trim().toLowerCase();
if(StringUtil.isEmpty(strAction))action=ACTION_READ;
else if("border".equals(strAction))action=ACTION_BORDER;
else if("captcha".equals(strAction))action=ACTION_CAPTCHA;
else if("convert".equals(strAction))action=ACTION_CONVERT;
else if("info".equals(strAction))action=ACTION_INFO;
else if("read".equals(strAction))action=ACTION_READ;
else if("resize".equals(strAction))action=ACTION_RESIZE;
else if("rotate".equals(strAction))action=ACTION_ROTATE;
else if("write".equals(strAction))action=ACTION_WRITE;
else if("writetobrowser".equals(strAction))action=ACTION_WRITE_TO_BROWSER;
else if("write-to-browser".equals(strAction))action=ACTION_WRITE_TO_BROWSER;
else if("write_to_browser".equals(strAction))action=ACTION_WRITE_TO_BROWSER;
else throw new ApplicationException("invalid action ["+this.strAction+"], " +
"valid actions are [border,captcha,convert,info,read,resize,rotate,write,writeToBrowser]");
}
/**
* @param base64 the base64 to set
*/
public void setBase64(boolean base64) {
this.base64 = base64;
}
/**
* @param angle the angle to set
*/
public void setAngle(double angle) {
this.angle = (int) angle;
}
/**
* @param color the color to set
* @throws ExpressionException
*/
public void setColor(String strColor) throws ExpressionException {
this.color = ColorCaster.toColor(strColor);
}
/**
* @param destination the destination to set
*/
public void setDestination(String destination) {
this.destination = ResourceUtil.toResourceNotExisting(pageContext, destination);
}
/**
* @param difficulty the difficulty to set
* @throws ApplicationException
*/
public void setDifficulty(String strDifficulty) throws ApplicationException {
strDifficulty=strDifficulty.trim().toLowerCase();
if(StringUtil.isEmpty(strDifficulty)) difficulty=MarpleCaptcha.DIFFICULTY_LOW;
else if("low".equals(strDifficulty)) difficulty=MarpleCaptcha.DIFFICULTY_LOW;
else if("medium".equals(strDifficulty)) difficulty=MarpleCaptcha.DIFFICULTY_MEDIUM;
else if("high".equals(strDifficulty)) difficulty=MarpleCaptcha.DIFFICULTY_HIGH;
else throw new ApplicationException("invalid difficulty level ["+strDifficulty+"], " +
"valid difficulty level are [low,medium,high]");
}
/**
* @param fonts the fonts to set
* @throws PageException
*/
public void setFonts(String fontList) throws PageException {
fonts=ArrayUtil.trim(ListUtil.toStringArray(ListUtil.listToArray(fontList, ',')));
}
/**
* @param passthrough the passthrough to set
*/
public void setPassthrough(String passthrough) {
this.passthrough = passthrough;
}
/**
* @param fontsize the fontsize to set
*/
public void setFontsize(double fontsize) {
this.fontsize = (int) fontsize;
}
/**
* @param format the format to set
* @throws ApplicationException
*/
public void setFormat(String format) throws ApplicationException {
format=format.trim().toLowerCase();
if("gif".equalsIgnoreCase(format)) this.format = "gif";
else if("jpg".equalsIgnoreCase(format)) this.format = "jpg";
else if("jpe".equalsIgnoreCase(format)) this.format = "jpg";
else if("jpeg".equalsIgnoreCase(format))this.format = "jpg";
else if("png".equalsIgnoreCase(format)) this.format = "png";
else if("tiff".equalsIgnoreCase(format))this.format = "tiff";
else if("bmp".equalsIgnoreCase(format)) this.format = "bmp";
else throw new ApplicationException("invalid format ["+format+"], " +
"valid formats are [gif,jpg,png,tiff,bmp]");
}
/**
* @param height the height to set
*/
public void setHeight(String height) {
this.height = height;
}
/**
* @param width the width to set
*/
public void setWidth(String width) {
this.width = width;
}
/**
* @param isbase64 the isbase64 to set
*/
public void setIsbase64(boolean isbase64) {
this.isbase64 = isbase64;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @param overwrite the overwrite to set
*/
public void setOverwrite(boolean overwrite) {
this.overwrite = overwrite;
}
/**
* @param quality the quality to set
* @throws ApplicationException
*/
public void setQuality(double quality) throws ApplicationException {
this.quality = (float) quality;
if(quality<0 || quality>1)
throw new ApplicationException("quality ("+Caster.toString(quality)+") has to be a value between 0 and 1");
}
/**
* @param source the source to set
* @throws PageException
*/
public void setSource(Object source) {
this.oSource=source;
// this.source=railo.runtime.img.Image.createImage(pageContext, source, false, false);
}
/**
* @param structName the structName to set
*/
public void setStructname(String structName) {
this.structName = structName;
}
/**
* @param structName the structName to set
*/
public void setResult(String structName) {
this.structName = structName;
}
/**
* @param text the text to set
*/
public void setText(String text) {
this.text = text;
}
/**
* @param thickness the thickness to set
*/
public void setThickness(double thickness) {
this.thickness = (int) thickness;
}
@Override
public int doStartTag() throws JspException {
try {
if(this.oSource!=null){
if(isbase64) this.source=new railo.runtime.img.Image(Caster.toString(oSource));
else this.source=railo.runtime.img.Image.createImage(pageContext, oSource, false, false,true,null);
}
if(action==ACTION_BORDER) doActionBorder();
else if(action==ACTION_CAPTCHA) doActionCaptcha();
else if(action==ACTION_CONVERT) doActionConvert();
else if(action==ACTION_INFO) doActionInfo();
else if(action==ACTION_READ) doActionRead();
else if(action==ACTION_RESIZE) doActionResize();
else if(action==ACTION_ROTATE) doActionRotate();
else if(action==ACTION_WRITE) doActionWrite();
else if(action==ACTION_WRITE_TO_BROWSER)
doActionWriteToBrowser();
}
catch(Throwable t) {
throw Caster.toPageException(t);
}
return SKIP_BODY;
}
// Add a border to an image
private void doActionBorder() throws PageException, IOException {
required("source", source);
source.addBorder(thickness,color,railo.runtime.img.Image.BORDER_TYPE_CONSTANT);
write();
}
// Create a CAPTCHA image
private void doActionCaptcha() throws PageException, IOException {
required("height", height);
required("width", width);
required("text", text);
boolean doRenderHtmlTag = ( destination == null );
String path=null;
// create destination
if(StringUtil.isEmpty(name))path=touchDestination();
MarpleCaptcha c=new MarpleCaptcha();
source=new railo.runtime.img.Image(c.generate(text, Caster.toIntValue(width),Caster.toIntValue(height), fonts, true,
Color.BLACK,fontsize,
difficulty));
// link destination
if ( doRenderHtmlTag )
writeLink( path );
// write out
write();
}
private void writeLink(String path) throws IOException, PageException {
String add="";
if(passthrough!=null) {
add=" "+passthrough;
}
if(base64) {
String b64 = source.getBase64String(format);
pageContext.write("<img src=\"data:image/"+ImageUtil.getMimeTypeFromFormat(format)+";base64,"+b64+"\" width=\""+source.getWidth()+"\" height=\""+source.getHeight()+"\""+add+" />");
return;
}
pageContext.write("<img src=\""+path+"\" width=\""+source.getWidth()+"\" height=\""+source.getHeight()+"\""+add+" />");
}
private String touchDestination() throws IOException {
if(destination==null) {
String name=CreateUUID.call(pageContext)+"."+format;
Resource folder = pageContext.getConfig().getTempDirectory().getRealResource("graph");
if(!folder.exists())folder.createDirectory(true);
destination = folder.getRealResource(name);
cleanOld(folder);
// create path
String cp = pageContext.getHttpServletRequest().getContextPath();
if(StringUtil.isEmpty(cp)) cp="";
return cp+"/railo-context/graph.cfm?img="+name+"&type="+(ListUtil.last(ImageUtil.getMimeTypeFromFormat(format),'/').trim());
}
return ContractPath.call(pageContext, destination.getAbsolutePath());
}
private static void cleanOld(Resource folder) throws IOException {
if(!folder.exists())folder.createDirectory(true);
else if(folder.isDirectory() && ResourceUtil.getRealSize(folder)>(1024*1024)) {
Resource[] children = folder.listResources();
long maxAge=System.currentTimeMillis()-(1000*60);
for(int i=0;i<children.length;i++) {
if(children[i].lastModified()<maxAge)
children[i].delete();
}
}
}
// Convert an image file format
private void doActionConvert() throws PageException, IOException {
required("source", source);
required("destination", destination);
source.convert(ImageUtil.getFormat(destination));
write();
}
// Retrieve information about an image
private void doActionInfo() throws PageException {
required("source",source);
required("structname",structName);
pageContext.setVariable(structName, source.info());
}
private Struct doActionInfo(railo.runtime.img.Image source) throws PageException {
return source.info();
}
// Read an image into memory
private void doActionRead() throws PageException {
required("source",source);
required("name",name);
pageContext.setVariable(name, source);
}
// Resize an image
private void doActionResize() throws PageException, IOException {
required("source", source);
//Info i = new Info(source);
/*int w = toDimension("width",width,i);
int h = toDimension("height",height,i);
if(w==-1 && h==-1)
throw new ApplicationException("Missing attribute [width or height]. The action ["+strAction+"] requires the attribute [width or height].");
*/
source.resize(width,height,"highestquality",1D);
write();
}
// Rotate an image
private void doActionRotate() throws PageException, IOException {
required("source",source);
required("angle",angle,-1);
source.rotate(-1, -1, angle, railo.runtime.img.Image.INTERPOLATION_NONE);
write();
}
// Write an image to a file
private void doActionWrite() throws ApplicationException, IOException, ExpressionException {
required("source", source);
required("destination", destination);
source.writeOut(destination,overwrite,quality);
}
// Write an image to the browser
private void doActionWriteToBrowser() throws IOException, PageException {
required("source", source);
String path=null;
// create destination
if(!base64 || !StringUtil.isEmpty(name)) {
path=touchDestination();
write();
}
// link destination
if(StringUtil.isEmpty(name))writeLink(path);
}
private void required(String label, Object value) throws ApplicationException {
if(value==null)
throw new ApplicationException("Missing attribute ["+label+"]. The action ["+strAction+"] requires the attribute [" + label + "].");
//throw new ApplicationException("missing attribute ["+label+"], for the action ["+strAction+"] this attribute is required but was not passed in");
}
private void required(String label, int value,int nullValue) throws ApplicationException {
if(value==nullValue)
throw new ApplicationException("Missing attribute ["+label+"]. The action ["+strAction+"] requires the attribute [" + label + "].");
//throw new ApplicationException("missing attribute ["+label+"], for the action ["+strAction+"] this attribute is required but was not passed in");
}
private void write() throws IOException, PageException {
if(destination!=null) {
doActionWrite();
}
if(!StringUtil.isEmpty(name)) {
required("source", source);
pageContext.setVariable(name, source);// TODO ist das so gut
}
//if(writeToResponseWhenNoOtherTarget) doActionWriteToBrowser();
}
class Info {
Struct struct;
private railo.runtime.img.Image img;
public Info(railo.runtime.img.Image img) {
this.img=img;
}
/**
* @return the sct
* @throws PageException
*/
public Struct getStruct() throws PageException {
if(struct==null)
struct=doActionInfo(img);
return struct;
}
}
private int toDimension(String label, String dimension, Info info) throws PageException {
if(StringUtil.isEmpty(dimension)) return -1;
dimension=dimension.trim();
// int value
int i=Caster.toIntValue(dimension,-1);
if(i>-1) return i;
// percent value
if(StringUtil.endsWith(dimension, '%')) {
float pro=Caster.toIntValue(
dimension.substring(0,dimension.length()-1).trim(),
-1);
if(pro<0 || pro>100)
throw new ExpressionException("attribute ["+label+"] value has an invalid percent definition ["+dimension+"]");
pro/=100F;
return (int)(Caster.toFloatValue(info.getStruct().get(label))*pro);
}
throw new ExpressionException("attribute ["+label+"] value has an invalid definition ["+dimension+"]");
}
}