java动态生成图片

本文章是个人的一个记录,各位可以看看。

题目:根据数据动态生成图片,已上网随便找的一个医院报告单为例:

图片:

要求有一下几点:

        1.里面所有的数据除了报告单名称都是动态的,还需要支持自动换行,换行后的数据单独占一行,其他行数据自动下移

        2.图片大小为一张a4纸大小,如果数据内容过多,超过了页面长度,则需要自动扩容

        3.下面的检验者和审核者姓名是一张图片,需要覆盖到上面去

要求大概就这么多,但由于没有数据,所以数据都是自定义的,有些地方因为没有数据格式也做不到完全动态。代码如下:

import cn.lili.common.utils.StringUtils;
import org.springframework.util.CollectionUtils;


import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class image {
    // 当前行Y轴坐标
    private static int y = 0;
    // 出现换行时下一行Y坐标
    private static int nextY = 0;
    // 图片宽度
    private static int width = 842;
    // 图片高度
    private static int height = 594;
    // 旧图片高度,用于长度扩展
    private static int oldHeight = 592;
    // 下一项坐标间隔
    private static int xNextSize = 200;
    // 下一行坐标间隔
    private static int yNextSize = 20;
    // 边框
    private static int broder = 21;
    private static int x = broder;
    // 项目名称前序号像素大小
    private static int itemNumSize = 32;
    
    public static void main(String[] args) throws IOException {
        Long t1 = System.currentTimeMillis();
        Map<String, String> data = new HashMap<>(16);
        data.put("姓名", "病人");
        data.put("门诊类型", "门诊");
        data.put("送检科室", "急诊科");
        data.put("标本号", "167");
        data.put("性别", "男");
        data.put("病历号", "W001121");
        // 标本类型为血清的检查项目类名称
        List<String> serumItems = new ArrayList();
        serumItems.add("校验项目名称");
        serumItems.add("英文名称");
        serumItems.add("检验结果");
        serumItems.add("参考范围");
        serumItems.add("单位");
        // 标本类型为血清的检查项目类数据
        List<Map<String, String>> serumData = new ArrayList();
        Map<String, String> serumDataMap = new HashMap();
        for (int i = 0; i < 10; i++) {
            serumDataMap = new HashMap();
            serumDataMap.put("1", "校验项目名称A" + i + 1);
            serumDataMap.put("2", "英文名称A" + i + 1);
            serumDataMap.put("3", "检验结果A" + i + 1);
            serumDataMap.put("4", "参考范围A" + i + 1);
            serumDataMap.put("5", "单位A" + i + 1);
            serumData.add(serumDataMap);
        }
        // 图片最底部数据
        List<Map<String, String>> tailList = new ArrayList();
        Map<String, String> tailMap = new HashMap<>();
        tailMap.put("送检日期", "2018-12-14 15:26:06");
        tailList.add(tailMap);
        Map<String, String> tailMap1 = new HashMap<>();
        tailMap1.put("检验时间", "2018-12-14 15:26:15");
        tailList.add(tailMap1);
        Map<String, String> tailMap2 = new HashMap<>();
        tailMap2.put("校验者", "C:/Users/紫影/OneDrive/图片/name.png");
        tailList.add(tailMap2);
        Map<String, String> tailMap3 = new HashMap<>();
        tailMap3.put("审核者", "C:/Users/紫影/OneDrive/图片/name.png");
        tailList.add(tailMap3);
        getImage(data, serumItems, serumData, tailList);
        Long t2 = System.currentTimeMillis();
        System.out.printf("时间:" + (t2 - t1));
    }
    
    public static void getImage(Map<String, String> data, List<String> items, List<Map<String, String>> itemData,
                                List<Map<String, String>> tailList) throws IOException {
        // 创建一个画布对象
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        BufferedImage newImage = null;
        Graphics2D g2d = image.createGraphics();
        
        // 在画布上绘制图像元素
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, width, height);
        g2d.setColor(Color.BLACK);
        // 标题
        String str = "XXXX市人民医院检验报告单";
        Font font = new Font("宋体", Font.BOLD, 26);
        Map<String, Integer> juZhong = getImageJuZhong(g2d, font, str, width, height);
        y = 40;
        g2d.setFont(font);
        g2d.drawString(str, juZhong.get("x"), y);
        Font font1 = new Font("宋体", Font.PLAIN, 16);
        y += yNextSize + 10;
        for (Map.Entry<String, String> entry : data.entrySet()) {
            // 填入数据
            str = entry.getKey() + ":" + entry.getValue();
            // 想图片中添加数据
            draw(g2d, font1, str);
        }
        // 画横线
        g2d.drawLine(broder, y += 10, width - broder, y);
        if (!CollectionUtils.isEmpty(itemData)) {
            // 项目名称需要空出下面具体项目的需要位置
            x = broder + itemNumSize;
            y = y + yNextSize;
            // 计算项目名称所需要的位置大小
            xNextSize = (width - itemNumSize - broder * 2) / items.size();
            // 平均数差出来的值,放入第一项中,防止出现多个项目名称除不开的情况,多出来的这个值放在第一位数据中
            int ys = (width - itemNumSize - broder * 2) % items.size();
            
            for (int i = 0; i < items.size(); i++) {
                // 将项目名称写入图像
                // 第一个项需要将差值放入
                if (i == 0 && ys != 0) {
                    xNextSize = xNextSize + ys;
                }
                // 第二个项之后需要将差值减掉
                if (i == 1 && ys != 0) {
                    xNextSize = xNextSize - ys;
                }
                // 写入项目名称
                draw(g2d, font1, items.get(i));
                // 超出页面长度,扩宽图片长度,按理说每个写入都需要带这么一段代码,但有些基本项一般不会超出长度,有一些就不写了
                if (y >= height - 60) {
                    // 这里新增的高度应该空出尾部图片的高度
                    height = y + yNextSize + 60;
                    newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                    // 复制原始图像到新图像
                    WritableRaster originalRaster = image.getRaster();
                    WritableRaster resizedRaster = newImage.getRaster();
                    int[] pixelData = new int[width * height];
                    resizedRaster.setDataElements(0, 0, width, height, originalRaster.getDataElements(0, 0, width
                            , y, pixelData));
                    oldHeight = height;
                    image = newImage;
                    g2d = newImage.createGraphics();
                    g2d.setColor(Color.WHITE);
                    g2d.fillRect(0, y, width, height);
                    g2d.setColor(Color.BLACK);
                }
            }
            // 画横线
            g2d.drawLine(broder, y - 10, width - broder, y - 10);
            // 处理具体项目数据
            y += 10;
            for (int i = 1; i <= itemData.size(); i++) {
                Map<String, String> item = itemData.get(i - 1);
                x = broder;
                int copyXnextSize = xNextSize;
                xNextSize = itemNumSize;
                // 写入项目前的序号,一般也不会超,所以不增加扩容代码
                draw(g2d, font1, String.valueOf(i));
                xNextSize = copyXnextSize;
                // 放入项目数据
                for (int j = 1; j <= item.size(); j++) {
                    // 第一个项需要将差值放入
                    if (j == 1 && ys != 0) {
                        xNextSize = xNextSize + ys;
                    }
                    // 第二个项之后需要将差值减掉
                    if (j == 2 && ys != 0) {
                        xNextSize = xNextSize - ys;
                    }
                    // 写入项目
                    draw(g2d, font1, item.get(String.valueOf(j)));
                    // 超出页面长度,扩宽图片长度
                    if (y >= height - 60) {
                        height = y + yNextSize + 60;
                        newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                        // 复制原始图像到新图像
                        WritableRaster originalRaster = image.getRaster();
                        WritableRaster resizedRaster = newImage.getRaster();
                        int[] pixelData = new int[width * height];
                        resizedRaster.setDataElements(0, 0, width, height, originalRaster.getDataElements(0, 0, width
                                , y, pixelData));
                        oldHeight = height;
                        image = newImage;
                        g2d = newImage.createGraphics();
                        g2d.setColor(Color.WHITE);
                        g2d.fillRect(0, y, width, height);
                        g2d.setColor(Color.BLACK);
                    }
                }
            }
            
        }
        // 处理最后的项目,先生成一个图片,然后用覆盖的方式方法哦要返回的图片中
        // 高度默认50,如果不够进行线下扩容
        BufferedImage tail = new BufferedImage(width, 50, BufferedImage.TYPE_INT_RGB);
        BufferedImage newTail = null;
        Graphics2D g2d1 = tail.createGraphics();
        g2d1.setColor(Color.WHITE);
        g2d1.fillRect(0, 0, width, 50);
        g2d1.setColor(Color.BLACK);
        Font font2 = new Font("宋体", Font.PLAIN, 16);
        g2d1.setFont(font2);
        // 画横线
        g2d1.drawLine(broder, 10, width - broder, 10);
        x = broder;
        y = 30;
        xNextSize = 250;
        if (!CollectionUtils.isEmpty(tailList)) {
            for (int i = 0; i < tailList.size(); i++) {
                Map<String, String> tailMap = tailList.get(i);
                for (Map.Entry<String, String> entry : tailMap.entrySet()) {
                    String key = entry.getKey();
                    String value = entry.getValue();
                    str = entry.getKey() + ":" + entry.getValue();
                    // 处理图片,如果按照实际情况,应该会有一个特殊标识说明他是图片,但实验数据里没有,就这么写了
                    if (key.equals("校验者") || key.equals("审核者")) {
                        // 这里为了达到图片上的效果的宽度也需要进行计算,就是数据的长度,上面的为了保持公证都是一个定好的数据
                        FontMetrics fm = g2d.getFontMetrics(font2);
                        // 这里 + 10 是为了让图片和文字之间有一个间隔,在写入数据方法内部判断是否换行时 -10,所以这里加10
                        int i1 = fm.stringWidth(key) + 10;
                        int oldNextX = xNextSize;
                        xNextSize = i1;
                        // 覆盖图片
                        BufferedImage name = ImageIO.read(new File(value));
                        // 写入名称
                        draw(g2d1, font2, key);
                        // 写入图片
                        g2d1.drawImage(name, x, 15, 30, 30, null);
                        // 这里加30是未了让x坐标移动到图片的后面,应该还需要对宽度是否需要换行进行判断,但在这里就不进行判断了
                        x += 30;
                        xNextSize = oldNextX;
                    } else {
                        FontMetrics fm = g2d.getFontMetrics(font2);
                        // 这里 + 10 是为了让图片和文字之间有一个间隔,在写入数据方法内部判断是否换行时 -10,所以这里加10
                        int i1 = fm.stringWidth(str) + 10;
                        int oldNextX = xNextSize;
                        xNextSize = i1;
                        // 处理其他数据
                        draw(g2d1, font2, str);
                        xNextSize = oldNextX;
                    }
                    // 扩容
                    if (y >= height - 60) {
                        // 这里新增的高度就正常一行高度即可
                        height = y + yNextSize;
                        newImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                        // 复制原始图像到新图像
                        WritableRaster originalRaster = image.getRaster();
                        WritableRaster resizedRaster = newImage.getRaster();
                        int[] pixelData = new int[width * height];
                        resizedRaster.setDataElements(0, 0, width, height, originalRaster.getDataElements(0, 0, width
                                , y, pixelData));
                        oldHeight = height;
                        image = newImage;
                        g2d = newImage.createGraphics();
                        g2d.setColor(Color.WHITE);
                        g2d.fillRect(0, y, width, height);
                        g2d.setColor(Color.BLACK);
                    }
                }
            }
            
        }
        // 将生成的尾部图片覆盖到结果图片中
        g2d.drawImage(tail, 0, height - 50, width, 50, null);
        // 保存生成的图像到文件
        File outputFile = new File("iamge.png");
        
        try {
            ImageIO.write(image, "png", outputFile);
            System.out.println("Image generated successfully.");
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        
        // 释放资源
        g2d.dispose();
        g2d1.dispose();
    }
    
    /**
     * 写数据
     *
     * @param g2d
     * @param font1 样式
     * @param str   数据
     */
    private static void draw(Graphics2D g2d, Font font1, String str) {
        BufferedImage a = null;
        // 超出长度切割文字进行换行
        List<String> strs = getStrWith(g2d, font1, str, xNextSize);
        // 读取换行文字
        for (int i = 0; i < strs.size(); i++) {
            // data内容
            g2d.setFont(font1);
            if (StringUtils.isNotBlank(strs.get(i))) {
                // 只有一个数据说明不需要换行
                if (i == 0) {
                    g2d.drawString(strs.get(i), x, y);
                } else {
                    // 产生了换行,y轴坐标需要向下移动
                    nextY = y + yNextSize * i;
                    g2d.drawString(strs.get(i), x, nextY);
                }
            }
        }
        // 判断是否需要移动到下一行展示,这个下一行是整个数据的下一行,不是换行数据
        if (x + xNextSize >= width - broder) {
            // 如果换行下一行数据有值,表示这一样数据发生了换行,下一行数据需要再换行数据的下一行展示
            if (0 != nextY) {
                y = nextY + yNextSize;
                nextY = 0;
            } else {
                // 没有换行,正常换一行就行
                y += yNextSize;
                nextY = 0;
            }
            // 将x轴重置到最开始
            x = broder;
        } else {
            // 不需要换行,将x轴移动到下一个数据的起始为止
            x += xNextSize;
        }
    }
    
    /**
     * 根据文字样式和图片大小计算居中位置坐标
     *
     * @param g2d    图片
     * @param font   文字样式
     * @param str    文字
     * @param width  宽
     * @param height 高
     * @return X轴和Y轴坐标
     **/
    private static Map<String, Integer> getImageJuZhong(Graphics2D g2d, Font font, String str, int width, int height) {
        Map<String, Integer> map = new HashMap<>();
        FontMetrics fm = g2d.getFontMetrics(font);
        int ascent = fm.getAscent();
        int descent = fm.getDescent();
        int y = (height - (ascent + descent)) / 2 + ascent;
        
        /**获取起始x轴*/
        int strLen = fm.stringWidth(str);
        int x = (width - strLen) / 2;
        map.put("x", x);
        map.put("y", y);
        return map;
    }
    
    /**
     * 对超出长度的字符串进行切割
     *
     * @param g2d
     * @param font
     * @param str
     * @param width
     * @date 下午2:18 10/5/2024
     **/
    private static List<String> getStrWith(Graphics2D g2d, Font font, String str, int width) {
        // 循环字符串,每次添加判断长度是否超过指定宽度,超过就截取,然后放入新的集合汇总
        List<String> strs = new ArrayList<>();
        FontMetrics fm = g2d.getFontMetrics(font);
        String s = "";
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            int strLen = fm.stringWidth(s);
            // 这里减10是未了流出一定的空白,防止数据连一起
            if (strLen > width - 10) {
                strs.add(s);
                s = String.valueOf(c);
            } else {
                s = s + c;
            }
        }
        strs.add(s);
        return strs;
    }
}

        这是整个代码文件,可以直接复制运行,如果各位感兴趣可以直接复制运行一下然后自己改改数据试试。这个代码里有一个需要说明的地方,可以看到扩容代码在整个方法中复制了好几次,这个代码是不能抽出来做成一个单独方法的,原因没太弄明白,如果抽出来做成一个方法,在方法内部的引用修改回到外部方法就失效了,还是原值。如果是增加一个返回值,那么返回值一直都是null,如果各位大佬有懂的希望能评论说明一下。

        代码写的有些简陋,希望各位多多包涵

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值