当前位置: 首页 > article >正文

Java 图片合成

前序

本周接到了新项目中的一个需求:根据给定的内容合成一张图片,需求如下:

  1. 标题自动换行,如果标题中出现英文单词时,以单词为最小单元进行换行。
  2. 如果行数超过5行省略用 … 代替。
  3. 符号是下一行首字母时,自动截留到上一行末尾。
  4. 空格为下一行开头,则删除空格,显示单词,保持内容左对齐
技术栈(JAI)

Java Advanced Imaging (JAI) 是一个用于处理图像的开源Java库。它提供了一个框架,可以用来访问各种图像源,包括本地文件系统、网络资源以及数据库等,并可以对这些图像进行转换和分析。

优点:JDK 自带内容,操作简单,不用引入新的依赖。

代码概述

本功能通过加载本地的背景图片和传入的参数进行图片的合成。主要包含了背景图、二维码插图和文本内容。其中二维码插图通过 HuTool 工具包提供生成方法。

具体代码

QrCodeImageConfig.java(二维码插图配置类)

import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

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

/**
 * 插图参数
 */
@Getter
@Slf4j
public class QrCodeImageConfig {
    /**
     * 二维码内容
     */
    private final String text;
    /**
     * 二维码宽度
     */
    private final Integer width;
    /**
     * 二维码高度
     */
    private final Integer height;
    /**
     * 二维码颜色
     */
    private final Color color;
    /**
     * 二维码图片接收对象
     */
    private final File qrCodeImgFile;
    /**
     * JAI 图片对象
     */
    private final BufferedImage qrCodeBuffer;

    /**
     * 构造函数
     *
     * @param text 二维码内容
     * @param width 二维码宽度
     * @param height 二维码高度
     * @param color 二维码颜色
     * @param qrCodeImg 二维码图片接收对象
     */
    public QrCodeImageConfig(String text, Integer width, Integer height, Color color, File qrCodeImg) {
        if (StringUtils.isBlank(text) || width == null || height == null || color == null || qrCodeImg == null) {
            throw new IllegalArgumentException("QrCodeImageParam.QrCodeImageParam 参数异常");
        }
        this.color = color;
        this.width = width;
        this.height = height;
        this.text = text;
        this.qrCodeImgFile = qrCodeImg;
        try {
            QrConfig config = new QrConfig(width, height);
            config.setBackColor(color);
            config.setMargin(1);
            QrCodeUtil.generate(text, config, qrCodeImg);
            this.qrCodeBuffer = ImageIO.read(qrCodeImg);
        } catch (IOException e) {
            throw new RuntimeException("QrCodeImageParam.QrCodeImageParam2 二维码图片配置创建异常");
        }
    }

    /**
     * 获取图片高度
     */
    public int getImageHeight() {
        return qrCodeBuffer.getHeight();
    }

    /**
     * 获取图片宽度
     */
    public int getImageWidth() {
        return qrCodeBuffer.getWidth();
    }
}

TextConfig.java(文本内容配置类)

import lombok.Getter;

import java.awt.*;

/**
 * 文本内容配置
 */
@Getter
public class TextConfig {
    /**
     * 文本内容
     */
    private final String text;
    /**
     * 字体库
     */
    private final String typeface;
    /**
     * 是否加粗
     */
    private final Boolean boldFont;
    /**
     * 字号
     */
    private final Integer fontSize;
    /**
     * 字体颜色
     */
    private final Color fontColor;
    /**
     * 行间距
     */
    private final Integer lineSpacing;
    /**
     * 字间距
     */
    private final Integer wordSpace;

    /**
     * x轴坐标
     */
    private final Integer x;
    /**
     * y轴坐标
     */
    private Integer y;

    /**
     * 构造函数
     *
     * @param text 文本内容
     * @param typeface 字体库
     * @param boldFont 是否加粗
     * @param fontSize 字号
     * @param fontColor 字体颜色
     * @param lineSpacing 行间距
     * @param wordSpace 字间距
     * @param x x轴坐标
     * @param y y轴坐标
     */
    public TextConfig(String text, String typeface, Boolean boldFont, Integer fontSize, Color fontColor,
                      Integer lineSpacing, Integer wordSpace, Integer x, Integer y) {
        this.text = text;
        this.typeface = typeface;
        this.boldFont = boldFont;
        this.fontSize = fontSize;
        this.fontColor = fontColor;
        this.lineSpacing = lineSpacing;
        this.wordSpace = wordSpace;
        this.x = x;
        this.y = y;
    }

