基于java的图片水印添加方法
效果图:
添加水印前 | 添加时间水印后 |
![]() | ![]() |
一、应用场景
水印添加可以保留部分实时信息,如位置、时间等,也可作为图片防盗使用等。有时候由于手机型号等问题在前端添加水印不成功或图片失真,此时可通过Java后端绘图添加。
二、文字水印添加处理
在文件上传到minio之前做处理,主要用到两个类:FileImageWatermarkUtils (水印添加处理)、MultipartFileDto (辅助实体类)
1、水印添加处理类FileImageWatermarkUtils
package com.inspur.common.utils.file;
import com.inspur.common.utils.file.MultipartFileDto;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.*;
/**
* @author inspur
* 定义文字加水印
*/
public class FileImageWatermarkUtils {
/**
* 水印字体
*/
private static final Font FONT = new Font("微软雅黑", Font.PLAIN, 40);
/**
* 透明度
*/
private static final AlphaComposite COMPOSITE = AlphaComposite
.getInstance(AlphaComposite.SRC_OVER, 0.9f);
/**
* 水印之间的间隔
*/
private static final int X_MOVE = 150;
/**
* 水印之间的间隔
*/
private static final int Y_MOVE = 200;
/**
* 打水印(文字)
*
* @param file MultipartFile
* @return MultipartFile
*/
public static MultipartFile markWithContent(MultipartFile file,String keyword) {
FileOutputStream fos = null;
try {
BufferedImage srcImg = ImageIO.read(file.getInputStream());
// 图片宽、高
int imgWidth = srcImg.getWidth();
int imgHeight = srcImg.getHeight();
// 图片缓存
BufferedImage bufImg = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
// 创建绘图工具
Graphics2D graphics = bufImg.createGraphics();
// 画入原始图像
graphics.drawImage(srcImg, 0, 0, imgWidth, imgHeight, null);
// 设置水印颜色
graphics.setColor(Color.WHITE);
// 设置水印透明度
graphics.setComposite(COMPOSITE);
// 设置倾斜角度
/* graphics.rotate(Math.toRadians(-35), (double) bufImg.getWidth() / 2,
(double) bufImg.getHeight() / 2);*/
// 设置水印字体
graphics.setFont(FONT);
// 消除java.awt.Font字体的锯齿
// graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int xCoordinate , yCoordinate;
String mark = "ls65535";
// 字体长度
int markWidth = FONT.getSize() * getTextLength(keyword);
// 字体高度
int markHeight = FONT.getSize();
//位置控制 水印位于图片 下方右下角
xCoordinate = (int)((imgWidth-getWatermarkLength(keyword,graphics) )*0.9);
yCoordinate = (int)(imgHeight * 0.93 );
graphics.drawString(keyword, xCoordinate, yCoordinate);
InputStream inputStream = buffToInputStream(bufImg);
// 释放画图工具
graphics.dispose();
return new MultipartFileDto(file.getName(),file.getOriginalFilename(),file.getContentType(),inputStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* 计算水印文本长度
* 1、中文长度即文本长度 2、英文长度为文本长度二分之一
* @param text
* @return
*/
public 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;
}
public static InputStream buffToInputStream(BufferedImage buffer) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(buffer, "png", os);
return new ByteArrayInputStream(os.toByteArray());
}
public static int getWatermarkLength(String waterMarkContent, Graphics2D g) {
return g.getFontMetrics(g.getFont()).charsWidth(waterMarkContent.toCharArray(), 0, waterMarkContent.length());
}
}
2、实体类MultipartFileDto
package com.inspur.common.utils.file;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
* @author
* file转MultipartFile的时候会用到MockMultipartFile
* 当你导入spring-test依赖的时候 会跟某些依赖冲突(暂未找到具体是哪个冲突)
* 解决方法 重写一个类去实现MultipartFile接口
* 直接用MockMultipartFile的源码
*
*/
public class MultipartFileDto implements MultipartFile {
private final String name;
private String originalFilename;
private String contentType;
private final byte[] content;
/**
* Create a new MultipartFileDto with the given content.
* @param name the name of the file
* @param content the content of the file
*/
public MultipartFileDto(String name, byte[] content) {
this(name, "", null, content);
}
/**
* Create a new MultipartFileDto 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 MultipartFileDto(String name, InputStream contentStream) throws IOException {
this(name, "", null, FileCopyUtils.copyToByteArray(contentStream));
}
/**
* Create a new MultipartFileDto 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 MultipartFileDto(String name, String originalFilename, String contentType, byte[] content) {
this.name = name;
this.originalFilename = (originalFilename != null ? originalFilename : "");
this.contentType = contentType;
this.content = (content != null ? content : new byte[0]);
}
/**
* Create a new MultipartFileDto 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 MultipartFileDto(String name, String originalFilename, String contentType, InputStream contentStream)
throws IOException {
this(name, originalFilename, contentType, FileCopyUtils.copyToByteArray(contentStream));
}
@Override
public String getName() {
return this.name;
}
@Override
public String getOriginalFilename() {
return this.originalFilename;
}
@Override
public String getContentType() {
return this.contentType;
}
@Override
public boolean isEmpty() {
return (this.content.length == 0);
}
@Override
public long getSize() {
return this.content.length;
}
@Override
public byte[] getBytes() throws IOException {
return this.content;
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.content);
}
@Override
public void transferTo(File dest) throws IOException, IllegalStateException {
FileCopyUtils.copy(this.content, dest);
}
}
三、调用方法
1、文件上传
/**
* 文件上传(使用附件表进行管理)
*
* @param file
* @return
* @throws Exception
*/
@RequestMapping("/uploadWithManage")
public Map<String, Object> uploadWithManage(MultipartFile file,@RequestParam(required = false,defaultValue = "default") String bucket) throws Exception {
String mimeType = FileUploadUtils.getMimeType(file);
logger.debug(mimeType);
// 对文件ContenType校验
if (!FileUploadUtils.checkContenType(mimeType)) {
return AjaxResult.error("文件格式不支持");
}
/** 时间水印添加 */
// 获取当前时间
LocalDateTime date = LocalDateTime.now();
// 设置日期格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 通过format调用转换的方法
String dateWatermark = formatter.format(date);
MultipartFile watermarkFile = FileImageWatermarkUtils.markWithContent(file,dateWatermark);
String url = fileService.uploadFile(watermarkFile,bucket);
if (StringUtils.isBlank(url)) {
return AjaxResult.error("文件上传失败");
}
String oriName = FileUtils.getOriName(file);
url = AESUtil.encrypt(url);
// 存储到fastdfs文件管理表中
SysFastdfsFile sysFastdfsFile = new SysFastdfsFile();
if (StringUtils.isNotEmpty(url)) {
// 上传成功之后会生成路径信息
String userName = SecurityUtils.getUsername();
sysFastdfsFile.setCreateBy(userName);
sysFastdfsFile.setAppendixPath(url);
sysFastdfsFile.setAppendixName(oriName);
sysFastdfsFile.setImageThumb(SysFastdfsConstant.NOT_IMAGE_THUMB);
fastdfsFileService.insertSysFastdfsFile(sysFastdfsFile);
}
AjaxResult result = AjaxResult.success("文件上传成功");
result.put("url", url);
result.put("fileId", sysFastdfsFile.getId());
result.put("appendix", sysFastdfsFile);
return result;
}
2、储存到minio
@Override
public String uploadFile(MultipartFile file, String bucket) throws IOException {
this.createBucket(bucket);
String fileName = IdUtils.fastUUID() + "." + FileTypeUtils.getExtension(file);
PutObjectArgs args = PutObjectArgs.builder()
.bucket(bucket)
.object(fileName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build();
try {
ObjectWriteResponse objectWriteResponse = minioClient.putObject(args);
return objectWriteResponse.bucket() + "/" + objectWriteResponse.object();
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
logger.error("minio上传文件异常",e);
throw new RuntimeException(e.getMessage());
}
}
3、其他子方法
private void createBucket(String bucketName) {
try {
// 检查文件夹是否已经存在
boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!isExist) {
// 创建的文件夹
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
} catch (ErrorResponseException | InsufficientDataException | InternalException | InvalidKeyException | InvalidResponseException | IOException | NoSuchAlgorithmException | ServerException | XmlParserException e) {
logger.error("minio创建bucket异常",e);
throw new RuntimeException(e.getMessage());
}
}
四、效果
添加水印前 | 添加水印后 |
![]() | ![]() |
附:
主要参考:
java 加文字水印MultipartFile方式_multipartfile 添加水印-CSDN博客
本文基于此 主要优化了水印位置和水印文字样式hui