2010年12月24日

浏览器预览本地图片

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
 <html xmlns="http://www.w3.org/1999/xhtml">  
 <head>  
 <script>  
 var __userAgent = window.navigator.userAgent;  
 isIE = /msie\s+(.*?)\;/i.test(__userAgent);   
 isFF = !isIE&&/rv\:(.*?)\)\s+gecko\//i.test(__userAgent);  
 function imageChange() {  
     var file = document.getElementById('__file__');  
     var _fileurl = file.value;  
     if (typeof FileReader != 'undefined' && !!FileReader) {  
     //html5  
       var reader = new FileReader();   
           reader.readAsDataURL(file.files[0]);    
           reader.onload = preview;  
           reader.onerror = function(evt){console.log("load fail:[error="+evt.target.error.code+"]")};  
     } else {  
         if (isIE) {  
             var _version = getInternetExplorerVersion();  
             if (_version >= 7.0) {  
                 file.select();  
                   try{  
                   _fileurl = document.selection.createRange().text;  
                   if (!_fileurl || _fileurl == '') {  
                       _fileurl = file.value;  
                   }  
                   } finally { document.selection.empty(); }  
             }  
         }   
         if (isFF){  
             //不支持html5的低版本ff  
             _fileurl = file.files[0].getAsDataURL();  
         }  
           preview(_fileurl);  
     }  
 }  
 function preview(fileurl){  
     var img = document.getElementById('__image__');  
     if (typeof FileReader != 'undefined' && !!FileReader) {  
         fileurl = fileurl.target.result;  
     }   
     if (!isImage(fileurl)){  
         alert('请选择正确的图片文件!'); return;  
     }  
     img.src = fileurl;  
 }  
 function isImage(url) {  
     return /^data:image|\.(?:jpg|png|jpeg|gif|bmp)\s*$/i.test(url);  
 }  
 function getInternetExplorerVersion() {  
     var rv = -1; // Return value assumes failure.  
     if (navigator.appName == 'Microsoft Internet Explorer') {  
         var ua = navigator.userAgent;  
         var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");  
         if (re.exec(ua) != null)  
              rv = parseFloat( RegExp.$1 );  
     }  
     return rv;  
 };  
 </script>  
 <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />  
 </head>  
 <body>  
 <input type="file" id="__file__" onchange="imageChange()" onpropertychange="imageChange()"/>  
 <div><img id="__image__" src=""></div>  
 </body>  
 </html>

不支持safari和opera。

在chrome中直接打开html文件不能加载图片,会报NOT_READABLE_ERR,必须通过服务器来访问。

2010年12月4日

在sping中获取自身的代理

假设有这么一个类

public class A{
  func1(){}
  func2(){}
}

func1加了事务的aop,并且func2需要通过proxy来调用func1,那func2如何拿到A的proxy呢?

一个办法是实现BeanFactoryAware接口,另一个办法就是这样

A self = this;  
 try {  
     A proxy = (A) AopContext.currentProxy();  
     if (proxy != null) {  
         self = proxy;  
     }  
 } catch (IllegalStateException e) {  
     e.printStackTrace();  
 }  

然后在bean的配置中加上 <property name="exposeProxy" value="true" />

这个方法和第一种办法比起来,和spring的耦合少一点。

2010年11月30日

ant在eclipse中的日志输出问题

今天在eclipse中运行ant时,碰到一个诡异问题:执行javac之后,console就没有任何输出了,没有任何错误信息,同时编译是成功的。

经过一番搜索,终于找到这篇文章,又是字符集的问题。

具体原因就不去深究了,经过我的试验,只要保证workspace、javac和console都使用同一种encoding(比如UTF-8),就能确保ant能在console正常输出日志。

2010年11月26日

html生成图片

根据html来生成图片很多时候都会被用到,最常见的就是网页截图。通过html模板技术,可以让用户自定义一段html,然后为用户实时生成图片。

以下就是使用java 2D实现的html生成图片的方法,需要用到两个开源的jar包。core-renderer.jarjtidy-r938.jar,这两个jar包是用来渲染html页面的,但是不支持执行javasrcipt。另外还需要xmlgraphics-commons-1.4.jar,这是用来生成PNG图片的。

为了支持中文显示自动换行,需要修改org.xhtmlrenderer.layout.Breaker类。

因为xhtmlrenderer是外国人写的,默认是根据空格来决定是否可以换行的,但是中文语句都是没有空格的,修改后的行为就像样式word-break:break-all。core-renderer-repack.jar是我重新打的包,包含了Breaker.java的源代码。

以下是代码:

