需求背景
之前遇到过需要后端,根据每个用户生成带背景宣传图带二维码带用户图片带文字的合成图片的需求,自己当时花了半天的时间整理了资料,今天把自己写的代码分享出来,如果有同样需求的人,希望能给个好评,有其他建议的童鞋,可以一起讨论交流。
文章下面贴有项目地址,感谢star
项目整合了二维码和日志,有需要的童鞋也可以做参考
图片缓冲类 BufferedImage
BufferedImage类是Image的实现类,是可以把图片加载到内存的缓冲类,我写的代码中就基本上都是基于该类实现对图片的操作。
图片加载 Thumbnails
Thumbnails是谷歌开源的一套图片工具类
当我需要把本地图片加载进来得到BufferedImage对象的时候,我们可以通过
Thumbnails.of(filePath)
.scale(1)
.asBufferedImage();
如果是远程图片
Thumbnails.of(new URL(filePath))
.scale(1)
.asBufferedImage();
添加文本
/**
* 修改图片,返回修改后的图片缓冲区(只输出一行文本)
*/
public BufferedImage modifyImage(BufferedImage img, Object content, int x, int y) {
try {
int w = img.getWidth();
int h = img.getHeight();
g = img.createGraphics();
g.setBackground(Color.WHITE);
g.setColor(Color.BLACK);//设置字体颜色
if (this.font != null) {
g.setFont(this.font);
}
// 验证输出位置的纵坐标和横坐标
if (x >= h || y >= w) {
this.x = h - this.fontsize + 2;
this.y = w;
} else {
this.x = x;
this.y = y;
}
g.setFont( new Font("微软雅黑", Font.BOLD, 22));
if (content != null) {
g.drawString(content.toString(), this.x, this.y);
}
g.setBackground(Color.WHITE);
g.dispose();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return img;
}
生成图片到本地
ImageIO提供了write的方法,将图片文件打印到本地
/**
* 生成新图片到本地
*/
public boolean writeImageLocal(String newImage, BufferedImage img) {
boolean isok = false;
if (newImage != null && img != null) {
try {
File outputfile = new File(newImage);
isok = ImageIO.write(img, "jpg", outputfile);
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
return isok;
}
工具类代码
有了上面四个点,我们基本上就可以图片为所欲为了
package com.xwn.utils;
import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.awt.Rectangle;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import com.xwn.constant.Constant;
import com.xwn.constant.PicConstant;
import com.xwn.model.UserInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import net.coobird.thumbnailator.Thumbnails;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
/**
* 服务端画图
* 绘制,拼接,修改图片
* @author xwn
*/
public class PictureUtils {
private static final Logger log = LoggerFactory.getLogger(PictureUtils.class);
private Font font = new Font("微软雅黑", Font.BOLD, 70);// 添加字体的属性设置
private Graphics2D g = null;
private int fontsize = 0;
private int x = 0;
private int y = 0;
/**
* 导入本地图片到缓冲区
*/
public BufferedImage loadImageLocal(String imgName) {
try {
return ImageIO.read(new File(imgName));
} catch (IOException e) {
System.out.println(e.getMessage());
}
return null;
}
/**
* 导入网络图片到缓冲区
*/
public BufferedImage loadImageUrl(String imgName) {
try {
URL url = new URL(imgName);
return ImageIO.read(url);
} catch (IOException e) {
System.out.println(e.getMessage());
}
return null;
}
/**
* 生成新图片到本地
*/
public boolean writeImageLocal(String newImage, BufferedImage img) {
boolean isok = false;
if (newImage != null && img != null) {
try {
File outputfile = new File(newImage);
isok = ImageIO.write(img, "jpg", outputfile);
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
return isok;
}
/**
* 将{@link BufferedImage}生成formatName指定格式的图像数据
* @param source
* @param formatName 图像格式名,图像格式名错误则抛出异常
* @return
*/
public static void wirteBytes(BufferedImage source,String formatName,String newImage){
Assert.notNull(source, "source");
ByteArrayOutputStream output = new ByteArrayOutputStream();
Graphics2D g = null;
try {
for(BufferedImage s=source;!ImageIO.write(s, formatName, output);){
if(null!=g) {
throw new IllegalArgumentException(String.format("not found writer for '%s'",formatName));
}
s = new BufferedImage(source.getWidth(),source.getHeight(), BufferedImage.TYPE_INT_RGB);
g = s.createGraphics();
g.drawImage(source, 0, 0,null);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (null != g) {
g.dispose();
}
}
}
/**
* 将指定颜色变透明 只能保存 png jpg
* @param imageSrc 图片
* @param mask 颜色
* @return
*/
public static BufferedImage createImageByMaskColorEx(BufferedImage imageSrc, Color mask) {
int x, y;
x = imageSrc.getWidth(null);
y = imageSrc.getHeight(null);
BufferedImage imageDes = new BufferedImage(x, y,
BufferedImage.TYPE_4BYTE_ABGR);
WritableRaster rasterDes = imageDes.getRaster();
int[] des = new int[4];
while (--x >= 0) {
for (int j = 0; j < y; ++j) {
int rgb = imageSrc.getRGB(x, j);
int sr, sg, sb;
sr = (rgb & 0xFF0000) >> 16;
sg = (rgb & 0xFF00) >> 8;
sb = rgb & 0xFF;
if (sr == mask.getRed() && sg == mask.getGreen()
&& sb == mask.getBlue()) {
des[3] = 0;
} else {
des[0] = sr;
des[1] = sg;
des[2] = sb;
des[3] = 255;
}
rasterDes.setPixel(x, j, des);
}
}
return imageDes;
}
/**
* 设置颜色
* @param img 图片
* @return
*/
public BufferedImage setColor(BufferedImage img){
//定义一个数组,存放RGB值
int[] rgb = new int[3];
int width = img.getWidth();
int height = img.getHeight();
int minx = img.getMinTileX();
int miny = img.getMinTileY();
//遍历像素点,判断是否更换颜色
for (int i = minx; i < width; i++) {
for (int j = miny; j < height; j++) {
//换色
int pixel = img.getRGB(i, j);
rgb[0] = (pixel & 0xff0000) >>16;
rgb[1] = (pixel & 0xff00) >>8;
rgb[2] = (pixel & 0xff) ;
if (rgb[0]<230&&rgb[0]>100&& rgb[1]<230&&rgb[1]>100 && rgb[2]<230&&rgb[2]>100) {
img.setRGB(i, j, 0xffffff);
}
}
}
return img;
}
/**
* 设定文字的字体等
* @param fontStyle 文字字体
* @param fontSize 大小
*/
public void setFont(String fontStyle, int fontSize) {
this.fontsize = fontSize;
this.font = new Font(fontStyle, Font.PLAIN, fontSize);
}
/**
* 修改图片,返回修改后的图片缓冲区(只输出一行文本)
*/
public BufferedImage modifyImage(BufferedImage img, Object content, int x, int y) {
try {
int w = img.getWidth();
int h = img.getHeight();
g = img.createGraphics();
g.setBackground(Color.WHITE);
g.setColor(Color.BLACK);//设置字体颜色
if (this.font != null) {
g.setFont(this.font);
}
// 验证输出位置的纵坐标和横坐标
if (x >= h || y >= w) {
this.x = h - this.fontsize + 2;
this.y = w;
} else {
this.x = x;
this.y = y;
}
g.setFont( new Font("微软雅黑", Font.BOLD, 22));
if (content != null) {
g.drawString(content.toString(), this.x, this.y);
}
g.setBackground(Color.WHITE);
g.dispose();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return img;
}
/**
* 修改图片,返回修改后的图片缓冲区(只输出一行文本)
* @param img 图片
* @return
*/
public BufferedImage modifyImageYe(BufferedImage img) {
try {
int w = img.getWidth();
int h = img.getHeight();
g = img.createGraphics();
g.setBackground(Color.WHITE);
g.setColor(Color.blue);//设置字体颜色
if (this.font != null)
g.setFont(this.font);
g.drawString("reyo.cn", w - 85, h - 5);
g.dispose();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return img;
}
/**
* 合成图片
* @param b 图片b
* @param d 图片d
* @param x x坐标
* @param y y坐标
* @param height 高度
* @param weight 宽度
* @return
*/
public BufferedImage modifyImagetogeter(BufferedImage b, BufferedImage d,int x,int y,int height ,int weight) {
try {
g = d.createGraphics();
g.drawImage(b, x, y, weight, height, null);
g.dispose();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return d;
}
/**
* 对图片进行剪裁 返回字节数组
* @param is 图片输入流
* @param width 裁剪图片的宽
* @param height 裁剪图片的高
* @param imageFormat 输出图片的格式 "jpeg jpg等"
* @return
*/
public static byte[] clipImage(InputStream is,int width, int height, String imageFormat){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
// 构造Image对象
BufferedImage src = javax.imageio.ImageIO.read(is);
// 缩小边长
BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 绘制 缩小 后的图片
tag.getGraphics().drawImage(src, 0, 0, width, height, null);
ImageIO.write(tag, imageFormat, bos);
} catch (IOException e) {
e.printStackTrace();
}
return bos.toByteArray();
}
/**
* 对图片裁剪,并把裁剪新图片保存
* @param srcPath 读取源图片路径
* @param toPath 写入图片路径
* @param x 剪切起始点x坐标
* @param y 剪切起始点y坐标
* @param width 剪切宽度
* @param height 剪切高度
* @param readImageFormat 读取图片格式
* @param writeImageFormat 写入图片格式
*/
public static void cropImage(String srcPath, String toPath, int x,int y,int width,int height, String readImageFormat,String writeImageFormat){
FileInputStream fis = null ;
ImageInputStream iis =null ;
try{
//读取图片文件
fis = new FileInputStream(srcPath);
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(readImageFormat);
ImageReader reader = readers.next();
//获取图片流
iis = ImageIO.createImageInputStream(fis);
reader.setInput(iis, true);
ImageReadParam param = reader.getDefaultReadParam();
//定义一个矩形
Rectangle rect = new Rectangle(x, y, width, height);
//提供一个 BufferedImage,将其用作解码像素数据的目标。
param.setSourceRegion(rect);
BufferedImage bi = reader.read(0, param);
//保存新图片
ImageIO.write(bi, writeImageFormat, new File(toPath));
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(fis!=null){
fis.close();
}
if(iis!=null){
iis.close();
}
}catch(Exception e){
e.printStackTrace();
}
}
}
/**
* 传入的图像必须是正方形的 才会 圆形 如果是长方形的比例则会变成椭圆的
* @param bi1 图片
* @return
* @throws IOException
*/
public static BufferedImage convertCircular(BufferedImage bi1) throws IOException {
// 透明底的图片
BufferedImage bi2 = new BufferedImage(bi1.getWidth(), bi1.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
Ellipse2D.Double shape = new Ellipse2D.Double(0, 0, bi1.getWidth(), bi1.getHeight());
Graphics2D g2 = bi2.createGraphics();
g2.setClip(shape);
// 使用 setRenderingHint 设置抗锯齿
g2.drawImage(bi1, 0, 0, null);
// 设置颜色
g2.setBackground(Color.green);
g2.dispose();
return bi2;
}
/**
* 缩小Image,此方法返回源图像按给定宽度、高度限制下缩放后的图像
* @param inputImage
* @param newWidth:压缩后宽度
* @param newHeight:压缩后高度
* @param beijing:背景图片
* @throws java.io.IOException
*/
public static BufferedImage scaleByPercentage(BufferedImage inputImage, int newWidth, int newHeight,BufferedImage beijing) throws Exception {
// 获取原始图像透明度类型
int type = inputImage.getColorModel().getTransparency();
int width = inputImage.getWidth();
int height = inputImage.getHeight();
// 开启抗锯齿
RenderingHints renderingHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 使用高质量压缩
renderingHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
BufferedImage img = new BufferedImage(newWidth, newHeight, type);
Graphics2D graphics2d = img.createGraphics();
graphics2d.setRenderingHints(renderingHints);
graphics2d.drawImage(inputImage, 0, 0, newWidth, newHeight, 0, 0, width, height, null);
graphics2d.dispose();
return img;
}
/**
* 头像缩放 四舍五入 double转int
* @param beijing_
* @return
*/
public static Integer doubleToInt(BufferedImage beijing_,double d){
return (new Double((d*beijing_.getHeight()))).intValue();
}
/**
* 让字符串隔开展现
* @param code
* @return
*/
public static String getCode(String code){
StringBuilder sb = new StringBuilder();
for (int i = 0; i < code.length(); i++) {
if(i != (code.length()-1)){
sb.append(code.charAt(i)).append(" ");
}else{
sb.append(code.charAt(i));
}
}
return sb.toString();
}
/**
* 网络图片输出到BufferedImage
* @throws IOException
*/
private BufferedImage getBufferedImageForURL(String filePath) throws IOException {
//asBufferedImage() 返回BufferedImage
BufferedImage thumbnail = Thumbnails.of(new URL(filePath))
.scale(1)
.asBufferedImage();
return thumbnail;
}
/**
* 本地图片输出到BufferedImage
* @throws IOException
*/
private BufferedImage getBufferedImageForLocal(String filePath) throws IOException {
/**
* asBufferedImage() 返回BufferedImage
*/
BufferedImage thumbnail = Thumbnails.of(filePath)
.scale(1)
.asBufferedImage();
return thumbnail;
}
/**
* 合成图片
* @param user 用户
* @throws IOException
*/
public static void initImage(UserInfo user) throws IOException {
Map<String,String> map = new HashMap<String,String>();
PictureUtils tt = new PictureUtils();
String baseUrl = Constant.BASE_URL;
String beijing = PicConstant.yaoqingbeijing128;
String dibian = PicConstant.yaoqingdibian128;
//底边合成背景
String newPicUrl = new StringBuffer(baseUrl).append("ok/").append(user.getUserId()).append(".jpg").toString();
if(!new File(newPicUrl).exists()){
//获取绑定二维码图片
Map<String,String> qrCodeMap = QrCodeUtil.getUserQrImgURL(user);
if(qrCodeMap != null){
String qrCodeImgURL = qrCodeMap.get("qrPath");
String qrCode = qrCodeMap.get("qrCode");
boolean isOk = true;
try {
BufferedImage head_ = tt.getBufferedImageForURL(user.getHeadImgUrl());
BufferedImage qrCodeImg = tt.getBufferedImageForLocal(qrCodeImgURL);
BufferedImage beijing_ = tt.getBufferedImageForURL(beijing);
BufferedImage dibian_ = tt.getBufferedImageForURL(dibian);
head_ = convertCircular(head_);
head_ = scaleByPercentage(head_, doubleToInt(dibian_,PicConstant.DOUBLE_TO_INT_HEAD), doubleToInt(dibian_,PicConstant.DOUBLE_TO_INT_HEAD),beijing_);
isOk = tt.writeImageLocal(new StringBuilder(baseUrl).append("head.jpg").toString(),head_);
//合成头像
int x = 30;
int y = 75;
int weight1 = head_.getWidth();
int height1 = head_.getHeight();
tt.modifyImagetogeter(head_, dibian_,x,y,height1,weight1);
isOk = tt.writeImageLocal(new StringBuilder(baseUrl).append("dibian.jpg").toString(), tt.modifyImagetogeter(head_, dibian_,x,y,height1,weight1));
//添加文字
x = 100;
y = 135;
tt.modifyImage(dibian_,getCode(user.getInviteCode()),x,y);
isOk = tt.writeImageLocal(new StringBuilder(baseUrl).append("xiabufen1.jpg").toString(),tt.modifyImage(dibian_,getCode(user.getInviteCode()),x,y));
//合成二维码
x = 325;
y = 12;
int weight3 = doubleToInt(qrCodeImg,PicConstant.DOUBLE_TO_INT_QR);
int height3 = doubleToInt(qrCodeImg,PicConstant.DOUBLE_TO_INT_QR);
tt.modifyImagetogeter(qrCodeImg, dibian_,x,y,doubleToInt(qrCodeImg,PicConstant.DOUBLE_TO_INT_QR),doubleToInt(qrCodeImg,PicConstant.DOUBLE_TO_INT_QR));
isOk = tt.writeImageLocal(new StringBuilder(baseUrl).append("xiabufen2.jpg").toString(), tt.modifyImagetogeter(qrCodeImg, dibian_,x,y,height3,weight3));
x = 0;
y = beijing_.getHeight()-dibian_.getHeight();
int weight = dibian_.getWidth();
int height = dibian_.getHeight();
isOk = tt.writeImageLocal(newPicUrl, tt.modifyImagetogeter(dibian_, beijing_,x,y,height,weight));
} catch (Exception e) {
log.error("合成"+ e.getMessage());
}
if(!isOk){
log.error("生成图片失败!");
}
map.put("qrCode", qrCode);
map.put("newPicUrl", newPicUrl);
}
}else{
map.put("qrCode", new StringBuffer(Constant.DEFAULT_IMG_BASEURL).append(user.getUserId()).toString());
map.put("newPicUrl", newPicUrl);
}
log.info(map.toString());
}
public static void main(String[] args) throws IOException {
UserInfo user = new UserInfo();
user.setUserId(1);
user.setHeadImgUrl(PicConstant.Head_Test);
user.setInviteCode("123456");
initImage(user);
}
}
这里我只放主工具类的代码,想要全部代码可以点击文章下面的码云地址下载项目。
效果图片
示例样本用到了我的对象存储,有兴趣的童鞋可以点击
阿里云COS
附带图片
完成的图片
码云地址
版权声明:本文为qq_24073619原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。