java 图片合成_Java 实现图片合成

图片合成

利用Java的绘图方法,实现图片合成

在开始之前,先定一个小目标,我们希望通过图片合成的方式,创建一个类似下面样式的图片

I. 设计思路

首先解析一下我们的目标实现图片合成,那么这些合成的基本组成单元有些什么?

组成基本单元

图片

文字

几何图形

也就是说,我们可以将任意个图片,文字,几何图形,按照自己的意愿进行拼接,那么问题就转变成两个

基本单元如何在画布上渲染

基本单元之间如何配合使用

II. 基本单元绘制

首先定义一个基本单元的接口,之后所有组合的元素都继承自这个接口

接口IMergeCell只定义一个绘制的方法,用于实现该基本单元的绘制方式

public interface IMergeCell {

void draw(Graphics2D g2d);

}

1. 图片绘制

绘制图片,一般来讲需要知道:

绘制的坐标(x,y)

绘制图片的宽高(w,h),当目标是绘制原图时,宽高一般为图片本身的宽高

结合上面两点,图片组成单元的定义如下: ImgCell

@Data

@Builder

public class ImgCell implements IMergeCell {

private BufferedImage img;

private Integer x, y, w, h;

@Override

public void draw(Graphics2D g2d) {

if (w == null) {

w = img.getWidth();

}

if (h == null) {

h = img.getHeight();

}

g2d.drawImage(img, x, y, w, h, null);

}

}

2. 文本绘制

图片绘制比较简单,相比而言,文字绘制就麻烦一点,主要是文本绘制的对齐方式,竖排还是横排布局

首先分析我们需要的基本信息

考虑对齐方式(居中对齐,靠左,靠上,靠右,靠下)

因此需要确定文本绘制的区域,所以需要两个坐标 (startX, startY), (endX, endY)

文本绘制参数

可以指定字体Font,文本颜色 Color,行间距 lineSpace

绘制的文本信息

文本内容 List

绘制实现

若单行的文本超过长度上限,则需要自动换行,所以有 batchSplitText 方法,对原文本内容进行分割,确保不会超过边界

不同的对齐方式,绘制的起始坐标需要计算, 所以在水平布局文字时,需要通过 calculateX方法获取新的x坐标;竖直布局文字时,需要通过 calculateY获取新的y坐标

实际代码如下

@Data

public class TextCell implements IMergeCell {

private List texts;

private Color color = Color.black;

private Font font = FontUtil.DEFAULT_FONT;

private int lineSpace;

private int startX, startY;

private int endX, endY;

/**

* 绘制样式

*/

private ImgCreateOptions.DrawStyle drawStyle = ImgCreateOptions.DrawStyle.HORIZONTAL;

private ImgCreateOptions.AlignStyle alignStyle = ImgCreateOptions.AlignStyle.LEFT;

public void addText(String text) {

if (texts == null) {

texts = new ArrayList<>();

}

texts.add(text);

}

@Override

public void draw(Graphics2D g2d) {

g2d.setColor(color);

g2d.setFont(font);

FontMetrics fontMetrics = FontUtil.getFontMetric(font);

int tmpHeight = fontMetrics.getHeight(), tmpW = font.getSize() >>> 1;

int tmpY = startY, tmpX = startX;

int offsetX = drawStyle == ImgCreateOptions.DrawStyle.VERTICAL_LEFT

? (font.getSize() + fontMetrics.getDescent() + lineSpace)

: -(font.getSize() + fontMetrics.getDescent() + lineSpace);

// 单行文本自动换行分割

List splitText = batchSplitText(texts, fontMetrics);

for (String info : splitText) {

if (drawStyle == ImgCreateOptions.DrawStyle.HORIZONTAL) {

g2d.drawString(info, calculateX(info, fontMetrics), tmpY);

// 换行,y坐标递增一位

tmpY += fontMetrics.getHeight() + lineSpace;

} else { // 垂直绘制文本

char[] chars = info.toCharArray();

tmpY = calculateY(info, fontMetrics);

for (int i = 0; i < chars.length; i++) {

tmpX = PunctuationUtil.isPunctuation(chars[i]) ? tmpW : 0;

g2d.drawString(chars[i] + "",

tmpX + (PunctuationUtil.isPunctuation(chars[i]) ? tmpW : 0),

tmpY);

tmpY += tmpHeight;

}

// 换一列

tmpX += offsetX;

}

}

}

// 若单行文本超过长度限制,则自动进行换行

private List batchSplitText(List texts, FontMetrics fontMetrics) {

List ans = new ArrayList<>();

if (drawStyle == ImgCreateOptions.DrawStyle.HORIZONTAL) {

int lineLen = Math.abs(endX - startX);

for(String t: texts) {

ans.addAll(Arrays.asList(GraphicUtil.splitStr(t, lineLen, fontMetrics)));

}

} else {

int lineLen = Math.abs(endY - startY);

for(String t: texts) {

ans.addAll(Arrays.asList(GraphicUtil.splitVerticalStr(t, lineLen, fontMetrics)));

}

}

return ans;

}

private int calculateX(String text, FontMetrics fontMetrics) {

if (alignStyle == ImgCreateOptions.AlignStyle.LEFT) {

return startX;

} else if (alignStyle == ImgCreateOptions.AlignStyle.RIGHT) {

return endX - fontMetrics.stringWidth(text);

} else {

return startX + ((endX - startX - fontMetrics.stringWidth(text)) >>> 1);

}

}

private int calculateY(String text, FontMetrics fontMetrics) {

if (alignStyle == ImgCreateOptions.AlignStyle.TOP) {

return startY;

} else if (alignStyle == ImgCreateOptions.AlignStyle.BOTTOM) {

int size = fontMetrics.stringWidth(text) + fontMetrics.getDescent() * (text.length() - 1);

return endY - size;

} else {

int size = fontMetrics.stringWidth(text) + fontMetrics.getDescent() * (text.length() - 1);

return startY + ((endY - endX - size) >>> 1);

}

}

}

