springboot通过ftl模板动态生成图片(html生成图片imgBase64),java生成图片

第一步需要的依赖

<!-- ftl模板依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
            <version>2.2.9.RELEASE</version>
        </dependency>

        <!-- html转图片 -->
        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-core</artifactId>
            <version>9.1.22</version>
        </dependency>

第二步建一个ftl模板

ftl其实就是html,但是填充数据相关的标签需要特定的标签,有其他需求的可以百度ftl标签用法

test.ftl

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="text/html;charset=UTF-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            font-size: ${minFontSize};
            font-family: SimSun;
        }
        table{
            border:1px solid #000; border-collapse: collapse;
            background-color: #F1F4E8;
            margin-bottom: 20px;
        }
        table thead tr{
            border:1px solid #888;
            padding:3px 0px;font-weight: bold; background-color: #DDE3C6;
        }
        table tr td{
            border:1px solid #000;
        }

    </style>
</head>
<body>
<div >
    <table style="margin-left:2px;" >
        <thead>
            <tr>
                <td colspan="8" align="center" style="font-size:10px;padding:5px 0px; font-weight: bold">${headName}</td>
            </tr>
            <tr align="center">
                <td >序号</td>
                <td >姓名</td>
                <td >证件号</td>
            </tr>
        </thead>
        <tbody>
            <#list mapList as t>
            <tr align="center">
                <td>${t.index}</td>
                <td>${t.idCardNo}</td>
                <td>${t.idCardNo}</td>
            </tr>
            </#list>
        </tbody>
    </table>
</div>

</body>
</html>

 

第三步创建一个工具类

可以看到在html2ImgBase64方法中有一部分代码是我给图片存在本地目录中,返回的是imgbase64,这个看个人需求看是需要图片还是base64自行处理

import com.qxnw.healthserver.core.util.AliossUtil;
import com.qxnw.healthserver.core.util.Assert;
import com.qxnw.healthserver.entity.CompanyReimburseDetail;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.w3c.dom.Document;
import org.xhtmlrenderer.layout.SharedContext;
import org.xhtmlrenderer.swing.Java2DRenderer;

import javax.imageio.ImageIO;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @description 工具类
 */
@Slf4j
public class FreemarkerUtil {
    public static void main(String[] args) throws Exception {
        List<Object> li = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            Map<String, Object> d = new HashMap<>();
            d.put("index", i+1);
            d.put("idCardNo", "110102196310213038");
            li.add(d);
        }

