这一篇文章咱们就用 Java 来生成一下仿金山词霸的海报。html
As long as you can still grab a breath, you fight.
只要一息尚存,就不得不战。
有那么一段时间,我特别迷恋金山词霸的每日一句分享海报。由于不只海报上的图片美,文字也特别美,美得让我感受生活都有了诗意。就像文章开头的那句中英文对照,中文和英文都妙极了。java
最近,又有不少人迷恋上了流利说的小程序分享海报(朋友圈比比皆是)。但不论是金山词霸仍是流利说,分享的海报都不是本身的二维码,这对于我的品牌的缔造者来讲,实在是一件出力不讨好的事。git
固然了,这种事难不倒做为程序员的我。程序员
第一步,获取网络图片的路径。金山词霸每日一句的图片路径地址形式以下所示。能够根据当前日期获取最新的图片路径。github
// 金山词霸的图片路径 String formatDate = DateFormatUtils.format(new Date(), "yyyyMMdd"); String picURL = "http://cdn.iciba.com/news/word/big_" + formatDate + "b.jpg";
第二步,有了图片路径后,能够根据此路径建立 HTTP get 请求。编程
// 根据路径发起 HTTP get 请求 HttpGet httpget = new HttpGet(picURL); // 使用 addHeader 方法添加请求头部 httpget.addHeader("Content-Type", "text/html;charset=UTF-8"); // 配置请求的超时设置 RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(500).setConnectTimeout(500) .setSocketTimeout(500).build(); httpget.setConfig(requestConfig);
第三步,建立 CloseableHttpClient
对象来执行 HTTP get 请求,并获取响应信息 CloseableHttpResponse
。CloseableHttpClient
是一个抽象类,它是 HttpClient
的基本实现,也实现了 java.io.Closeable
接口。小程序
CloseableHttpClient httpclient = HttpClientBuilder.create().build(); CloseableHttpResponse response = httpclient.execute(httpget);
第四步,从 CloseableHttpResponse
中获取图片的输入流。网络
HttpEntity entity = response.getEntity(); InputStream picStream = entity.getContent();
第五步,从图片输入流中读取信息,并输出到本地文件中。app
File pic = Files.createTempFile(Paths.get("D:\\test"), "pic_", ".jpg"); FileOutputStream fos = new FileOutputStream(pic); int read = 0; // 1024Byte(字节)=1KB 1024KB=1MB byte[] bytes = new byte[1024 * 100]; while ((read = inputStream.read(bytes)) != -1) { fos.write(bytes, 0, read); } fos.flush(); fos.close();
在指定的临时目录下能够查看采集到的图片,以下所示。eclipse
海报背景的大小为 678 1013 像素,我的品牌二维码的大小为 128 128 像素。两张图片都是事先准备好的,放在 src 目录下。整个项目的目录结构图以下所示。
接下来,咱们把这两张图片分别读取到临时文件当中,供后续动做使用。
第一步,建立 ClassLoader
对象,从 classpath 的根路径下查找资源。
ClassLoader classLoader = ReadBgAndQrcode.class.getClassLoader();
第二步,经过 classLoader.getResourceAsStream()
读取海报背景和我的品牌二维码,复制到临时文件中。
File bgFile = Files.createTempFile(DIRECTORY, "bg_", ".jpg").toFile(); InputStream inputStream = classLoader.getResourceAsStream("default_bgimg.jpg"); FileUtils.copyInputStreamToFile(inputStream, bgFile); logger.debug("背景:" + bgFile.getAbsolutePath()); File qrcodeFile = Files.createTempFile(DIRECTORY, "qrcode_", ".jpg").toFile(); InputStream qrcodeInputStream = classLoader.getResourceAsStream("default_qrcodeimg.jpg"); FileUtils.copyInputStreamToFile(qrcodeInputStream, qrcodeFile); logger.debug("二维码:" + qrcodeFile.getAbsolutePath());
在指定的临时目录下能够查看海报背景和我的品牌二维码,以下所示。
Graphics2D
类扩展了 Graphics
类,提供了对几何形状、坐标转换、颜色管理和文本布局更为复杂的控制,是用于呈现二维形状、文本和图像的基础类。
BufferedImage
使用可访问的图像数据缓冲区描述图像,由颜色模型和图像数据栅格组成,全部 BufferedImage 对象的左上角坐标为(0,0)。
能够利用 BufferedImage
类的 createGraphics()
方法获取 Graphics2D
对象。
第一步,将海报背景和海报封面读入到 BufferedImage 对象中。注意,deleteOnExit()
方法请求在虚拟机终止时删除此抽象路径名所表示的文件或目录。
// 背景 File bgFile = FileUtil.read("bg_", ".jpg", "default_bgimg.jpg"); bgFile.deleteOnExit(); BufferedImage bgImage = ImageIO.read(bgFile); // 封面图 File picFile = CapturePic.capture(); picFile.deleteOnExit(); BufferedImage picImage = ImageIO.read(picFile);
第二步,计算封面图的起始坐标,以及高度和宽度。
// 封面图的起始坐标 int pic_x = MARGIN, pic_y = MARGIN; // 封面图的宽度 int pic_width = bgImage.getWidth() - MARGIN * 2; // 封面图的高度 int pic_height = picImage.getHeight() * pic_width / picImage.getWidth();
第三步,在海报背景上绘制封面图。
Graphics2D graphics2d = bgImage.createGraphics(); // 在背景上绘制封面图 graphics2d.drawImage(picImage, pic_x, pic_y, pic_width, pic_height, null); // 释放图形上下文,以及它正在使用的任何系统资源。 graphics2d.dispose();
第四步,将绘制好的图像输出到文件中。
File posterFile = Files.createTempFile(FileUtil.DIRECTORY, "poster_", ".jpg").toFile(); ImageIO.write(bgImage, "jpg", posterFile);
在指定的临时目录下能够查看海报,以下所示。
Font
类表示字体,用于以可见的方式呈现文本。字体提供了将字符序列映射到象形文字序列以及在图形和组件对象上呈现象形文字序列所需的信息。
第一步,经过 GraphicsEnvironment
类的 getAvailableFontFamilyNames()
查看计算机上容许使用的字体。
String[] fontNames = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames(); for (String fontName : fontNames) { System.out.println(fontName); }
大体的中文字体有这么一些(还有更多,未列出):
宋体
幼圆
微软雅黑
微软雅黑 Light
新宋体
方正姚体
方正舒体
楷体
隶书
黑体
第二步,设置字体和颜色。
// Font 的构造参数依次是字体名字,字体式样,字体大小 Font font = new Font("微软雅黑", Font.PLAIN, 28); g.setFont(font); // RGB g.setColor(new Color(71, 71, 71));
第三步,根据当前字体下每一个中文字符的宽度,以及海报可容纳的最大文本宽度,对文本进行换行。
计算每一个字体的宽度时,须要用到 sun.font.FontDesignMetrics
,它扩展了 java.awt.FontMetrics
。FentMetrics
类定义了一个字体度量对象,该对象封装了有关在特定屏幕上呈现特定字体的信息。FontDesignMetrics
提供了更多指标的 Font 信息。
FontDesignMetrics
有几个重要的值须要说明一下:
baseline
FontDesignMetrics
的 charWidth()
方法能够计算字符的宽度。
public static String makeLineFeed(String zh, FontDesignMetrics metrics, int max_width) { StringBuilder sb = new StringBuilder(); int line_width = 0; for (int i = 0; i < zh.length(); i++) { char c = zh.charAt(i); sb.append(c); // 若是主动换行则跳过 if (sb.toString().endsWith("\n")) { line_width = 0; continue; } // FontDesignMetrics 的 charWidth() 方法能够计算字符的宽度 int char_width = metrics.charWidth(c); line_width += char_width; // 若是当前字符的宽度加上以前字符串的已有宽度超出了海报的最大宽度,则换行 if (line_width >= max_width - char_width) { line_width = 0; sb.append("\n"); } } return sb.toString(); }
假如文本是“沉默王二,《Web 全栈开发进阶之路》做者;一个不止写代码的程序员,还写有趣有益的文字,给不喜欢严肃的你。”咱们来经过 makeLineFeed()
方法试验一下。
Font font = new Font("微软雅黑", Font.PLAIN, 28); FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font); String zh = "沉默王二,《Web 全栈开发进阶之路》做者;一个不止写代码的程序员,还写有趣有益的文字,给不喜欢严肃的你。"; String[] rows = makeLineFeed(zh, metrics, 600).split("\n"); for (int i = 0; i < rows.length; i++) { System.out.println(rows[i]); }
其结果以下所示。
沉默王二,《Web 全栈开发进阶之路》做者;
一个不止写代码的程序员,还写有趣有益的文字
,给不喜欢严肃的你。
第四步,将自动换行后的文本在海报背景上打印。
这里须要用到 FontDesignMetrics
的 getHeight()
方法获取每行文本的高度。对照下面的示意图,理解 height 的具体高度。
// 自动换行后的文本 String zhWrap = FontUtil.makeLineFeed(graphics2dPoster.getZh(), metrics, graphics2dPoster.getSuitableWidth()); // 拆分行 String[] zhWraps = zhWrap.split("\n"); // 将每一行在海报背景上打印 for (int i = 0; i < zhWraps.length; i++) { graphics2dPoster.addCurrentY(metrics.getHeight()); graphics2d.drawString(zhWraps[i], MARGIN, graphics2dPoster.getCurrentY()); }
此时的海报效果以下图所示。
能够看得出,文字带有很强的锯齿感,怎么消除呢?
graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
若是英语很差的话,看起来这段代码会很吃力。ANTIALIASING
单词的意思就是“消除混叠现象,消除走样,图形保真”。
英文和中文最大的不一样在于,换行的单位再也不是单个字符,而是整个单词。
第一步,根据当前字体下每一个英文单词的宽度,以及海报可容纳的最大文本宽度,对文本进行换行。
public static String makeEnLineFeed(String en, FontDesignMetrics metrics, int max_width) { // 每一个单词后追加空格 char space = ' '; int spaceWidth = metrics.charWidth(space); // 按照空格对英文文本进行拆分 String[] words = en.split(String.valueOf(space)); // 利用 StringBuilder 对字符串进行修改 StringBuilder sb = new StringBuilder(); // 每行文本的宽度 int len = 0; for (int i = 0; i < words.length; i++) { String word = words[i]; int wordWidth = metrics.stringWidth(word); // 叠加当前单词的宽度 len += wordWidth; // 超出最大宽度,进行换行 if (len > max_width) { sb.append("\n"); sb.append(word); sb.append(space); // 下一行的起始宽度 len = wordWidth + spaceWidth; } else { sb.append(word); sb.append(space); // 多了一个空格 len += spaceWidth; } } return sb.toString(); }
假如文本是“Fear can hold you prisoner. Hope can set you free. It takes a strong man to save himself, and a great man to save another.”咱们来经过 makeEnLineFeed() 方法试验一下。
Font font = new Font("微软雅黑", Font.PLAIN, 28); FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font); String en = "Fear can hold you prisoner. Hope can set you free. It takes a strong man to save himself, and a great man to save another."; String[] rows = makeEnLineFeed(en, metrics, 600).split("\n"); for (int i = 0; i < rows.length; i++) { System.out.println(rows[i]); }
其结果以下所示。
Fear can hold you prisoner. Hope can set
you free. It takes a strong man to save
himself, and a great man to save another.
第三步,将自动换行后的文本在海报背景上打印。
// 设置封面图和下方中文之间的距离 graphics2dPoster.addCurrentY(20); Graphics2D graphics2d = graphics2dPoster.getGraphics2d(); graphics2d.setColor(new Color(157, 157, 157)); FontDesignMetrics metrics = FontDesignMetrics.getMetrics(graphics2d.getFont()); String enWrap = FontUtil.makeEnLineFeed(graphics2dPoster.getEn(), metrics, graphics2dPoster.getSuitableWidth()); String[] enWraps = enWrap.split("\n"); for (int i = 0; i < enWraps.length; i++) { graphics2dPoster.addCurrentY(metrics.getHeight()); graphics2d.drawString(enWraps[i], MARGIN, graphics2dPoster.getCurrentY()); }
此时的海报效果以下图所示。
有了前面绘制海报封面的经验,绘制二维码就变得垂手可得了。
// 二维码 File qrcodeFile = FileUtil.read("qrcode_", ".jpg", "default_qrcodeimg.jpg"); qrcodeFile.deleteOnExit(); BufferedImage qrcodeImage = ImageIO.read(qrcodeFile); // 二维码起始坐标 int qrcode_x = bgImage.getWidth() - qrcodeImage.getWidth() - MARGIN; int qrcode_y = bgImage.getHeight() - qrcodeImage.getHeight() - MARGIN; graphics2dPoster.getGraphics2d().drawImage(qrcodeImage, qrcode_x, qrcode_y, qrcodeImage.getWidth(), qrcodeImage.getHeight(), null);
此时的海报效果以下图所示。
是否是感受海报的左下角比较空白,总体的对称性不够天然,那就在左下角追加一些二维码的描述文本吧。
graphics2d.setColor(new Color(71, 71, 71)); Font font = new Font(USE_FONT_NAME, Font.PLAIN, 22); graphics2d.setFont(font); FontDesignMetrics metrics = FontDesignMetrics.getMetrics(graphics2d.getFont()); graphics2d.drawString("沉默王二", MARGIN, bgImage.getHeight() - MARGIN - metrics.getHeight() * 2); graphics2d.drawString("一个幽默的程序员", MARGIN, bgImage.getHeight() - MARGIN - metrics.getDescent());
此时的海报效果以下图所示。
Swing 是一个用于 Java GUI 编程(图形界面设计)的工具包(类库);换句话说,Java 之因此能够用来开发带界面的 PC 软件,就是由于 Swing 的存在。
Swing 使用纯粹的 Java 代码来模拟各类控件,没有使用本地操做系统的内在方法,因此 Swing 是跨平台的。也正是由于 Swing 的这种特性,人们一般把 Swing 控件称为轻量级控件。
Eclipse 默认是不支持可视化的 Swing 编程的,但 Eclipse 的插件市场上有这样一个好插件——WindowBuilder,使用它能够大幅度地下降开发难度,迅速地提高开发效率。
下载地址:https://marketplace.eclipse.o...
可直接拖拽到 Eclipse 进行安装,以下图。
注意,Eclipse 的版本要求为:
2018-09 (4.9), Photon (4.8), Oxygen (4.7), Neon (4.6), 2018-12 (4.10), 2019-03 (4.11)
拖拽到 Eclipse 后的效果以下:
安装完成后,会提醒你重启 Eclipse。
舒适提示:安装的过程大约持续 3 分钟的时间,中间可能会失败,重试几回便可。不用担忧,Eclipse 会智能地保存失败前的进度。
安装成功后,就可使用可视化工具设计界面了,以下图所示:
在将应用程序进行打包时,使用者都但愿开发者只提供一个单独的文件,而不是包含大量源码的文件夹。jar 包存在的目的正源于此。
将项目打成 jar 包也很简单,在 Eclipse 中,可依次右键项目→Export→Runnable JAR file。你将会看到如下界面。
选择 main 方法所在类,指定导出目标,选择 Copy required libraries
选项,点击「Finish」便可。在指定的目录下可找到生成的 jar 包文件。
若是电脑上安装了 Java 的运行环境,双击该 jar 包文件就能够运行。运行后的界面,以下图所示。能够填写中文、英文、海报封面路径,而后点击按钮生成海报。
PS:为了便于你们的学习,我已经将源码放在了 GitHub 上,地址以下。
https://github.com/qinggee/po...
赶快去 star 吧!