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.0
        g2d.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());
    }

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

台式机安装Windows 11和Ubuntu 22双系统引导问题

一、基本情况 1.1、硬件情况 电脑有2个NVMe固态硬盘&#xff0c;1个SATA固态硬盘&#xff0c;1个机械硬盘。其中一个NVMe固态硬盘是Windows系统盘&#xff0c;另一个NVMe固态为Windows软件和文件盘&#xff0c;SATA固态硬盘为Ubuntu专用&#xff0c;机械硬盘为数据备份盘。 …

Find My电动螺丝刀|苹果Find My技术与螺丝刀结合,智能防丢,全球定位

电动螺丝刀&#xff0c;别名电批、电动起子&#xff0c;是用于拧紧和旋松螺钉用的电动工具。它不仅提高了工作效率&#xff0c;还大大减轻了工作者的体力负担。在装配线等生产环境中&#xff0c;电动螺丝刀已经成为了不可或缺的工具。电动螺丝刀的批头还具备接地防静电功能&…

Leetcode:四数之和

题目链接&#xff1a;18. 四数之和 - 力扣&#xff08;LeetCode&#xff09; 普通版本&#xff08;排序 双指针&#xff09; 主旨&#xff1a;类似于三数之和的解法&#xff0c;但需要多加一些限制&#xff0c;同时为了防止多个数组元素的相加之和出现整型溢出问题还要将整型…

IDEA 2022

介绍 【尚硅谷IDEA安装idea实战教程&#xff08;百万播放&#xff0c;新版来袭&#xff09;】 jetbrains 中文官网 IDEA 官网 IDEA 从 IDEA 2022.1 版本开始支持 JDK 17&#xff0c;也就是说如果想要使用 JDK 17&#xff0c;那么就要下载 IDEA 2022.1 或之后的版本。 公司…

《TCP/IP网络编程》(第十三章)多种I/O函数(2)

使用readv和writev函数可以提高数据通信的效率&#xff0c;它们的功能可以概括为**“对数据进行整合传输及发送”**。 即使用writev函数可以将分散在多个缓冲中的数据一并发送&#xff0c;使用readv函数可以由多个缓冲分别接受&#xff0c;所以适当使用他们可以减少I/O函数的调…

Refused to load the stylesheet问题解决方案

今天项目部署的过程中遇到一个安全策略问题的报错&#xff0c;大概意思就是处于安全考虑&#xff0c;不允许src外链其他不安全的静态文件 解决这种问题的一个思路大概就是找到index.html文件先看下是否存在 <meta http-equiv"Content-Security-Policy" content&…

用PlayCanvas打造一个令人惊叹的3D图在线展示

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 PlayCanvas实例化渲染&#xff1a;大规模渲染优化 应用场景 在游戏开发中&#xff0c;经常需要渲染大量相同或相似模型。传统方法需要为每个模型创建单独的渲染对象&#xff0c;这会消耗大量内存和GPU资源。实…

问你为什么选择Kafka,你会怎么回答?

可靠的含义在百度百科的解释是&#xff1a;可以信赖、可以相信、可靠的朋友。那Kafka究竟是不是一个可靠的朋友呢&#xff1f;既然全世界绝大部分高可用系统都有Kafka的支持&#xff0c;Kafka必定有其过人之处&#xff0c;跟着我来分析分析。 另外多提一嘴Kafka在GitHub目前已…

【AIGC X UML 落地】通过多智能体实现自然语言绘制UML图

前天写了篇博文讲到用PlantUML来绘制C类图和流程图。后台有读者留言&#xff0c;问这步能否自动化生成&#xff0c;不想学习 PlantUML 语法。 我想了下&#xff0c;发现这事可行&#xff0c;确实可以做到通过自然语言的描述就能实现 UML图的绘制&#xff0c;昨天晚上加了个班到…

安装TPMmanager