        Map<String, Object> map = new HashMap<>();
        map.put("headName", "结算单个人明细");
        map.put("mapList",li);
        ftiToHtmlToImg("test.ftl", map, 6, 1800);
    }
    /**
     * templatename模板名称,如:test.ftl
     * */
    public static void ftiToHtmlToImg(String templatename, Map<String, Object> map, Integer minFontSize, Integer IMG_WIDTH_PX) throws Exception{
        Assert.notNull(minFontSize, "最小字体大小必传,若生成3000~4000的明细图片数据,需调字体大小");//范例:5px
        Assert.notNull(IMG_WIDTH_PX, "IMG_WIDTH_PX必传(生成的图片画布宽度越大,图片占像素越少,留白越多)");
        map.put("minFontSize", minFontSize+"px");

        String html = FreemarkerUtil.generate(templatename, map);
        String baseCode = FreemarkerUtil.html2ImgBase64(html, IMG_WIDTH_PX, -1);
        baseCode = baseCode.replace("data:image/jpg;base64,","");
        AliossUtil.base64ToImage2(baseCode);
    }

    private static Configuration config = null;

    /**
     * 初始化获取html模板
     */
    static {
        config = new Configuration(Configuration.VERSION_2_3_20);
        config.setDefaultEncoding("UTF-8");
        try {
            config.setClassForTemplateLoading(FreemarkerUtil.class, "/template");
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.toString());
        }
    }

    /**
     * 把BufferedImage 图片转base64
     *
     * @param bufferedImage
     * @return
     * @throws Exception
     */
    private static String bufferedImageToBase64(BufferedImage bufferedImage) throws Exception {
        String png_base64;//转换成base64串
        //io流
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            ImageIO.write(bufferedImage, "png", baos);//写入流中
            byte[] bytes = baos.toByteArray();//转换成字节
            png_base64 = Base64.getEncoder().encodeToString(bytes);
            png_base64 = png_base64.replaceAll("\n", "").replaceAll("\r", "");//删除 \r\n
        }
        return "data:image/jpg;base64," + png_base64;
    }

    /**
     * 将html转成base64字节
     *
     * @param html
     * @param width
     * @param height
     * @return
     * @throws Exception
     */
    public static String html2ImgBase64(String html, int width, int height) throws Exception {
        byte[] bytes = html.getBytes();
        BufferedImage img;
        try (ByteArrayInputStream bin = new ByteArrayInputStream(bytes)) {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(bin);
            Java2DRenderer renderer = new Java2DRenderer(document, width, height);
            SharedContext sharedContext = renderer.getSharedContext();
            sharedContext.setDotsPerPixel(3);
            sharedContext.setDPI(523);
            img = renderer.getImage();
        }
        return bufferedImageToBase64(img);
    }

    /**
     * 将html转成 图片
     *
     * @param html
     * @param width
     * @param height
     * @return
     * @throws Exception
     */
    public static BufferedImage html2Img(String html, int width, int height) throws Exception {
        byte[] bytes = html.getBytes();
        BufferedImage img;
        //转BufferedImage
        try (ByteArrayInputStream bin = new ByteArrayInputStream(bytes)) {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(bin);
            Java2DRenderer renderer = new Java2DRenderer(document, width, height);
            SharedContext sharedContext = renderer.getSharedContext();
            sharedContext.setDotsPerPixel(3);
            sharedContext.setDPI(523);
            //字体
            Font simsun = getSIMSUN(Font.BOLD, 24);
            sharedContext.setFontMapping("simsun", simsun);//这样设置字体无效
            Map map = new HashMap<>();//设置参数
            map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            map.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            map.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
            map.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            map.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
            map.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            map.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            map.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
            map.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
            renderer.setRenderingHints(map);
            img = renderer.getImage();
        }
        return img;
    }

    /**
     * 获取模板数据
     *
     * @param template
     * @param params
     * @return
     * @throws Exception
     */
    public static String generate(String template, Map params) throws Exception {
        Template tp = config.getTemplate(template);
        tp.setEncoding("UTF-8");
        StringWriter stringWriter = new StringWriter();
        String htmlStr;
        try {
            tp.process(params, stringWriter);
            htmlStr = stringWriter.toString();
            stringWriter.flush();
        } finally {
            stringWriter.close();
        }
        return htmlStr;
    }

    /**
     * 宋体
     *
     * @param style Font.BOLD
     * @param size  24
     */
    public static Font getSIMSUN(int style, float size) {
        Font font = null;
        //获取字体流
        InputStream simsunFontFile = FreemarkerUtil.class.getResourceAsStream("/fonts/simsun.ttc");
        try {
            //创建字体
            font = Font.createFont(Font.PLAIN, simsunFontFile).deriveFont(style, size);
        } catch (FontFormatException e) {
            log.error("", e);
        } catch (IOException e) {
            font = new Font("宋体", Font.BOLD, 6);
            log.error("", e);
        }
        return font;
    }
}

 

附上一个工具类 AliossUtil.java  简单封装了一些写入到本地的方法

package com.qxnw.healthserver.core.util;

import cn.hutool.core.net.URLEncodeUtil;
import com.qxnw.healthserver.core.base.constants.Constants;
import lombok.extern.slf4j.Slf4j;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Base64;

@Slf4j
public class AliossUtil {
    /**目标图片大小,至于高度,等比例即可,ps:width在2000以上,已看不出区别*/
    public static final int TARGET_WIDTH = 2500;

    public static void main(String[] args) throws IOException {
        Long a = System.currentTimeMillis();
//        String url = "https://file-qx.siriushealth.net/afterSales/e271cbf8-795b-447d-a087-c9cb791c5694.jpg";
        String url = "http://admin.siriushealth.net/api/attachment/getIDCard?fileName=张飞_身份证.jpg";
//        resizeImgToLocal(url);
        resizeImg(url);
        Long b = System.currentTimeMillis();
        System.err.println(b-a);
    }

    /**压缩图片,存到本地*/
    public static InputStream resizeImg(String url){
        try {
            url = urlEncode(url);
            URL img = new URL(url);
            BufferedImage image = ImageIO.read(img);

            double rate = image.getWidth()*1.0/image.getHeight();
            int targetHeight = (int) (TARGET_WIDTH*1.0/rate);
            //调整图片大小为 3000 X 等比高度 尺寸
            BufferedImage newImage = resizeImage(image,TARGET_WIDTH,targetHeight);

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ImageIO.write(newImage, "jpg", outputStream);//这里写jpg,5.2M的图片是900kb,若写png,图片是12M
            return new ByteArrayInputStream(outputStream.toByteArray());
        } catch (IOException e) {
            log.error("===resizeImg错误,fileUrl="+url,e);
        }
        return null;
    }

