当前位置: 首页 > news >正文

Java核心: 为图片生成水印

今天干了一件特别不务正业的事,做了一个小程序用来给图片添加水印。事情的起因是需要将自己的身份证照片分享给别人,手边并没有一个趁手的工具来生成图片水印。很多APP提供了水印的功能,但会把我的图片上传到他们的服务器,身份证太敏感了,显然我并不想让别人有机会保留照片。

我把图片处理做了一个抽象,入参是BufferedImage,对图片添加水印、盲印、隐式写入后返回新的BufferedImage作为结果。

package org.keyniu.watermark.image;import java.awt.image.BufferedImage;public interface ImageProcess {/*** @param org* @return*/public BufferedImage process(BufferedImage org) throws Exception;}

1. 基本实现

我们先给出一版基本的实现

package org.keyniu.watermark.image;...
/*** 基于JDK的Graphics2D实现*/
public class Graphics2DWatermark implements ImageProcess {...     public BufferedImage process(BufferedImage org) throws UnsupportedEncodingException, NoSuchAlgorithmException {BufferedImage marked = new BufferedImage(org.getWidth(), org.getHeight(), BufferedImage.TYPE_INT_RGB);Graphics2D g2d = marked.createGraphics();g2d.drawImage(org, 0, 0, null); // 创建结果图片,并绘制原图// 设置字体,计算每个水印文字的块大小FontRenderContext context = g2d.getFontRenderContext();Font font = new Font(fontName, Font.BOLD, fontSize);g2d.setFont(font);TextMetadata textMeta = getTextMetadata(font, context, text);// 设置水印透明度,默认选择45°g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); // 设置透明度,0.0~1.0g2d.rotate(Math.PI * rotateArch / 180, org.getWidth() / 2, org.getHeight() / 2);// 计算图片中每行能放几个水印,要放多少行ImageMetadata imageMeta = new ImageMetadata(org.getWidth(), org.getHeight());int columnCount = imageMeta.getColumnCount(textMeta.getWidth());int rowCount = imageMeta.getRowCount(textMeta.getHeight() + textMeta.getCrcHeight());AffineTransform transform = g2d.getTransform();for (int rIdx = 0; rIdx < rowCount; rIdx++) {for (int cIdx = 0; cIdx < columnCount; cIdx++) {g2d.setTransform(transform);randomRotate(g2d, imageMeta);randomTransform(g2d);watermark(g2d, imageMeta, textMeta, rIdx, cIdx);}}g2d.setTransform(transform);// 结束绘制,释放资源g2d.dispose();return marked;}private void watermark(Graphics2D g2d, ImageMetadata imageMeta, TextMetadata textMeta, int rIdx, int cIdx) {Point offset = imageMeta.getOffset();Point textLoc = textMeta.textLocation(rIdx, cIdx);Point crcLoc = textMeta.crcLocation(rIdx, cIdx);randomGradient(g2d, offset.x + textLoc.x, offset.y + textLoc.y, textMeta.totalTextWidth(), textMeta.totalTextHeight());g2d.drawString(textMeta.getText(), offset.x + textLoc.x, offset.y + textLoc.y);randomGradient(g2d, offset.x + crcLoc.x, offset.y + crcLoc.y, textMeta.totalCrcWidth(), textMeta.totalCrcHeight());g2d.drawString(textMeta.getCrc(), offset.x + crcLoc.x, offset.y + crcLoc.y);}protected void randomRotate(Graphics2D g2d, ImageMetadata imageMeta) { // 供子类覆盖,自定义旋转的逻辑}protected void randomTransform(Graphics2D g2d) { // 供子类覆盖,自定义AffineTransform的逻辑}protected void randomGradient(Graphics2D g2d, int x, int y, int dx, int dy) { // 供子类覆盖,实现渐变色的逻辑}... 
}

本地main方法测试,测试代码是这样的的。

public static void main(String[] args) throws Exception {Graphics2DWatermark watermark = new Graphics2DWatermark("仅用于车险办理");BufferedImage image = ImageIO.read(new File("D:\\blog\\linux.png"));BufferedImage certified = watermark.process(image);ImageIO.write(certified, "jpg", new File("D:\\blog\\linux_mark.png"));
}

左边是原始图片,右边是加了水印后的图片

2. 旋转变换

太有规律的水印很容易就被擦除水印,上面的实现中我们预留了3个接口,用来扩展实现,分别是:

  1. randomRotate,输出一行水印之前,有机会做旋转
  2. randomTransform,输出一行水印前,有机会执行AffineTransfrom
  3. randomGradient,输出水印文字和CRC之前,有机会设置渐变色

我们提供了一个增强实现

public class EnhancedGraphics2DWatermark extends Graphics2DWatermark {public EnhancedGraphics2DWatermark(String text) {super(text);}protected void randomRotate(Graphics2D g2d, ImageMetadata imageMeta) {g2d.rotate(Math.PI * (Math.random() * 45 - 45) / 180, imageMeta.getSourceX(), imageMeta.getSourceY());}@Overrideprotected void randomTransform(Graphics2D g2d) {if (Math.random() < 0.5) {g2d.shear(Math.random() * 0.2, 0);} else {g2d.shear(0, Math.random() * 0.2);}}protected void randomGradient(Graphics2D g2d, int fx, int fy, int tx, int ty) {Color from = generateColor();Color to = reverse(from);GradientPaint gp = new GradientPaint(fx, fy, from, tx, ty, to);g2d.setPaint(gp);}private Color generateColor() {int r = (int) (256 * Math.random() + fontColor.getRed()) & 0xFF;int g = (int) (256 * Math.random() + fontColor.getGreen()) & 0xFF;int b = (int) (256 * Math.random() + fontColor.getBlue()) & 0xFF;return new Color(r, g, b);}private Color reverse(Color c) {return new Color((256 - c.getRed()) & 0xFF, (256 - c.getGreen()) & 0xFF, (256 - c.getBlue()) & 0XFF, c.getAlpha());}}

修改测试的main方法,改用这个实现

public static void main(String[] args) throws Exception {Graphics2DWatermark watermark = new EnhancedGraphics2DWatermark("仅用于车险办理");BufferedImage image = ImageIO.read(new File("D:\\blog\\linux.png"));BufferedImage certified = watermark.process(image);ImageIO.write(certified, "jpg", new File("D:\\blog\\linux_mark.png"));
}

这是新的水印效果

3. 提供GUI访问

直接通过代码来调用对非程序来说太有友好了,所以我在上一篇的基础上做了一点点改成,做了一个GUI入口,通过菜单设置水印的文案

然后再使用JFileChooser打开一个图片文件,最终展示水印后的图片。

完整的项目代码见附件,如果使用GraalVM打包称为可执行文件,就可以分享给你的小伙伴们使用啦。

相关文章:

Java核心: 为图片生成水印

今天干了一件特别不务正业的事&#xff0c;做了一个小程序用来给图片添加水印。事情的起因是需要将自己的身份证照片分享给别人&#xff0c;手边并没有一个趁手的工具来生成图片水印。很多APP提供了水印的功能&#xff0c;但会把我的图片上传到他们的服务器&#xff0c;身份证太…...

Spark MLlib 机器学习详解

目录 &#x1f349;引言 &#x1f349;Spark MLlib 简介 &#x1f348; 主要特点 &#x1f348;常见应用场景 &#x1f349;安装与配置 &#x1f349;数据处理与准备 &#x1f348;加载数据 &#x1f348;数据预处理 &#x1f349;分类模型 &#x1f348;逻辑回归 &a…...

MySQL报ERROR 2002 (HY000)解决

今天在连接客户服务器时MySQL的时候报: ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ‘/tmp/mysql/mysql.sock’ (2) [rootXXX ~]# mysql -uroot -p Enter password: ERROR 2002 (HY000): Can’t connect to local MySQL server through socket…...

【校招】【社招】字节跳动UG营销算法工程师招聘

【校招】【社招】字节跳动UG营销算法工程师招聘 需要营销、广告、搜索、推荐等领域的人才加入 岗位简介 字节跳动增长智能-激励中台团队负责公司国内字节所有主要App&#xff08;包含但不仅限于抖音/抖音极速版/抖音火山版/今日头条/头条极速版/番茄小说/番茄畅听/西瓜视频&…...

Go实战 | 使用Go-Fiber采用分层架构搭建一个简单的Web服务

前言 &#x1f4e2;博客主页&#xff1a;程序源⠀-CSDN博客 &#x1f4e2;欢迎点赞&#x1f44d;收藏⭐留言&#x1f4dd;如有错误敬请指正&#xff01; 一、环境准备、示例介绍 Go语言安装&#xff0c;GoLand编辑器 这个示例实现了一个简单的待办事项&#xff08;todo&#xf…...

Web自动化测试框架+PO模式分层实战(超细整理)

前言 PO模式 在UI级的自动化测试中&#xff0c;对象设计模式表示测试正在交互的web应用&#xff0c;程序用户界面中的一个区域&#xff0c;这个是减少了代码的重复&#xff0c;也就是说&#xff0c;如果用户界面发生了改变&#xff0c;只需要在一个地方修改程序就可以了。 优…...

光猫、路由器的路由模式、桥接模式、拨号上网

下面提到的路由器都是家用路由器 一、联网条件 1.每台电脑、路由器、光猫想要上网&#xff0c;都必须有ip地址。 2.电脑获取ip 可以设置静态ip 或 向DHCP服务器(集成在路由器上) 请求ip 电话线上网时期&#xff0c;猫只负责模拟信号和数字信号的转换&#xff0c;电脑需要使…...

iOS--工厂设计模式

iOS--工厂设计模式 设计模式的概念和意义类族模式UIButton作为类族模式的例子总结 三种工厂设计模式简单工厂模式&#xff08;Simple Factory Pattern&#xff09;&#xff1a;代码实例 工厂方法模式&#xff08;Factory Method Pattern&#xff09;&#xff1a;代码实例 抽象工…...

[Python]用Qt6和Pillow实现截图小工具

本文章主要讲述的内容是&#xff0c;使用python语言借助PyQt6和Pillow库进行简单截图工具的开发&#xff0c;含义一个简单的范围裁剪和软件界面。 主要解决的问题是&#xff0c;在高DPI显示屏下&#xff0c;坐标点的偏差导致QWidget显示图片不全、剪裁范围偏差问题。 适合有一点…...

Podman和Docker的区别

Podman 和 Docker 都是用于容器化的工具&#xff0c;但它们在架构、安全性、容器编排以及一些设计理念上有显著的区别&#xff1a; 架构设计: Docker 使用客户端-服务器&#xff08;C/S&#xff09;架构&#xff0c;包含一个名为 dockerd 的守护进程&#xff0c;该进程以 root …...

Go微服务: 分布式Cap定理和Base理论

分布式中的Cap定理 CAP理论 C: 一致性&#xff0c;是站在分布式的角度&#xff0c;要么读取到数据&#xff0c;要么读取失败&#xff0c;比如数据库主从&#xff0c;同步时的时候加锁&#xff0c;同步完成才能读到同步的数据&#xff0c;同步完成&#xff0c;才返回数据给程序&…...

Mysql学习(四)——SQL通用语法之DQL

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 DQLDQL-语法基本查询条件查询聚合函数分组查询排序查询分页查询 DQL DQL数据查询语言&#xff0c;用来查询数据库中表的记录。 DQL-语法 select 字段列表 from 表…...

【ARFoundation自学05】人脸追踪(AR Face manager)实现

1. 修改摄像机朝向渲染方式-选中user 这个方式就会调用前置摄像头 2 创建 AR Session、XR Origin&#xff0c;然后在XR Origin上面添加组件 注意&#xff1a;XR Origin 老版本仍然叫 AR Session Origin 接下来在XR Origin上面添加AR Face Manager组件&#xff0c;如下图&am…...

Vulnhub-DC-2

靶机IP:192.168.20.135 网络有问题的可以看下搭建Vulnhub靶机网络问题(获取不到IP) kaliIP:192.168.20.128 扫描靶机端口及服务版本 发现开放了80和7744端口 并且是wordpress建站 dirsearch扫描目录 访问前端界面&#xff0c;发现存在重定向 在hosts文件中增加192.168.2…...

VNC server ubuntu20 配置

介绍 最近想使用实验室的4卡服务器跑一些深度学习实验&#xff0c;因为跑的是三维建图实验&#xff0c;需要配上可视化界面&#xff0c;本来自带的IPMI可以可视化&#xff0c;但分辨率固定在640*480&#xff0c;看起来很别扭&#xff0c;就捣鼓服务器远程可视化访问了两天&…...

c++--priority_queue和仿函数

目录 1.priority_queue 实现&#xff1a; 2.仿函数 priority_queue仿函数 实现代码 1.priority_queue 优先队列是一种容器适配器&#xff0c;根据严格的弱排序标准&#xff0c;它的第一个元素总是它所包含的元素中最大的&#xff0c;其实就是个堆&#xff0c;默认是大根堆。…...

Harmony os Next——关系型数据库relationalStore.RdbStore的使用

Harmony os Next——关系型数据库relationalStore.RdbStore的使用 描述数据库的使用建表定义表信息创建数据库表 创建数据库操作对象增更新查询删数据库的初始化 描述 本文通过存储一个简单的用户信息到数据库中为例&#xff0c;进行阐述relationalStore.RdbStore数据库的CRUD…...

快手直播限流怎么办?

直播限流怎么办&#xff1f;这期把直播间限流的所有原因都讲得明明白白&#xff0c;如果你直播间昨天还播的好好的&#xff0c;今天突然间贴地飞行&#xff0c;按照这个思路框架去排查&#xff0c;准没问题。 第一件事情肯定是排查一下评分问题&#xff0c; 信用分、口碑分、…...

【MySQL】数据库入门基础

文章目录 一、数据库的概念1. 什么是数据库2. 主流数据库3. mysql和mysqld的区别 二、MySQL基本使用1. 安装MySQL服务器在 CentOS 上安装 MySQL 服务器在 Ubuntu 上安装 MySQL 服务器验证安装 2. 服务器管理启动服务器查看服务器连接服务器停止服务器重启服务器 3. 服务器&…...

cannot allocate memory in static TLS block

如果不是内存太小&#xff0c;那是不是因为glibc太旧呢&#xff1f; 考虑 glibc 2.22 以后的版本。 glibc-2.22 中加入了如下commit&#xff1a;f8aeae347377f3dfa8cbadde057adf1827fb1d44 https://sourceware.org/git/?pglibc.git;acommit;hf8aeae347377f3dfa8cbadde057adf1…...

uniapp 对接腾讯云IM群组成员管理(增删改查)

UniApp 实战&#xff1a;腾讯云IM群组成员管理&#xff08;增删改查&#xff09; 一、前言 在社交类App开发中&#xff0c;群组成员管理是核心功能之一。本文将基于UniApp框架&#xff0c;结合腾讯云IM SDK&#xff0c;详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

Web后端基础(基础知识)

BS架构&#xff1a;Browser/Server&#xff0c;浏览器/服务器架构模式。客户端只需要浏览器&#xff0c;应用程序的逻辑和数据都存储在服务端。 优点&#xff1a;维护方便缺点&#xff1a;体验一般 CS架构&#xff1a;Client/Server&#xff0c;客户端/服务器架构模式。需要单独…...

淘宝扭蛋机小程序系统开发:打造互动性强的购物平台

淘宝扭蛋机小程序系统的开发&#xff0c;旨在打造一个互动性强的购物平台&#xff0c;让用户在购物的同时&#xff0c;能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机&#xff0c;实现旋转、抽拉等动作&#xff0c;增…...

Leetcode33( 搜索旋转排序数组)

题目表述 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...

GraphQL 实战篇:Apollo Client 配置与缓存

GraphQL 实战篇&#xff1a;Apollo Client 配置与缓存 上一篇&#xff1a;GraphQL 入门篇&#xff1a;基础查询语法 依旧和上一篇的笔记一样&#xff0c;主实操&#xff0c;没啥过多的细节讲解&#xff0c;代码具体在&#xff1a; https://github.com/GoldenaArcher/graphql…...

sshd代码修改banner

sshd服务连接之后会收到字符串&#xff1a; SSH-2.0-OpenSSH_9.5 容易被hacker识别此服务为sshd服务。 是否可以通过修改此banner达到让人无法识别此服务的目的呢&#xff1f; 不能。因为这是写的SSH的协议中的。 也就是协议规定了banner必须这么写。 SSH- 开头&#xff0c…...