spring上傳文件添加水印

1、實現??MultipartFile

package com.pojo.common.core.domain;import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;public class InMultipartFile implements MultipartFile {private final String name;private String originalFilename;@Nullableprivate String contentType;private final byte[] content;/*** Create a new MockMultipartFile with the given content.* @param name the name of the file* @param content the content of the file*/public InMultipartFile(String name, @Nullable byte[] content) {this(name, "", null, content);}/*** Create a new MockMultipartFile with the given content.* @param name the name of the file* @param contentStream the content of the file as stream* @throws IOException if reading from the stream failed*/public InMultipartFile(String name, InputStream contentStream) throws IOException {this(name, "", null, FileCopyUtils.copyToByteArray(contentStream));}/*** Create a new MockMultipartFile with the given content.* @param name the name of the file* @param originalFilename the original filename (as on the client's machine)* @param contentType the content type (if known)* @param content the content of the file*/public InMultipartFile(String name, @Nullable String originalFilename, @Nullable String contentType, @Nullable byte[] content) {Assert.hasLength(name, "Name must not be null");this.name = name;this.originalFilename = (originalFilename != null ? originalFilename : "");this.contentType = contentType;this.content = (content != null ? content : new byte[0]);}/*** Create a new MockMultipartFile with the given content.* @param name the name of the file* @param originalFilename the original filename (as on the client's machine)* @param contentType the content type (if known)* @param contentStream the content of the file as stream* @throws IOException if reading from the stream failed*/public InMultipartFile(String name, @Nullable String originalFilename, @Nullable String contentType, InputStream contentStream)throws IOException {this(name, originalFilename, contentType, FileCopyUtils.copyToByteArray(contentStream));}@Overridepublic String getName() {return this.name;}@Overridepublic String getOriginalFilename() {return this.originalFilename;}@Override@Nullablepublic String getContentType() {return this.contentType;}@Overridepublic boolean isEmpty() {return (this.content.length == 0);}@Overridepublic long getSize() {return this.content.length;}@Overridepublic byte[] getBytes() throws IOException {return this.content;}@Overridepublic InputStream getInputStream() throws IOException {return new ByteArrayInputStream(this.content);}@Overridepublic void transferTo(File dest) throws IOException, IllegalStateException {FileCopyUtils.copy(this.content, dest);}}

2、添加水印工具類

