In diesem Artikel wird hauptsächlich der Beispielcode zum Generieren langer Grafiken und Texte in Java vorgestellt. Der Herausgeber findet ihn recht gut, daher werde ich ihn jetzt mit Ihnen teilen und als Referenz verwenden. Schauen wir uns den Editor an.
Vor langer Zeit fand ich es sehr interessant, das Layout direkt als endgültiges Bild auszugeben zum Sammeln, Anzeigen und Teilen. Jetzt kann ich es selbst tun. Beginnen wir mit einer einfachen Version von
Ziel
Definieren Sie zunächst das Ziel, das wir erreichen möchten: lange generieren Grafiktext basierend auf Text + Bildern
Zielzerlegung
Unterstützt die Generierung von Bildern aus großem Text
Unterstützt das Einfügen von Bildern
Unterstützt Einstellungen für den oberen, unteren, linken und rechten Rand
Unterstützt die Auswahl der Schriftart
Unterstützte Schriftfarbe
Unterstützt linksbündig, zentriert, rechtsbündig
Erwartete Ergebnisse
Wir werden eine http-Schnittstelle zum Generieren langer Bilder und Texte durch Spring-Boot erstellen. Geben Sie verschiedene Konfigurationsinformationen an, indem Sie Parameter übergeben. Das Folgende ist ein schematisches Diagramm des letzten Aufrufs
Design & Implementierung
Langes Bild Um Text zu generieren, verwenden Sie awt zum Zeichnen von Text und Bildern
1. Parameteroptionen ImgCreateOptions
Legen Sie Konfigurationsparameter basierend auf unseren erwarteten Zielen fest, die im Wesentlichen die folgenden Parameter umfassen:
@Getter @Setter @ToString public class ImgCreateOptions { /** * 绘制的背景图 */ private BufferedImage bgImg; /** * 生成图片的宽 */ private Integer imgW; private Font font = new Font("宋体", Font.PLAIN, 18); /** * 字体色 */ private Color fontColor = Color.BLACK; /** * 两边边距 */ private int leftPadding; /** * 上边距 */ private int topPadding; /** * 底边距 */ private int bottomPadding; /** * 行距 */ private int linePadding; private AlignStyle alignStyle; /** * 对齐方式 */ public enum AlignStyle { LEFT, CENTER, RIGHT; private static Map<String, AlignStyle> map = new HashMap<>(); static { for(AlignStyle style: AlignStyle.values()) { map.put(style.name(), style); } } public static AlignStyle getStyle(String name) { name = name.toUpperCase(); if (map.containsKey(name)) { return map.get(name); } return LEFT; } } }
2. Kapselungsklasse ImageCreateWrapper
kapselt die Einstellung von Konfigurationsparametern, Zeichnungstext und Zeichnungsbildern, Ausgabestil und anderen Schnittstellen
public class ImgCreateWrapper { public static Builder build() { return new Builder(); } public static class Builder { /** * 生成的图片创建参数 */ private ImgCreateOptions options = new ImgCreateOptions(); /** * 输出的结果 */ private BufferedImage result; private final int addH = 1000; /** * 实际填充的内容高度 */ private int contentH; private Color bgColor; public Builder setBgColor(int color) { return setBgColor(ColorUtil.int2color(color)); } /** * 设置背景图 * * @param bgColor * @return */ public Builder setBgColor(Color bgColor) { this.bgColor = bgColor; return this; } public Builder setBgImg(BufferedImage bgImg) { options.setBgImg(bgImg); return this; } public Builder setImgW(int w) { options.setImgW(w); return this; } public Builder setFont(Font font) { options.setFont(font); return this; } public Builder setFontName(String fontName) { Font font = options.getFont(); options.setFont(new Font(fontName, font.getStyle(), font.getSize())); return this; } public Builder setFontColor(int fontColor) { return setFontColor(ColorUtil.int2color(fontColor)); } public Builder setFontColor(Color fontColor) { options.setFontColor(fontColor); return this; } public Builder setFontSize(Integer fontSize) { Font font = options.getFont(); options.setFont(new Font(font.getName(), font.getStyle(), fontSize)); return this; } public Builder setLeftPadding(int leftPadding) { options.setLeftPadding(leftPadding); return this; } public Builder setTopPadding(int topPadding) { options.setTopPadding(topPadding); contentH = topPadding; return this; } public Builder setBottomPadding(int bottomPadding) { options.setBottomPadding(bottomPadding); return this; } public Builder setLinePadding(int linePadding) { options.setLinePadding(linePadding); return this; } public Builder setAlignStyle(String style) { return setAlignStyle(ImgCreateOptions.AlignStyle.getStyle(style)); } public Builder setAlignStyle(ImgCreateOptions.AlignStyle alignStyle) { options.setAlignStyle(alignStyle); return this; } public Builder drawContent(String content) { // xxx return this; } public Builder drawImage(String img) { BufferedImage bfImg; try { bfImg = ImageUtil.getImageByPath(img); } catch (IOException e) { log.error("load draw img error! img: {}, e:{}", img, e); throw new IllegalStateException("load draw img error! img: " + img, e); } return drawImage(bfImg); } public Builder drawImage(BufferedImage bufferedImage) { // xxx return this; } public BufferedImage asImage() { int realH = contentH + options.getBottomPadding(); BufferedImage bf = new BufferedImage(options.getImgW(), realH, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = bf.createGraphics(); if (options.getBgImg() == null) { g2d.setColor(bgColor == null ? Color.WHITE : bgColor); g2d.fillRect(0, 0, options.getImgW(), realH); } else { g2d.drawImage(options.getBgImg(), 0, 0, options.getImgW(), realH, null); } g2d.drawImage(result, 0, 0, null); g2d.dispose(); return bf; } public String asString() throws IOException { BufferedImage img = asImage(); return Base64Util.encode(img, "png"); } }
Es gibt keine spezifischen Die oben beschriebene Implementierung von Text und Bildern wird später im Detail erläutert. Der Schwerpunkt liegt hier auf einem Parameter, der die tatsächliche Höhe des Inhalts (einschließlich des oberen Rands) darstellt Das Bild sollte
int realH = contentH + options.getBottomPadding();
sein. Zweitens sprechen wir kurz über die obige Bildausgabemethode: com.hust.hui .quickmedia.common.image.ImgCreateWrapper.Builder#asImage
Berechnen Sie die Höhe des endgültig generierten Bildes (die Breite wird durch den Eingabeparameter angegeben)
Hintergrund zeichnen (wenn kein Hintergrundbild vorhanden ist). , füllen Sie es mit einer Volltonfarbe)
Entitätsinhalt zeichnen (d. h. gezeichneter Text, Bilder)
3. Inhaltsfüllung GraphicUtil
Die spezifische Inhaltsfüllung ist in Textzeichnung und Bildzeichnung unterteilt.
Design
berücksichtigt die Füllung. Während des Prozesses können Sie Legen Sie Schriftarten, Farben usw. frei fest, sodass in unserer Zeichenmethode das Zeichnen und Füllen des Inhalts direkt implementiert wird, dh die Methode drawXXX realisiert das Füllen des Inhalts tatsächlich Leinwand
Bildzeichnung. Da die Größe des Bildes selbst und die Größe des Endergebnisses in Konflikt geraten können, werden die folgenden Regeln verwendet
Bild zeichnen width <= (Geben Sie die Breite des generierten Bildes an – Rand), füllen Sie alles aus
Breite des Bildes zeichnen> (Geben Sie die Breite des generierten Bildes an – Rand), skalieren Sie das gezeichnete Bild
Probleme bei der Textzeichnung und dem Zeilenumbruch
Die zulässige Textlänge für jede Zeile ist begrenzt. Wenn sie überschritten wird, ist ein automatischer Zeilenumbruch erforderlich
Textzeichnung
Betrachten Sie das grundlegende Textzeichnen. Der Vorgang ist wie folgt
1 Erstellen Sie ein BufferImage-Objekt
2. Holen Sie sich das Graphic2d-Objekt und führen Sie die Zeichnung aus
3. Legen Sie grundlegende Konfigurationsinformationen fest
4. Teilen Sie den Text entsprechend den Zeilenumbrüchen in ein Zeichenfolgenarray auf und zeichnen Sie das einzelne -Zeileninhalt in einer Schleife
, um die aktuelle Zeichenfolge und die tatsächliche Anzahl der gezeichneten Zeilen zu berechnen und dann aufzuteilen
Text der Reihe nach zeichnen (Beachten Sie die Änderung der Y-Koordinate)
Das Folgende ist die spezifische Implementierung
public static int drawContent(Graphics2D g2d, String content, int y, ImgCreateOptions options) { int w = options.getImgW(); int leftPadding = options.getLeftPadding(); int linePadding = options.getLinePadding(); Font font = options.getFont(); // 一行容纳的字符个数 int lineNum = (int) Math.floor((w - (leftPadding << 1)) / (double) font.getSize()); // 对长串字符串进行分割成多行进行绘制 String[] strs = splitStr(content, lineNum); g2d.setFont(font); g2d.setColor(options.getFontColor()); int index = 0; int x; for (String tmp : strs) { x = calOffsetX(leftPadding, w, tmp.length() * font.getSize(), options.getAlignStyle()); g2d.drawString(tmp, x, y + (linePadding + font.getSize()) * index); index++; } return y + (linePadding + font.getSize()) * (index); } /** * 计算不同对其方式时,对应的x坐标 * * @param padding 左右边距 * @param width 图片总宽 * @param strSize 字符串总长 * @param style 对其方式 * @return 返回计算后的x坐标 */ private static int calOffsetX(int padding, int width, int strSize, ImgCreateOptions.AlignStyle style) { if (style == ImgCreateOptions.AlignStyle.LEFT) { return padding; } else if (style == ImgCreateOptions.AlignStyle.RIGHT) { return width - padding - strSize; } else { return (width - strSize) >> 1; } } /** * 按照长度对字符串进行分割 * <p> * fixme 包含emoj表情时,兼容一把 * * @param str 原始字符串 * @param splitLen 分割的长度 * @return */ public static String[] splitStr(String str, int splitLen) { int len = str.length(); int size = (int) Math.ceil(len / (float) splitLen); String[] ans = new String[size]; int start = 0; int end = splitLen; for (int i = 0; i < size; i++) { ans[i] = str.substring(start, end > len ? len : end); start = end; end += splitLen; } return ans; }
Die obige Implementierung ist klarer, und das Zeichnen von Bildern ist noch einfacher
Bildzeichnung
muss nur die Breite und Höhe des Bildes neu berechnet werden. Die konkrete Umsetzung ist wie folgt
/** * 在原图上绘制图片 * * @param source 原图 * @param dest 待绘制图片 * @param y 待绘制的y坐标 * @param options * @return 绘制图片的高度 */ public static int drawImage(BufferedImage source, BufferedImage dest, int y, ImgCreateOptions options) { Graphics2D g2d = getG2d(source); int w = Math.min(dest.getWidth(), options.getImgW() - (options.getLeftPadding() << 1)); int h = w * dest.getHeight() / dest.getWidth(); int x = calOffsetX(options.getLeftPadding(), options.getImgW(), w, options.getAlignStyle()); // 绘制图片 g2d.drawImage(dest, x, y + options.getLinePadding(), w, h, null); g2d.dispose(); return h; } public static Graphics2D getG2d(BufferedImage bf) { Graphics2D g2d = bf.createGraphics(); g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY); g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE); g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); return g2d; }
Im Vordergrund steht nur ein einzelner Inhaltsblock (z. B. ein Absatz). Es gibt einige Probleme bei der Textwiedergabe, a Bild)
Teilen Sie die Zeichenfolge entsprechend dem Zeilenumbruchzeichen auf.
Berechnen Sie den belegten Platz, wenn der gezeichnete Inhalt endgültig konvertiert wird in ein Bild Höhe
Canvas BufferedImage-Ergebnis neu generieren
public Builder drawContent(String content) { String[] strs = StringUtils.split(content, "\n"); if (strs.length == 0) { // empty line strs = new String[1]; strs[0] = " "; } int fontSize = options.getFont().getSize(); int lineNum = calLineNum(strs, options.getImgW(), options.getLeftPadding(), fontSize); // 填写内容需要占用的高度 int height = lineNum * (fontSize + options.getLinePadding()); if (result == null) { result = GraphicUtil.createImg(options.getImgW(), Math.max(height + options.getTopPadding() + options.getBottomPadding(), BASE_ADD_H), null); } else if (result.getHeight() < contentH + height + options.getBottomPadding()) { // 超过原来图片高度的上限, 则需要扩充图片长度 result = GraphicUtil.createImg(options.getImgW(), result.getHeight() + Math.max(height + options.getBottomPadding(), BASE_ADD_H), result); } // 绘制文字 Graphics2D g2d = GraphicUtil.getG2d(result); int index = 0; for (String str : strs) { GraphicUtil.drawContent(g2d, str, contentH + (fontSize + options.getLinePadding()) * (++index) , options); } g2d.dispose(); contentH += height; return this; } /** * 计算总行数 * * @param strs 字符串列表 * @param w 生成图片的宽 * @param padding 渲染内容的左右边距 * @param fontSize 字体大小 * @return */ private int calLineNum(String[] strs, int w, int padding, int fontSize) { // 每行的字符数 double lineFontLen = Math.floor((w - (padding << 1)) / (double) fontSize); int totalLine = 0; for (String str : strs) { totalLine += Math.ceil(str.length() / lineFontLen); } return totalLine; }
上面需要注意的是画布的生成规则,特别是高度超过上限之后,重新计算图片高度时,需要额外注意新增的高度,应该为基本的增量与(绘制内容高度+下边距)的较大值
代码如下:
int realAddH = Math.max(bufferedImage.getHeight() + options.getBottomPadding() + options.getTopPadding(), BASE_ADD_H)
重新生成画布实现 com.hust.hui.quickmedia.common.util.GraphicUtil#createImg
public static BufferedImage createImg(int w, int h, BufferedImage img) { BufferedImage bf = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = bf.createGraphics(); if (img != null) { g2d.setComposite(AlphaComposite.Src); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.drawImage(img, 0, 0, null); } g2d.dispose(); return bf; }
上面理解之后,绘制图片就比较简单了,基本上行没什么差别
public Builder drawImage(String img) { BufferedImage bfImg; try { bfImg = ImageUtil.getImageByPath(img); } catch (IOException e) { log.error("load draw img error! img: {}, e:{}", img, e); throw new IllegalStateException("load draw img error! img: " + img, e); } return drawImage(bfImg); } public Builder drawImage(BufferedImage bufferedImage) { if (result == null) { result = GraphicUtil.createImg(options.getImgW(), Math.max(bufferedImage.getHeight() + options.getBottomPadding() + options.getTopPadding(), BASE_ADD_H), null); } else if (result.getHeight() < contentH + bufferedImage.getHeight() + options.getBottomPadding()) { // 超过阀值 result = GraphicUtil.createImg(options.getImgW(), result.getHeight() + Math.max(bufferedImage.getHeight() + options.getBottomPadding() + options.getTopPadding(), BASE_ADD_H), result); } // 更新实际高度 int h = GraphicUtil.drawImage(result, bufferedImage, contentH, options); contentH += h + options.getLinePadding(); return this; }
5. http接口
上面实现的生成图片的公共方法,在 quick-media 工程中,利用spring-boot搭建了一个web服务,提供了一个http接口,用于生成长图文,最终的成果就是我们开头的那个gif图的效果,相关代码就没啥好说的,有兴趣的可以直接查看工程源码,链接看最后
测试验证
上面基本上完成了我们预期的目标,接下来则是进行验证,测试代码比较简单,先准备一段文本,这里拉了一首诗
招魂酹翁宾旸
郑起
君之在世帝敕下,君之谢世帝敕回。
魂之为变性原返,气之为物情本开。
於戏龙兮凤兮神气盛,噫嘻鬼兮归兮大块埃。
身可朽名不可朽,骨可灰神不可灰。
采石捉月李白非醉,耒阳避水子美非灾。
长孙王吉命不夭,玉川老子诗不徘。
新城罗隐在奇特,钱塘潘阆终崔嵬。
阴兮魄兮曷往,阳兮魄兮曷来。
君其归来,故交寥落更散漫。
君来归来,帝城绚烂可徘徊。
君其归来,东西南北不可去。
君其归来。
春秋霜露令人哀。
花之明吾无与笑,叶之陨吾实若摧。
晓猿啸吾闻泪堕,宵鹤立吾见心猜。
玉泉其清可鉴,西湖其甘可杯。
孤山暖梅香可嗅,花翁葬荐菊之隈。
君其归来,可伴逋仙之梅,去此又奚之哉。
测试代码
@Test public void testGenImg() throws IOException { int w = 400; int leftPadding = 10; int topPadding = 40; int bottomPadding = 40; int linePadding = 10; Font font = new Font("宋体", Font.PLAIN, 18); ImgCreateWrapper.Builder build = ImgCreateWrapper.build() .setImgW(w) .setLeftPadding(leftPadding) .setTopPadding(topPadding) .setBottomPadding(bottomPadding) .setLinePadding(linePadding) .setFont(font) .setAlignStyle(ImgCreateOptions.AlignStyle.CENTER) // .setBgImg(ImageUtil.getImageByPath("qrbg.jpg")) .setBgColor(0xFFF7EED6) ; BufferedReader reader = FileReadUtil.createLineRead("text/poem.txt"); String line; int index = 0; while ((line = reader.readLine()) != null) { build.drawContent(line); if (++index == 5) { build.drawImage(ImageUtil.getImageByPath("https://static.oschina.net/uploads/img/201708/12175633_sOfz.png")); } if (index == 7) { build.setFontSize(25); } if (index == 10) { build.setFontSize(20); build.setFontColor(Color.RED); } } BufferedImage img = build.asImage(); String out = Base64Util.encode(img, "png"); System.out.println("<img src=\"data:image/png;base64," + out + "\" />"); }
输出图片
Das obige ist der detaillierte Inhalt vonJava-Codefall zur Realisierung einer langen Bild- und Textgenerierung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!