Java使用Graphics2D画图,画圆,矩形,透明度等实现

背景

如上图,需要使用Java生成一个图片, 并以base64编码的形式返回给前端展示。

使用Graphics2D类,来进行画图,其中需要画方框、原型、插入图标、写入文字等,同时需要设置透明度等细节点 

环境:Jdk17,springboot2.7.13

代码如下

有详细的注释

package com.demo;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.FileUtil;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ThreadLocalRandom;

/**
 * <p>
 * 功能描述:
 * </p>
 *
 * @author MILLA
 * @version 1.0
 * @since 2024/06/24 9:14
 */
@Slf4j
public class ImageDemo {

    /**
     * 每个div的高度
     */
    private static final int LINE_HEIGHT = 80;

    private static final double COLOR_WIDTH = 0.7;

    /**
     * 处方笺图片宽
     */
    private static final int PIC_WIDTH = 1200;

    /**
     * 顶部与底部留白
     */
    private static final int MARGIN_Y = 52;

    /**
     * 左右留白
     */
    private static final int MARGIN_X = 50;

    /**
     * 生成图片后缀
     */
    private static final String FILE_SUFFIX = ".jpg";

    public static void main(String[] args) throws Exception {
        List<Object> objects = Lists.newArrayList(1, 2, 3, 4, 5, 6);
        String base64 = new ImageDemo().getImage(objects);
        System.out.println(base64);
    }

    /**
     * 初始化
     *
     * @param image    画布
     * @param graphics 画笔
     */
    private void initiation(BufferedImage image, Graphics2D graphics) {
        int width = image.getWidth();
        int height = image.getHeight();
        graphics.setClip(0, 0, width, height);
        // 设置画笔颜色
        graphics.setColor(Color.white);
        // 绘制背景
        graphics.fillRect(0, 0, width, height);
        // 设置抗锯齿
        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    }

    private String getImage(List<Object> objects) throws IOException {
        //读取图标流
        InputStream stream = this.getClass().getClassLoader().getResourceAsStream("static/icon.png");
        BufferedImage avatar = ImageIO.read(stream);
        // 新建图片
        BufferedImage image = new BufferedImage(PIC_WIDTH, objects.size() * LINE_HEIGHT + MARGIN_Y * 2, BufferedImage.TYPE_INT_BGR);
        // 创建画笔
        Graphics2D graphics = image.createGraphics();
        // 初始化背景色
        initiation(image, graphics);

        // 定义margin
        Margin margin = new Margin(MARGIN_Y, MARGIN_Y, MARGIN_X, MARGIN_X);
        // 初始化坐标
        Point point = new Point(margin.getLeft(), margin.getTop());
        ThreadLocalRandom random = ThreadLocalRandom.current();
        for (int i = 0; i < objects.size(); i++) {
            Color color = new Color(random.nextInt(0, 255), random.nextInt(0, 255), random.nextInt(0, 255));
            drawDiv(point, image, graphics, color, avatar, "颜色名称: " + (i + 1), "P", "颜色编码:" + (i + 1));
        }
        // 销毁画笔,结束绘制
        graphics.dispose();
        byte[] bytes = toByteArray(image);
        //文件生成
        log.info("文件路径", FileUtil.writeBytes(bytes, "test" + FILE_SUFFIX));
        String prefix = "data:image/jpg;base64,";
        return prefix + Base64.encode(bytes);
    }

