通过 JAVA 实现 Windows 屏幕水印

不是图片加水印,是 Windows 电脑屏幕上加水印,防拍照。图片加水印网上很多实用文章。Windows 电脑屏幕上加水印更多是一些企业信息安全工具的广告,或者弹出图片加水印的文章,或者使用非JAVA语言实现的屏幕加水印文章

效果

  1. 支持多屏幕水印添加
  2. 水印运行在所有程序顶层,不会被遮住
  3. 运行窗口隐藏
  4. 水印上的鼠标事件会透传到水印下的程序
  5. 水印透明的可调整
  6. 可关闭水印

效果图:可参考 Windows 未激活或预览版的水印
20240222172513149.png

原理

通过 JAVA 的 JFrame 在 windows 屏幕增加一个不影响点击透传的顶层透明遮罩层,配合 Graphics 在遮罩层上添加文字水印,但加上的水印会与鼠标产生交互,无法禁用或透传给水印下的其它 Windows 程序,所以再通过 jna 包调用 Windows 相关 api 对窗口做了处理,使窗口内的文字水印不与鼠标交互。

源码

环境:JAVA JDK1.8

[pom.xml]{.label .info}

<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna</artifactId>
    <version>5.5.0</version>
</dependency>
<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna-platform</artifactId>
    <version>5.5.0</version>
</dependency>

[FullScreenWatermark.java]{.label .primary}

import com.sun.jna.Native;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinUser;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;

public class FullScreenWatermark extends JFrame {
    private static final int WHITE_COLOR_KEY = 0xFFFFFF; // White color RGB value

    private Rectangle screenBounds;

    public FullScreenWatermark(GraphicsDevice screen) {
        setUndecorated(true); // 无边框
        setAlwaysOnTop(true); // 始终在最前面
        setType(Window.Type.UTILITY); // 不在任务栏展示窗口

        // 获取屏幕的大小
        screenBounds = screen.getDefaultConfiguration().getBounds();
        setSize(screenBounds.width, screenBounds.height); // 设置窗口宽高
        setLocation(screenBounds.x, screenBounds.y);// 将窗口定位到屏幕的左上角
        setBackground(new Color(0, 0, 0, 0)); // 白色透明背景,最后一个参数是透明度
        // 添加窗口事件
        addComponentListener(new ComponentAdapter() {
            // 窗口展示后触发事件
            @Override
            public void componentShown(ComponentEvent e) {
                // 为窗口透传鼠标交互事件,用的是JNI调用 Windows 的API
                makeClickThrough(FullScreenWatermark.this);
            }
        });
        // 设置窗口可见
        setVisible(true);
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2d = (Graphics2D) g; // 用于水印文字倾斜
        // 水印颜色与透明度
        g2d.setColor(new Color(1f, 1f, 1f, 0.5f));
        Font watermarkFont = new Font("SimSun", Font.PLAIN, 36);// 字体,注意部分字体不支持中文
        g2d.setFont(watermarkFont);// 设置字体
        // 计算水印文本的位置长宽度
        int frontWidth = g.getFontMetrics().stringWidth("昔日长廊");
        int frontHeight = g.getFontMetrics().getHeight();
        int x = (screenBounds.width - frontWidth) / 2;
        int y = (screenBounds.height - frontHeight) / 2;
        // 保存当前的变换矩阵,这个是倾斜的核心
        g2d.translate(0, y);
        g2d.shear(0, -0.5); // 沿着Y轴进行倾斜变化
        // 这块就是循环画水印了
        boolean flag = true;
        for (int iy=screenBounds.width;iy>0;iy-=100){
            flag = !flag;
            for (int ix=0;ix<screenBounds.width;ix+=200){
                int twox = ix+(flag?80:0);
                // 画单个水印函数
                g2d.drawString("昔日长廊", twox, twox/2+y-iy);
            }
        }

    }

    public static void makeClickThrough(Window window) {
        WinDef.HWND hwnd = new WinDef.HWND(Native.getWindowPointer(window));
        User32.INSTANCE.SetWindowLong(hwnd, WinUser.GWL_EXSTYLE,
                User32.INSTANCE.GetWindowLong(hwnd, WinUser.GWL_EXSTYLE) | WinUser.WS_EX_LAYERED | WinUser.WS_EX_TRANSPARENT);
        // 这段 WinUser.LWA_COLORKEY 代表指定颜色透明、WHITE_COLOR_KEY 就是前面定义的白色、 0 就是透明度,表示完全透明
        User32.INSTANCE.SetLayeredWindowAttributes(hwnd, WHITE_COLOR_KEY, (byte) 0, WinUser.LWA_COLORKEY);
    }

    public static void main(String[] args) {
        // 1、获取所有屏幕设备
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] screens = ge.getScreenDevices();

        ArrayList<FullScreenWatermark> fullScreenWatermarks = new ArrayList<>();
        // 遍历每个屏幕设备并创建全屏水印窗口
        for (GraphicsDevice screen : screens) {
            fullScreenWatermarks.add(new FullScreenWatermark(screen));
        }
        try {
            // 过渡10秒
            Thread.sleep(10000);
        } catch (InterruptedException e) {}
        for (FullScreenWatermark fullScreenWatermark : fullScreenWatermarks) {
            // 7、关闭水印
            //fullScreenWatermark.dispose();
        }
    }
}

Windows 水印颜色设计

可通过加两层水印,一层白色水印、一层黑色的水印,为了避免覆盖,核心亮点是透明度。这样不管用户桌面软件背景是什么颜色,都能很好的展示出来

结语

欢迎交流,可以说说更好的实现方式