提升GraphicsMagick图片压缩软件性能使用心得

      关于这款图片裁剪软件的介绍就不多说了,给出官网地址:http://www.graphicsmagick.org/

       由于工作需要,对图片进行压缩处理,因此便研究了下图片压缩软件。一开始选择的方案是ImageMagick+JMagick,且各种编码工作也完成,业务场景都能够满足,结果却夭折在了软件安装上,在生产环境上折腾了好久还是搞不定ImageMagick,于是只好放弃,听上去真是扯淡。。。后来便开始了另一种实现方案:GraphicsMagick+im4java,研究下来,发现处理效果比上一套效果更好,关键是使用起来顺手多了,于是就开始各种研究gm命令+im4java实现了,目前成效还是不错的,经过不断优化后,一秒钟能有大几十张的处理速度。当然这过程中也走了不少弯路,现在就将优化后的命令及java代码实现贴出来,以供参考。

先来看一下常用的缩放命令:

缩放命令:

scale%:宽和高都按指定的比例缩放
scale-x%xscale-y% :宽和高分别按指定的比例缩放,可以写成200x50%这样用一个%表示的形式,宽放大200%,高缩小50%
width按指定的宽来等比缩放
xheight按指定的高来等比缩放
widthxheight按最大边来等比缩放
widthxheight^:按最小边来等比缩放
widthxheight!:按指定了的宽和高缩放,不等比
widthxheight>:如果原尺寸大于指定的宽和/或高,则等比缩小
widthxheight< :如果原尺寸小于指定的宽和/或高,则等比放大
area@ :按指定的像素区域等比缩放图片
{size}{offset}
{size}{+-}x{+-}y水平与垂直的位偏移量x和y

上面的命令一般可以处理各种不同业务场景上的压缩了:等比、不等比、按最小边等比压缩、按最大边等比压缩等。

这里要对性能进行优化,这时就要考虑下压缩命令的选择了。

 

压缩命令:
thumbnail、geometry、resize、sample、scale
每个命令都有各自的处理场景,因此压缩的质量、压缩的时长等,都有区别。下面是一篇博文,专门有对不同的命令进行了测试:http://hi.baidu.com/xzp19841203/item/dcf0e0495b052c0f6cc2f037,虽然没有验证过,但结果具有可参考性。有的命令处理时间长,但效果好,同时质量也相对较大,反之有的命令处理时间短,但效果却不怎么好,质量也相对较小,所以效果与处理时间两者不能同时满足,只能取折中的方案,处理时间相对较短,效果一般,参照博文上的参数,也通过本地测试简单验证了下,决定采用scale命令,也上官网查了下这个命令,发现scale命令是特地针对数码摄像机等设备拍出来的图片做处理的,如今大多数图片基本上都是由数码摄像机拍摄出来的,因此用此命令也能符合绝大多数的图片处理需求。
 