    private void drawDiv(Point point, BufferedImage image, Graphics2D graphics, Color color, BufferedImage avatar, String name, String type, String code) {
        Font font = new Font("宋体", Font.BOLD, 28);
        int width = image.getWidth() - 2 * point.getX();
        // 设置div的绘制区域
        graphics.setClip(point.getX(), point.getY(), width, LINE_HEIGHT);
//         设置画笔颜色
        graphics.setColor(color);
        int firstWidth = (int) (COLOR_WIDTH * width);
//         绘制背景 一行的前半部分
        graphics.fillRect(point.getX(), point.getY() + 1, firstWidth, LINE_HEIGHT - 2);
        // 设置画笔
        int nameX = point.getX() + 18;
        drawContent(name, graphics, nameX, point.getY(), Color.WHITE, image.getWidth(), point, font);
        int circleX = firstWidth - 15;
        drawCircle(point, graphics, circleX);
        font = new Font("宋体", Font.BOLD, 23);
        drawContent(type.toUpperCase(Locale.ROOT), graphics, circleX + 5, point.getY(), Color.WHITE, image.getWidth(), point, font);

        // 绘制背景 一行的后半部分--外部矩形框
        Color outerColor = new Color(Integer.parseInt("DDDDDD", 16));
        graphics.setColor(outerColor);
        int secondWidth = (int) ((1 - COLOR_WIDTH) * width);
        graphics.fillRect(point.getX() + firstWidth, point.getY(), secondWidth, LINE_HEIGHT);
        // 绘制背景 一行的后半部分---内部矩形框
        Color innerColor = new Color(Integer.parseInt("F4F4F4", 16));
        graphics.setColor(innerColor);
        graphics.fillRect(point.getX() + firstWidth + 1, point.getY() + 1, secondWidth - 2, LINE_HEIGHT - 2);
        //图标
        int avatarHeight = avatar.getHeight() / 2;
        int avatarX = point.getX() + firstWidth + 31;
        graphics.drawImage(avatar, avatarX, point.getY() + 1 + (LINE_HEIGHT - avatarHeight) / 2, avatar.getWidth() / 2, avatarHeight, innerColor, null);
        //图标 --文字
        int codTextX = avatarX + avatar.getWidth() / 3 + 33;
        font = new Font("宋体", Font.PLAIN, 20);
        drawContent(code, graphics, codTextX, point.getY(), Color.BLACK, image.getWidth(), point, font);
        point.setY(point.y + LINE_HEIGHT - 1);
    }

    private void drawCircle(Point point, Graphics2D graphics, int circleX) {
        Composite composite = graphics.getComposite();
        //透明度设置
        AlphaComposite instance = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);
        graphics.setComposite(instance);
        graphics.setColor(Color.BLACK);
        graphics.fillOval(circleX, point.getY() + (LINE_HEIGHT - 32) / 2, 32, 32);
        //恢复原来的透明度
        graphics.setComposite(composite);
    }

    private void drawContent(String text, Graphics2D cs, int x, int y, Color color, int width, Point point, Font font) {
        //临时将需要裁剪区域置空
        cs.setClip(null);
        //设置文本颜色
        cs.setColor(color);
        //设置文本字体
        cs.setFont(font);
        //文本抗锯齿
        cs.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        cs.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        //为画布添加文字,并居中
        FontMetrics fm = cs.getFontMetrics(font);
        int ascent = fm.getAscent();
        int descent = fm.getDescent();
        cs.drawString(text, x + 5, y + (LINE_HEIGHT - (ascent + descent)) / 2 + ascent);

        //恢复之前的裁剪区域
        cs.setClip(point.getX(), point.getY(), width - 2 * point.getX(), LINE_HEIGHT);
    }


    private byte[] toByteArray(BufferedImage image) throws IOException {
        // 输出png图片
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        image.flush();
        ImageIO.write(image, "png", os);
        return os.toByteArray();
    }

    @Data
    @AllArgsConstructor
    public static class Margin {
        /**
         * 上
         */
        private int top;

        /**
         * 底
         */
        private int bottom;

        /**
         * 左
         */
        private int left;

        /**
         * 右
         */
        private int right;
    }

    @Data
    @AllArgsConstructor
    public static class Point {
        private int x;
        private int y;
    }

}

 PS:生成的图片如文头,base64编码如下图

 

 但是在移植到docker容器中部署的时候,报以下错误

