この記事では、Java で長いグラフィックとテキストを生成する方法のサンプル コードを主に紹介します。編集者が非常に優れていると考えたので、参考として共有します。編集者をフォローして見てみましょう
昔、Weiboで長い写真とテキストを実装するのが非常に興味深かったので、レイアウトを最終画像として直接出力し、保存、表示、共有するのが非常に便利でした。今、私は自分で簡単なものを実装しました
目標のバージョン
まず達成することが期待される目標を定義します: テキストと画像に基づいて長いグラフィックを生成します
ターゲットの解体
大きな段落をサポートします。画像を生成するためのテキスト
画像の挿入をサポート
上下左右の余白設定をサポート
フォントの選択をサポート
フォントカラーをサポート
左揃え、中央揃え、右揃えをサポートアラインメント
期待される結果
Spring -boot は、長いグラフィックスとテキストを生成する http インターフェイスを構築し、パラメーターを渡すことでさまざまな構成情報を指定します。以下は、最終的な呼び出しの概略図です
。
設計と実装
awt を使用した長いグラフィックスとテキストの生成テキスト描画と画像描画を実行します
1. パラメーター オプション ImgCreateOptions
予想される目標に従って構成パラメーターを設定します。次のパラメータを含めます
@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. カプセル化クラス ImageCreateWrapper
カプセル化 設定パラメータの設定、描画テキスト、画像を描画するための操作メソッド、出力スタイルおよびその他のインターフェイス
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"); } }
の特定の実装はありません上のテキストと画像の描画については、後で詳しく説明します。ここでの主な焦点は、実際の描画を表すパラメータ contentH です。つまり、最終的に生成される画像の高さになります。
int realH = contentH + options.getBottomPadding();
次に、上記の画像出力メソッドについて簡単に説明します: com.hust.hui.quickmedia.common.image.ImgCreateWrapper.Builder#asImage
最終的に生成される画像の高さ (幅) を計算します。は入力パラメータで指定されます)
背景を描画します(背景画像がない場合は単色で塗りつぶします)
エンティティコンテンツを描画します(つまり、描画されたテキスト、画像)
3.コンテンツの塗りつぶし GraphicUtil
具体的なコンテンツの塗りつぶしは文字描画と絵描画に分かれます
デザイン
塗りつぶしの際にフォントや色などを自由に設定できることを考慮し、弊社の描画メソッドでは、コンテンツの描画と塗りつぶしが直接実現されます。つまり、drawXXX メソッドは、実行後、コンテンツがキャンバス上に塗りつぶされます
画像のサイズを考慮して描画します。それ自体と最終結果のサイズが競合する可能性があります。次のルールを使用してください
描画幅 <= (生成された画像の幅 - マージンを指定)、すべてを埋めます
描画幅 > (生成される画像の幅 - マージンを指定します) など。 絵を描くための比例拡大縮小
テキストの描画、行の折り返しの問題
各行のテキストの長さが制限されている場合、それを超えると自動的に行が折り返します。が必要です
基本的なテキスト描画を検討します。プロセスは次のとおりです
1. BufferImage オブジェクトを作成し、描画を操作します3.情報
4. テキストは改行に従って文字列配列に分割され、単一行の内容がループで描画されます
現在の行文字列と実際に描画される行数を計算して分割します
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; }
描画する絵の幅と高さを再計算するだけです
/** * 在原图上绘制图片 * * @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; }
描画されたコンテンツがキャンバスの高さを超えた場合の対処方法
改行文字に従って文字列を分割
キャンバスのBufferedImage結果を再生成します
結果が空の場合は直接生成されます
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 + "\" />"); }
输出图片
以上が長い画像とテキストの生成を実現する Java コードの例の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。