    /**
     * 构造函数
     *
     * @param text 文本内容
     * @param typeface 字体库
     * @param boldFont 是否加粗
     * @param fontSize 字号
     * @param fontColor 字体颜色
     * @param lineSpacing 行间距
     * @param wordSpace 字间距
     * @param x x轴坐标
     */
    public TextConfig(String text, String typeface, Boolean boldFont, Integer fontSize, Color fontColor,
                      Integer lineSpacing, Integer wordSpace, Integer x) {
        this.text = text;
        this.typeface = typeface;
        this.boldFont = boldFont;
        this.fontSize = fontSize;
        this.fontColor = fontColor;
        this.lineSpacing = lineSpacing;
        this.wordSpace = wordSpace;
        this.x = x;
    }
}

GenerateAttendanceImageUtil.java(签到图片生成工具类)

import lombok.extern.slf4j.Slf4j;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * 生成签到图片工具类
 */
@Slf4j
public class GenerateAttendanceImageUtil {
    private static int y = 0;

    /**
     * 生成签到页图片(PNG格式)
     *
     * @param backgroundImage 背景图片
     * @param title           会议主题配置
     * @param dateAddress     时间地址配置
     * @param qrCodeImgCfg    二维码图配置
     * @param conferee        与会人配置
     * @param footer          页脚配置
     * @param outputFile      图片输出位置
     */
    public static void createCompositeImage(File backgroundImage, TextConfig title, TextConfig dateAddress,
                                            QrCodeImageConfig qrCodeImgCfg, TextConfig conferee, TextConfig footer, File outputFile) {
        try {
            // 加载背景图片
            BufferedImage bgImgBuffer = ImageIO.read(backgroundImage);
            Graphics g = bgImgBuffer.getGraphics();

            // 插入文字准备
            Graphics2D g2d = (Graphics2D) g;
            g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

            // 插入会议主题
            y = title.getY();
            drawString(title, g, g2d, bgImgBuffer.getWidth() - title.getX(), false);
            // 插入日期、地点
            y += 40;
            drawString(dateAddress, g, g2d, bgImgBuffer.getWidth() - dateAddress.getX(), false);
            // 插入二维码
            y = Math.max(y + 30, 310);
            g.drawImage(qrCodeImgCfg.getQrCodeBuffer(), (bgImgBuffer.getWidth() - qrCodeImgCfg.getWidth()) / 2, y, null);
            // 插入与会人
            y += 40 + qrCodeImgCfg.getImageHeight();
            drawString(conferee, g, g2d, bgImgBuffer.getWidth(), true);
            // 插入页脚
            y = bgImgBuffer.getHeight() - 40;
            drawString(footer, g, g2d, bgImgBuffer.getWidth(), true);

            g.dispose();
            // 将结果保存为新的图片
            ImageIO.write(bgImgBuffer, "png", outputFile);
        } catch (IOException e) {
            log.error(e.getMessage());
        } finally {
            if (qrCodeImgCfg.getQrCodeImgFile().exists()) {
                boolean delete = qrCodeImgCfg.getQrCodeImgFile().delete();
                if (!delete) {
                    log.error("GenerateAttendanceImageUtil.createCompositeImage finally 二维码图片删除失败");
                }
            }
        }

    }

    /**
     * 绘制文字
     */
    private static void drawString(TextConfig textConfig, Graphics g, Graphics2D g2d, Integer bgImgWidth, boolean horizontalCenter) {
        g.setFont(new Font(textConfig.getTypeface(), textConfig.getBoldFont() ? Font.BOLD : Font.PLAIN, textConfig.getFontSize()));
        g.setColor(textConfig.getFontColor());

        FontMetrics fm = g2d.getFontMetrics();
        int lineHeight = fm.getHeight();
        int x = horizontalCenter ? (bgImgWidth - fm.stringWidth(textConfig.getText())) / 2 : textConfig.getX();
        int lingNum = 1;
        for (String text : convertToArray(textConfig.getText())) {
            int textWidth = lingNum > 4 ? fm.stringWidth(text + "...") : fm.stringWidth(text);
            boolean needTrim = false;
            // 最多显示5行超出部分省略
            if (x + textWidth > bgImgWidth) {
                if (lingNum >= 5) {
                    g2d.drawString("...", x, y);
                    break;
                }

                // 符号截留在上一行,不做下一行的首字母
                if (!Character.isLetterOrDigit(text.charAt(0)) && !Character.isWhitespace(text.charAt(0))) {
                    g2d.drawString(text, x, y);
                    continue;
                }
                x = textConfig.getX();
                y += lineHeight + textConfig.getLineSpacing();
                needTrim = true;
                lingNum++;
            }
            // 取消换行后首字母的空格
            if (needTrim && text.equals(" ")) {
                continue;
            }
            g2d.drawString(text, x, y);
            x += fm.stringWidth(text) + textConfig.getWordSpace();
        }
    }

