使用 Grapicmagick 和 Im4java 處理圖片

gary-liu發表於2017-02-13

ImageMagick是個圖片處理工具可以安裝在絕大多數的平臺上使用,Linux、Mac、Windows都沒有問題。GraphicsMagick是在ImageMagick基礎上的另一個專案,大大提高了圖片處理的效能,在linux平臺上,可以使用命令列的形式處理圖片。Im4java 和Jmagick 都是開源社群為上面兩個工具開發的 Java API,效能和方便度上im4java是更好的選擇。

JMagick vs Im4Java

  1. JMagick是一個開源API,利用JNI(Java Native Interface)技術實現了對ImageMagick API的Java訪問介面,因此也將比純Java實現的圖片操作函式在速度上要快。JMagick只實現了ImageMagicAPI的一部分功能,它的發行遵循LGPL協議。

  2. im4java是ImageMagick的另一個Java開源介面。與JMagick不同之處在於im4java只是生成與ImageMagick相對應的命令列,然後將生成的命令列傳至選中的ImageCommand(使用java.lang.ProcessBuilder.start()實現)來執行相應的操作。它支援大部分ImageMagick命令,可以針對不同組的圖片多次複用同一個命令列。

im4java只是封裝ImageMagick的命令。所以不需要依賴dll,也不存在64位系統呼叫32位dll的問題.而且im4java支援GraphicsMagick,GraphicsMagick是ImageMagick的分支。相對ImageMagick ,GraphicsMagick更穩定,消耗資源更少。最重要的是不依賴dll環境
所以使用 im4java 是更好的選擇。

Im4java處理圖片示例

這篇文章主要是 im4java 的使用,而 im4java 又是對 GraphicsMagick 命令的封裝,GraphicsMagick 命令的使用可以看另一篇文章 GraphicsMagick 1.3.23 常用命令

先寫個包含獲取圖片資訊的簡單工具類,如下。

public class Im4JavaUtils {

    private static Logger logger = LoggerFactory.getLogger(Im4JavaUtils.class);

    // 圖片質量
    public static final String IMAGE_QUALITY = "quality";
    // 圖片高度
    public static final String IMAGE_HEIGHT = "height";
    // 圖片寬度
    public static final String IMAGE_WIDTH = "width";
    // 圖片格式
    public static final String IMAGE_SUFFIX = "suffix";
    // 圖片大小
    public static final String IMAGE_SIZE = "size";
    // 圖片路徑
    public static final String IMAGE_PATH = "path";

    /**
     * 是否使用 GraphicsMagick
     */
    private static final boolean IS_USE_GRAPHICS_MAGICK = true;

    /**
     * ImageMagick安裝路徑,windows下使用
     */
    private static final String IMAGE_MAGICK_PATH = "D:\\software\\ImageMagick-6.2.7-Q8";

    /**
     *  gm 命令所在目錄
     */
    private static final String GRAPHICS_MAGICK_PATH = "/usr/local/bin";

