博主最近项目有个需要,为门店做海报,涉及到图片的压缩,图片固定位置生成二维码、门店信息。
当然,生成海报,需要的规范的模板,才好确定你需要填充二维码、文字的坐标。废话少说,直接上代码吧。
海报下载有两种情况,
- 打印图 很大,一般下载直接贴在门店上,差不多10M左右
- 网络图 在打印图上面压缩10倍,用于app上的展示
当然,模板都是打印图,为了生成网络图快速,在上传打印图的时间,异步压缩
其次,无论是网络图,还是打印图,我们下载的时候生成一个zip包,先上工具类的代码吧
import com.alibaba.csp.sentinel.util.StringUtil;
import com.tuhu.intl.promotion.common.base.ZipFileInfo;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;
@Slf4j
public class FileUtil {
public static void compressFile(InputStream inputStream,String destUrl){
StopWatch stopWatch=new StopWatch();
stopWatch.start();
if(new File(destUrl).exists()){
return;
}
log.info("图片缩放开始========");
try {
// 1、定义图像文件对象
// 2、定义图像图像数据的访问的缓冲器
BufferedImage bufferedImage = ImageIO.read(inputStream);
// 3、获取图片的原始宽高
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
// 4、获取图片的缩放【宽高都是*了缩放比例的再取整】
Image scaledInstance = bufferedImage.getScaledInstance(Double.valueOf(width*0.1).intValue(),Double.valueOf(height * 0.1).intValue(),Image.SCALE_DEFAULT);
// 5、将Image类型转换成BufferedImage对象[BufferedImage.TYPE_INT_ARGB:表示具有8位RGBA颜色成分的整数像素的图像]
BufferedImage newImage = new BufferedImage(Double.valueOf(width*0.1).intValue(),Double.valueOf(height * 0.1).intValue(),BufferedImage.TYPE_INT_BGR);
// 一个新的图形上下文,这是这个图形上下文的副本
Graphics g = newImage.getGraphics();
// 绘制图片大小
g.drawImage(scaledInstance, 0, 0, null);
// 释放文件资源
g.dispose();
// 将新的图片文件写入到指定的文件夹中
String formatName=destUrl.substring(destUrl.lastIndexOf(".")+1);
ImageIO.write(newImage,formatName,new File(destUrl));
} catch (Exception e) {
log.error("图片缩放出错,错误信息{}",e.getMessage());
}
stopWatch.stop();
log.info("图片缩放成功 总耗时:{}",stopWatch.getTotalTimeMillis());
}
public static void zipFiles(List list, File zipFile) {
StopWatch stopWatch=new StopWatch();
stopWatch.start();
FileOutputStream fileOutputStream = null;
ZipOutputStream zipOutputStream = null;
try {
fileOutputStream = new FileOutputStream(zipFile);
zipOutputStream = new ZipOutputStream(fileOutputStream);
// 创建 ZipEntry 对象
ZipEntry zipEntry;
// 遍历源文件数组
for(ZipFileInfo shop:list){
// 将源文件数组中的当前文件读入 FileInputStream 流中
// 实例化 ZipEntry 对象,源文件数组中的当前文件
zipEntry = new ZipEntry(shop.getFileName());
zipOutputStream.putNextEntry(zipEntry);
// 该变量记录每次真正读的字节个数
int len;
// 定义每次读取的字节数组
byte[] buffer = new byte[1024];
while (!((len = shop.getInputStream().read(buffer)) <= 0)) {
zipOutputStream.write(buffer, 0, len);
}
}
} catch (Exception e) {
log.error("压缩图片出错,错误信息{}",e.getMessage());
} finally {
try {
if (zipOutputStream != null) {
zipOutputStream.closeEntry();
zipOutputStream.close();
}
} catch (IOException e) {
log.error("压缩图片出错,错误信息{}",e.getMessage());
}
try {
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
log.error("压缩图片出错,错误信息{}",e.getMessage());
}
}
stopWatch.stop();
log.info("图片压缩zip包耗时:{}",stopWatch.getTotalTimeMillis());
}
public static void downloadUrlFile(String sourceUrl, String descUrl){
if(new File(descUrl).exists()){
return;
}
InputStream is=null;
FileOutputStream os=null;
try {
// 构造URL
URL url = new URL(sourceUrl);
// 打开连接
URLConnection con = url.openConnection();
// 输入流
is = con.getInputStream();
// 1K的数据缓冲
byte[] bs = new byte[1024];
// 读取到的数据长度
int len;
// 输出的文件流
File file = new File(descUrl);
os = new FileOutputStream(file, true);
// 开始读取
while ((len = is.read(bs)) != -1) {
os.write(bs, 0, len);
}
}catch (Exception e){
log.error("downloadUrlFile error sourceUrl={},descUrl={}",sourceUrl,descUrl,e);
}finally {
// 完毕,关闭所有链接
try {
if(os!=null){
os.close();
}
} catch (IOException e) {
log.error(e.getMessage());
}
try {
if(is!=null){
is.close();
}
} catch (IOException e) {
log.error(e.getMessage());
}
}
}
public static void deleteFile(String... urls){
for(String url:urls){
try {
if(StringUtil.isNotBlank(url)){
Files.deleteIfExists(Paths.get(url));
}
} catch (IOException e) {
log.error("deleteFile error url={} error ",url,e);
}
}
}
}
二维码生成添加依赖
com.google.zxing
core
3.0.1
生成二维码图片+背景+文字描述工具类
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.tuhu.intl.promotion.common.base.ImgFIllInfo;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.imageio.ImageIO;
import javax.swing.JLabel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;
@Slf4j
public class QRCodeMaxUtil {
//文字显示
private static final int QR_COLOR = 0x201f1f; // 二维码颜色:黑色
private static final int BG_WHITE = 0xFFFFFF; //二维码背景颜色:白色
private static final String[] SHOP_KEY = {"shopName", "telephone", "address"};
private static final String QR_CODE_KEY = "qrCode";
// 设置QR二维码参数信息
private static final Map hints = new HashMap() {
private static final long serialVersionUID = 1L;
{
put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);// 设置QR二维码的纠错级别(H为最高级别)
put(EncodeHintType.CHARACTER_SET, "utf-8");// 设置编码方式
put(EncodeHintType.MARGIN, 0);// 白边
}
};
public static InputStream getFileInputStream(InputStream inputStream,String formatName,List fillList) {
BufferedImage bufferedImage = creatQRCode(inputStream,fillList);
return bufferImageToInputStream(bufferedImage, formatName);
}
private static InputStream bufferImageToInputStream(BufferedImage bufferedImage,String formatName) {
log.info("BufferedImage 转换为 InputStream 开始");
StopWatch stopWatch=new StopWatch();
stopWatch.start();
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ImageIO.write(bufferedImage, formatName, os);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(os.toByteArray());
stopWatch.stop();
log.info("BufferedImage 转换为 InputStream 完成,耗时{}",stopWatch.getTotalTimeMillis());
return byteArrayInputStream;
} catch (IOException e) {
log.error("BufferedImage 转换为 InputStream 出错,错误信息{}",e.getMessage());
}
return null;
}
public static BufferedImage creatQRCode(InputStream bgImgFile,List contentList) {
log.info("生成二维码图片+背景+文字描述开始");
StopWatch stopWatch=new StopWatch();
stopWatch.start();
//获取二维码配置
ImgFIllInfo qrCode = contentList.stream()
.filter(item -> item.getKey().equals(QR_CODE_KEY)).collect(Collectors.toList()).get(0);
try {
MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
// 参数顺序分别为: 编码内容,编码类型,生成图片宽度,生成图片高度,设置参数
BitMatrix bm = multiFormatWriter
.encode(MessageFormat.format(qrCode.getValue(),qrCode.getExtraData()), BarcodeFormat.QR_CODE, qrCode.getMaxWidth(), qrCode.getMaxWidth(), hints);
BufferedImage image = new BufferedImage(qrCode.getMaxWidth(), qrCode.getMaxWidth(),
BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < qrCode.getMaxWidth(); x++) {
for (int y = 0; y < qrCode.getMaxWidth(); y++) {
image.setRGB(x, y, bm.get(x, y) ? QR_COLOR //int 0
: BG_WHITE);
}
}
BufferedImage backgroundImage = ImageIO.read(bgImgFile);
Graphics2D rng = backgroundImage.createGraphics();
rng.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP));
rng.drawImage(image, qrCode.getTextX(), qrCode.getTextY(), qrCode.getMaxWidth(), qrCode.getMaxWidth(), null);
//文字描述参数设置
Color textColor = Color.white;
rng.setColor(textColor);
List shopList = contentList.stream()
.filter(ImgFIllInfo -> Arrays.asList(SHOP_KEY).contains(ImgFIllInfo.getKey()))
.collect(Collectors.toList());
for (ImgFIllInfo item : shopList) {
///设置字体类型和大小,颜色(BOLD加粗/ PLAIN平常)
Font font = new Font(item.getFontName(), item.getFontStyle()==1?Font.BOLD:Font.PLAIN, item.getFontSize());
rng.setFont(font);
rng.setColor(Color.black);
drawString(rng, font, item.getValue(), item.getTextX(),item.getTextY(), item.getMaxWidth());
}
rng.dispose();
image = backgroundImage;
image.flush();
stopWatch.stop();
log.info("生成二维码图片+背景+文字描述开始结束,总耗时:{}",stopWatch.getTotalTimeMillis());
return image;
} catch (Exception e) {
log.error("生成二维码图片+背景+文字描述出错,错误信息{}",e.getMessage());
}
return null;
}
public static void drawString(Graphics2D g, Font font, String text, int x, int y, int maxWidth) {
JLabel label = new JLabel(text);
label.setFont(font);
FontMetrics metrics = label.getFontMetrics(label.getFont());
int textH = metrics.getHeight();
int textW = metrics.stringWidth(label.getText()); //字符串的宽
String tempText = text;
while (textW > maxWidth) {
int n = textW / maxWidth;
int subPos = tempText.length() / n;
String drawText = tempText.substring(0, subPos);
int subTxtW = metrics.stringWidth(drawText);
while (subTxtW > maxWidth) {
subPos--;
drawText = tempText.substring(0, subPos);
subTxtW = metrics.stringWidth(drawText);
}
g.drawString(drawText, x, y);
y += textH;
textW = textW - subTxtW;
tempText = tempText.substring(subPos);
}
g.drawString(tempText, x, y);
}
}
压缩zip包的实体类
import java.io.InputStream;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors
@AllArgsConstructor
public class ZipFileInfo {
private InputStream inputStream;
private String fileName;
private String formatName;
}
生成二维码图片+背景+文字描述实体类
package com.tuhu.intl.promotion.common.base;
import java.util.List;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class ImgFIllInfo {
private String key;
private String value;
private int fontSize;
private int fontStyle;
private String fontName;
private int maxWidth;
private int textX;
private int textY;
private List extraData;
}
测试
public static void main(String[] args) throws IOException {
String bigUrl = "https://ptttor-dev-1309300786.cos.ap-bangkok.myqcloud.com/tech-dev/web/image/bddee7e0_1655805533304.png";
String bigContent = "[{"extraData":[],"key":"qrCode","maxWidth":2802,"textX":2349,"textY":5600,"value":"http://www.baidu.com?shopCode={0}"},{"key":"shopName","fontSize":320,"fontStyle":1,"fontName":"SimSun,Cordia New","maxWidth":5480,"textX":1100,"textY":10150,"value":"门店23名称的专กรุงเทพมหานคร属位置"},{"key":"telephone","fontSize":240,"fontStyle":0,"fontName":"SimSun,Cordia New","maxWidth":5480,"textX":1100,"textY":11200,"value":"123-001"},{"key":"address","fontSize":240,"fontStyle":0,"fontName":"SimSun,Cordia New","maxWidth":5480,"textX":1100,"textY":11700,"value":"金45融กรุงเทพมหานคร港站"}]";
String smallUrl="http://ptttor-dev-1309300786.cos.ap-bangkok.myqcloud.com/intl-promotion/upload/bddee7e0_1655805533304.png/compress_bddee7e0_1655805533304.png";
String samllContent="[{"extraData":[],"key":"qrCode","maxWidth":295,"textX":230,"textY":560,"value":"http://www.baidu.com?shopCode={0}"},{"key":"shopName","fontSize":32,"fontStyle":1,"fontName":"SimSun,Cordia New","maxWidth":548,"textX":110,"textY":1015,"value":"哈哈12门店名称的哈哈专属位置กรุงเทพมหานค超长拉,测试换行没"},{"key":"telephone","fontSize":24,"fontStyle":0,"fontName":"SimSun,Cordia New","maxWidth":548,"textX":110,"textY":1120,"value":"123-001"},{"key":"address","maxWidth":548,"fontSize":24,"fontStyle":0,"fontName":"SimSun,Cordia New","textX":110,"textY":1170,"value":"กรุงเทพมหานคร/เขตพระนคร/แขวงพระบรมมหาราชวัง,好长的地址呀,该换行拉"}]";
boolean bigFlag=true;
List fillList = JsonUtils.toList(bigFlag?bigContent:samllContent, ImgFIllInfo.class);
File zipFile = new File("D:\temp\ZipFieeeele.zip");
List zipFileList=new ArrayList<>();
String down="D://temp//down.png";
FileUtil.downloadUrlFile(bigFlag?bigUrl:smallUrl,down);
for(int i=0;i<2;i++){
InputStream inputStream = QRCodeMaxUtil
.getFileInputStream(new FileInputStream(down), "png",fillList);
zipFileList.add(new ZipFileInfo(inputStream,i+".png","png"));
}
// 调用压缩方法
FileUtil.zipFiles(zipFileList, zipFile);
}
三、问题总结
1 生成二维码白边
根据二维码链接的不同,以及二维码图片、面积的不同,生成的白边面积不同,可以自行去掉白边,但是去掉后会影响预期效果,也可以自行微调二维码宽高及x,y坐标
2 图片填充文字乱码常见是服务器上没有相应的字体,或者设置了字体,字体的名称填写错误
new Font 第一个参数是设置字体名称,注意字体名称不要写错,当然可以支持多种字体,以逗号分割 SimSun,Cordia New
Font font = new Font("SimSun,Cordia New", item.getFontStyle()==1?Font.BOLD:Font.PLAIN, item.getFontSize());
rng.setFont(font);
rng.setColor(Color.black);
服务器查看已安装的字体
或者代码查看服务已安装的字体
import java.awt.*;
public class FontTest {
public static void main(String[] args) {
Font[] fonts = GraphicsEnvironment
.getLocalGraphicsEnvironment().getAllFonts();
for (Font f : fonts) {
System.out.println("Name:" + f.getFontName());
}
}
}
在控制台寻找自己想要的字体的名称,再替换到new Font()的指定位置就可以了
3 图片压缩后蒙上一层红色因为使用错误的色彩空间渲染图像,给BufferedImage设置type为BufferedImage.TYPE_INT_RGB,如果不行再设置为TYPE_INT_ARGB,或者TYPE_INT_BGR,因为原本图片的位深度是24,替换的位深度是32或者原本图片的位深度是32,替换的位深度是24这两种都是不行的
BitMatrix bm = multiFormatWriter .encode(MessageFormat.format(qrCode.getValue(),qrCode.getExtraData()), BarcodeFormat.QR_CODE, qrCode.getMaxWidth(), qrCode.getMaxWidth(), hints);
BufferedImage image = new BufferedImage(qrCode.getMaxWidth(), qrCode.getMaxWidth(),
BufferedImage.TYPE_INT_RGB);
4 图片压缩变模糊
博主模板图片压缩10倍后变的模糊,5倍还好,然后向产品反映,经确认,原先图片确实不是很支持压缩10倍,然后UI、产品重新给了一个模板,压缩10倍后很完美。