    /**
     * 判断是否为英文或数字
     */
    private static boolean isLetterOrNumber(char c) {
        String character = String.valueOf(c);
        return Pattern.matches("[a-zA-Z0-9]", character);
    }

    /**
     * 将字符串拆分成最小单元
     */
    public static String[] convertToArray(String input) {
        List<String> resultList = new ArrayList<>();
        for (int i = 0; i < input.length(); i++) {
            char s = input.charAt(i);
            if (isLetterOrNumber(s)) {
                StringBuilder sb = new StringBuilder();
                while (i < input.length() && isLetterOrNumber(input.charAt(i))) {
                    sb.append(input.charAt(i));
                    i++;
                }
                i--;
                resultList.add(sb.toString());
            } else {
                resultList.add(String.valueOf(s));
            }
        }
        return resultList.toArray(new String[0]);
    }
}

注:

  1. 工具的核心方法有两个,一是 convertToArray() 函数,我们需要通过该方法将文本内容拆分成最小单元,每一个最小单元为数组中的一个元素。换行时需要通过判断元素和本行以生成内容的长度来判断是否进行换行。
  2. 二是drawString()函数,通过该函数实现了自动换行,符号截留,首字母空格消除等功能。
  3. 该工具类由于高度耦合需求所以没有抽取成一个大家都能公用的类库大家使用,需要使用的同学请根据自己的需求进行逻辑的调整。
测试类
import java.awt.*;
import java.io.File;
import java.util.UUID;

/**
 * 测试 生成签到二维码图片
 */
public class Test {
    public static void main(String[] args) {
        File bgImg = new File("/Users/Desktop/background.png");
        TextConfig title = new TextConfig("aaaaaaaaaaaaaaaaa aaaaaaaaaaaa aa aaaaaaaaaa, aaaaaaaaa, aaa aaaaaaaa aaaaaaaaaaaaaaaaa aaaaaaaaaaaa aa aaaaaaaaa aaaaaaaaaa aaaaaaaaaa",
                "Arial", true, 24, Color.BLACK, 2, 1, 24, 144);
        TextConfig dateAddress = new TextConfig("January 7, 2023-March 7, 2024 | Hong Kong, China", "Arial",
                false, 16, Color.GRAY, 2, 0, 24);
        QrCodeImageConfig qrCodeImgCfg = new QrCodeImageConfig("https://www.baidu.com", 200, 200,
                Color.WHITE, new File("/Users/Desktop/" + UUID.randomUUID() + "qrcode.png"));
        TextConfig conferee = new TextConfig("Hello World", "Arial", false, 30, Color.BLACK,
                0, 0, 24);
        TextConfig footer = new TextConfig("保存图片用于XXXX", "Arial", false, 14,
                Color.GRAY, 0, 0, 24);

        File outputFile = new File("/Users/Desktop/composite_image.png");

        GenerateAttendanceImageUtil.createCompositeImage(bgImg, title, dateAddress, qrCodeImgCfg, conferee, footer, outputFile);
    }
}

--------------------------------------------人生哪能多如意,万事只求半称心--------------------------------------------


http://www.kler.cn/a/324752.html

相关文章:

  • FreeSWITCH chat 得到的是 Error! Message Not Sent
  • 效率工具-tig的使用
  • Godot的开发框架应当是什么样子的?
  • TCP(下):三次握手四次挥手 动态控制
  • 机器学习 决策树
  • 《基于Oracle的SQL优化》读书笔记
  • 【CKA】二、节点管理-设置节点不可用
  • UDP与TCP那个传输更快
  • 【高阶数据结构】平衡二叉树(AVL)的插入(4种旋转方法+精美图解+完整代码)
  • 深度解析:Debian 与 Ubuntu 常用命令的区别与联系
  • Electron 安装以及搭建一个工程
  • GGHead:基于3D高斯的快速可泛化3D数字人生成技术
  • TCN预测 | MATLAB实现TCN时间卷积神经网络多输入单输出回归预测
  • WPF入门教学十三 MVVM模式简介
  • 极狐GitLab 17.4 重点功能解读【二】
  • Git 工作区、暂存区和版本库
  • 从事人工智能学习Python还是学习C++?
  • 巴鲁夫rfid读头国产平替版——高频RFID读写器
  • element的描述列表<el-descriptions>添加字典翻译功能
  • Lodash库
  • 24年Novartis诺华制药社招入职SHL测评:综合能力、性格问卷、动机问卷高分攻略
  • count(1),count(*)与 count(‘列名‘) 的区别
  • Docker部署MongoDB教程
  • 3. 轴指令(omron 机器自动化控制器)——>MC_MoveZeroPosition
  • Linux内核启动之根文件系统挂载
  • 串、数组和广义表