開源)嗨,Java,你能夠生成金山詞霸的二維碼分享海報嗎?

As long as you can still grab a breath, you fight. 只要一息尚存,就不得不戰。html

有那麼一段時間,我特別迷戀金山詞霸的每日一句分享海報。由於不只海報上的圖片美,文字也特別美,美得讓我感受生活都有了詩意。就像文章開頭的那句中英文對照,中文和英文都妙極了。java

最近,又有不少人迷戀上了流利說的小程序分享海報(朋友圈比比皆是)。但不論是金山詞霸仍是流利說,分享的海報都不是本身的二維碼,這對於我的品牌的締造者來講,實在是一件出力不討好的事。git

固然了,這種事難不倒做爲程序員的我。這一篇文章咱們就用 Java 來生成一下仿金山詞霸的海報。程序員

0一、大體思路

  • 採集網絡圖片github

  • 加載海報背景和我的品牌二維碼編程

  • 利用 Graphics2D 將網絡圖片繪製成海報封面小程序

  • 利用 Graphics2D 在海報上打印中英文對照語網絡

  • 利用 Graphics2D 在海報上繪製我的專屬二維碼app

  • 使用 Swing 構建圖形化界面eclipse

  • 將項目打成 jar 包發行

  • 運行 jar 包,填寫必要信息後生成海報

0二、採集網絡圖片

第一步,獲取網絡圖片的路徑。金山詞霸每日一句的圖片路徑地址形式以下所示。能夠根據當前日期獲取最新的圖片路徑。

// 金山詞霸的圖片路徑
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 請求,並獲取響應信息 CloseableHttpResponseCloseableHttpClient 是一個抽象類,它是 HttpClient 的基本實現,也實現了 java.io.Closeable 接口。

CloseableHttpClient httpclient = HttpClientBuilder.create().build();
CloseableHttpResponse response = httpclient.execute(httpget);
複製代碼

第四步,從 CloseableHttpResponse 中獲取圖片的輸入流。

HttpEntity entity = response.getEntity();
InputStream picStream = entity.getContent();
複製代碼

第五步,從圖片輸入流中讀取信息,並輸出到本地文件中。

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();
複製代碼

在指定的臨時目錄下能夠查看採集到的圖片,以下所示。

0三、加載海報背景和我的品牌二維碼

海報背景的大小爲 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());
複製代碼

在指定的臨時目錄下能夠查看海報背景和我的品牌二維碼,以下所示。

0五、利用 Graphics2D 將網絡圖片繪製成海報封面

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);
複製代碼

在指定的臨時目錄下能夠查看海報,以下所示。

0六、利用 Graphics2D 在海報上打印中文

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.FontMetricsFentMetrics 類定義了一個字體度量對象,該對象封裝了有關在特定屏幕上呈現特定字體的信息。FontDesignMetrics 提供了更多指標的 Font 信息。

FontDesignMetrics 有幾個重要的值須要說明一下:

  • 基準點是 baseline
  • ascent 是 baseline 之上至字符最高處的距離
  • descent 是 baseline 之下至字符最低處的距離
  • leading 文檔說的很含糊,實際上是上一行字符的 descent 到下一行的 ascent 之間的距離
  • top 指的是指的是最高字符到 baseline 的值,即 ascent 的最大值
  • bottom 指的是最下字符到 baseline 的值,即 descent 的最大值

FontDesignMetricscharWidth() 方法能夠計算字符的寬度。

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 全棧開發進階之路》做者; 一個不止寫代碼的程序員,還寫有趣有益的文字 ,給不喜歡嚴肅的你。

第四步,將自動換行後的文本在海報背景上打印。

這裏須要用到 FontDesignMetricsgetHeight() 方法獲取每行文本的高度。對照下面的示意圖,理解 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 單詞的意思就是「消除混疊現象,消除走樣,圖形保真」。

0七、利用 Graphics2D 在海報上打印英文

英文和中文最大的不一樣在於,換行的單位再也不是單個字符,而是整個單詞。

第一步,根據當前字體下每一個英文單詞的寬度,以及海報可容納的最大文本寬度,對文本進行換行。

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());
}
複製代碼

此時的海報效果以下圖所示。

0七、利用 Graphics2D 在海報上繪製我的專屬二維碼

有了前面繪製海報封面的經驗,繪製二維碼就變得垂手可得了。

// 二維碼
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());
複製代碼

此時的海報效果以下圖所示。

0八、使用 Swing 構建圖形化界面

Swing 是一個用於 Java GUI 編程(圖形界面設計)的工具包(類庫);換句話說,Java 之因此能夠用來開發帶界面的 PC 軟件,就是由於 Swing 的存在。

Swing 使用純粹的 Java 代碼來模擬各類控件,沒有使用本地操做系統的內在方法,因此 Swing 是跨平臺的。也正是由於 Swing 的這種特性,人們一般把 Swing 控件稱爲輕量級控件。

Eclipse 默認是不支持可視化的 Swing 編程的,但 Eclipse 的插件市場上有這樣一個好插件——WindowBuilder,使用它能夠大幅度地下降開發難度,迅速地提高開發效率。

下載地址:marketplace.eclipse.org/content/win…

可直接拖拽到 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 會智能地保存失敗前的進度。

安裝成功後,就可使用可視化工具設計界面了,以下圖所示:

0九、將項目打成 jar 包發行

在將應用程序進行打包時,使用者都但願開發者只提供一個單獨的文件,而不是包含大量源碼的文件夾。jar 包存在的目的正源於此。

將項目打成 jar 包也很簡單,在 Eclipse 中,可依次右鍵項目→Export→Runnable JAR file。你將會看到如下界面。

選擇 main 方法所在類,指定導出目標,選擇 Copy required libraries 選項,點擊「Finish」便可。在指定的目錄下可找到生成的 jar 包文件。

十、運行 jar 包,填寫必要信息後生成海報

若是電腦上安裝了 Java 的運行環境,雙擊該 jar 包文件就能夠運行。運行後的界面,以下圖所示。能夠填寫中文、英文、海報封面路徑,而後點擊按鈕生成海報。

PS:爲了便於你們的學習,我已經將源碼放在了 GitHub 上,地址以下。

github.com/qinggee/pos…

趕快去 star 吧!

相關文章
相關標籤/搜索