2024-06-25 16:26:31.019 [http-nio-10008-exec-7] ERROR com.a.mybatis.common.exception.RestfulExceptionHandler - 异常堆栈:
jakarta.servlet.ServletException: Handler dispatch failed: java.lang.UnsatisfiedLinkError: /opt/java/openjdk/lib/libfontmanager.so: Error loading shared library libfreetype.so.6: No such file or directory (needed by /opt/java/openjdk/lib/libfontmanager.so)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1096)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:705)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
        at com.github.xiaoymin.knife4j.extend.filter.basic.JakartaServletSecurityBasicAuthFilter.doFilter(JakartaServletSecurityBasicAuthFilter.java:55)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
        at com.huanyu.common.config.filter.TokenFilter.doFilter(TokenFilter.java:58)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:177)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400)
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:859)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1734)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.base/java.lang.Thread.run(Unknown Source)
Caused by: java.lang.UnsatisfiedLinkError: /opt/java/openjdk/lib/libfontmanager.so: Error loading shared library libfreetype.so.6: No such file or directory (needed by /opt/java/openjdk/lib/libfontmanager.so)
        at java.base/jdk.internal.loader.NativeLibraries.load(Native Method)
        at java.base/jdk.internal.loader.NativeLibraries$NativeLibraryImpl.open(Unknown Source)
        at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(Unknown Source)
        at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(Unknown Source)
        at java.base/jdk.internal.loader.NativeLibraries.findFromPaths(Unknown Source)
        at java.base/jdk.internal.loader.NativeLibraries.loadLibrary(Unknown Source)
        at java.base/java.lang.ClassLoader.loadLibrary(Unknown Source)
        at java.base/java.lang.Runtime.loadLibrary0(Unknown Source)
        at java.base/java.lang.System.loadLibrary(Unknown Source)
        at java.desktop/sun.font.FontManagerNativeLibrary$1.run(Unknown Source)
        at java.base/java.security.AccessController.doPrivileged(Unknown Source)
        at java.desktop/sun.font.FontManagerNativeLibrary.<clinit>(Unknown Source)
        at java.desktop/sun.font.SunFontManager$1.run(Unknown Source)
        at java.desktop/sun.font.SunFontManager$1.run(Unknown Source)
        at java.base/java.security.AccessController.doPrivileged(Unknown Source)
        at java.desktop/sun.font.SunFontManager.initStatic(Unknown Source)
        at java.desktop/sun.font.SunFontManager.<clinit>(Unknown Source)
        at java.base/java.lang.Class.forName0(Native Method)
        at java.base/java.lang.Class.forName(Unknown Source)
        at java.desktop/sun.font.FontManagerFactory$1.run(Unknown Source)
        at java.base/java.security.AccessController.doPrivileged(Unknown Source)
        at java.desktop/sun.font.FontManagerFactory.getInstance(Unknown Source)
        at java.desktop/java.awt.Font.getFont2D(Unknown Source)
        at java.desktop/java.awt.Font$FontAccessImpl.getFont2D(Unknown Source)
        at java.desktop/sun.font.FontUtilities.getFont2D(Unknown Source)
        at java.desktop/sun.java2d.SunGraphics2D.checkFontInfo(Unknown Source)
        at java.desktop/sun.java2d.SunGraphics2D.getFontInfo(Unknown Source)
        at java.desktop/sun.java2d.pipe.GlyphListPipe.drawString(Unknown Source)
        at java.desktop/sun.java2d.pipe.ValidatePipe.drawString(Unknown Source)
        at java.desktop/sun.java2d.SunGraphics2D.drawString(Unknown Source)

原因分析:

 Graphics2D类在执行文本写入的时候,需要使用字体插件,因为当前的运行环境中没有对应的 Error loading shared library libfreetype.so.6插件,因此就会报上述的错误。

经排查,博主使用的docker镜像是精简版本的,将一些不常用的功能代码都去除了,因此会出现这样那样的问题,最终使用完全的jre17,解决了该问题,备查!

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值