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.mzph.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;