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打包称为可执行文件,就可以分享给你的小伙伴们使用啦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/342977.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenCV的“画笔”功能

类似于画图软件的自由笔刷功能&#xff0c;当按住鼠标左键&#xff0c;在屏幕上画出连续的线条。 定义函数&#xff1a; import cv2 import numpy as np# 初始化参数 drawing False # 鼠标左键按下时为True ix, iy -1, -1 # 鼠标初始位置# 鼠标回调函数 def mouse_paint(…

冯喜运:6.7今日外汇黄金原油走势分析及日内操作策略

【黄金消息面分析】&#xff1a;美国初请失业金人数超预期&#xff0c;市场对美联储9月降息预期升温&#xff0c;全球降息潮起&#xff0c;黄金市场受支撑。北京时间本周四&#xff0c;美国劳工部公布的数据显示&#xff0c;截至6月1日当周初请失业金人数增加至22.9万人&#x…

docker-compose 最新详细安装教程

方法1.安装Compose单机版 此方法是网上大部分教程的办法&#xff0c;官方不提倡这种方法安装&#xff1a; curl -SL https://github.com/docker/compose/releases/download/v2.27.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose sudo chmod x /usr/local/…

Diffusers代码学习: IP-Adapter

从操作的角度来看&#xff0c;IP-Adapter和图生图是很相似的&#xff0c;都是有一个原始的图片&#xff0c;加上提示词&#xff0c;生成目标图片。但它们的底层实现方式是完全不一样的&#xff0c;我们通过源码解读来看一下。以下是ip adapter的实现方式 # 以下代码为程序运行…

BGP基础实验

BGP协议中的建邻&#xff0c;与宣告路由分开的 在任何一台BGP路由上&#xff0c;均可宣告本地路由表中通过任何形势获取的路由条目&#xff0c;将其共享给其他BGP邻居&#xff1b; 然后display ip rou查看 *>代表状态 *的意思是可用 >代表优 i和*>无关&#x…

【面试题-004】ArrayList 和 LinkList区别

文章目录 List和setArrayList扩容机制HashMap扩容机制HashMap初始容量&#xff08;Initial Capacity&#xff09;和负载因子&#xff08;Load Factor&#xff09;HashMap与HashTable区别 &#xff1f;HashMap底层数据结构&#xff1f;ConcurrentHashMap底层数据结构&#xff1f…

顶顶通呼叫中心中间件(mod_cti基于FreeSWITCH)-同振与顺振的用法

文章目录 前言联系我们实现步骤同振顺振 前言 什么是同振、顺振&#xff1f; 同振 &#xff1a;同振是指多个终端同时振铃顺振&#xff1a;顺振是指多个终端顺序振铃 联系我们 有意向了解呼叫中心中间件的用户&#xff0c;可以点击该链接添加工作人员的微信&#xff1a;顶顶…

Hi3519DV500 学习摘录

文章目录 一、问题1、open-vm-tools 安装2、pushd: not found3、autoreconf4、编译util-linux源码时报错 ERROR: You must have autopoint installed to 二、NFS1、服务器搭建2、u-boot常用命令3、配置4、问题 三、补缺1、make 一、问题 1、open-vm-tools 安装 open-vm-tools…

【51单片机】智能百叶窗项目

文章目录 功能演示&#xff1a;前置要求&#xff1a;主要功能&#xff1a;主要模块&#xff1a;主函数代码&#xff1a; 具体的仿真程序和代码程序已经免费放置在资源中&#xff0c;如有需要&#xff0c;可以下载进行操作。 功能演示&#xff1a; 前置要求&#xff1a; 编译软…

Visual Studio 2022创建dll并调用

需求&#xff1a; 创建A项目&#xff0c;有函数和类&#xff0c;将A项目生成DLL动态链接库 创建B项目&#xff0c;使用A项目生成的dll和lib相关文件 正常项目开发.h用于函数声明&#xff0c;.cpp用于函数实现&#xff0c;但是项目开发往往不喜欢将.cpp函数实现的代码发给别人&…

