Cet article présente principalement l'exemple de code sur la façon de générer de longs graphiques et du texte en Java. L'éditeur pense que c'est plutôt bon, je vais donc le partager avec vous maintenant et le donner comme référence. Suivons l'éditeur pour y jeter un œil.
Il y a longtemps, je pensais qu'il était très intéressant d'implémenter de longues images et textes sur Weibo. J'édite la mise en page directement comme image finale. enregistrer, visualiser et partager. Maintenant, je peux le faire moi-même. Commençons par une version simple de
Objectif
Définissons d'abord l'objectif que nous espérons atteindre : générer un long graphique. texte basé sur du texte + des images
Démantèlement de la cible
Prend en charge la génération d'images à partir d'un texte volumineux
Prend en charge l'insertion d'images
Prise en charge des paramètres de marge supérieure, inférieure, gauche et droite
Prise en charge de la sélection de police
Prise en charge de la couleur de la police
Prise en charge de l'alignement à gauche, au centre et à droite
Résultats attendus
Nous allons construire une interface http pour générer de longues images et du texte via spring-boot. Spécifiez diverses informations de configuration en passant des paramètres. Ce qui suit est un diagramme schématique de l'appel final
<.>
Conception et mise en œuvre
Image longue Générer du texte, utiliser awt pour le dessin de texte et le dessin d'imagesOptions de paramètres ImgCreateOptions
Définissez les paramètres de configuration en fonction de nos objectifs attendus, qui incluent essentiellement les paramètres suivants@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; } } }
Classe d'encapsulation ImageCreateWrapper .
encapsule le réglage des paramètres de configuration, le dessin du texte et le dessin des images, le mode, le style de sortie et d'autres interfacespublic 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"); } }
int realH = contentH + options.getBottomPadding();
Le remplissage de contenu spécifique est divisé en dessin de texte et dessin d'image
Designprend en compte le remplissage Pendant le processus, vous pouvez définir librement les polices, couleurs, etc., donc dans notre méthode de dessin, le dessin et le remplissage du contenu sont directement implémentés, c'est-à-dire que la méthode drawXXX réalise véritablement le remplissage du contenu après exécution, le contenu a été rempli sur le canevas
.
Dessin d'image, étant donné que la taille de l'image elle-même et la taille du résultat final peuvent entrer en conflit, les règles suivantes sont utilisées
<. 🎜>
Dessin de texte2. Obtenez l'objet Graphic2d. et utilisez le dessin
3. Définissez les informations de configuration de base
4. Divisez le texte en un tableau de chaînes en fonction des sauts de ligne et dessinez le contenu d'une seule ligne dans une boucle
La mise en œuvre ci-dessus est plus claire et le dessin de les images sont encore plus simples
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; }
<. 🎜>
4. Rendu du contenu
/** * 在原图上绘制图片 * * @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; }
Comment gérer le contenu dessiné dépassant la hauteur de la toile
Exigences de dessin du texte entrant Le texte n'a pas de sauts de ligne, sinon des sauts de ligne ne prendra pas effet
Dans les scénarios croisés, comment recalculer la coordonnée y
La solution à ces problèmes est ImgCreateWrapper est implémentée dans le dessin spécifique. Regardons d'abord le dessin du texte
Si le résultat est vide, il sera généré directement
S'il l'est finalement généré Si la hauteur dépasse la hauteur du canevas existant, un canevas plus haut sera généré et le contenu original sera dessiné dessus
Dessiner de manière itérative le contenu d'une seule ligne
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 + "\" />"); }
输出图片
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!