结合上面两个命令,基本上既满足了压缩场景的需求,也对性能方面有点小小地提升。但是往往在生产上是同一张图片需要压缩成不同尺寸的,这时就要考虑到使用多线程来处理了,针对每一个压缩的尺寸开启一个线程,通过多线程来处理,性能会大大提升。im4java正好提供了该多线程处理实现,下面便是简单的代码示例:
 
    /**
     * 功能描述:批量处理图像压缩
     * @param filePathBeans,要处理的图片信息的集合
     * @return Result,处理的结果
     */
    public Result processThumbnailImage(List<FilePathBean> filePathBeans) {
        Result result = new Result();
        // 可执行线程数16
        ProcessExecutor exec = new ProcessExecutor(16);
        try {
            // FilePathBean封装了图片处理参数,具体实现类可参见下面
            for (FilePathBean filePathBean : filePathBeans) {
                // 获取IMOperation操作接口
                IMOperation op = this.getOpThumbnailImage(filePathBean.getFileFullPath(),
                        filePathBean.getWidth(), filePathBean.getHeight(),
                        filePathBean.getQuality());
                if (null == op) {
                    logger.error(filePathBean.getFileName() + " the file is damaged!");
                    continue;
                }
                ConvertCmd cmd = new ConvertCmd(true);
                // 传入IMOperation操作接口,获取ProcessTask处理任务
                ProcessTask pt = cmd.getProcessTask(op, filePathBean.getFileFullPath(),
                        filePathBean.getFileFullToPath());
                // 执行任务
                exec.execute(pt);
                logger.debug("processing: " + filePathBean.getFileName());
            }
            exec.shutdown();

            while(true){
                if (exec.awaitTermination(5, TimeUnit.SECONDS)) {
                    logger.debug("Batch processing thumbnail over!");
                    // Result封装了处理结果,具体实现类可参见下面
                    result.setResult(Result.SUCCESS);
                    result.setMessage(Result.MESSAGE_SUCCESS);
                    break;
                }
            }
        } catch (Exception e) {
            logger.error("Batch processing thumbnail error: " + e.getMessage());
            result.setResult(Result.FAILURE);
            result.setMessage("文件处理出错!");
        }
        return result;
    }
   /**
    * 
    * 功能描述:压缩图片操作的接口
    * @param filePath 原图路径
    * @param width 压缩图宽度
    * @param height 压缩图高度
    * @param quality 设置图片的质量
    * @return IMOperation,im4java处理图片操作的接口
    */
    public IMOperation getOpThumbnailImage(String filePath, String width, String height,
            String quality) {
        IMOperation op = new IMOperation();
        op.addImage();

        if (!StringUtils.isEmpty(width) && StringUtils.isEmpty(height)) {
            op.addRawArgs("-scale", width);
        } else if (!StringUtils.isEmpty(height) && StringUtils.isEmpty(width)) {
            op.addRawArgs("-scale", "x" + height);
        } else if (StringUtils.isEmpty(width) && StringUtils.isEmpty(height)) {
            try {
                // 这里如果图片是损坏的,则直接返回null
                Info info = new Info(filePath, true);
                width = String.valueOf(info.getImageWidth());
                height = String.valueOf(info.getImageHeight());
            } catch (InfoException e) {
                logger.error("Get picture information error, please ensure image is complete: "
                        + e.getMessage());
                return null;
            }
            op.addRawArgs("-scale", width + "x" + height + "^");
            op.addRawArgs("-gravity", "center");
            op.addRawArgs("-extent", width + "x" + height);
        } else {
            op.addRawArgs("-scale", width + "x" + height + "^");
            op.addRawArgs("-gravity", "center");
            op.addRawArgs("-extent", width + "x" + height);
        }
        if (StringUtils.isEmpty(quality)) {
            op.addRawArgs("-quality", MagickConstants.GENERAL_QUALITY);
        } else {
            op.addRawArgs("-quality", quality);
        }
        op.strip();
        op.addImage();
        return op;
    }

上面两个是主要的操作函数,下面便是封装了图片处理信息及处理结果的部分Entity定义 :

   /**
    * 
    * 功能描述: 封装了图片处理信息的Entity
    */
    public class FilePathBean {

        // 图片名
        String fileName;

        //图片源路径
        String fileFullPath;
    
        //图片压缩后路径
        String fileFullToPath;
    
        //压缩高度
        String height;
    
        //压缩宽度
        String width;
    
        //压缩质量
        String quality;

	// 相应的get、set方法
	...
    /**
     * 
     * 功能描述: 返回图片处理结果Entity
     */
    public class Result {
         public static String SUCCESS ="1";
         public static String FAILURE ="0";
         public static String MESSAGE_SUCCESS ="success";
         public static String MESSAGE_FAILURE ="failure";

         private String result;

         private String message;
	 // 相应的get、set方法
	 ...


通过上面以多线程的方式来对图片进行处理,性能上有很明显地提升,目前处理的速度已经达到了每秒几十张,当然机器如果够强悍的话,处理速度会更快。


关于gm的其他命令,还有很多,这里再给出一个水印的多线程批量操作接口,一般也有压缩过后再对图片进行水印的场景:

    /**
     * 功能描述:水印的操作接口,水印图片将会被印在右下角,透明度为80,位偏移量x轴10像素,y轴5像素的位置
     * @param filePath 源图片路径,这里生成的目标图片路径将覆盖掉源图片
     * @return IMOperation,im4java处理图片操作的接口
     */
    public IMOperation getOpWaterMark(String filePath) {
        IMOperation op = new IMOperation();      
	op.gravity("SouthEast"); 
	op.dissolve("80"); 
	op.geometry(null, null, 10, 5); 
	op.addImage("/opt/water.jpg"); 
	op.addImage(filePath); 
	op.addImage(filePath); 
	return op; 
    }



 

你可能感兴趣的:(GraphicsMagick,ImageMagick,图片压缩,Im4java)