package com.pojo.common.core.utils;import com.pojo.common.core.domain.InMultipartFile;
import org.springframework.web.multipart.MultipartFile;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;public class WatermarkUtil {/*** 添加多行文字水印** @param file        原始文件* @param lines       水印文本行列表* @param font        字體對象* @param color       顏色(支持透明度)* @param startXRatio 起始X坐標比例(0.0~1.0)* @param startYRatio 起始Y坐標比例(0.0~1.0)* @param lineSpacing 行間距倍數* @return 帶水印的MultipartFile*/public static MultipartFile addTextWatermark(MultipartFile file,List<String> lines,Font font,Color color,float startXRatio,float startYRatio,float lineSpacing) throws IOException {// 讀取原始圖片(保留透明度通道)BufferedImage sourceImage = ImageIO.read(file.getInputStream());BufferedImage watermarkedImage = new BufferedImage(sourceImage.getWidth(),sourceImage.getHeight(),BufferedImage.TYPE_INT_ARGB);// 創建圖形上下文Graphics2D g2d = watermarkedImage.createGraphics();configureGraphicsQuality(g2d);g2d.drawImage(sourceImage, 0, 0, null);// 設置水印樣式g2d.setFont(font);g2d.setColor(color);g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, color.getAlpha() / 255f));// 計算實際繪制位置int baseX = (int) (sourceImage.getWidth() * startXRatio);int baseY = (int) (sourceImage.getHeight() * startYRatio);// 繪制多行文本drawWrappedText(g2d, lines, baseX, baseY, lineSpacing, sourceImage.getWidth());g2d.dispose();// 轉換回MultipartFilereturn createOutputFile(watermarkedImage, file);}/*** 配置圖形渲染質量*/private static void configureGraphicsQuality(Graphics2D g2d) {g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);}/*** 智能換行繪制*/private static void drawWrappedText(Graphics2D g2d, List<String> lines,int startX, int startY, float lineSpacing,int imageWidth) {FontMetrics metrics = g2d.getFontMetrics();int lineHeight = metrics.getHeight();int currentY = startY + metrics.getAscent();for (String line : lines) {List<String> wrappedLines = wrapChineseText(line, metrics, imageWidth - startX);for (String wrappedLine : wrappedLines) {int textWidth = metrics.stringWidth(wrappedLine);int x = calculateHorizontalPosition(startX, textWidth, imageWidth);g2d.drawString(wrappedLine, x, currentY);currentY += lineHeight * lineSpacing;}}}/*** 中文自動換行算法*/private static List<String> wrapChineseText(String text, FontMetrics metrics, int maxWidth) {List<String> result = new ArrayList<>();StringBuilder currentLine = new StringBuilder();int currentWidth = 0;for (int i = 0; i < text.length(); i++) {char c = text.charAt(i);int charWidth = metrics.charWidth(c);if (currentWidth + charWidth > maxWidth) {result.add(currentLine.toString());currentLine = new StringBuilder();currentWidth = 0;}currentLine.append(c);currentWidth += charWidth;}if (currentLine.length() > 0) {result.add(currentLine.toString());}return result;}/*** 計算水平位置(支持左對齊/居中/右對齊)*/private static int calculateHorizontalPosition(int startX, int textWidth, int imageWidth) {// 此處實現居中邏輯,可根據需要擴展return startX;}/*** 創建輸出文件*/private static MultipartFile createOutputFile(BufferedImage image, MultipartFile originalFile)throws IOException {String formatName = getImageFormat(originalFile.getContentType());ByteArrayOutputStream baos = new ByteArrayOutputStream();if (!ImageIO.write(image, "png", baos)) {throw new IOException("不支持的圖片格式: " + formatName);}return new InMultipartFile("watermarked." + formatName,originalFile.getOriginalFilename(),originalFile.getContentType(),baos.toByteArray());}/*** 從ContentType提取圖片格式*/private static String getImageFormat(String contentType) {return contentType.substring("image/".length()).split(";")[0];}}

3、使用

   // 準備水印參數List<String> watermarkLines = new ArrayList<>();watermarkLines.add("機密文件 嚴禁外傳");watermarkLines.add("編號:2023-0012");watermarkLines.add("有效期至:2025-12-31");// 創建字體(建議使用物理字體文件更可靠)Font font = new Font("微軟雅黑", Font.BOLD, 16);Color color = new Color(255, 0, 0, 180); // 半透明白色MultipartFile result = null;// 添加水印try {result = WatermarkUtil.addTextWatermark(file,watermarkLines,font,color,0.05f,   // 左側5%位置0.7f,    // 頂部70%位置(靠近底部)1.0f     // 1.0倍行間距);} catch (IOException e) {throw new RuntimeException(e);}

4、測試效果

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/904383.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/904383.shtml
英文地址,請注明出處:http://en.pswp.cn/news/904383.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

嵌入式MCU語音識別算法及實現方案

在嵌入式MCU&#xff08;微控制器單元&#xff09;中實現語音識別&#xff0c;由于資源限制&#xff08;如處理能力、內存、功耗等&#xff09;&#xff0c;通常需要輕量級算法和優化技術。以下是常見的語音識別算法及實現方案&#xff1a; 一、傳統語音識別算法 動態時間規整&…

【論文閱讀】DETR+Deformable DETR

可變形注意力是目前transformer結構中經常使用的一種注意力機制&#xff0c;最近補了一下這類注意力的論文&#xff0c;提出可變形注意力的論文叫Deformable DETR&#xff0c;是在DETR的基礎上進行的改進&#xff0c;所以順帶著把原本的DETR也看了一下。 一、DETR DETR本身是…

大模型在宮頸癌診療全流程預測與應用研究報告

目錄 一、引言 1.1 研究背景與意義 1.2 研究目的與創新點 二、大模型預測宮頸癌術前風險 2.1 術前數據收集與預處理 2.2 預測模型構建與算法選擇 2.3 術前風險預測指標與案例分析 三、大模型輔助制定術中方案 3.1 術中風險動態監測與預測 3.2 基于預測的手術方案優化…

【Python 文件I/O】

Python 的文件 I/O 操作是數據處理的基礎技能&#xff0c;涉及文件的讀寫、路徑管理、異常處理等核心功能。以下是文件 I/O 的核心知識點&#xff1a; 一、基礎文件操作 1. 打開文件 # 通用模式&#xff1a;r(讀)/w(寫)/a(追加) b(二進制)/t(文本&#xff0c;默認) f open(…

Twin Builder 中的電池等效電路模型仿真

電池單元熱設計挑戰 電池熱管理的主要挑戰之一是確保溫度低于最大工作限值。較高的溫度會導致效率降低、加速老化和潛在的安全隱患。工程師必須了解電池產生的熱量&#xff0c;才能充分設計冷卻系統。 了解和預測電池模塊的熱行為需要將電池的熱損耗與電池單元的電氣機械特性…

一種基于條件生成對抗網絡(cGAN)的CT重建算法

簡介 簡介:該文提出了一種基于條件生成對抗網絡(cGAN)的CT重建算法,通過引入CBAM注意力機制增強網絡對關鍵特征的提取能力,有效解決了CT成像中因噪聲干擾導致的重建精度下降問題。實驗采用固體火箭發動機模擬件數據集,將正弦圖分為五組并添加不同程度的噪聲進行訓練。結…

【Redis篇】linux 7.6安裝單機Redis7.0(參數優化詳解)

&#x1f4ab;《博主主頁》&#xff1a; &#x1f50e; CSDN主頁 &#x1f50e; IF Club社區主頁 &#x1f525;《擅長領域》&#xff1a;擅長阿里云AnalyticDB for MySQL(分布式數據倉庫)、Oracle、MySQL、Linux、prometheus監控&#xff1b;并對SQLserver、NoSQL(MongoDB)有了…

【BUG】‘DetDataSample‘ object has no attribute ‘_gt_sem_seg‘

問題&#xff1a; 使用mmdetection框架使用COCO格式訓練自定義數據集時&#xff0c;其中模型使用HTC模型時出現如下問題&#xff1a; AttributeError: ‘DetDataSample’ object has no attribute ‘_gt_sem_seg’. Did you mean: ‘gt_sem_seg’? results self(**data, mode…

Java日期格式化方法總結

在Java中&#xff0c;日期格式化主要涉及將 Date、LocalDate、LocalDateTime 等日期時間對象轉換為指定格式的字符串&#xff0c;或將字符串解析為日期對象。以下是兩種常用的日期格式化方式&#xff1a; 一、使用 SimpleDateFormat&#xff08;舊版API&#xff0c;Java 8之前&…

【NLP】27. 語言模型訓練以及模型選擇:從預訓練到下游任務

語言模型訓練&#xff1a;從預訓練到下游任務 本文詳細講解大型語言模型&#xff08;LLMs&#xff09;是如何訓練的&#xff0c;包括不同的模型類型&#xff08;Encoder、Decoder、Encoder-Decoder&#xff09;&#xff0c;以及各類預訓練任務的原理、對比、適用場景&#xff0…

通過 ModernBERT 實現零樣本分類的性能提升

文本分類 是機器學習中最基礎的任務之一&#xff0c;擁有悠久的研究歷史和深遠的實用價值。更重要的是&#xff0c;它是許多實際項目中不可或缺的組成部分&#xff0c;從搜索引擎到生物醫學研究都離不開它。文本分類方法被廣泛應用于科學論文分類、用戶工單分類、社交媒體情感分…

基于SpringBoot網上書店的設計與實現

pom.xml配置文件 1. 項目基本信息(沒什么作用) <groupId>com.spring</groupId> <!--項目組織標識&#xff0c;通常對應包結構--> <artifactId>boot</artifactId> <!--項目唯一標識--> <version>0.0.1-SNAPSHOT</ve…

STM32H743單片機實現ADC+DMA多通道檢測+事件組

在上個文章基礎上改用事件組進行處理&#xff0c;以便實時任務。 stm32cubeMX自動生成代碼 osEventFlagsId_t adctestEventHandle; const osEventFlagsAttr_t adctestEvent_attributes {.name "adctestEvent" };adctestEventHandle osEventFlagsNew(&adctest…

AI Agent開發第57課-AI用在銷售歸因分析場景中-用隨機森林從0構建自己的“小模型”

開篇 在前一篇《機器學習的基礎-線性回歸如何應用在商業場景中》里,我們說到了如果我們只是簡單的分析和預測一下投入廣告費用和銷售額增長是否存在必然關系,我們用了線性回歸法得到了分析,得到的分析結果極其精準,以及提到了:如果當銷售因素是非線性的并且有著額外一些如…

Linux運維——Vim技巧三

Vim技巧 一、按 按模 模式 式匹 匹配 配及 及按 按原 原義 義匹 匹配1.1、調整查找模式的大小寫敏感性1.2、按正則表達式查找時&#xff0c;使用 \v 模式開關1.3、按原義查找文本時&#xff0c;使用 \V 原義開關1.4、使用圓括號捕獲子匹配1.5、界定單詞的邊界1.6、界定匹配的邊…

NLTK庫(1): 數據集-語料庫(Corpus)

1.簡介 NLTK &#xff08;Natural Language Toolkit&#xff09; 是自然語言處理&#xff08;NLP&#xff09;任務的 Python 庫&#xff0c;內置大量NLP數據集與計算包。 NLP數據集也叫語料庫 (Corpus), 若無特殊格式或標記&#xff0c;數據集通常來自txt等文本文件。 本教程…

spring詳解-循環依賴的解決

Spring循環依賴 重點提示&#xff1a; 本文都快寫完了&#xff0c;發現“丈夫” 的英文是husband… 在“②有AOP循環依賴” 改過來了&#xff0c;前面用到的位置太多了就沒改。我是說怎么idea的hansband英文下面怎么有波浪線。各位能夠理解意思就行&#xff0c;英文拼寫不要過…

隨機快速排序算法

一、隨機化原理 經典快速排序 選取固定的“樞軸”&#xff08;通常取第一個或最后一個元素&#xff09;&#xff0c;在最壞情況下&#xff08;如已經有序&#xff09;會退化為 。 隨機快速排序 在每次分區前隨機地從當前區間 [p..r] 中等概率選取一個樞軸&#xff0c;將它與末…

數據可視化與分析

數據可視化的目的是為了數據分析&#xff0c;而非僅僅是數據的圖形化展示。 項目介紹 項目案例為電商雙11美妝數據分析&#xff0c;分析品牌銷售量、性價比等。 數據集包括更新日期、ID、title、品牌名、克數容量、價格、銷售數量、評論數量、店名等信息。 1、數據初步了解…

美團Java高級配送員面經分享|玩梗版

美團Java高級配送員面經分享&#xff01;純玩梗&#xff01;