利用Java的绘图方法,实现图片合成html
在开始以前,先定一个小目标,咱们但愿经过图片合成的方式,建立一个相似下面样式的图片java
首先解析一下咱们的目标实现图片合成,那么这些合成的基本组成单元有些什么?git
组成基本单元github
也就是说,咱们能够将任意个图片,文字,几何图形,按照本身的意愿进行拼接,那么问题就转变成两个spring
首先定义一个基本单元的接口,以后全部组合的元素都继承自这个接口markdown
接口IMergeCell
只定义一个绘制的方法,用于实现该基本单元的绘制方式app
public interface IMergeCell { void draw(Graphics2D g2d); }
绘制图片,通常来说须要知道:ide
结合上面两点,图片组成单元的定义以下: ImgCell
spring-boot
@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); } }
图片绘制比较简单,相比而言,文字绘制就麻烦一点,主要是文本绘制的对齐方式,竖排仍是横排布局工具
首先分析咱们须要的基本信息
考虑对齐方式(居中对齐,靠左,靠上,靠右,靠下)
文本绘制参数
Font
,文本颜色 Color
,行间距 lineSpace
绘制的文本信息
List<String>
绘制实现
若单行的文本超过长度上限,则须要自动换行,因此有 batchSplitText
方法,对原文本内容进行分割,确保不会超过边界
不一样的对齐方式,绘制的起始坐标须要计算, 因此在水平布局文字时,须要经过 calculateX
方法获取新的x坐标;竖直布局文字时,须要经过 calculateY
获取新的y坐标
实际代码以下
@Data public class TextCell implements IMergeCell { private List<String> 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<String> 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<String> batchSplitText(List<String> texts, FontMetrics fontMetrics) { List<String> 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
, 从习惯来说,基本上咱们都是从左到右进行阅读startY < endY
几何图形之直线绘制,给出起点和结束点坐标,绘制一条直线,比较简单;这里给出了虚线的支持
@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); } } }
矩形框绘制,同直线绘制,支持圆角矩形,支持虚线框
@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); } } }
@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); } }
上面实现了几个常见的基本单元绘制,接下来则是封装绘制, 这块的逻辑就比较简单了以下
public class ImgMergeWrapper { public static BufferedImage merge(List<IMergeCell> 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; } }
写了一个模板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<String> desc = Arrays.asList("我是一灰灰,一匹不吃羊的狼 专一码农技术分享"); int w = QrCodeCardTemplate.w, h = QrCodeCardTemplate.h; List<IMergeCell> 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(); } }
演示图以下:
项目地址:
QuickMedia
目标是建立一个专一图文,音视频,二维码处理的开源项目系列博文
扫描关注,java分享