2019独角兽企业重金招聘Python工程师标准>>>

图片合成

利用Java的绘图方法,实现图片合成

在开始之前,先定一个小目标,我们希望通过图片合成的方式,创建一个类似下面样式的图片

I. 设计思路

首先解析一下我们的目标实现图片合成,那么这些合成的基本组成单元有些什么?

组成基本单元

  • 图片
  • 文字
  • 几何图形

也就是说,我们可以将任意个图片,文字,几何图形,按照自己的意愿进行拼接,那么问题就转变成两个

  • 基本单元如何在画布上渲染
  • 基本单元之间如何配合使用

II. 基本单元绘制

首先定义一个基本单元的接口,之后所有组合的元素都继承自这个接口

接口IMergeCell只定义一个绘制的方法,用于实现该基本单元的绘制方式

public interface IMergeCell {void draw(Graphics2D g2d);
}

1. 图片绘制

绘制图片,一般来讲需要知道:

  • 绘制的坐标(x,y)
  • 绘制图片的宽高(w,h),当目标是绘制原图时,宽高一般为图片本身的宽高

结合上面两点,图片组成单元的定义如下: ImgCell

@Data
@Builder
public class ImgCell implements IMergeCell {private BufferedImage img;private Integer x, y, w, h;@Overridepublic void draw(Graphics2D g2d) {if (w == null) {w = img.getWidth();}if (h == null) {h = img.getHeight();}g2d.drawImage(img, x, y, w, h, null);}
}

2. 文本绘制

图片绘制比较简单,相比而言,文字绘制就麻烦一点,主要是文本绘制的对齐方式,竖排还是横排布局

首先分析我们需要的基本信息

  • 考虑对齐方式(居中对齐,靠左,靠上,靠右,靠下)

    • 因此需要确定文本绘制的区域,所以需要两个坐标 (startX, startY), (endX, endY)
  • 文本绘制参数

    • 可以指定字体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);}@Overridepublic 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, 从习惯来讲,基本上我们都是从左到右进行阅读
  • 水平or垂直布局,都希望是 startY < endY
  • 垂直布局时,以字符为单位进行绘制;标点符号的绘制时,x坐标有一个偏移量

3. Line直线绘制

几何图形之直线绘制,给出起点和结束点坐标,绘制一条直线,比较简单;这里给出了虚线的支持

@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;@Overridepublic 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);}}
}

4. 矩形框绘制

矩形框绘制,同直线绘制,支持圆角矩形,支持虚线框

@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;@Overridepublic 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);}}
}

5. 矩形区域填充

@Data
@Builder
public class RectFillCell implements IMergeCell {private Font font;private Color color;private int x,y,w,h;@Overridepublic void draw(Graphics2D g2d) {g2d.setFont(font);g2d.setColor(color);;g2d.fillRect(x, y, w, h);}
}

III. 封装

上面实现了几个常见的基本单元绘制,接下来则是封装绘制, 这块的逻辑就比较简单了如下

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;}
}

IV. 测试

写了一个模板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();}
}

演示图如下:

V. 其他

项目地址:

  • https://github.com/liuyueyi/quick-media
  • QuickMedia 目标是创建一个专注图文,音视频,二维码处理的开源项目

系列博文

  • spring-boot & ffmpeg 搭建一个音频转码服务
  • spring-boot & zxing 搭建二维码服务
  • 二维码服务拓展(支持logo,圆角logo,背景图,颜色配置)
  • zxing二维码生成服务之深度定制
  • Java实现长图文生成
  • Java竖排长图文生成
  • Java实现markdown 转 html
  • Java实现html 转 image

扫描关注,java分享

转载于:https://my.oschina.net/u/566591/blog/1551577

