GraphicsMagick gm4java im4java

最近对使用Java对操作这方面问题进行了下调研, 随即写了个图片操作工具类, 包括图片的裁剪, 缩放, 移动, 旋转, 加水印, 后续待追加

  • 首先有两个成熟的操作图片的工具库, 分别是GM 和 IM, 在安装了两者其一的基础上, 打开CMD, 执行相关的命令, 就可实现对图片的一些操作
  • 然后在Java领域里, 就有封装好了的通过调用对应的类库, 底层再去调用上面两者的命令行执行, 进而实现图片操作, gm4java, im4javajmagick都是这样的工具
  • GM算是IM的分支, 但是与IM相比有几个优势, 其中最大的优点就是它的性能要好
  • gm4java本身比较简单, 就是一个执行器帮我们间接的去执行GM提供的一些命令, 但是它也提供了对im4java的支持!

现选择GM+gm4java实现图片的裁剪, 缩放, 拼合, 旋转, 移动功能, 然后在实现图片加水印的功能时一直行不通, 最后还是借用了im4java实现了, 后续有待改进

工具类实现:

package org.hinsteny.commons.io.image;

import org.gm4java.engine.GMException;
import org.gm4java.engine.GMService;
import org.gm4java.engine.GMServiceException;
import org.gm4java.engine.support.SimpleGMService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 图片裁剪, 变形, 加水印工具类
 * @author Hinsteny
 * @date 2017-08-07
 * @since 1.0.0
 */
public class ImageUtil {

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

    static {
        try {
            Runtime.getRuntime().exec("gm");
        } catch (IOException e) {
            logger.info("graphics_magick_path not set, so the ImageUtil may not work properly!");
            e.printStackTrace();
        }
    }

    /**
     * 对图片进行指定宽高的缩放, 保持原图的宽高比, 对于所给宽高与原图呼应适配
     * @param width
     * @param height
     * @param srcPath
     * @param desPath
     * @return
     */
    public static boolean convertReSizeImage(Integer width, Integer height, String srcPath, String desPath) {
        List<String> commonds = Arrays.asList(ActionEnum.CONVERT.getAction(), srcPath, OptionEnum.RESIZE.getOption(), width + "x" +height, desPath );
        return doExecute(commonds);
    }

    /**
     * 对图片进行指定宽高的变形, 如果宽高大于原尺寸会做拉伸处理, 但是小于的话不会压缩, 会进行裁剪
     * @param width
     * @param height
     * @param srcPath
     * @param desPath
     * @return
     */
    public static boolean convertReSizeImageTile(Integer width, Integer height, String srcPath, String desPath) {
        List<String> commonds = Arrays.asList(ActionEnum.CONVERT.getAction(), OptionEnum.SIZE.getOption(), width + "x" +height, "tile:" + srcPath, desPath );
        return doExecute(commonds);
    }

    /**
     * 对两张图片进行叠加结合, 以baseImage为底, changeImage在上, 两张图片左上角对其, 最终outputImage和baseImage等大小
     * @param changeImage
     * @param baseImage
     * @param outputImage
     * @return
     */
    public static boolean compositeImage(String changeImage, String baseImage, String outputImage) {
        List<String> commonds = Arrays.asList(ActionEnum.COMPOSITE.getAction(), changeImage, baseImage, outputImage );
        return doExecute(commonds);
    }

    /**
     * 对两张图片进行混合, 比较preImage和sufImage的颜色不同之处, 进行反向颜色混合
     * @param preImage
     * @param sufImage
     * @param maskImage
     * @param outputImage
     * @return
     */
    public static boolean compositeImage(String preImage, String sufImage, String maskImage, String outputImage) {
        List<String>  commonds = new ArrayList<>();
        commonds.addAll(Arrays.asList(ActionEnum.COMPOSITE.getAction(), OptionEnum.COMPOSE.getOption(), OptionEnum.DIFFERENCE.getOption(), preImage, sufImage));
        if (null != maskImage && maskImage.length() > 0)
            commonds.add(maskImage);
        commonds.add(outputImage);
        return doExecute(commonds);
    }

    /**
     * 把超过两张的图片进行拼合, 最终呈现从上至下的依次连接
     * @param inputImage ...
     * @param outputImage
     * @return
     */
    public static boolean convertAppendImages(String outputImage, String ... inputImage) {
        assert inputImage != null && inputImage.length > 1;
        List<String> commonds = new ArrayList<>();
        commonds.add(ActionEnum.CONVERT.getAction());
        commonds.addAll(Arrays.asList(inputImage));
        commonds.add(OptionEnum.APPEND.getOption());
        commonds.add(outputImage);

        return doExecute(commonds);
    }