    /**压缩图片,存到本地*/
    public static void resizeImgToLocal(String url){
        try {
            url = urlEncode(url);
            //通过url获取BufferedImage图像缓冲区
            URL img = new URL(url);
            BufferedImage image = ImageIO.read(img);
            //获取图片的宽、高
            double rate = image.getWidth()*1.0/image.getHeight();
            int targetHeight = (int) (TARGET_WIDTH*1.0/rate);
            //调整图片大小为 400X400尺寸
            BufferedImage newImage = resizeImage(image,TARGET_WIDTH,targetHeight);

            System.out.println("old:w,h= " + image.getWidth()+","+image.getHeight()+"\nnew:w,h="+TARGET_WIDTH+","+targetHeight);

            //将缓冲区图片保存到 F:/test/pic1.jpg (文件不存在会自动创建文件保存,文件存在会覆盖原文件保存)
            ImageIO.write(newImage, "jpg", new File("d:/a.png"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 没这个方法的话, 入参是:http://admin.siriushealth.net/api/attachment/getIDCard?fileName=张飞_身份证.jpg
     * 其实能读取到的地址是  :http://admin.siriushealth.net/api/attachment/getIDCard?fileName=%E5%BC%A0%E9%A3%9E_%E8%BA%AB%E4%BB%BD%E8%AF%81.jpg
     * 含问号的,不能把问号一起转义了
     * 需要注意的是,若fileName=other/张飞_身份证.jpg 怎是否需要将“/”转义,目前例子不够,不搞了
     * */
    private static String urlEncode(String url){
        Boolean contrinsChinese = containsChinese(url);
        if(url.contains("?") && contrinsChinese){
            int charAt = url.indexOf("?");
            String urlFront = url.substring(0, charAt+1);
            String urlBack = url.substring(charAt+1);
            urlBack = URLEncodeUtil.encode(urlBack);
            url = urlFront + urlBack;
            url = url.replace("%2F","/");
            url = url.replace("%3D","=");// 把/和=转义回来
        }else if(contrinsChinese){
            url = URLEncodeUtil.encode(url);
        }
        return url;
    }
    public static long getFileSize(String fileUrl) throws IOException {
        fileUrl = urlEncode(fileUrl);
        URL url = new URL(fileUrl);
        byte[] array = new byte[1024];
        InputStream inputStream = url.openStream();
        int size = 0;
        int length = 0;
        // fourth Exception --IOException
        while ((length = inputStream.read(array)) != -1) {
            size += length;
            //不必要完全获取文件大小,只要大于 Constants.minFileNoZip, 即,需要压缩
            if(size>= Constants.minFileNoZip){
                return size;
            }
        }

        return size; // 返回-1表示获取文件大小失败
    }

    public static boolean containsChinese(String input) {
        return input.chars().anyMatch(c -> Character.toString((char) c).matches("[\\u4e00-\\u9fa5]"));
    }

        /**
     * 通过BufferedImage图片流调整图片大小
     */
    public static BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight) throws IOException {
        Image resultingImage = originalImage.getScaledInstance(targetWidth, targetHeight, Image.SCALE_AREA_AVERAGING);
        BufferedImage outputImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
        outputImage.getGraphics().drawImage(resultingImage, 0, 0, null);
        return outputImage;
    }

    /**
     * img的base64转图片
     * */
    public static Image base64ToImage(String base64String) {
        byte[] imageBytes = Base64.getDecoder().decode(base64String);
        try {
            ByteArrayInputStream bis = new ByteArrayInputStream(imageBytes);
            BufferedImage image = ImageIO.read(bis);
            bis.close();
            return image;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * img的base64转图片
     * */
    public static Image base64ToImage2(String base64String) {
        byte[] imageBytes = Base64.getDecoder().decode(base64String);
        try {
            ByteArrayInputStream bis = new ByteArrayInputStream(imageBytes);
            BufferedImage image = ImageIO.read(bis);

            //获取图片的宽、高
            double rate = image.getWidth()*1.0/image.getHeight();
            int targetHeight = (int) (TARGET_WIDTH*1.0/rate);
            //调整图片大小为 400X400尺寸
            BufferedImage newImage = resizeImage(image,TARGET_WIDTH,targetHeight);

            System.out.println("old:w,h= " + image.getWidth()+","+image.getHeight()+"\nnew:w,h="+TARGET_WIDTH+","+targetHeight);

            //将缓冲区图片保存到 F:/test/pic1.jpg (文件不存在会自动创建文件保存,文件存在会覆盖原文件保存)
            ImageIO.write(newImage, "jpg", new File("d:/b.png"));


            bis.close();
            return image;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}
View Code

 

原文:https://blog.csdn.net/qq_41973632/article/details/131052877

 

posted @ 2024-06-25 11:51  君子笑而不语  阅读(357)  评论(0编辑  收藏  举报