自定义复杂图片水印

我的社交能力还不如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自适应传送门

猜你喜欢

转载自blog.csdn.net/gfl1427097103/article/details/129185277
今日推荐