说明:

单行文本的分割,使用了博文系列中的工具方法 GraphicUtil.splitStr,有兴趣的关注源码进行查看

水平布局时,期望 startX < endX, 从习惯来讲,基本上我们都是从左到右进行阅读

水平or垂直布局,都希望是 startY < endY

垂直布局时,以字符为单位进行绘制;标点符号的绘制时,x坐标有一个偏移量

3. Line直线绘制

几何图形之直线绘制,给出起点和结束点坐标,绘制一条直线,比较简单;这里给出了虚线的支持

@Data

@Builder

public class LineCell implements IMergeCell {

/**

* 起点坐标

*/

private int x1, y1;

/**

* 终点坐标

*/

private int x2, y2;

/**

* 颜色

*/

private Color color;

/**

* 是否是虚线

*/

private boolean dashed;

/**

* 虚线样式

*/

private Stroke stroke = CellConstants.LINE_DEFAULT_STROKE;

@Override

public void draw(Graphics2D g2d) {

g2d.setColor(color);

if (!dashed) {

g2d.drawLine(x1, y1, x2, y2);

} else { // 绘制虚线时,需要保存下原有的画笔用于恢复

Stroke origin = g2d.getStroke();

g2d.setStroke(stroke);

g2d.drawLine(x1, y1, x2, y2);

g2d.setStroke(origin);

}

}

}

4. 矩形框绘制

矩形框绘制,同直线绘制,支持圆角矩形,支持虚线框

@Data

@NoArgsConstructor

@AllArgsConstructor

@Builder

public class RectCell implements IMergeCell {

/**

* 起始坐标

*/

private int x, y;

/**

* 矩形宽高

*/

private int w, h;

/**

* 颜色

*/

private Color color;

/**

* 是否为虚线

*/

private boolean dashed;

/**

* 虚线样式

*/

private Stroke stroke;

/**

* 圆角弧度

*/

private int radius;

@Override

public void draw(Graphics2D g2d) {

g2d.setColor(color);

if (!dashed) {

g2d.drawRoundRect(x, y, w, h, radius, radius);

} else {

Stroke stroke = g2d.getStroke();

g2d.setStroke(stroke);

g2d.drawRoundRect(x, y, w, h, radius, radius);

g2d.setStroke(stroke);

}

}

}

5. 矩形区域填充

@Data

@Builder

public class RectFillCell implements IMergeCell {

private Font font;

private Color color;

private int x,y,w,h;

@Override

public void draw(Graphics2D g2d) {

g2d.setFont(font);

g2d.setColor(color);;

g2d.fillRect(x, y, w, h);

}

}

III. 封装

上面实现了几个常见的基本单元绘制,接下来则是封装绘制, 这块的逻辑就比较简单了如下

public class ImgMergeWrapper {

public static BufferedImage merge(List list, int w, int h) {

BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

Graphics2D g2d = GraphicUtil.getG2d(img);

list.forEach(cell -> cell.draw(g2d));

return img;

}

}

IV. 测试

写了一个模板QrCodeCardTemplateBuilder,用于拼装上图的样式,代码较长,不贴了,有兴趣的查看原图

测试代码如下

@Test

public void testTemplate() throws IOException {

BufferedImage logo = ImageUtil.getImageByPath("logo.jpg");

BufferedImage qrCode = ImageUtil.getImageByPath("/Users/yihui/Desktop/12.jpg");

String name = "小灰灰blog";

List desc = Arrays.asList("我是一灰灰,一匹不吃羊的狼 专注码农技术分享");

int w = QrCodeCardTemplate.w, h = QrCodeCardTemplate.h;

List list = QrCodeCardTemplateBuilder.build(logo, name, desc, qrCode, "微 信 公 众 号");

BufferedImage bg = ImgMergeWrapper.merge(list, w, h);

try {

ImageIO.write(bg, "jpg", new File("/Users/yihui/Desktop/merge.jpg"));

} catch (Exception e) {

e.printStackTrace();

}

}

演示图如下:

84004b0fcfedfaac2b79514d3b51fc2b.gif

V. 其他

项目地址:

系列博文

扫描关注,java分享

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值