    /**
     * 水印圖片路徑
     */
    private static final String watermarkImagePath = "/Users/gary/Documents/Job/ImageProcessTool/Im4java/Linux_logo.jpg";
    /**
     * 水印圖片
     */
    private static Image watermarkImage = null;
    static {
        try {
            watermarkImage = ImageIO.read(new File(watermarkImagePath));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 命令型別
     *
     */
    private enum CommandType {
        convert("轉換處理"), identify("圖片資訊"), compositecmd("圖片合成");
        private String name;

        CommandType(String name) {
            this.name = name;
        }
    }

    /**
     * 查詢圖片的基本資訊:格式,質量,寬度,高度
     * <p>
     * gm identify -format %w,%h,%d/%f,%Q,%b,%e /Users/gary/Documents/999999999/10005582/1.jpg
     * <p>
     *
     * @param imagePath
     * @return
     */
    public static Map<String, String> getImageInfo(String imagePath) {
        long startTime = System.currentTimeMillis();
        Map<String, String> imageInfo = new HashMap<>();
        try {
            IMOperation op = new IMOperation();
            op.format("%w,%h,%d/%f,%Q,%b,%e");
            op.addImage();
            ImageCommand identifyCmd = getImageCommand(CommandType.identify);
            ArrayListOutputConsumer output = new ArrayListOutputConsumer();
            identifyCmd.setOutputConsumer(output);
            identifyCmd.run(op, imagePath);
            ArrayList<String> cmdOutput = output.getOutput();
            String[] result = cmdOutput.get(0).split(",");
            if (result.length == 6) {
                imageInfo.put(IMAGE_WIDTH, result[0]);
                imageInfo.put(IMAGE_HEIGHT, result[1]);
                imageInfo.put(IMAGE_PATH, result[2]);
                imageInfo.put(IMAGE_QUALITY, result[3]);
                imageInfo.put(IMAGE_SIZE, result[4]);
                imageInfo.put(IMAGE_SUFFIX, result[5]);
            }
        } catch (Exception e) {
            // e.printStackTrace();
            logger.error("圖片工具獲取圖片基本資訊異常" + e.getMessage(), e);
        }
        long endTime = System.currentTimeMillis();
        // logger.info("take time: " + (endTime - startTime));
        return imageInfo;
    }

    /**
    * 獲取 ImageCommand
    *
    * @param command 命令型別
    * @return
    */
   private static ImageCommand getImageCommand(CommandType command) {
       ImageCommand cmd = null;
       switch (command) {
           case convert:
               cmd = new ConvertCmd(IS_USE_GRAPHICS_MAGICK);
               break;
           case identify:
               cmd = new IdentifyCmd(IS_USE_GRAPHICS_MAGICK);
               break;
           case compositecmd:
               cmd = new CompositeCmd(IS_USE_GRAPHICS_MAGICK);
               break;
       }
       if (cmd != null && System.getProperty("os.name").toLowerCase().indexOf("windows") == -1) {
           cmd.setSearchPath(IS_USE_GRAPHICS_MAGICK ? GRAPHICS_MAGICK_PATH : IMAGE_MAGICK_PATH);
       }
       return cmd;
   }
  }

上面的程式碼中要注意幾個問題:

  1. 獲取 ImageCommand時,在new ConvertCmd(true),引數要填true,不填的預設使用的是ImageMagick,引數為true時,才會使用grapicmagick的命令。
  2. 獲取 ImageCommand 後,要設定命令的搜尋路徑,有時可能會不穩定找不到gm命令,例如 cmd.setSearchPath(“/usr/local/bin”); (gm 命令在該路徑下)

獲取圖片資訊的命令中有個引數 -format ,該引數可取的值如下。



 %b   file size of image read in
 %c   comment property
 %d   directory component of path
 %e   filename extension or suffix
 %f   filename (including suffix)
 %g   layer canvas page geometry   ( = %Wx%H%X%Y )
 %h   current image height in pixels
 %i   image filename (note: becomes output filename for "info:")
 %k   number of unique colors
 %l   label property
 %m   image file format (file magic)
 %n   exact number of images in current image sequence
 %o   output filename  (used for delegates)
 %p   index of image in current image list
 %q   quantum depth (compile-time constant)
 %r   image class and colorspace
 %s   scene number (from input unless re-assigned)
 %t   filename without directory or extension (suffix)
 %u   unique temporary filename (used for delegates)
 %w   current width in pixels
 %x   x resolution (density)
 %y   y resolution (density)
 %z   image depth (as read in unless modified, image save depth)
 %A   image transparency channel enabled (true/false)
 %C   image compression type
 %D   image dispose method
 %G   image size ( = %wx%h )
 %H   page (canvas) height
 %M   Magick filename (original file exactly as given,  including read mods)
 %O   page (canvas) offset ( = %X%Y )
 %P   page (canvas) size ( = %Wx%H )
 %Q   image compression quality ( 0 = default )
 %S   ?? scenes ??
 %T   image time delay
 %W   page (canvas) width
 %X   page (canvas) x offset (including sign)
 %Y   page (canvas) y offset (including sign)
 %Z   unique filename (used for delegates)
 %@   bounding box
 %#   signature
 %%   a percent sign
 \n   newline
 \r   carriage return

壓縮圖片質量

  /**
     * 圖片壓縮
     * <p>
     * 拼裝命令示例: gm convert -quality 80 /apps/watch.jpg /apps/watch_compress.jpg
     *
     * @param srcImagePath
     * @param destImagePath
     * @param quality
     * @throws Exception
     */
    public static void compressImage(String srcImagePath, String destImagePath, double quality) throws Exception {
        IMOperation op = new IMOperation();
        op.quality(quality);
        op.addImage();
        op.addImage();
        ImageCommand cmd = getImageCommand(CommandType.convert);
        cmd.run(op, srcImagePath, destImagePath);
    }

裁剪圖片

 /**
     * 裁剪圖片
     *
     * @param imagePath 源圖片路徑
     * @param newPath   處理後圖片路徑
     * @param x         起始X座標
     * @param y         起始Y座標
     * @param width     裁剪寬度
     * @param height    裁剪高度
     * @return 返回true說明裁剪成功, 否則失敗
     */
    public static boolean cutImage(String imagePath, String newPath, int x, int y, int width, int height) {
        boolean flag = false;
        try {
            IMOperation op = new IMOperation();
            op.addImage(imagePath);
            /** width:裁剪的寬度 * height:裁剪的高度 * x:裁剪的橫座標 * y:裁剪縱座標 */
            op.crop(width, height, x, y);
            op.addImage(newPath);
            ConvertCmd convert = new ConvertCmd(true);
            convert.run(op);
            flag = true;
        } catch (IOException e) {
            System.out.println("檔案讀取錯誤!");
            flag = false;
        } catch (InterruptedException e) {
            flag = false;
        } catch (IM4JavaException e) {
            flag = false;
        } finally {

        }
        return flag;
    }

縮放圖片

/**
     * 根據尺寸縮放圖片[等比例縮放:引數height為null,按寬度縮放比例縮放;引數width為null,按高度縮放比例縮放]
     *
     * @param imagePath 源圖片路徑
     * @param newPath   處理後圖片路徑
     * @param width     縮放後的圖片寬度
     * @param height    縮放後的圖片高度
     * @return 返回true說明縮放成功, 否則失敗
     */
    public static boolean zoomImage(String imagePath, String newPath, Integer width, Integer height) {

        boolean flag;
        try {
            IMOperation op = new IMOperation();
            op.addImage(imagePath);
            if (width == null) {// 根據高度縮放圖片
                op.resize(null, height);
            } else if (height == null) {// 根據寬度縮放圖片
                op.resize(width);
            } else {
                op.resize(width, height);
            }
            op.addImage(newPath);
            ConvertCmd convert = new ConvertCmd(true);
            convert.run(op);
            flag = true;
        } catch (IOException e) {
            System.out.println("檔案讀取錯誤!");
            flag = false;
        } catch (InterruptedException e) {
            flag = false;
        } catch (IM4JavaException e) {
            flag = false;
        }
        return flag;
    }

圖片旋轉


  /**
     * 圖片旋轉(順時針旋轉) 
     * 拼裝命令示例: gm convert -rotate 90 /apps/watch.jpg /apps/watch_compress.jpg
     *
     * @param imagePath 源圖片路徑
     * @param newPath   處理後圖片路徑
     * @param degree    旋轉角度
     */
    public static boolean rotate(String imagePath, String newPath, double degree) {
        boolean flag;
        try {
            // 1.將角度轉換到0-360度之間
            degree = degree % 360;
            if (degree <= 0) {
                degree = 360 + degree;
            }
            IMOperation op = new IMOperation();
            op.rotate(degree);
            op.addImage(imagePath);
            op.addImage(newPath);
            ConvertCmd cmd = new ConvertCmd(true);
            cmd.run(op);
            flag = true;
        } catch (Exception e) {
            flag = false;
            System.out.println("圖片旋轉失敗!");
        }
        return flag;
    }

新增文字水印

 /**
     * 文字水印
     *
     * @param srcImagePath  源圖片路徑
     * @param destImagePath 目標圖片路徑
     * @param content       文字內容(不支援漢字)
     * @throws Exception
     */
    public static void addTextWatermark(String srcImagePath, String destImagePath, String content) throws Exception {
        IMOperation op = new IMOperation();

        op.font("ArialBold");
        // 文字方位-東南
        op.gravity("southeast");
        // 文字資訊
        op.pointsize(60).fill("#F2F2F2").draw("text 10,10 " + content);

        // 原圖
        op.addImage(srcImagePath);
        // 目標
        op.addImage(destImagePath);
        ImageCommand cmd = getImageCommand(CommandType.convert);
        cmd.run(op);
    }

新增文字前要安裝文字包,或直接指向 ttf 文字格式檔案上。
OSX 安裝


brew install ghostscript

新增中文水印會亂碼,待解決。

新增圖片水印

 /**
     * 圖片水印
     *
     * @param srcImagePath  源圖片路徑
     * @param destImagePath 目標圖片路徑
     * @param dissolve      透明度(0-100)
     * @throws Exception
     */
    public static void addImgWatermark(String srcImagePath, String destImagePath, Integer dissolve) throws Exception {
        // 原始圖片資訊
        BufferedImage buffimg = ImageIO.read(new File(srcImagePath));
        int w = buffimg.getWidth();
        int h = buffimg.getHeight();
        IMOperation op = new IMOperation();
        // 水印圖片位置
        op.geometry(watermarkImage.getWidth(null), watermarkImage.getHeight(null),
                w - watermarkImage.getWidth(null) - 10, h - watermarkImage.getHeight(null) - 10);
        // 水印透明度
        op.dissolve(dissolve);
        // 水印
        op.addImage(watermarkImagePath);
        // 原圖
        op.addImage(srcImagePath);
        // 目標
        op.addImage(destImagePath);
        ImageCommand cmd = getImageCommand(CommandType.compositecmd);
        cmd.run(op);
    }

參考資料

im4java官網
hailin0/im4java-util

相關文章