Java: 使用AWT工具Graphics2D后端生成图片

发布于 2023-09-21  1423 次阅读


公司出现一个业务,需要动态生成图片,并且要放到后端,不能前端生成。

在多个技术方案选型对比下,最终选择Graphics2D来画

使用上会复杂一点,但是可自定义程度会高一点,最开始有想过通过html页面来转化和浏览器截图模式,但是会增加额外的耦合,比如连接无头浏览器啥的。

Graphics2D是一个抽象类,并且由于Graphics2D它必须由子类针对不同的输出设备进行自定义,因此Graphics2D无法直接创建对象。相反,对象必须从另一个Graphics2D对象获取,由 创建, Graphics2D Component或从图像(如BufferedImage对象)获取

实现:

我定义好的工具类:

package com.solveyourworries.server.utils.posterUtil;

import com.solveyourworries.server.utils.posterUtil.bo.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public interface PosterUtils {

    default void background(Graphics2D g2d, int width, int height) {
        Color bg = new Color(255, 255, 255);
        g2d.setColor(bg);
        g2d.fillRect(0, 0, width, height);
    }

    default String saveImage(Graphics2D g2d, BufferedImage image) throws IOException {
        //释放对象
        g2d.dispose();
        // 保存文件
        File tempFile = File.createTempFile("share", "png");
        ImageIO.write(image, "png", tempFile);

        byte[] bytesArray = new byte[(int) tempFile.length()];
        FileInputStream fis = new FileInputStream(tempFile);
        fis.read(bytesArray);
        fis.close();
        tempFile.delete();

        var res = Base64.encodeBase64String(bytesArray);
        if (StringUtils.isNotBlank(res)) {
            return "data:image/jpg;base64," + res.replaceAll("[\\s*\t\n\r]", "");
        }
        return null;
    }

    default void drawFont(Graphics2D g2d, FontBO fontBo) {
        if (fontBo != null) {
            g2d.setFont(fontBo.getFont());
            g2d.setColor(fontBo.getColor());
            g2d.drawString(fontBo.getMsgs(), fontBo.getX(), fontBo.getY());
        }
    }
    default void drawImage(Graphics2D g2d, ImgBO imgBo) {
        if (imgBo != null) {
            g2d.drawImage(imgBo.getImage(), imgBo.getX(), imgBo.getY(), imgBo.getW(), imgBo.getH(), null);
        }
    }

    default void drawLine(Graphics2D g2d, LineBO lineBO) {
        if(lineBO == null) return;
        g2d.setColor(lineBO.getColor());
        if (lineBO.isDashed()) {
            Stroke stroke = g2d.getStroke();
            g2d.setStroke(LineBO.DEFAULT_STROKE);
            g2d.drawLine(lineBO.getX1(), lineBO.getY1(), lineBO.getX2(), lineBO.getY2());
            g2d.setStroke(stroke);
        } else {
            g2d.drawLine(lineBO.getX1(), lineBO.getY1(), lineBO.getX2(), lineBO.getY2());
        }
    }
    default void drawRoundRect(Graphics2D g2d, RoundRectBO roundRectBO) {
        if(roundRectBO == null) return;
        g2d.setColor(roundRectBO.getColor());
        if (!roundRectBO.isDashed()) {
            g2d.drawRoundRect(roundRectBO.getX(), roundRectBO.getY(),
                    roundRectBO.getW(),
                    roundRectBO.getH(),
                    roundRectBO.getRadius(),
                    roundRectBO.getRadius());
        } else {
            Stroke stroke = g2d.getStroke();

            g2d.setStroke(RoundRectBO.DEFAULT_DASH);
            g2d.drawRoundRect(roundRectBO.getX(), roundRectBO.getY(),
                    roundRectBO.getW(),
                    roundRectBO.getH(),
                    roundRectBO.getRadius(),
                    roundRectBO.getRadius());
            g2d.setStroke(stroke);
        }


        if (roundRectBO.getW() > 0) { // 上边距空白的宽度
            int x = roundRectBO.getX();
            int y = roundRectBO.getY() - 2;
            int w = roundRectBO.getW();
            int h = 4;
            g2d.setColor(roundRectBO.getColor());
            g2d.fillRect(x, y, w, h);
        }
    }
    default void drawColorBG(Graphics2D g2d, ColorBgBO color) {
        if(color == null) return;
        g2d.setColor(color.getColor());

        Composite composite = null;
        if (color.isTransparence()) {
            composite = g2d.getComposite();
            g2d.setComposite(AlphaComposite.Src);
        }

        if (color.getRadius() == 0) {
            g2d.fillRect(color.getX(), color.getY(), color.getW(), color.getH());
        } else {
            g2d.fill(new RoundRectangle2D.Float(color.getX(), color.getY(), color.getW(), color.getH(), color.getRadius(), color.getRadius()));
        }

        if (color.isTransparence()) {
            g2d.setComposite(composite);
        }
    }
}

实体类:

package com.solveyourworries.server.utils.posterUtil.bo;

import lombok.Data;

import java.awt.*;

@Data
public class ColorBgBO implements IDrawBO {

    private Color color;

    private int w;

    private int h;

    private int x;

    private int y;

    private int radius;

    private boolean transparence;
}
package com.solveyourworries.server.utils.posterUtil.bo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.awt.*;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class FontBO implements IDrawBO {

    private String msgs;

    private Font font;

    private Color color;

    private int x;

    private int y;

    private boolean deleted = false;
}
package com.solveyourworries.server.utils.posterUtil.bo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.awt.*;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ImgBO implements IDrawBO {
    private Image image;

    private int x;

    private int y;

    private int w;

    private int h;

}
package com.solveyourworries.server.utils.posterUtil.bo;

import lombok.Data;

import java.awt.*;

@Data
public class LineBO implements IDrawBO {

    public static final Stroke DEFAULT_STROKE = new BasicStroke(2,
            BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND,
            3.5f,
            new float[]{12, 6, 6, 6},
            0f);

    private Color color;

    private int x1;

    private int y1;

    private int x2;

    private int y2;

    /**
     * 是否是虚线
     */
    private boolean dashed;
}
package com.solveyourworries.server.utils.posterUtil.bo;

import lombok.Data;

import java.awt.*;

@Data
public class RoundRectBO implements IDrawBO {

    public static final Stroke DEFAULT_DASH = new BasicStroke(1,
            BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND,
            3.5f,
            new float[]{4, 2,},
            0f);

    private int x;

    private int y;

    private int w;

    private int h;

    private Color color;

    /**
     * 是否为虚线
     */
    private boolean dashed;


    /**
     * 圆角弧度
     */
    private int radius;
}

最总效果:


欢迎欢迎~热烈欢迎~