sudo apt-get install qt4-qmake sudo apt-get install libqt4-dev下载TPMManager&#xff0c;解压之后拖入Ubuntu&#xff0c;进入目录 https://gitcode.com/Rohde-Schwarz/TPMManager/overview?utm_sourcecsdn_github_accelerator&isLogin1 cd tpmmanager-master qmake…

快速排序(Quick Sort)(C语言) 超详细解析!!!

生活的本质是什么呢? 无非就是你要什么就不给你什么. 而生活的智慧是什么呢? 是给你什么就用好什么. ---马斯克 索引 一. 前言二. 快速排序的概念三. 快速排序的实现1. hoare2. 挖坑法3. 前后指针法 总结 正文开始 一. 前言 接上文, 前面我们了解了插入排序, 与优化版本希尔…

Vulnhub-DC-2

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

【UML用户指南】-09-对基本结构建模-类图

目录 1、概述 2、引入 3、过程 4、常用建模技术 4.1、对简单协作建模 4.2、对逻辑数据库模式建模 4.3、正向工程 1、概述 类图是面向对象系统建模中最常见的图。 类图显示一组类、接口、协作以及它们之间的关系 类图用于对系统静态设计视图建模。其大多数涉及到对系统的…

这个世界,对于心态好的人,就是个大游乐场,越刺激越好玩。对于胆小鬼,那就是地狱,随时随地都会受伤

心态决定你的世界&#xff1a;游乐场还是地狱 在这个充满变数的世界里&#xff0c;我们的心态决定了我们看待世界的方式。对于心态积极的人来说&#xff0c;世界就像一个巨大的游乐场&#xff0c;每一个挑战都是一个新的游戏&#xff0c;每一个刺激都是乐趣的一部分。而对于那…

纷享销客安全体系: 组织及人员安全

组织及人员安全是纷享销客安全战略中的重要组成部分。 我们致力于确保组织内部和员工的安全&#xff0c;并采取一系列措施来预防和应对安全威胁。我们将持续改进和更新安全措施&#xff0c;以适应不断变化的威胁环境&#xff0c;并确保组织和员工的安全意识和培训得到充分关注…

鹧鸪云设计系统:太阳能光伏发电设计图纸绘制全攻略

随着全球对可持续能源的需求不断增加&#xff0c;越来越多的人开始关注太阳能发电技术。对于初学者来说&#xff0c;掌握一套有效的太阳能光伏发电设计图纸至关重要。本为了实现这一目标&#xff0c;鹧鸪云设计系统的出现为初学者的电站图纸绘制降低了难度。 接下来&#xff0c…

微信小程序uniapp的父子之间的通信传递

1.父传递给子信息 my-test是子组件 demo是父组件 这是定义在父组件中的的info信息 要将这个传递给子组件 子组件在properties 中接收父组件传递来的数据 msg type 是类型 value是默认值&#xff0c;当父组件没有传递数据时&#xff0c;就会默认使用value的数据 子组件…

STM32 音乐播放器之音频入门实验(pwm、dac、.wav、.mp3)

1.pwm实现简易电子琴实验 1.改变PWM频率&#xff0c;输出不同音调 2.改变占空比&#xff0c;调节音量大小 3.按键弹奏&#xff0c;支持按按键录取弹奏音 4.播放:中高低音&#xff1b;录取音&#xff1b;指定歌曲 5.支持按上一首&#xff0c;下一首&#xff0c;调弹奏速度&#…

To C道路越走越夯实,1688彻底变身了?

在偌大的电商市场&#xff0c;消费者都是专业的“掘宝者”&#xff0c;热衷于发现各种新奇商品和采购新通路。 拼多多、1688等平台也正是在这种情况下&#xff0c;成为消费市场的“宠儿”。其中&#xff0c;1688的发展路径较为独特&#xff0c;据天眼查&#xff0c;其为源头厂…

springboot大学生就业管理系统-计算机毕业设计源码89344

摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。针对大学生就业管理系统等问题&#xff0c;对大…