30天收入500万美金!揭秘超休闲手游《Royal Match》吸金秘诀!

据AppMagic发布的收入榜中&#xff0c;超休闲手游《Royal Match》成绩斐然&#xff0c;不仅在三消赛道排名第一&#xff0c;更是冲上了应用畅销榜第四名&#xff0c;30天内增收超500万美元&#xff01; 来源&#xff1a;AppMagic 6月畅销榜 三消解谜&#xff0c;作为全球范围内…

新品发布 | 飞凌嵌入式RK3562J核心板,智能工业时代的国产智慧引擎

飞凌嵌入式推出FET3562J-C全国产核心板&#xff0c;专为工业自动化及消费类电子设备设计&#xff0c;打造智能工业时代的国产智慧新引擎。 FET3562J-C核心板基于Rockchip RK3562J处理器开发设计&#xff0c;该处理器采用22nm先进制程工艺&#xff0c;集成了4个ARM Cortex-A53高…

如何调用地方天地图?

我们在《如何申请自己的专属天地图&#xff1f;》一文中&#xff0c;为大家分享了如果申请专属天地图&#xff0c;并在水经微图&#xff08;以下简称“微图”&#xff09;中加载的具体方法。 于是&#xff0c;就有朋友问如何调地方用天地图。 现在&#xff0c;我们就以四川地…

【重磅开源】MapleBoot权限控制使用介绍(菜单权限、按钮权限、数据权限)

基于SpringBootVue3开发的轻量级快速开发脚手架 ## &#x1f341;项目简介 一个通用的前、后端项目模板 一个快速开发管理系统的项目 一个可以生成SpringBootVue代码的项目 一个持续迭代的开源项目 一个程序员的心血合集 度过严寒&#xff0c;终有春日&#…

蓝桥杯物联网竞赛_STM32L071_19_输出方波信号(PWM)

国赛考了一个方波&#xff0c;第一次考这个&#xff0c;连个示波器都没有 CUBMX配置&#xff1a; 按上述配置刚好是32MHZ / 32 / 100 10KHZ 理论&#xff1a; 频率&#xff1a;就是一秒钟能产生多少个脉冲&#xff0c;如下图: 这算是一个脉冲&#xff0c;1KHZ说明一秒钟产生…

Facechain系列: constants.py文件解读

在根目录下还有个facechain目录&#xff0c;其中的constants.py文件中定义了代码控制的重要参数。 1.姿态控制 在应用代码进行推理&#xff08;见这里Facechain系列: 通过代码进行推理&#xff09;中&#xff0c;如果将以下代码 use_pose_model False 修改为 use_pose_mo…

hot100_62不同路径

不同路径 题目思路、代码1.排列组合2.动态规划 题目 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff0…

ubuntu-server(22.04)安装

准备工作 首先我们先从网上获取ubuntu的iso镜像文件 Index of /ubuntu-releases/22.04/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 我们安装这个最小包即可 找到我们ubuntu安装完成后所需要下载安装源的网址&#xff08;常用是阿里云&#xff09; ubuntu安装…

TPM仿真环境搭建

文章目录 背景及注意事项一、CMake二、m4三、GNU MP Library四、TPM_Emulator五、TSS协议栈&#xff08;trousers-0.3.14.tar.gz&#xff09;六、 tpm-tools七、查看是否安装成功八、测试 TPM环境&#xff08;需要开三个终端分别运行&#xff09;8.1 启动TPM &#xff08;第一个…

SpringBootWeb 篇-深入了解 AOP 面向切面编程与 AOP 记录操作日志案例

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 AOP 概述 1.1 构造简单 AOP 类 2.0 AOP 核心概念 2.1 AOP 执行流程 3.0 AOP 通知类型 4.0 AOP 通知顺序 4.1 默认按照切面类的类名字母排序 4.2 用 Order(数字) 注…