public class ImageGenerator {  
   private float jpgQuality = 0.9F;  
   private boolean useHighScaleQuality = true;  
   private ImageFormat imageFormat = ImageFormat.JPG;  
   private ChineseFontResolver fontResolver;  
   private static final Logger logger = Logger.getLogger(ImageGenerator.class);  
   public void init() {  
    XRLog.init("init log...");  
    initTidy();  
   }  
   private Tidy tidy = null;  
   private static HashMap RENDER_HINT = new HashMap();  
   static{  
    RENDER_HINT.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);  
    RENDER_HINT.put(RenderingHints.KEY_ANTIALIASING,RenderingHints. VALUE_ANTIALIAS_ON);  
    RENDER_HINT.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);  
    RENDER_HINT.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);  
    RENDER_HINT.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);  
    RENDER_HINT.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);  
    RENDER_HINT.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);  
   }  
   private static HashMap RENDER_SCALE_HINT = new HashMap();  
   static{  
    RENDER_SCALE_HINT.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);  
   }  
   private void initTidy() {  
    tidy = new Tidy();  
    tidy.setQuiet(true);  
    tidy.setXHTML(true);  
    tidy.setHideComments(true);  
    tidy.setInputEncoding("UTF-8");  
    tidy.setOutputEncoding("UTF-8");  
    tidy.setShowErrors(0);  
    tidy.setShowWarnings(false);  
   }  
   /**  
   * 按默认配置生成图片  
   * @param input  
   * @param width  
   * @param height  
   * @return  
   */  
   public ByteArrayOutputStream html2Image(InputStream input, int width, int height) {  
    ByteArrayOutputStream output = null;  
    try {  
      BufferedImage image = generateImage(input, width, height, RENDER_HINT);  
      output = generateOutput(image);  
    } catch (Exception e) {  
      logger.error("generate image fail.");  
      e.printStackTrace();  
    }  
    return output;  
   }  
   /**  
   * 生成图片并缩放  
   * @param input  
   * @param width  
   * @param height  
   * @param scaledWidth  
   * @param scaledHeight  
   * @return  
   */  
   public ByteArrayOutputStream html2Image(InputStream input, int width, int height, int scaledWidth, int scaledHeight) {  
    ByteArrayOutputStream output = null;  
    try {  
      HashMap hints = scaledWidth < width ? RENDER_SCALE_HINT : RENDER_HINT;  
      BufferedImage image = generateImage(input, width, height, hints);  
      BufferedImage scaled = scale(image, width, height, scaledWidth, scaledHeight, hints);  
      output = generateOutput(scaled);  
    } catch (Exception e) {  
      logger.error("generate image fail:[width=" + width + ",height=" + height + ",scaledWidth=" + scaledWidth + ",scaledHeight=" + scaledHeight + "]");  
      e.printStackTrace();  
    }  
    return output;  
   }  
   private BufferedImage scale(BufferedImage image, int width, int height, int scaledWidth, int scaledHeight, HashMap hints) {  
    if (scaledWidth < width && isUseHighScaleQuality()) {  
      do {  
         if (width > scaledWidth) {  
          width /= 2;  
          if (width < scaledWidth) {  
            width = scaledWidth;  
          }  
         }  
         if (height > scaledHeight) {  
          height /= 2;  
          if (height < scaledHeight) {  
            height = scaledHeight;  
          }  
         }  
         BufferedImage tmp = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);  
         Graphics2D g = tmp.createGraphics();  
         g.setRenderingHints(hints);  
         g.drawImage(image, 0, 0, width, height, null);  
         g.dispose();  
         image = tmp;  
       } while (width != scaledWidth || height != scaledHeight);  
    } else {  
      BufferedImage tmp = new BufferedImage(scaledWidth,scaledHeight, BufferedImage.TYPE_INT_RGB);  
      Graphics2D g = tmp.createGraphics();  
      g.setRenderingHints(hints);  
      g.drawImage(image, 0, 0, scaledWidth, scaledHeight, null);  
      g.dispose();  
      image = tmp;  
    }  
    return image;  
   }  
   private BufferedImage generateImage(InputStream input, int width, int height, HashMap hints) {  
    Document doc = tidy.parseDOM(input, null);  
    if (doc == null) {  
      logger.error("parse html file fail, invalid html file.");  
      return null;  
    }  
    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);  
    Graphics2D graphics = (Graphics2D) image.getGraphics();  
    graphics.setRenderingHints(RENDER_HINT);  
    Graphics2DRenderer renderer = new Graphics2DRenderer();  
    SharedContext context = renderer.getSharedContext();  
    context.setFontResolver(getFontResolver());  
    context.setNamespaceHandler(new XhtmlNamespaceHandler());  
    renderer.setDocument(doc, "");  
    renderer.layout(graphics, new Dimension(width, height));  
    renderer.render(graphics);  
    graphics.dispose();  
    return image;  
   }  
   private ByteArrayOutputStream generateOutput(BufferedImage image) {  
    ByteArrayOutputStream output = new ByteArrayOutputStream();  
    try {  
      switch (getImageFormat()) {  
      case JPG:  
       encodeJpg(image, output);  
       break;  
      case PNG:  
       default:  
       encodePng(image, output);  
       break;  
      }  
    } catch (ImageFormatException e) {  
      logger.error("generate image fail.");  
      e.printStackTrace();  
      output = null;  
    } catch (IOException e) {  
      logger.error("generate image fail.");  
      e.printStackTrace();  
      output = null;  
    }  
    return output;  
   }  
   private void encodeJpg(BufferedImage image, ByteArrayOutputStream output, float quality) throws ImageFormatException, IOException {  
    JPEGEncodeParam param = JPEGCodec.getDefaultJPEGEncodeParam(image);  
    param.setQuality(quality, true);  
    JPEGImageEncoder imageEncoder = JPEGCodec.createJPEGEncoder(output, param);  
    imageEncoder.encode(image);  
   }  
   private void encodeJpg(BufferedImage image, ByteArrayOutputStream output) throws ImageFormatException, IOException {  
    encodeJpg(image, output, getJpgQuality());  
   }  
   private void encodePng(BufferedImage image, ByteArrayOutputStream output) throws IOException {  
    PNGEncodeParam param = PNGEncodeParam.RGB.getDefaultEncodeParam(image);  
    PNGImageEncoder imageEncoder = new PNGImageEncoder(output, param);  
    imageEncoder.encode(image);  
   }  
   public void setJpgQuality(float jpgQuality) {  
    this.jpgQuality = jpgQuality;  
   }  
   public float getJpgQuality() {  
    return jpgQuality;  
   }  
   public void setImageFormat(ImageFormat imageFormat) {  
    this.imageFormat = imageFormat;  
   }  
   public ImageFormat getImageFormat() {  
    return imageFormat;  
   }  
   public void setFontResolver(ChineseFontResolver fontResolver) {  
    this.fontResolver = fontResolver;  
   }  
   public ChineseFontResolver getFontResolver() {  
    return fontResolver;  
   }  
   public void setUseHighScaleQuality(boolean useHighScaleQuality) {  
    this.useHighScaleQuality = useHighScaleQuality;  
   }  
   public boolean isUseHighScaleQuality() {  
    return useHighScaleQuality;  
   }  
 }  
 enum ImageFormat {  
   JPG, PNG  
 }

