我的社交能力还不如5岁儿童和狗。
文章目录
- 前言
- 一、主要工具类
- 总结
前言
之前写过一些简单的图片压缩和图片加水印:
JAVA实现图片质量压缩和加水印
本次主要是针对图片加水印进行一个升级,图片水印自定义,自适应大小。
来,先看几张不同分辨率的图片加过水印的效果
分辨率:1702 x 1276
分辨率:4000 x 3000
分辨率:1080 x 1440
其实大家看右下角,csdn自动给我图片加的水印就能发现,4000*3000的图片几乎都看不到右下角的水印,说明他这个水印不是自适应的。但是我左下角加的,三张图片都是这么大,根据图片自动变化大小。
提示:以下是本篇文章正文内容,下面案例可供参考
一、主要工具类
其中主要是进行了坐标点的计算,通过拼接计算来确定各个水印的坐标点,从而进行绘制。
其中进行了一部分封装
/**
* 添加图片文字水印(此方法不在考虑文字过长需要换行等情况)
*
* @param targetImg 原图片路径,需要是本地路径,如是网络url需要单独更改
* @param x 水印x轴坐标
* @param y 水印y轴坐标
* @param fontSize 水印文字大小 英镑,会根据图片分辨率自动放大和缩小,只需要指定标准350像素的文字标准值即可
* @param c 水印文字颜色 例如:Color.WHITE
* @param fontName 水印文字字体 例如:宋体,微软雅黑
* @param fontStyle 水印文字字体风格,例如:Font.PLAIN正常字体/BOLD加粗/ITALIC斜体.
* @param alpha 水印文字透明度,例如: 1.0F,范围 0-1F
* @param text 水印文字
*/
public static void pressText(String targetImg, int x, int y, int fontSize, Color c, String fontName
, int fontStyle, float alpha, String text) {
try {
// 读取原图片
BufferedImage src = ImageIO.read(new File(targetImg));
// 获取原图的宽和高
int width = src.getWidth(null);
int height = src.getHeight(null);
// 动态设置字体大小
fontSize = width < height ? width / 350 * fontSize : height / 350 * fontSize;
// 创建空图片,指定宽高
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 准备绘制图片
Graphics2D g = image.createGraphics();
// 将原图绘制到此空图片上,左上角为坐标起点,(0,0),宽和高为原图大小
g.drawImage(src, 0, 0, width, height, null);
// 设置水印字体颜色.
g.setColor(c);
// 设置水印字体,Font.PLAIN正常字体/BOLD加粗/ITALIC斜体.
g.setFont(new Font(fontName, fontStyle, fontSize));
// 透明度
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
/* 消除java.awt.Font字体的锯齿 */
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
// 添加文字水印
g.drawString(text, x, y);
// 输出图像
g.dispose();
File f = new File(targetImg);
// 将加过水印的图片保存到指定文件
ImageIO.write(image, targetImg.substring(targetImg.lastIndexOf(".") + 1), f);
} catch (Exception e) {
log.error("水印添加失败:{}", e.getMessage());
}
}
中间还有一部分的坐标点计算,感觉写的有点乱了,没有梳理好,仅供参考。
下面是整个工具类的代码:
package com.example.demo.util;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.Week;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.util.ZipUtil;
import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Date;
/**
* @author GMaya
* @create 2023/2/22 9:44
* @Description 类描述: 图片相关的工具类
*/
@Slf4j
public class ImageUtils {
/**
* 图片加水印
*
* @param json 相关设置
* @param targetImg 要加水印的图片路径
* @param staticUrl 静态地址
*/
public static void pressTexts(JSONObject json, String targetImg, String staticUrl) {
try {
long l = System.currentTimeMillis();
// 读取原图片,获取宽度和高度 TODO 使用BufferedImage中没有exif信息,导致有exif的图片宽和高会读取错误,需要进行修正。目前本地图片修正暂时没有处理。线上图片来源为阿里oss,在链接后拼接自动修正参数即可:?x-oss-process=image/auto-orient,1
// String uuu = "https://xxx-dev.oss-cn-shanghai.aliyuncs.com/upload/123123/aa/20230223/b811b496cff29f02f0fa5a994c66867e.jpg";
// String uuu = "https://xxx-dev.oss-cn-shanghai.aliyuncs.com/upload/123123/aa/20230223/b811b496cff29f02f0fa5a994c66867e.jpg?x-oss-process=image/auto-orient,1";
// URL url = new URL(uuu);
// BufferedImage src = ImgUtil.read(url);
BufferedImage src = ImgUtil.read(targetImg);
int width = src.getWidth(null);
int height = src.getHeight(null);
log.info("原图宽:{},高:{}",width,height);
// 动态设置字体大小 , 以350像素9的基数大小设置
int fontSize = width < height ? width / 350 * 9 : height / 350 * 9;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
// 将原图绘制
g.drawImage(src, 0, 0, width, height, null);
// 设置水印字体颜色.
g.setColor(Color.WHITE);
// 设置水印字体,Font.PLAIN正常字体/BOLD加粗/ITALIC斜体.
g.setFont(new Font("微软雅黑", Font.PLAIN, fontSize));
// 透明度
// g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.9f));
// 消除java.awt.Font字体的锯齿
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
// 比例
int rat = width < height ? width / 350 : height / 350;
BufferedImage readAddress = ImgUtil.read(staticUrl + "address.png");
BufferedImage readUserName = ImgUtil.read(staticUrl + "userName.png");
BufferedImage readCompanyName = ImgUtil.read(staticUrl + "companyName.png");
BufferedImage readFloor = ImgUtil.read(staticUrl + "floor.png");
int imageWidth = rat * 6;
int imageHeight = rat * 7;
// 计算各个坐标点
int xx1 = 0; // 姓名图标
int yy1 = 0; // 姓名图标
int xx2 = 0; // 姓名
int yy2 = 0; // 姓名
int xx3 = 0; // 公司图标
int yy3 = 0; // 公司图标
int xx4 = 0; // 公司
int yy4 = 0; // 公司
int xx5 = 0; // 地址图标
int yy5 = 0; // 地址图标
int xx6 = 0; // 地址
int yy6 = 0; // 地址
int xx7 = 0; // 左侧竖线
int yy7 = 0; // 左侧竖线
int xx8 = 0; // 拍照标签
int yy8 = 0; // 拍照标签
int xx9 = 0; // logo图标
int yy9 = 0; // logo图标
int xx10 = 32; // 年月日坐标
int yy10 = 0; // 年月日坐标
int xx11 = 32; // 时分秒坐标
int yy11 = 0; // 时分秒坐标
int w12 = 0; // 底板宽 底板坐标点无需计算,但是底板的宽高需要计算
int h12 = 0; // 底板高
int sd = 0; // 标准差
// 公共坐标计算点 xx为图标,xxx为文字
int xx = 0;
int yy = 0;
int xxx = 0;
int yyy = 0;
// 提前计算出各个坐标点
for (int i = 0; i < 7; i++) {
if (i == 0) {
// 用户名称
String userNameIsShow = json.getStr("userNameIsShow");
if ("0".equals(userNameIsShow)) {
// x轴默认距离左边16*比例
xx1 = 10 * rat;
yy1 = height - (10 * rat) - imageHeight;
xx = xx1;
yy = yy1;
xx2 = 10 * rat + imageWidth + (4 * rat);
yy2 = height - (10 * rat);
xxx = xx2;
yyy = yy2;
}
continue;
}
if (i == 1) {
// 公司名称
String companyNameIsShow = json.getStr("companyNameIsShow");
if ("0".equals(companyNameIsShow)) {
// 如果公司展示,则需要判断上一个用户字段是否展示,如果展示和不展示,x,y轴坐标将不一样
if (xx == 0) {
xx3 = 10 * rat;
yy3 = height - (10 * rat) - imageHeight;
xx = xx3;
yy = yy3;
xx4 = 10 * rat + imageWidth + (4 * rat);
yy4 = height - (10 * rat);
xxx = xx4;
yyy = yy4;
} else {
xx3 = xx;
yy3 = yy - (5 * rat) - imageHeight;
yy = yy3;
xx4 = xxx;
yy4 = yyy - (5 * rat) - fontSize;
yyy = yy4;
}
}
continue;
}
if (i == 2) {
// 全部默认0是,1否
String addressIsShow = json.getStr("addressIsShow");
if ("0".equals(addressIsShow)) {
// 此处定义最高位置的地址即可,换行的在真正生成时进行处理
String address = json.getStr("address");
int maxLength = 13;
// 计算出地址的占用行数
int i2 = address.length() / maxLength + (address.length() % maxLength == 0 ? 0 : 1);
if (xx == 0) {
// 公司和姓名都不展示
xx5 = 10 * rat;
yy5 = height - 12 - (fontSize * i2);
xx6 = 10 * rat + imageWidth + 10;
yy6 = height - (12 * (i2 + 1)) - (fontSize * (i2 + 1));
xxx = xx6;
yyy = yy6;
continue;
} else {
xx5 = xx;
yy5 = yy - 12 - (fontSize * i2);
yy = yy5;
xx6 = xxx;
yy6 = yyy - (12 * (i2 + 1)) - (fontSize * (i2 + 1));
yyy = yy6;
}
}
}
if (i == 5) {
// 时间控制
String timeIsShow = json.getStr("timeIsShow");
if ("0".equals(timeIsShow)) {
xx10 = xx;
xx11 = xx;
if (xx == 0) {
yy10 = height - (10 * rat);
yy11 = yy10 - fontSize - 8 * rat;
}else{
yy10 = yyy - (8 * rat);
yy11 = yy10 - fontSize - 8 * rat;
}
yyy = yy11;
yy = yy11;
}
continue;
}
if (i == 6) {
// 计算地图蒙版,需要在所有坐标计算后,在计算地图大小
// 缩放比例
int i1 = width < height ? width / 350 : height / 350;
w12 = readFloor.getWidth() * i1 / 3 * 2 ;
String timeIsShow = json.getStr("timeIsShow");
if ("0".equals(timeIsShow)) {
h12 = height - yyy + (i1 * 18);
continue;
}
String addressIsShow = json.getStr("addressIsShow");
String companyNameIsShow = json.getStr("companyNameIsShow");
String userNameIsShow = json.getStr("userNameIsShow");
if("0".equals(addressIsShow) || "0".equals(companyNameIsShow) ||"0".equals(userNameIsShow)){
h12 = height - yyy - 6 + 30;
}
System.out.println(h12);
continue;
}
}
for (int i = 0; i < 6; i++) {
if (i == 1) {
// 优先绘制底板
String addressIsShow = json.getStr("addressIsShow");
String companyNameIsShow = json.getStr("companyNameIsShow");
String userNameIsShow = json.getStr("userNameIsShow");
String timeIsShow = json.getStr("timeIsShow");
if (!"0".equals(addressIsShow) && !"0".equals(companyNameIsShow) && !"0".equals(userNameIsShow) &&
!"0".equals(timeIsShow)) {
// 如果所有的都关闭,则无需添加底板和水印,直接跳出循环,结束水印绘制。
break;
}
g.drawImage(readFloor, 6 * rat, height - h12, w12, h12 - 6 * rat, null);
continue;
}
if (i == 2) {
// 全部默认0是,1否
String userNameIsShow = json.getStr("userNameIsShow");
if ("0".equals(userNameIsShow)) {
String userName = json.getStr("userName");
g.drawImage(readUserName, xx1, yy1,imageWidth,imageHeight, null);
g.drawString(userName, xx2, yy2);
}
continue;
}
if (i == 3) {
// 全部默认0是,1否
String companyNameIsShow = json.getStr("companyNameIsShow");
if ("0".equals(companyNameIsShow)) {
String companyName = json.getStr("companyName");
g.drawImage(readCompanyName, xx3, yy3,imageWidth,imageHeight, null);
g.drawString(companyName, xx4, yy4);
}
continue;
}
if (i == 4) {
// 全部默认0是,1否
String addressIsShow = json.getStr("addressIsShow");
if ("0".equals(addressIsShow)) {
String address = json.getStr("address");
int maxLength = 13;
// 计算出地址的占用行数
int i2 = address.length() / maxLength + (address.length() % maxLength == 0 ? 0 : 1);
int len = 0;
for (int j = i2 - 1; j >= 0; j--) {
int mlen = maxLength * (j + 1);
len = j * maxLength;
if (mlen < address.length()) {
g.drawString(address.substring(len, mlen), xx6, yy6 + (fontSize * (j + 1)));
} else {
g.drawString(address.substring(len), xx6, yy6 + (fontSize * (j + 1)));
}
}
// 图标
g.drawImage(readAddress, xx5, yy5,imageWidth,imageHeight, null);
}
continue;
}
}
// 输出图像
g.dispose();
File f = new File(targetImg);
ImageIO.write(image, targetImg.substring(targetImg.lastIndexOf(".") + 1), f);
// 如果年月日展示,则进行坐标计算
String timeIsShow = json.getStr("timeIsShow");
if ("0".equals(timeIsShow)) {
String s = DateUtil.formatDate(new Date());
Week week = DateUtil.dayOfWeekEnum(new Date());
// 文字水印为:2023-02-22 星期三
pressText(targetImg, xx10, yy10, 12, Color.WHITE, "微软雅黑", Font.PLAIN, 0.9F, s + " " + week.toChinese());
String time = DateUtil.format(new Date(), "HH:mm:ss");
pressText(targetImg, xx11, yy11, 18, Color.WHITE, "微软雅黑", Font.BOLD, 0.9F, time);
}
//花费毫秒数
long ll = System.currentTimeMillis();
log.info("图片加水印耗时:{}{}", ll - l, "ms");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 添加图片文字水印(此方法不在考虑文字过长需要换行等情况)
*
* @param targetImg 原图片路径,需要是本地路径,如是网络url需要单独更改
* @param x 水印x轴坐标
* @param y 水印y轴坐标
* @param fontSize 水印文字大小 英镑,会根据图片分辨率自动放大和缩小,只需要指定标准350像素的文字标准值即可
* @param c 水印文字颜色 例如:Color.WHITE
* @param fontName 水印文字字体 例如:宋体,微软雅黑
* @param fontStyle 水印文字字体风格,例如:Font.PLAIN正常字体/BOLD加粗/ITALIC斜体.
* @param alpha 水印文字透明度,例如: 1.0F,范围 0-1F
* @param text 水印文字
*/
public static void pressText(String targetImg, int x, int y, int fontSize, Color c, String fontName
, int fontStyle, float alpha, String text) {
try {
// 读取原图片
BufferedImage src = ImageIO.read(new File(targetImg));
// 获取原图的宽和高
int width = src.getWidth(null);
int height = src.getHeight(null);
// 动态设置字体大小
fontSize = width < height ? width / 350 * fontSize : height / 350 * fontSize;
// 创建空图片,指定宽高
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 准备绘制图片
Graphics2D g = image.createGraphics();
// 将原图绘制到此空图片上,左上角为坐标起点,(0,0),宽和高为原图大小
g.drawImage(src, 0, 0, width, height, null);
// 设置水印字体颜色.
g.setColor(c);
// 设置水印字体,Font.PLAIN正常字体/BOLD加粗/ITALIC斜体.
g.setFont(new Font(fontName, fontStyle, fontSize));
// 透明度
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
/* 消除java.awt.Font字体的锯齿 */
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
// 添加文字水印
g.drawString(text, x, y);
// 输出图像
g.dispose();
File f = new File(targetImg);
// 将加过水印的图片保存到指定文件
ImageIO.write(image, targetImg.substring(targetImg.lastIndexOf(".") + 1), f);
} catch (Exception e) {
log.error("水印添加失败:{}", e.getMessage());
}
}
/**
* 计算文字长度,一个中文按两个长度,英文按一个长度计算
*
* @param text 文字
* @return
*/
public static int getLength(String text) {
int length = 0;
for (int i = 0; i < text.length(); i++) {
if (new String(text.charAt(i) + "").getBytes().length > 1) {
length += 2;
} else {
length += 1;
}
}
return length / 2;
}
/**
* 导出zip压缩包
*
* @param srcPath 要压缩的文件夹路径
* @param response 输出流
*/
public static void downloadZip(String srcPath, HttpServletResponse response) {
// 将此目录打包成zip
File file = ZipUtil.zip(srcPath);
OutputStream toClient = null;
try {
// 以流的形式下载文件。
BufferedInputStream fis = new BufferedInputStream(new FileInputStream(file.getPath()));
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
fis.close();
// 清空response
response.reset();
toClient = new BufferedOutputStream(response.getOutputStream());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment;filename=" + file.getName());
toClient.write(buffer);
toClient.flush();
} catch (Exception e) {
log.error("下载zip压缩包过程发生异常:", e);
} finally {
if (toClient != null) {
try {
toClient.close();
} catch (IOException e) {
log.error("zip包下载关流失败:", e);
}
}
//删除改临时zip包(此zip包任何时候都不需要保留,因为源文件随时可以再次进行压缩生成zip包)
file.delete();
}
}
}
总结
其中,通过ImageIO.read读取的图片,是没有exif信息的,这样就会导致有exif的图片,读取后宽和高可能不准确,最终生成的图片就是未旋转的,看起来就是颠倒的。如果有谁有办法处理本地图片exif问题,可以回复下。
让我学习学习。
如果使用的是阿里云oss地址,在后面拼接参数即可:
例如:
String uuu = "https://xxx-dev.oss-cn-shanghai.aliyuncs.com/upload/123123/aa/20230223/b811b496cff29f02f0fa5a994c66867e.jpg";
String uuu = "https://xxx-dev.oss-cn-shanghai.aliyuncs.com/upload/123123/aa/20230223/b811b496cff29f02f0fa5a994c66867e.jpg?x-oss-process=image/auto-orient,1";
在oss地址后面拼接?x-oss-process=image/auto-orient,1
即可。阿里云oss自适应传送门