利用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分享