图片合成工具类
本代码适用将多个图片进行合并,或者图片跟文字进行合并。
注意点:如果是文字合并时,某些字体需要通过购买才能在企业使用,否则会有侵权的风险。
import lombok.extern.slf4j.Slf4j;
import sun.misc.BASE64Decoder;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
/**
* @Description: 该类实现了图片的合并功能,可以选择水平合并或者垂直合并。
* 当然此例只是针对两个图片的合并,如果想要实现多个图片的合并,只需要自己实现方法 BufferedImage
* mergeImage(BufferedImage[] imgs, boolean isHorizontal)即可;
* 而且这个方法更加具有通用性,但是时间原因不实现了,方法和两张图片实现是一样的
* @date 2020/6/29 10:22
*/
@Slf4j
public class ImageUtil {
/**
* @param fileUrl 文件绝对路径或相对路径
* @return 读取到的缓存图像
* @throws IOException 路径错误或者不存在该文件时抛出IO异常
*/
public static BufferedImage getBufferedImage(String fileUrl)
throws IOException {
File f = new File(fileUrl);
return ImageIO.read(f);
}
/**
* 远程图片转BufferedImage
*
* @param destUrl 远程图片地址
* @return
*/
public static BufferedImage getBufferedImageDestUrl(String destUrl) {
HttpURLConnection conn = null;
BufferedImage image = null;
try {
URL url = new URL(destUrl);
conn = (HttpURLConnection) url.openConnection();
if (conn.getResponseCode() == 200) {
image = ImageIO.read(conn.getInputStream());
return image;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
conn.disconnect();
}
return image;
}
/**
* 输出图片
*
* @param buffImg 图像拼接叠加之后的BufferedImage对象
* @param savePath 图像拼接叠加之后的保存路径
*/
public static void generateSaveFile(BufferedImage buffImg, String savePath) {
int temp = savePath.lastIndexOf(".") + 1;
try {
File outFile = new File(savePath);
if (!outFile.exists()) {
outFile.createNewFile();
}
ImageIO.write(buffImg, savePath.substring(temp), outFile);
log.info("ImageIO write...");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* @param buffImg 源文件(BufferedImage)
* @param waterImg 水印文件(BufferedImage)
* @param x 距离右下角的X偏移量
* @param y 距离右下角的Y偏移量
* @param alpha 透明度, 选择值从0.0~1.0: 完全透明~完全不透明
* @return BufferedImage
* @throws IOException
* @Title: 构造图片
* @Description: 生成水印并返回java.awt.image.BufferedImage
*/
public static BufferedImage overlyingImage(BufferedImage buffImg, BufferedImage waterImg, int x, int y, float alpha) throws IOException {
// 创建Graphics2D对象,用在底图对象上绘图
Graphics2D g2d = buffImg.createGraphics();
int waterImgWidth = waterImg.getWidth();// 获取层图的宽度
int waterImgHeight = waterImg.getHeight();// 获取层图的高度
// 在图形和图像中实现混合和透明效果
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
// 绘制
g2d.drawImage(waterImg, x, y, waterImgWidth, waterImgHeight, null);
g2d.dispose();// 释放图形上下文使用的系统资源
return buffImg;
}
/**
* 待合并的两张图必须满足这样的前提,如果水平方向合并,则高度必须相等;如果是垂直方向合并,宽度必须相等。
* mergeImage方法不做判断,自己判断。
*
* @param img1 待合并的第一张图
* @param img2 带合并的第二张图
* @param isHorizontal 为true时表示水平方向合并,为false时表示垂直方向合并
* @return 返回合并后的BufferedImage对象
* @throws IOException
*/
public static BufferedImage mergeImage(BufferedImage img1,
BufferedImage img2, boolean isHorizontal) throws IOException {
int w1 = img1.getWidth();
int h1 = img1.getHeight();
int w2 = img2.getWidth();
int h2 = img2.getHeight();
// 从图片中读取RGB
int[] ImageArrayOne = new int[w1 * h1];
ImageArrayOne = img1.getRGB(0, 0, w1, h1, ImageArrayOne, 0, w1); // 逐行扫描图像中各个像素的RGB到数组中
int[] ImageArrayTwo = new int[w2 * h2];
ImageArrayTwo = img2.getRGB(0, 0, w2, h2, ImageArrayTwo, 0, w2);
// 生成新图片
BufferedImage DestImage = null;
if (isHorizontal) { // 水平方向合并
DestImage = new BufferedImage(w1 + w2, h1, BufferedImage.TYPE_INT_RGB);
DestImage.setRGB(0, 0, w1, h1, ImageArrayOne, 0, w1); // 设置上半部分或左半部分的RGB
DestImage.setRGB(w1, 0, w2, h2, ImageArrayTwo, 0, w2);
} else { // 垂直方向合并
DestImage = new BufferedImage(w1, h1 + h2, BufferedImage.TYPE_INT_RGB);
DestImage.setRGB(0, 0, w1, h1, ImageArrayOne, 0, w1); // 设置上半部分或左半部分的RGB
DestImage.setRGB(0, h1, w2, h2, ImageArrayTwo, 0, w2); // 设置下半部分的RGB
}
return DestImage;
}
/**
* 调整bufferedimage大小
*
* @param source BufferedImage 原始image
* @param targetW int 目标宽
* @param targetH int 目标高
* @param flag boolean 是否同比例调整
* @return BufferedImage 返回新image
*/
public static BufferedImage resizeBufferedImage(BufferedImage source, int targetW, int targetH, boolean flag) {
int type = source.getType();
BufferedImage target = null;
double sx = (double) targetW / source.getWidth();
double sy = (double) targetH / source.getHeight();
if (flag && sx > sy) {
sx = sy;
targetW = (int) (sx * source.getWidth());
} else if (flag && sx <= sy) {
sy = sx;
targetH = (int) (sy * source.getHeight());
}
if (type == BufferedImage.TYPE_CUSTOM) { // handmade
ColorModel cm = source.getColorModel();
WritableRaster raster = cm.createCompatibleWritableRaster(targetW, targetH);
boolean alphaPremultiplied = cm.isAlphaPremultiplied();
target = new BufferedImage(cm, raster, alphaPremultiplied, null);
} else {
target = new BufferedImage(targetW, targetH, type);
}
Graphics2D g = target.createGraphics();
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.drawRenderedImage(source, AffineTransform.getScaleInstance(sx, sy));
g.dispose();
return target;
}
/**
* @param base64String base64码字符串
**/
public static BufferedImage changeBase64StringtoBufferedImage(String base64String) {
BASE64Decoder decoder = new BASE64Decoder();
try {
byte[] bytes = decoder.decodeBuffer(base64String);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
BufferedImage buffer = ImageIO.read(bais);
return buffer;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* @Description: 图片转base64编码格式
* @Auther: pss
* @Date: 2019/9/6 19:39
*/
public static String readImage(String path) {
byte[] fileByte = null;
try {
File file = new File(path);
fileByte = Files.readAllBytes(file.toPath());
System.out.println("data:image/png;base64," + org.apache.commons.codec.binary.Base64.encodeBase64String(fileByte));
} catch (IOException e) {
e.printStackTrace();
}
return "data:image/png;base64," + org.apache.commons.codec.binary.Base64.encodeBase64String(fileByte);
}
public static void main(String[] args) throws IOException, FontFormatException {
// 测试图片的叠加
overlyingImageTest();
// 测试图片的垂直合并
//imageMargeTest();
}
/**
* Java 测试图片叠加方法
*/
public static void overlyingImageTest() throws IOException, FontFormatException {
String storeName = "【歌奈思美天河店】";
/**
* 注意-产品要求:
* 字体-方正兰亭
* 大小-60
* 颜色-148,0,0
*/
String sourceFilePath = "E:\\Demo\\test\\top.png";
String waterFilePath = "E:\\Demo\\test\\code.png";
String saveFilePath = "E:\\Demo\\test\\overlyingImageNew.png";
File fontFile = new File("E:\\Demo\\test\\fzltxhjw.ttf");
Font font = Font.createFont(Font.TRUETYPE_FONT, fontFile);
font = font.deriveFont(Font.PLAIN, 60);
Color color = new Color(148, 0, 0);
try {
BufferedImage bufferImage1 = getBufferedImage(sourceFilePath);
BufferedImage bufferImage2 = resizeBufferedImage(getBufferedImage(waterFilePath), 800, 800, true);
// 构建叠加层
BufferedImage buffImg = overlyingImage(bufferImage1, bufferImage2, 200, 510, 1.0f);
//填写店铺名称
int width = buffImg.getWidth();
Graphics2D graphics = buffImg.createGraphics();
FontMetrics metrics = graphics.getFontMetrics(font);
int strLen = metrics.stringWidth(storeName);
int x = (width - strLen) / 2;
graphics.setFont(font);
graphics.setColor(color);
graphics.drawString(storeName, x, 1700);
// 输出水印图片
generateSaveFile(buffImg, saveFilePath);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Java 测试图片合并方法
*/
public static void imageMargeTest() {
// 读取待合并的文件
BufferedImage bi1 = null;
BufferedImage bi2 = null;
// 调用mergeImage方法获得合并后的图像
BufferedImage destImg = null;
System.out.println("下面是垂直合并的情况:");
String saveFilePath = "D://test//new1.jpg";
String divingPath = "D://test//new2.jpg";
String margeImagePath = "D://test//margeNew.jpg";
try {
bi1 = getBufferedImage(saveFilePath);
bi2 = getBufferedImage(divingPath);
// 调用mergeImage方法获得合并后的图像
destImg = mergeImage(bi1, bi2, false);
} catch (IOException e) {
e.printStackTrace();
}
// 保存图像
generateSaveFile(destImg, margeImagePath);
System.out.println("垂直合并完毕!");
}
}
工具类的使用
注意图片格式只测试了两张png合成一张jpg,其他格式请自己调试。
微信base64二维码合成新的图片
String base64 = (String) data.get("base64");
if (StringUtils.isEmpty(base64)) {
throw new Exception("获取二维码失败");
}
log.info("获取微信公众号base64:{}", base64);
org.springframework.core.io.Resource resource = new ClassPathResource("static/MerchantQRCode.png");
String newPicBase64 = "";
if (resource.exists()) {
BufferedImage firstBufferImage = ImageIO.read(ImageIO.createImageInputStream(resource.getInputStream()));
BufferedImage secondBufferImage = changeBase64StringtoBufferedImage(base64);
newPicBase64 = composeNewPic(xdMerchantStoreInfo.getStoreName(), firstBufferImage, secondBufferImage);
if (StringUtils.isEmpty(newPicBase64)) {
newPicBase64 = base64;
}
} else {
newPicBase64 = base64;
}
log.info("合成新的图片base64:{}", newPicBase64);
合成关键方法
/**
* 合成新的图片
*
* @param storeName
* @param firstBufferImage 第一张图片
* @return
*/
private String composeNewPic(String storeName, BufferedImage firstBufferImage, BufferedImage secondBufferImage) {
InputStream inputStream = null;
try {
storeName = "【" + storeName + "】";
/**
* 注意-产品要求:
* 字体-方正兰亭
* 大小-60
* 颜色-148,0,0
*/
org.springframework.core.io.Resource resource = new ClassPathResource("static/fzltxhjw.ttf");
if (!resource.exists()) {
return null;
}
inputStream = resource.getInputStream();
File fontFile = File.createTempFile("fzltxhjw", ".ttf");
FileUtils.copyInputStreamToFile(inputStream, fontFile);
Font font = Font.createFont(Font.TRUETYPE_FONT, fontFile);
font = font.deriveFont(Font.PLAIN, 60);
Color color = new Color(148, 0, 0);
BufferedImage bufferImage2 = resizeBufferedImage(secondBufferImage, 800, 800, true);
// 构建叠加层
BufferedImage buffImg = overlyingImage(firstBufferImage, bufferImage2, 200, 510, 1.0f);
//填写店铺名称
int width = buffImg.getWidth();
Graphics2D graphics = buffImg.createGraphics();
FontMetrics metrics = graphics.getFontMetrics(font);
int strLen = metrics.stringWidth(storeName);
int x = (width - strLen) / 2;
graphics.setFont(font);
graphics.setColor(color);
graphics.drawString(storeName, x, 1700);
// 输出水印图片
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(buffImg, "png", baos); //第二个参数“jpg”不需要修改
byte[] bytes = baos.toByteArray();
return FileBase64Utils.bytesToBase64(bytes);
} catch (Exception e) {
log.error("合成图片失败", e);
} finally {
if (inputStream !=null) {
IOUtils.closeQuietly(inputStream);
}
}
//合成失败返回之前的
return null;
}
注意事项
- 某些稀有字体在字体类型中不支持。
- 公司等盈利性机构需要付费购买字体,当然有些字体是免费的。
工具类代码地址:https://github.com/gonghaiyu/common-utils