Java實(shí)現(xiàn)多行文字水印的方法詳解
一、業(yè)務(wù)場景
公司處方藥品銷售業(yè)務(wù),需要在線開具處方或者手動上傳處方圖片,處方圖片在購藥使用之后需要添加已使用的水印字樣,防止處方圖片的重復(fù)使用。因此,需要設(shè)計一個為圖片添加文字水印的Java工具類,針對這個問題,我展開了解決方案的研究。
1.1 UI設(shè)計的水印原型
二、實(shí)現(xiàn)方案探索
2.1 準(zhǔn)備處方圖片
我們先在互聯(lián)網(wǎng)醫(yī)院,給患者“程咬金”開個處方單
2.2 單行文字水印
首先是翻找項(xiàng)目中通用工具類,發(fā)現(xiàn)有一個WaterMarkUtils的類,應(yīng)該就是給圖片添加水印的工具類
/** * @author 遛馬少年 * @Title WaterMarkUtils * @Description */ public class WaterMarkUtils { /** * 圖片添加水印 * * @param imgFile * 需要添加水印的圖片 * @param markContentColor * 水印文字的顏色 * @param waterMarkContent * 水印的文字 * @return 水印圖片 */ public static File markStr(File imgFile, Color markContentColor, String waterMarkContent) { try { // 加水印 BufferedImage bufImg = ImageIO.read(imgFile); int width = bufImg.getWidth(); //圖片寬 int height = bufImg.getHeight(); //圖片高 Graphics2D g = bufImg.createGraphics(); g.drawImage(bufImg, 0, 0, width, height, null); Font font = new Font("微軟雅黑", Font.ITALIC, 45); g.setColor(markContentColor); // 根據(jù)圖片的背景設(shè)置水印顏色 g.setFont(font); int x = width -2*getWatermarkLength(waterMarkContent, g); //這是一個計算水印位置的函數(shù),可以根據(jù)需求添加 int y = height - 1*getWatermarkLength(waterMarkContent, g); g.drawString(waterMarkContent, x, y); g.dispose(); ImageIO.write(bufImg, "png", imgFile); return imgFile; } catch (Exception e) { log.error("WaterMarkUtils-markStr err:{} ",e.getMessage()); } return null; } /** * 獲取水印文字總長度 * * @param waterMarkContent * 水印的文字 * @param g * @return 水印文字總長度 */ public static int getWatermarkLength(String waterMarkContent, Graphics2D g) { return g.getFontMetrics(g.getFont()).charsWidth(waterMarkContent.toCharArray(), 0, waterMarkContent.length()); } }
寫個main方法試一下效果
public class TestWaterMark { public static void main(String[] args) { String rxPath = "F:\Temp\rxImage\程咬金.png"; File rxFile = new File(rxPath); WaterMarkUtils.markStr(rxFile, Color.RED, "該處方已使用,處方僅允許在本平臺進(jìn)行使用,用戶在其他平臺使用本處方引起的糾紛概不負(fù)責(zé)"); }
看看效果:
只在中間位置插入了一行水印,并且因?yàn)樗∥淖痔L而顯示不全,顯然是不符合要求的。
2.3 兩行水印方案
接著,基于上面的水印方案,做了點(diǎn)改造
首先將根據(jù)圖片大小計算字體大小,添加文字水印的本質(zhì)是基于Graphics2D在圖片上進(jìn)行繪制操作,要實(shí)現(xiàn)多行水印,就要循環(huán)遍歷圖片的行級像素,然后添加文字即可,最終代碼如下
/** * @author 遛馬少年 * @Title ImageWatermarkUtil * @Description */ @Slf4j public class ImageWatermarkUtil { // 水印透明度 private static float alpha = 0.1f; // 水印文字顏色 private static Color color = Color.RED; // 水印之間的間隔 private static final int XMOVE = 80; // 水印之間的間隔 private static final int YMOVE = 80; /** * 獲取文本長度。漢字為1:1,英文和數(shù)字為2:1 */ private static int getTextLength(String text) { int length = text.length(); for (int i = 0; i < text.length(); i++) { String s = String.valueOf(text.charAt(i)); if (s.getBytes().length > 1) { length++; } } length = length % 2 == 0 ? length / 2 : length / 2 + 1; return length; } /** * 給圖片添加水印文字、可設(shè)置水印文字的旋轉(zhuǎn)角度 * * @param srcImgPath 原圖片路徑 * @param dstImgPath 加完水印之后圖片路徑 * @param degree 旋轉(zhuǎn)角度 * @param logoText 水印主標(biāo)題 * @param logoTextPlus 水印副標(biāo)題 */ public static void ImageByText(String srcImgPath, String dstImgPath, Integer degree, String logoText, String logoTextPlus) { File srcFile = new File(srcImgPath); File dstFile = new File(dstImgPath); ImageByText(srcFile, dstFile, degree, logoText, logoTextPlus); } public static void ImageByText(File srcFile, File dstFile, Integer degree, String logoText, String logoTextPlus) { InputStream is = null; OutputStream os = null; try { long start = System.currentTimeMillis(); // 源圖片 Image srcImg = ImageIO.read(srcFile); int width = srcImg.getWidth(null);// 原圖寬度 int height = srcImg.getHeight(null);// 原圖高度 BufferedImage buffImg = new BufferedImage(srcImg.getWidth(null), srcImg.getHeight(null), BufferedImage.TYPE_INT_RGB); // 得到畫筆對象 Graphics2D g = buffImg.createGraphics(); // 設(shè)置對線段的鋸齒狀邊緣處理 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g.drawImage(srcImg.getScaledInstance(srcImg.getWidth(null), srcImg.getHeight(null), Image.SCALE_SMOOTH), 0, 0, null); // 設(shè)置水印旋轉(zhuǎn) if (null != degree) { g.rotate(Math.toRadians(degree), (double) buffImg.getWidth() / 2, (double) buffImg.getHeight() / 2); } int txtLen = logoTextPlus.length(); int FONT_SIZE = width / txtLen; Font font = new Font("微軟雅黑", Font.BOLD, FONT_SIZE); // 設(shè)置水印文字顏色 g.setColor(color); // 設(shè)置水印文字Font g.setFont(font); // 設(shè)置水印文字透明度 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)); int x = -width / 2; int y = -height / 2; int markWidth = FONT_SIZE * txtLen;// 字體長度 int markHeight = FONT_SIZE;// 字體高度 // 循環(huán)添加水印 while (x < width * 1.5) { y = -height / 2; while (y < height * 1.5) { g.drawString(logoText, x, y); y += markHeight + YMOVE; g.drawString(logoTextPlus, x, y); y += markHeight + YMOVE; } x += markWidth + XMOVE; } // 釋放資源 g.dispose(); // 生成圖片 os = new FileOutputStream(dstFile); ImageIO.write(buffImg, FileUtil.extName(srcFile), os); long time = System.currentTimeMillis() - start; log.info("添加水印文字成功!耗時(ms):{}", time); } catch (Exception e) { e.printStackTrace(); } finally { try { if (null != os) os.close(); } catch (Exception e) { e.printStackTrace(); } } } }
寫個main方法,試一下效果
public class TestWaterMark { public static void main(String[] args) { String rxPath = "F:\Temp\rxImage\程咬金.png"; File rxFile = new File(rxPath); String rxDstPath = "F:\Temp\rxImage\程咬金-加水印.png"; File rxDstFile = new File(rxDstPath); ImageWatermarkUtil.ImageByText(rxFile, rxDstFile, -40, "該處方已使用", "處方僅允許在本平臺進(jìn)行使用,用戶在其他平臺使用本處方引起的糾紛概不負(fù)責(zé)"); } }
效果如下:
已經(jīng)很接近原型設(shè)計了,為了實(shí)現(xiàn)多行效果,我在循環(huán)里面調(diào)用了兩次drawString
g.drawString(logoText, x, y); g.drawString(logoTextPlus, x, y);
但是還有問題,就是第二行文字太長,并不能完整地顯示,追求完美的我沒有放棄,接著改造
2.4 多行水印方案
既然第二行太長,那就再把第二行拆分,每行幾個字即可,然后再循環(huán)寫文字
最終代碼如下
/** * @author 遛馬少年 * @Title ImageWatermarkUtil2 * @Description */ public class ImageWatermarkUtil2 { // 水印透明度 private static float alpha = 0.1f; private static int fontSize = 80; // 水印文字字體 private static Font font = new Font("微軟雅黑", Font.BOLD, fontSize); // 水印文字顏色 private static Color color = Color.RED; /** * 水印之間的橫向間隔 */ private static final int XMOVE = 80; /** * 水印之間的縱向間隔 */ private static final int YMOVE = 80; /** * 給圖片添加水印文字、可設(shè)置水印文字的旋轉(zhuǎn)角度 * * @param logoText 水印文字 * @param srcFile 源文件 * @param dstFile 輸出文件 * @param degree 設(shè)置角度 */ public static void markImageByText(File srcFile, File dstFile, Integer degree, String logoText) { long start = System.currentTimeMillis(); InputStream is = null; try { String[] waterMarkContents = logoText.split("\|\|"); // 1、源圖片 Image srcImg = ImageIO.read(srcFile); // 原圖寬度 int srcImgWidth = srcImg.getWidth(null); // 原圖高度 int srcImgHeight = srcImg.getHeight(null); BufferedImage buffImg = new BufferedImage(srcImg.getWidth(null), srcImg.getHeight(null), BufferedImage.TYPE_INT_RGB); // 2、得到畫筆對象 Graphics2D g = buffImg.createGraphics(); // 3、設(shè)置對線段的鋸齒狀邊緣處理 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g.drawImage( srcImg.getScaledInstance(srcImg.getWidth(null), srcImg.getHeight(null), Image.SCALE_SMOOTH), 0, 0, null); // 4、設(shè)置水印旋轉(zhuǎn) if (null != degree) { g.rotate(Math.toRadians(degree), (double) buffImg.getWidth() / 2, (double) buffImg.getHeight() / 2); } // 5、設(shè)置水印文字顏色 g.setColor(color); // 6、設(shè)置水印文字Font g.setFont(font); // 7、設(shè)置水印文字透明度 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)); // 8、第一參數(shù)->設(shè)置的內(nèi)容,后面兩個參數(shù)->文字在圖片上的坐標(biāo)位置(x,y) // 獲取其中最長的文字水印的大小 int maxLen = 0; int maxHigh = 0; String waterMarkContent = ""; for (int i = 0; i < waterMarkContents.length; i++) { waterMarkContent = waterMarkContents[i]; int fontLen = getWatermarkLength(waterMarkContent, g); if (fontLen >= maxLen) { maxLen = fontLen; } maxHigh = maxHigh + (i + 1) * fontSize + 10; } // 文字長度相對于圖片寬度應(yīng)該有多少行 int line = srcImgWidth * 2 / maxLen; int co = srcImgHeight * 2 / maxHigh; int yz = 0; // 填充Y軸方向 for (int a = 0; a < co; a++) { int t = 0; for (int j = 0; j < waterMarkContents.length; j++) { waterMarkContent = waterMarkContents[j]; int y = (j + 1) * fontSize + 10 + t; // 文字疊加,自動換行疊加,注意符號 int tempX = -srcImgWidth / 2; int tempY = -srcImgHeight / 2 + y + yz; // 單字符長度 int tempCharLen = 0; // 單行字符總長度臨時計算 int tempLineLen = 0; StringBuffer sb = new StringBuffer(); for (int i = 0; i < waterMarkContent.length(); i++) { char tempChar = waterMarkContent.charAt(i); tempCharLen = getCharLen(tempChar, g); tempLineLen += tempCharLen; // 和圖片的長度進(jìn)行對應(yīng)的比較操作 if (tempLineLen >= srcImgWidth) { // 長度已經(jīng)滿一行,進(jìn)行文字疊加 g.drawString(sb.toString(), tempX, tempY); t = t + fontSize; // 清空內(nèi)容,重新追加 sb.delete(0, sb.length()); tempY += fontSize; tempLineLen = 0; } // 追加字符 sb.append(tempChar); } // 填充X軸 for (int z = 0; z < line; z++) { // 最后疊加余下的文字 g.drawString(sb.toString(), tempX, tempY); tempX = tempX + maxLen + XMOVE; } } yz = yz + maxHigh + YMOVE; } // 9、釋放資源 g.dispose(); // 10、生成圖片 ImageIO.write(buffImg, "png", new FileOutputStream(dstFile)); System.out.println("圖片完成添加水印文字,耗時:{}" + (System.currentTimeMillis() - start)); } catch (Exception e) { e.printStackTrace(); } finally { try { if (null != is) is.close(); } catch (Exception e) { e.printStackTrace(); } } } public static int getCharLen(char c, Graphics2D g) { return g.getFontMetrics(g.getFont()).charWidth(c); } /** * 獲取水印文字總長度 * * @paramwaterMarkContent水印的文字 * @paramg * @return水印文字總長度 */ public static int getWatermarkLength(String waterMarkContent, Graphics2D g) { return g.getFontMetrics(g.getFont()).charsWidth(waterMarkContent.toCharArray(), 0, waterMarkContent.length()); } }
再試下最終效果
public class TestWaterMark { public static void main(String[] args) { String rxPath = "F:\Temp\rxImage\程咬金.png"; File rxFile = new File(rxPath); String rxDstPath = "F:\Temp\rxImage\程咬金-加水印.png"; File rxDstFile = new File(rxDstPath); ImageWatermarkUtil2.markImageByText(rxFile, rxDstFile, -40, "該處方已使用||本處方僅允許在本平臺進(jìn)行使用,||用戶在其他平臺使用本處方引起的||糾紛概不負(fù)責(zé)"); } }
效果如下:
中間空白位置有點(diǎn)多,其實(shí)可以通過修改循環(huán)y的步長來控制
Anyway,這個效果最終滿足了UI驗(yàn)收要求
三、總結(jié)
多行水印實(shí)現(xiàn)起來,總體還是比較簡單的。唯一的難點(diǎn)的多行的實(shí)現(xiàn),目前網(wǎng)上搜索到的解決方案,大多都是單行的。多行的實(shí)現(xiàn)其實(shí)就是基于單行,多了一層循環(huán)遍歷。
有個不太確定的點(diǎn)是,添加水印的字體大小是否需要根據(jù)圖片大小動態(tài)調(diào)整,比如圖片較大時,水印文字字體也跟著變大。所以我的第二個方案是動態(tài)調(diào)整的,最終方案又是規(guī)定大小的。
至于怎么設(shè)計,看各位的實(shí)際業(yè)務(wù)需求。
到此這篇關(guān)于Java實(shí)現(xiàn)多行文字水印的方法詳解的文章就介紹到這了,更多相關(guān)Java多行文字水印內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java環(huán)境中MyBatis與Spring或Spring MVC框架的集成方法
和MyBatis類似,Spring或者Spring MVC框架在Web應(yīng)用程序的運(yùn)作中同樣主要負(fù)責(zé)處理數(shù)據(jù)庫事務(wù),這里我們就來看一下Java環(huán)境中MyBatis與Spring或Spring MVC框架的集成方法2016-06-06java?CompletableFuture異步任務(wù)編排示例詳解
這篇文章主要為大家介紹了java?CompletableFuture異步任務(wù)編排示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11一文學(xué)會使用sa-token解決網(wǎng)站權(quán)限驗(yàn)證
這篇文章主要為大家介紹了使用sa-token解決網(wǎng)站權(quán)限驗(yàn)證方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07springboot整合 beatlsql的實(shí)例代碼
這篇文章主要介紹了springboot整合 beatlsql的實(shí)例代碼,BeetSql是一個全功能DAO工具,同時具有hibernate 優(yōu)點(diǎn) & Mybatis優(yōu)點(diǎn)功能,有興趣的可以了解一下2017-05-05