    /**
     * rotate the image
     * 对一系列图片进行旋转
     * @param degrees
     * @param inputImage ...
     * @return
     */
    public static boolean mogrifyRotateImages(int degrees, String ... inputImage) {
        assert inputImage != null && inputImage.length > 0;
        List<String>  commonds = new ArrayList<>();
        commonds.add(ActionEnum.MOGRIFY.getAction());
        commonds.add(OptionEnum.ROTATE.getOption());
        commonds.add(String.valueOf(degrees));
        commonds.addAll(Arrays.asList(inputImage));

        return doExecute(commonds);
    }

    /**
     * roll an image vertically or horizontally
     * 对一系列图片基于原图基准位置进行左右或者上下移动
     * @param horizontal 水平位移
     * @param vertical 垂直位移
     * @param inputImage ...
     * @return
     */
    public static boolean mogrifyRollImages(int horizontal, int vertical, String ... inputImage) {
        assert inputImage != null && inputImage.length > 0;
        List<String> commonds = new ArrayList<>();
        commonds.addAll(Arrays.asList(ActionEnum.MOGRIFY.getAction(), OptionEnum.ROLL.getOption(), (horizontal > 0 ? "+" : "-") + horizontal + (vertical > 0 ? "+" : "-") + vertical));
        commonds.addAll(Arrays.asList(inputImage));

        return doExecute(commonds);
    }

    /**
     * annotate an image with one text string
     * 给一张图片加水印文字
     * @param inputImage
     * @param outputImage
     * @param text
     * @param text_color
     * @param text_size
     * @param x_point
     * @param y_point
     * @return
     */
    public static boolean convertDrawTextToImage(String inputImage, String outputImage, String text, String text_color, int text_size, int x_point, int y_point) {
        List<String> commonds = new ArrayList<>();
        commonds.addAll(Arrays.asList(ActionEnum.CONVERT.getAction(), OptionEnum.POINTSIZE.getOption(), String.valueOf(text_size), OptionEnum.FILL.getOption(),
                text_color,  OptionEnum.DRAW.getOption(), "\"text " + String.valueOf(x_point) + "," + String.valueOf(y_point) + " \'" + text + "\'\"", inputImage, outputImage));

        return doExecute(commonds);
    }

    /**
     * 执行gm commonds
     * @param commonds
     * @return
     */
    private static boolean doExecute(List<String> commonds) {
        assert null != commonds && commonds.size() > 0;
        String result = "";
        try {
//            System.err.println(commonds.stream().collect(Collectors.joining(" ")));
            if (logger.isInfoEnabled()) {
//                logger.info("Do excute commonds is: [{}]", commonds.stream().collect(Collectors.joining(" ")));
            }
            result = getGMService().execute(commonds);
        } catch (GMException |GMServiceException | IOException e) {
            e.printStackTrace();
            if (logger.isInfoEnabled()){
                logger.info("Result: {}, Exception: {}", result, e.toString());
            }
            return false;
        }
        return true;
    }

    private static GMService getGMService(){
        return new SimpleGMService();
    }

}

贴点测试代码:

package org.hinsteny.commons.io;

import org.hinsteny.commons.BaseTest;
import org.hinsteny.commons.ImageUitl;
import org.testng.annotations.Test;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

/**
 * @Auther Hinsteny
 * @Desc
 * @Date 2017-08-07
 * @copyright: 2017 All rights reserved.
 */
public class ImagesTest extends BaseTest {

    private static final String IMAGE_TYPE = ".jpg";
    private static final String IMAGE_Folder = "images";
    private static final String TEST_IMAGE = "togepi.jpg";

    private String getImgSourcePath(String fileName) {
        if (null == fileName || fileName.replace(" +", "").length() == 0)
            fileName = UUID.randomUUID().toString() + IMAGE_TYPE;
        String path = ImagesTest.this.getClass().getResource("").getPath();
        fileName = path.concat(IMAGE_Folder).concat(File.separator).concat(fileName);
        return System.getProperty("os.name").toLowerCase().indexOf("win") > -1 ? fileName.substring(1) : fileName;
    }

    /**
     * 等比缩放图片用例
     * @throws IOException
     */
    @Test
    public void testReSizeImage() throws IOException {
        String sourceFilePath = getImgSourcePath(TEST_IMAGE);
        ImageUitl.convertReSizeImage(300, 300, sourceFilePath, getImgSourcePath(null));
    }
    .....
}

  • O(∩_∩)O!*