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; } } }
原文:https://blog.csdn.net/qq_41973632/article/details/131052877