이 글은 Java에서 긴 그래픽과 텍스트를 생성하는 방법에 대한 샘플 코드를 주로 소개합니다. 편집자가 꽤 좋다고 생각해서 지금 공유하고 참고용으로 제공하겠습니다. 에디터 따라가서 살펴볼까요
오랜만에 웨이보에 긴 사진과 텍스트를 구현하는 게 참 재미있더라구요. 레이아웃을 최종 사진으로 직접 출력해서 저장하고 보기도 하고 공유하기도 너무 편해요. 이제
Goals
버전을 먼저 구현했습니다. 먼저 달성할 것으로 예상되는 목표를 정의하세요. 텍스트 + 그림을 기반으로 긴 그래픽을 생성하세요
Target dismantling
그림을 생성하는 텍스트
그림 삽입 지원
위, 아래, 왼쪽 및 오른쪽 여백 설정 지원
글꼴 선택 지원
글꼴 색상 지원
왼쪽 정렬, 가운데, 오른쪽 지원 alignment
예상 결과
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
encapsulation 구성 매개변수 설정, 텍스트 그리기, 그림 그리기 작업 방법, 출력 스타일 및 기타 인터페이스
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"); } }
에 대한 구체적인 구현은 없습니다. 위의 텍스트 및 그림 그림은 나중에 자세히 설명합니다. 여기서 주요 초점은 콘텐츠의 높이(상단 여백 포함)를 나타내는 하나의 매개변수이므로 최종 생성된 이미지의 높이는 다음과 같습니다. be
int realH = contentH + options.getBottomPadding();
두 번째로 위의 이미지 출력 방법에 대해 간략하게 설명하겠습니다. com.hust.hui.quickmedia.common.image.ImgCreateWrapper.Builder#asImage
최종 생성된 이미지의 높이(너비)를 계산합니다. 입력 매개변수로 지정)
배경 그리기(배경 이미지가 없으면 단색으로 채우기)
엔티티 콘텐츠 그리기(예: 그려진 텍스트, 그림)
3. 콘텐츠 채우기 GraphicUtil
특정 콘텐츠 채우기는 텍스트 그리기와 그림 그리기로 나누어집니다
Design
채우기 과정에서 글꼴, 색상 등을 자유롭게 설정할 수 있다는 점을 고려하여 우리 그리기 방법에서는, 콘텐츠 그리기 및 채우기가 직접 실현됩니다. 즉, drawXXX 메서드는 실제로 콘텐츠 채우기를 실현합니다. 실행 후 콘텐츠가 캔버스에 채워집니다.
그림 크기를 고려한 그림 그리기 자체와 최종 결과의 크기 충돌이 있을 수 있으므로 다음 규칙을 사용하십시오.
그림 너비 그리기 8c6628478f690a3e537d188b224bc8af (생성된 그림의 너비 지정 - 여백) 등 그림을 그리는 데 비례하는 크기 조정
텍스트 그리기, 줄 바꿈 문제
각 줄의 허용되는 텍스트 길이가 초과되면 자동 줄 바꿈이 제한됩니다.
텍스트 그리기
기본 텍스트 그리기를 고려하면, 과정은 다음과 같습니다
1. BufferImage 개체를 생성합니다
2. Graphic2d 개체를 가져와서 작업합니다
3. information
4 텍스트를 줄 바꿈에 따라 문자열 배열로 분할하고 루프에서 한 줄 내용을 그립니다.
현재 줄 문자열, 실제 그려진 줄 수를 계산한 다음 분할합니다
텍스트를 순서대로 그립니다(y 좌표의 변화에 주의해야 합니다)
다음은 구체적인 구현입니다
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; }
4. 콘텐츠 렌더링
단일 블록만 제공됩니다. 콘텐츠 렌더링(예: 텍스트, 그림)
그린 콘텐츠가 캔버스 높이를 초과하는 경우 어떻게 해야 합니까?
텍스트 그리기에는 들어오는 텍스트에 줄바꿈이 없어야 합니다. 그렇지 않으면 줄 바꿈은 적용되지 않습니다
크로스 드로잉 시나리오에서 y 좌표를 다시 계산하는 방법
이러한 문제를 해결하려면 ImgCreateWrapper의 특정 드로잉에서 구현됩니다. 먼저 텍스트 드로잉을 살펴보겠습니다
Split. 개행 문자에 따른 문자열
그리기 내용이 최종적으로 그림으로 변환될 때 차지하는 높이를 계산합니다.
캔버스 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!