为了使用中文字体,需要手工加载中文字体,这里都是用truetype字体:

/**
 * 中文字体支持,默认使用微软雅黑
 *
 */
public class ChineseFontResolver extends AWTFontResolver {

    private static Font DEFAULT_FONT;

    private String fontPath;

    public ChineseFontResolver() {
        super();
    }

    public void init() {
     try {
            InputStream input =  new FileInputStream(new File(getFontPath() + "msyh.ttf"));
            Font font = Font.createFont(Font.TRUETYPE_FONT, input);
            font = font.deriveFont(12.0F);
            setFontMapping("微软雅黑", font);
            DEFAULT_FONT = font;
            setFontMapping("arial", font);
            setFontMapping("serif", font);
            setFontMapping("SansSerif", font);
            setFontMapping("Monospaced", font);

        } catch (FontFormatException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    protected Font resolveFont(SharedContext ctx, String font, float size, IdentValue weight, IdentValue style, IdentValue variant) {
        Font result = super.resolveFont(ctx, "微软雅黑", size, weight, style, variant);
        if (result == null) {
            result = DEFAULT_FONT;
        }
        return result;
    }

    public void setFontPath(String fontPath) {
        this.fontPath = fontPath;
    }

    public String getFontPath() {
        return fontPath;
    }

}

truetype的微软雅黑结合ImageGenerator中的RENDER_HINT,渲染出来的中文字体质量还是相当不错的。
使用方法就像这样,为了不出现乱码,最好全部都是用UTF-8编码:

ByteArrayOutputStream output = null;  
 try {  
    Map data = new HashMap();  
    String html = getTemplateParser().parse("template.ftl", data);//使用freemarker填充数据  
    InputStream input = new ByteArrayInputStream(html.getBytes(Charset.forName("UTF-8")));  
    output = getImageGenerator().html2Image(input, 1260, 64);  
   //for test  
   // FileOutputStream file = new FileOutputStream(new File("d://images//result.jpg"));  
   // file.write(output.toByteArray());  
   // file.flush();  
   // file.close();  
 } catch (TemplateException e) {  
   e.printStackTrace();  
 } catch (IOException e) {  
   e.printStackTrace();  
 }  

以上代码是使用模板来生成图片,网站截图只要稍微修改一下代码即可,以下是个简单的例子:

public class Main {  
   public static void main(String[] args) {  
     try {  
       Tidy tidy = new Tidy();  
       tidy.setQuiet(true);  
       tidy.setXHTML(true);  
       tidy.setHideComments(true);  
       tidy.setInputEncoding("UTF-8");  
       tidy.setOutputEncoding("UTF-8");  
       tidy.setShowErrors(0);  
       tidy.setShowWarnings(false);  
       //for test start  
       FileOutputStream output = new FileOutputStream(new File("d:/1234.png"));  
       URL url=new URL("http://www.baidu.com");  
       URLConnection conn=url.openConnection();  
       conn.connect();  
       InputStream is=conn.getInputStream();  
       BufferedReader br=new BufferedReader(new InputStreamReader(is,"gbk"));  
       String s = "";   
       StringBuffer sb = new StringBuffer("");   
       while ((s = br.readLine()) != null) {    
           sb.append(s + "\r\n");   
       }  
       ByteArrayInputStream bis = new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));  
       //for test end  
       Document doc = tidy.parseDOM(bis, null);  
       Graphics2DRenderer g2r = new Graphics2DRenderer();  
       g2r.setDocument(doc,"");  
       //这里其实还是使用固定的width、height去渲染网页,但是可以设置一个较大的值  
       Dimension dim = new Dimension(1024, 2000);  
       BufferedImage buff = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_RGB);  
       Graphics2D g = (Graphics2D)buff.getGraphics();  
       g2r.layout(g, new Dimension(1024, 2000));  
       g.dispose();  
       //这里获取真正的网页大小,如果页面有背景图设了repeat,会导致无法获取真实大小  
       Rectangle rect = g2r.getMinimumSize();  
       buff = new BufferedImage((int)rect.getWidth(), (int)rect.getHeight(), BufferedImage.TYPE_INT_RGB);  
       g = (Graphics2D)buff.getGraphics();  
       g2r.render(g);  
       g.dispose();  
       PNGEncodeParam param = PNGEncodeParam.RGB.getDefaultEncodeParam(buff);  
       PNGImageEncoder imageEncoder = new PNGImageEncoder(output, param);  
       imageEncoder.encode(buff);  
       output.flush();  
       output.close();  
     } catch (FileNotFoundException e) {  
       e.printStackTrace();  
     } catch (IOException e) {  
       e.printStackTrace();  
     }  
   }  
 }  

