• Java给图片增加水印,根据图片大小自适应,右下角/斜角/平铺


    Hi,I’m Shendi
    最近写自己的文件服务器,上传图片时需要自动增加水印,在这里记录一下




    水印就是在图片上绘画,文字水印是最常见的,比如CSDN文章里图片右下角就会有文字水印

    在 Java 中,给图片添加水印一般可以分为以下几步

    1. 读取图片
    2. 获取/创建图片画板
    3. 将水印内容绘制到图片中
    4. 输出图片


    效果展示

    下面展示的是我所使用的水印效果

    测试图片是百度拿的


    原图
    测试图片

    加上水印后
    加上水印后的图片
    因我的要求不高,所以仅仅对角水印就可以了,可以根据自己需求绘制



    读取图片

    因为需要在图片上绘制,需要使用到 java.awt.image.BufferedImage 这个类

    创建此类对象的方法有多种,这里只列出使用 ImageIO 将原图读取为 BufferedImage 的方式

    // read 函数还可以传递输入流,例如直接使用FileInputStream -但需要自己关闭流
    BufferedImage img = ImageIO.read(new File("文件地址"));
    
    • 1
    • 2


    从 byte[] 读取图片

    // 图片数据
    byte[] imgData;
    ByteArrayInputStream bInput = new ByteArrayInputStream(imgData);
    BufferedImage img = ImageIO.read(bInput);
    
    • 1
    • 2
    • 3
    • 4


    获取画板

    Graphics g = img.getGraphics();
    
    • 1

    上面这种拿到的画板可以画线条圆圈文字等,但是不能旋转

    需要旋转的话使用 Graphics2D

    Graphics2D g = img.createGraphics();
    
    • 1


    绘制水印

    绘制文字的函数

    // 参数一为需要绘制的文字,参数2,3为绘制的位置
    img.drawString("文字", x, y);
    
    • 1
    • 2

    经过测试,在坐标 0,0 绘制的文字显示的位置是在左上角,但文字是往上显示的,如下图所示
    在这里插入图片描述
    所以如果想让文字在最左上角显示的话,y需要加上文字的大小

    // 设置文字大小,参数一为字体名称,二为样式,三为大小
    g.setFont(new Font("黑体", Font.PLAIN, 20));
    img.drawString("Shendi", x, 20);
    
    • 1
    • 2
    • 3

    效果如下
    在这里插入图片描述


    根据图片大小自适应水印大小

    首先需要知道如何获取图片大小,拿到BufferedImage,可以通过以下函数获取

    // width宽, height高
    int width = img.getWidth();
    int height = img.getHeight();
    
    • 1
    • 2
    • 3

    拿到宽高后,实现自适应就很简单了,按比例设置文字大小即可
    例如

    // 这里根据自己需求操作
    int fontSize = (width+height) / 40;
    g.setFont(new Font("黑体", Font.PLAIN, fontSize));
    
    • 1
    • 2
    • 3


    右下角文字水印

    上面已经实现左上角水印了,而且将图片位置拿到了,于是右下角水印就非常简单了

    因为在右下角,文字是往右显示的,所以需要得到文字显示占用的宽度,经过测试

    单个汉字占用宽度=文字大小
    单个字母数字符号占用=文字大小 / 2

    中文转bytes占3字节,字母数字只占一字节
    于是封装了以下函数

    /**
     * 获取字符串占用的宽度
     * 
    * @author Shendi QQ * @param str 字符串 * @param fontSize 文字大小 * @return 字符串占用的宽度 */
    public static int getStrWidth(String str, int fontSize) { char[] chars = str.toCharArray(); int fontSize2 = fontSize / 2; int width = 0; for (char c : chars) { int len = String.valueOf(c).getBytes().length; // 汉字为3,其余1 // 可能还有一些特殊字符占用2等等,统统计为汉字 if (len != 1) { width += fontSize; } else { width += fontSize2; } } return width; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    接下来将文字绘制到右下角就可以看到效果了

    String str = "Shendi 砷碲";
    g.setFont(new Font("黑体", Font.PLAIN, fontSize));
    g.drawString(str, width - getStrWidth(str, fontSize), height);
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    可以给 x 往左移动一点,y往上移动一点,不要在最右下角
    水印颜色一般都是透明的,设置透明度达到比较好的效果

    String str = "Shendi 砷碲";
    // 这里的 new Color四个参数为 rgba,值可以为 0-255, r红,g绿,b蓝,a透明度
    g.setColor(new Color(0, 0, 0, 50));
    g.setFont(new Font("黑体", Font.PLAIN, fontSize));
    g.drawString(str, width - getStrWidth(str, fontSize) - 30, height - 30);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    效果如下
    在这里插入图片描述

    换张图效果如下
    在这里插入图片描述

    完整代码如下

    BufferedImage img = ImageIO.read(new File("图片位置"));
    int width = img.getWidth();
    int height = img.getHeight();
    int fontSize = (width+height) / 40;
    
    String str = "Shendi 砷碲";
    
    Graphics2D g = img.createGraphics();
    g.setColor(new Color(0, 0, 0, 50));
    g.setFont(new Font("黑体", Font.PLAIN, fontSize));
    g.drawString(str, width - getStrWidth(str, fontSize) - 30, height - 30);
    g.dispose();
    
    ImageIO.write(img, "png", new File("水印图片保存地址"));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14


    斜角水印

    右下角水印只有一个,而且所在位置很容易被p图去掉

    于是我使用多个水印,简单的排列的话可以横着,竖着,但是斜着是比较好的,所以我使用了这种,也是最开始的展示效果

    绘制一定数量的水印,将图片宽高按比例分成 n 份,然后循环绘制水印

    BufferedImage img = ImageIO.read(new File("图片地址"));
    int width = img.getWidth();
    int height = img.getHeight();
    // 绘制几个水印/分成几份
    int num = 5;
    // 每一份x,y的大小
    int splitX = width / num, splitY = height / num;
    // 每一份中间的位置
    int centerX = splitX / 2, centerY = splitY / 2;
    int fontSize = (width+height) / 40;
    
    Graphics2D g = img.createGraphics();
    g.setFont(new Font("黑体", Font.PLAIN, fontSize));
    g.setColor(new Color(0, 0, 0, 30));
    // 设置旋转角度,可以为0-1
    g.rotate(0.2);
    
    for (int i = 1; i <= num; i++) {
    	int x = splitX * i - centerX - fontSize, y = splitY * i - centerY - fontSize;
    	g.drawString("砷碲测试水印", x, y);
    }
    g.dispose();
    ImageIO.write(img, "png", new File("输出位置"));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    效果如下
    在这里插入图片描述

    绘制了五个水印,其中一个因为旋转而超出图片范围了,问题不大



    平铺水印

    暴力横竖二层循环绘制就可以了,最后加个旋转

    首先需要计算横排能绘制多少个,竖排能绘制多少个

    String str = "砷碲测试水印 Shendi";
    // 文字占用宽度
    int xWidth = getStrWidth(str, fontSize);
    
    // x,y可以绘制的数量,多加一个补充空白
    int xCanNum = width / xWidth + 1;
    int yCanNum = height / fontSize + 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    按顺序绘制,所以需要有文字间隔,这里使用文字大小作为间隔

    // 间隔
    int split = fontSize;
    
    • 1
    • 2

    设置颜色,旋转,文字大小
    接下来循环绘制即可

    // i是y轴, 文字默认在y坐标上面,所以这里初始化1
    for (int i = 1; i <= yCanNum; i++) {
    	int y = fontSize * i + split * i;
    	for (int j = 0; j < xCanNum; j++) {
    		int x = xWidth * j + split * j;
    		
    		g.drawString(str, x, y);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    效果如下
    在这里插入图片描述

    这种效果不理想,加上旋转后,旋转点是左上角,于是要给 y 减去一定的数值将文字上拉
    我这里就使用 y - (文字大小+间隔大小) * j

    for (int i = 1; i <= yCanNum; i++) {
    	int y = fontSize * i + split * i;
    	for (int j = 0; j < xCanNum; j++) {
    		int x = xWidth * j + split * j;
    		
    		g.drawString(str, x, y-(fontSize+split)*j);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    对于这种水印,文字显得太大了,将文字改小,间距拉大

    int fontSize = (width+height) / 80;
    int split = fontSize*2;
    
    • 1
    • 2

    效果如下
    在这里插入图片描述
    在这里插入图片描述

    完整代码如下

    BufferedImage img = ImageIO.read(new File("图片文件"));
    int width = img.getWidth();
    int height = img.getHeight();
    
    int fontSize = (width+height) / 80;
    
    Graphics2D g = img.createGraphics();
    g.setFont(new Font("黑体", Font.PLAIN, fontSize));
    g.setColor(new Color(0, 0, 0, 30));
    g.rotate(0.2);
    
    String str = "砷碲测试水印 Shendi";
    // 间隔
    int split = fontSize*2;
    // 文字占用的宽度
    int xWidth = getStrWidth(str, fontSize);
    // x,y可以绘制的数量,多加一个补充空白
    intxCanNum = width / xWidth + 1;
    int yCanNum = height / fontSize + 1;
    for (int i = 1; i <= yCanNum; i++) {
    	int y = fontSize * i + split * i;
    	for (int j = 0; j < xCanNum; j++) {
    		int x = xWidth * j + split * j;
    		
    		g.drawString(str, x, y-(fontSize+split)*j);
    	}
    }
    g.dispose();
    ImageIO.write(img, "png", new File("水印图片保存地址"));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    图片水印

    绘制文字水印使用 g.drawString,绘制图片水印使用 g.drawImage
    例如

    BufferedImage wmImg = ImageIO.read(new File("水印图"));
    g.drawImage(wmImg, x, y, null);
    
    • 1
    • 2

    效果如下
    在这里插入图片描述

    与文字水印绘制方法一致,但是需要计算坐标和水印图片大小等



    输出图片

    使用 ImageIO.write 输出

    ImageIO.write(img, "png", new File("水印图片保存地址"));
    
    • 1

    其中第一个参数为图片,第二个参数为图片格式,第三个参数为保存地址

    需要注意的是,第二个参数只能为 ImageIO.getReaderFormatNames() 中的项
    在这里插入图片描述

    如果是图片,最好传 png,使用jpg增加透明度可能 ImageIO.write 为 false(写入失败)



    END

    一键三连嘛?

  • 相关阅读:
    Pnpm:包管理的新星,如何颠覆 Npm 和 Yarn
    c++知识点
    ArcGIS加载免费在线历史影像作为底图(不需要插件)
    LYVE1抗体丨Relia Tech LYVE1抗体解决方案
    Nginx概念
    SPark学习笔记:13 Spark Streaming 的Transform算子和Action算子
    ZIP压缩文件的打开密码和自动加密有什么不同?
    纳百川冲刺创业板上市:计划募资约8亿元,宁德时代为主要合作方
    【正点原子STM32连载】 第四十三章 SPI实验 摘自【正点原子】APM32F407最小系统板使用指南
    [PAT练级笔记] 35 Basic Level 1035 插入与归并
  • 原文地址:https://blog.csdn.net/qq_41806966/article/details/128088472