需求:后端提供批量下载二维码的功能,已压缩包的形式返回给前端。
二维码效果:
图片中需要后端绘制的部分:
- logo 图片
- 门店名
- 房间名
- 二维码
这里重点讲如何绘制圆形logo
快速入门
先简单介绍下 Graphics2D 对象,方便没接触过的同学快速上手。
官网地址:https://docs.oracle.com/javase/tutorial/2d/geometry/primitives.html
- BufferedImage 对象,继承了 Image对象,描述了一个具有可访问的图像数据缓冲区的图像,通常通过此对象读取源文件,常用的读取方式有:
// 本地文件
BufferedImage bufferedImage = ImageIO.read(new File(originImage));
// 网络资源
BufferedImage bufferedImage = ImageIO.read(new URL(originImage));
- Graphics2D 画笔对象,其中包含了多种方法,除了可以绘制直线,曲线,圆,椭圆等基础图像外,还能绘制图片
Graphics2D graphics = bufferImage.createGraphics();
// 绘制直线
public abstract void drawLine(int x1, int y1, int x2, int y2);
// 绘制椭圆
public abstract void drawOval(int x, int y, int width, int height);
// 绘制文字
public abstract void drawString(String str, int x, int y);
// 绘制图像
public abstract boolean drawImage(Image img, int x, int y, ImageObserver observer);
踩坑
Graphics2D 对象是通过x, y 轴开始绘制的,
Graphics2D 对象无法直接在背景图上绘制出圆形的logo, 直接裁剪会有边角问题,效果如图:
代码如下:
// 读取源logo文件
BufferedImage originLogo = ImageIO.read(new URL(logoUrl));
int diameter = Math.min(logoWidth, logoHeight);
// 创建一个新的 BufferedImage 对象,并指定大小
BufferedImage circularLogo = new BufferedImage(diameter, diameter, BufferedImage.TYPE_3BYTE_BGR);
// 获取画布对象
Graphics2D g2 = circularLogo.createGraphics();
// 创建一个圆形的剪切区域
Ellipse2D.Double circle = new Ellipse2D.Double(0, 0, diameter, diameter);
g2.setClip(circle);
// 使用 setRenderingHint 设置抗锯齿
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// 在圆形区域内绘制logo
g2.drawImage(originLogo, 0, 0, diameter, diameter, null);
g2.dispose();
// 将圆形的logo绘制到背景图片上
bg.getGraphics().drawImage(circularLogo, logoLeftBorder, logoTopBorder, null);
这里存在问题很明显,在设置圆形的剪切区域的时候,多余部分连着背景一起裁剪掉了。
所以这里需要一点小技巧:
先在空白画布上裁剪出圆形的 logo, 再在此基础上绘制背景图。
要实现第一张图的效果,需要简单修改一下代码:
/**
* 添加圆形 logo
*
* @param logoUrl logo url
* @param leftBorder 左边界
* @param topBorder 顶部边界
* @param width 宽度
* @param height 高度
* @param bgImage 背景 BufferedImage 对象
* @return
* @author haozai
* @date 2024-02-20
*/
private void addCircleLogo(String logoUrl, int leftBorder, int topBorder, int width, int height, BufferedImage bgImage) throws Exception {
BufferedImage logoImage = ImageIO.read(new URL(logoUrl));
// 透明底的图片
BufferedImage formatAvatarImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
// 获取透明画布对象
Graphics2D graphics = formatAvatarImage.createGraphics();
// 设置抗锯齿
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 留一个像素的空白区域,这个很重要,画圆的时候把这个覆盖
int border = 1;
// 设置圆形裁剪区
Ellipse2D.Double shape = new Ellipse2D.Double(border, border, width - border * 2, width - border * 2);
graphics.setClip(shape);
// 在圆形区域绘制 logo
graphics.drawImage(logoImage, border, border, width - border * 2, width - border * 2, null);
graphics.dispose();
// 在圆图外面再画一个圆
// 新创建一个graphics,这样画的圆不会有锯齿
graphics = formatAvatarImage.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int outBorder = 3;
// 画笔是4.5个像素,BasicStroke的使用可以查看下面的参考文档
// 使画笔时基本会像外延伸一定像素,具体可以自己使用的时候测试
Stroke s = new BasicStroke(4.5F, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
graphics.setStroke(s);
graphics.setColor(Color.WHITE);
graphics.drawOval(outBorder, outBorder, width - outBorder * 2, width - outBorder * 2);
graphics.dispose();
// 开始绘制背景
graphics = bgImage.createGraphics();
graphics.drawImage(bgImage, 0, 0, null);
// 将空白画布绘制到背景上
int x = (bgImage.getWidth() - width) / 2;
int y = (bgImage.getHeight() - width) / 2;
graphics.drawImage(formatAvatarImage, leftBorder, topBorder, width, width, null);
}
简单解释下这里的代码:
- 首先我们通过 new BufferedImage 创建了一个透明画布对象,此对象和 logo 大小相同。
- 然后我们通过 graphics.setClip(shape) 设置了圆形的裁剪区,裁剪出一个圆。
- 再之后我们通过 graphics.drawImage(logoImage, …)在圆形区域绘制了logo。
- 在之后我们通过 graphics.drawOval(outBorder, …) 方法在圆形logo外加了个外边框。
- logo绘制完毕,我们通过graphics = bgImage.createGraphics() 创建背景图的画布对象,注意这里的 graphics 对象是背景图的
- 最后我们通过 graphics.drawImage(formatAvatarImage, …) 将logo绘制到背景图上。