2010年11月25日

get请求参数乱码问题

如果提交的参数包含中文,只要实用post方式提交请求即可。但是使用post的话,每次刷新页面,浏览器都会弹出对话框提示用户,对于搜索功能就显得不可接受了。

通常一次搜索请求都是通过get方式提交的。但是在tomcat里,直接使用request的getParameter方法得到的参数总是会乱码。

对于这个问题的原因,从源代码级剖析tomcat乱码问题(如果这个链接失效,也可以访问这里)这篇文章已经解释的很清楚了,并且也提供解决方法。但是直接修改tomcat-util.jar显然不是很好。

以下是我现在采用的方法:

String queryString = request.getQueryString();//get raw query string  
 if (queryString != null && queryString.length() > 0) {  
   queryString = new String(queryString.getBytes("ISO-8859-1"), "GBK");//process url which is not urlencoded  
   queryString = URLDecoder.decode(queryString, "GBK");//decode url  
   Pattern pattern = Pattern.compile("q=([^&#]*)");  
   Matcher matcher = pattern.matcher(queryString);  
   if (matcher.find()) {  
    query = matcher.group(1);  
   }  
 }  

2010年11月11日

判断两个时间是否属于同一个月的问题

通常来讲,判断两个时间是否属于同一个月,只要判断year相等并且month相等即可,但是这样判断在某些情况下会失败。

前两天就碰到了这个问题,服务器上统计访问量时在判断当前时刻是否属于本月时,就使用了上述逻辑,但是每台服务器的系统时间都不尽相同,导致在同一时刻,两台服务器在做这个判断时得到不同结果。 所造成的结果就是上个月的记录被当前月的统计覆盖。

其实两个系统时间不同的服务器在判断两个时间是否属于某个区间(同天/同月/同年)时,都存在类似问题。 保险起见,应该减弱判断条件:如果系统时间小于等于输入时间,就可以认为相等。