Java 实现图片合成相关推荐

  1. java 处理图片图片合成

    最近写了一个java的图片合成相关的项目,真的是一踩一个坑,下面博主说说遇见的一些坑和怎么解决的这些问题. 本文主要讲图片合成,加文字还是比较简单的. 先讲下我的需求,把一张图片盖到另外一张图片上面, ...

  2. JAVA操作图片/合成/电子盖章等

    JAVA操作图片/合成/电子盖章等 /**** @param mainFile* @param file1* @param f 透明度 最大为1* @param response* @return* ...

  3. java进行图片合成以及写入自定义字体

    由于工作需要做了一个利用java的Graphics2D进行图片合成且在图片上写入自定义字体文字的小功能, 那就顺便记录一些痕迹,有什么不好的地方请见谅 public static void main( ...

  4. linux centos java kumo图片合成文字 词云插件 字体乱码问题

    linux下安装微软雅黑字体库 或者其他字体 jieba 词云 kumo相关 百度随便找个ttf安装就行 用winscp直接切目录 /usr/share/fonts/ 放入*.ttf字体文件 先看看f ...

  5. Java实现图片合成

    功能 竖向合成多张大小不一致的图片,以最宽图片的为宽为合成后的宽度, 高为所有图片的总高度.图片宽度没有达到合成的宽度则填充部分使用白色画布填充. 代码 /*** 多张图片进行竖向合并成一张图片* @ ...

  6. java实现图片合成gif图——输出到具体路径或流数据上传

    一.多图片转gif 转存到输出流 /*** 多图片转gif 转存到输出流** @param imageList* @param os* @throws IOException*/private sta ...

  7. JAVA 由图片合成gif文件

    1.需要四个类,直接应用到项目中,不需要做太多改动,除了导包: 2.jpgToGif函数需要传入两个参数,第一个参数为要合成的图片路径数组,第二个参数为合成的GIF图片将存在的路径. 例如: 第一个参 ...

  8. Java图片合成并引入外部字体写入文字

    业务需要将两个图片合成,并按指定字体写入标语以及用户的经纬度,代码如下: import cn.hutool.core.img.GraphicsUtil;import javax.imageio.Ima ...

  9. JAVA实现基于ZXing的二维码自动生成与图片合成

    JAVA实现基于ZXing的二维码自动生成与图片合成 近日做项目需要生成带有信息的二维码,并嵌入到一张图片中.实现思路采用Zxing生成二维码,java图形库进行图片的嵌入. 生成二维码 ZXing是 ...

最新文章

  1. haproxy实现高可用及负载均衡
  2. 利用闭包实现多次ajax请求只执行最后一次
  3. mysql数据库常见错误码大全
  4. [读书笔记][golang]《go语言-云动力》
  5. leetcode258. 各位相加(简单题,但是你不看答案想不出来)
  6. 使用Hyper-V Server创建Linux虚拟机
  7. git pull git add git commit git branch git更新代码git提交git分支管理
  8. 软件项目成员的业绩考核
  9. Linux的基本指令(2)-Linux从入门到精通第三天(非原创)
  10. axis2与cxf区别
  11. 服务器imm装系统,通过IMM With Remote Console为服务器安装操作系统
  12. jmeter---ftp性能测试
  13. centos双网卡不能同时工作解决
  14. texstudio统计字数
  15. PostgreSQL重启恢复---Log Buffer
  16. 通俗理解电磁干扰及共模电感的原理
  17. b标签、Strong标签、h1标签的区别以及使用
  18. python下载b站视频_爬虫可以当是一个批量下载工具!用Python批量下载B站视频
  19. 那些年,我们一起做过的 Java 课后练习题(56 - 60)
  20. 时空之轮Android手柄,经典角色扮演类游戏 Android时空之轮

热门文章

  1. suse linux ftp家目录,Suse linux下控制ftp用户访问目录
  2. 信息服务器 iis 7.0,iis7.0的技术参数汇总
  3. 浮栅场效应管 符号_MOS场效应管
  4. mysql三高讲解(二):2.3 InnoDB索引即数据
  5. php viewmodel,PHP日记——Lavarel常用语句之View篇
  6. setuptools Declaring Dependencies
  7. MySQL Mathematical Functions(数学方法)
  8. MySQL Flow Control Statements(流程控制)
  9. requests session
  10. Java JDBC c3p0