Java实现根据图片url下载图片并动态添加水印

 更新时间:2026年01月08日 08:42:59   作者:专注写bug  
这篇文章主要为大家详细介绍了如何使用Java实现根据图片url下载图片并动态添加水印,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下

前言

为了一些数据安全性,需要将图片打上自定义的水印。水印需要满足下列设置。

  • 支持横向或纵向。
  • 支持单挑或多条水印。
  • 支持自定义颜色配置。
  • 支持水印字体大小配置与透明度配置。

相关依赖

仅需要引入下列依赖。

<!-- 用于处理URL请求获取图片 -->
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.11.0</version>
</dependency>

完整代码

package img;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Objects;
import java.util.UUID;

/**
 * 变量控制水印
 */
public class WaterMarkExample {

    private static BufferedImage getBufferedImageByUrl(String imageUrl) throws IOException {
        // 从URL获取图片
        byte[] imageBytes = getImageBytesFromUrl(imageUrl);
        BufferedImage image = ImageIO.read(new java.io.ByteArrayInputStream(imageBytes));
        return image;
    }

    private static byte[] getImageBytesFromUrl(String imageUrl) throws IOException {
        OkHttpClient client = UnsafeOkHttpClient.getUnsafeClient();
        Request request = new Request.Builder()
                .url(imageUrl)
                .build();
        try (Response response = client.newCall(request).execute()) {
            return response.body().bytes();
        }
    }

    private static byte[] convertImageToBytes(BufferedImage image) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ImageIO.write(image, "png", bos);
        return bos.toByteArray();
    }

    /**
     * 图片追加水印
     * @param image 原始图片信息
     * @param watermarkText 水印文字
     * @param fontFilePath 字体文件
     * @param isItalic 是否字体为斜体
     * @param isSkew 水印是否倾斜
     * @param isMultiLine 水印行数是否为多行
     * @param waterMarkLineSize 水印行数
     * @param waterMarkFontSize 水印字体大小
     * @param alpha 透明度 [0,1]
     * @param waterMarkColor 水印颜色 取值来源 java.awt.Color
     * @return
     */
    private static BufferedImage addWatermark(BufferedImage image, String watermarkText, String fontFilePath,
                                              boolean isItalic, boolean isSkew, boolean isMultiLine,int waterMarkLineSize,float waterMarkFontSize,float alpha,Color waterMarkColor) {
        BufferedImage watermarkedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = watermarkedImage.createGraphics();
        g2d.drawImage(image, 0, 0, null);

        try {
            // 指定字体文件
            Font customFont = Font.createFont(Font.TRUETYPE_FONT, new File(fontFilePath));
            int fontStyle = isItalic? Font.ITALIC : Font.PLAIN;
            Font derivedFont = customFont.deriveFont(fontStyle, waterMarkFontSize);

            // 默认演示使用,linux使用下面的代码会加载失败字体
            //g2d.setFont(new Font("微软雅黑", isItalic? Font.ITALIC : Font.PLAIN, 30));

            g2d.setFont(derivedFont);
        } catch (FontFormatException | IOException e) {
            e.printStackTrace();
            g2d.setFont(new Font("Arial", isItalic? Font.ITALIC : Font.PLAIN, 30));
        }

        // 水印的颜色
        g2d.setColor(waterMarkColor);
        AlphaComposite alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
        g2d.setComposite(alphaComposite);

        // 图片的尺寸
        int stringWidth = g2d.getFontMetrics().stringWidth(watermarkText);
        int stringHeight = g2d.getFontMetrics().getHeight();

        if (isMultiLine) {
            // 多行
            if (isSkew) {
                // 旋转角度
                double rotationAngle = Math.toRadians(-45);
                int xStep = stringWidth + 50;
                int yStep = stringHeight + 50;
                for (int y = 0; y < image.getHeight(); y += yStep) {
                    for (int x = 0; x < image.getWidth(); x += xStep) {
                        AffineTransform oldTransform = g2d.getTransform();
                        g2d.rotate(rotationAngle, x + stringWidth / 2, y + stringHeight / 2);
                        g2d.drawString(watermarkText, x, y);
                        g2d.setTransform(oldTransform);
                    }
                }
            } else {
                waterMarkLineSize = Objects.isNull(waterMarkLineSize) ? 1:waterMarkLineSize;
                int x = (image.getWidth() - stringWidth) / 2;
                int y = (image.getHeight() - stringHeight) / 2;
                for (int i = 0; i < waterMarkLineSize; i++) {
                    g2d.drawString(watermarkText, x, y + i * (stringHeight + 20));
                }
            }
        } else {
            // 单行
            if (isSkew) {
                double rotationAngle = Math.toRadians(-45);
                int x = (image.getWidth() - stringWidth) / 2;
                int y = (image.getHeight() - stringHeight) / 2;
                AffineTransform oldTransform = g2d.getTransform();
                g2d.rotate(rotationAngle, x + stringWidth / 2, y + stringHeight / 2);
                g2d.drawString(watermarkText, x, y);
                g2d.setTransform(oldTransform);
            } else {
                int x = (image.getWidth() - stringWidth) / 2;
                int y = (image.getHeight() - stringHeight) / 2;
                g2d.drawString(watermarkText, x, y);
            }
        }

        g2d.dispose();
        return watermarkedImage;
    }

    public static void saveImage(byte[] imageBytes, String filePath) throws IOException {
        Path path = Path.of(filePath);
        Files.write(path, imageBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
    }
    public static void main(String[] args) throws IOException {
        String imageUrl = "https://i-blog.csdnimg.cn/direct/d102cabed76c4cd9b74b9469f42a4353.png";
        BufferedImage bufferedImageByUrl = getBufferedImageByUrl(imageUrl);

        String fontFilePath = System.getProperty("user.dir")+"/fill2word"+"/font/simfang.ttf";
        BufferedImage bufferedImage = addWatermark(bufferedImageByUrl,"香蕉集团的香蕉不拿拿爱吃香蕉香蕉集团的香蕉不拿拿爱吃香蕉香蕉集团的香蕉不拿拿爱吃香蕉",fontFilePath,
                false,false,true,3,50,0.3f,Color.blue);
        byte[] watermarkedImageBytes = convertImageToBytes(bufferedImage);

        String out_file_base_path = "doc/"+ UUID.randomUUID();
        System.out.println(out_file_base_path);
        saveImage(watermarkedImageBytes,out_file_base_path+".png");
    }
}

一些补充

1、使用okhttp下载时提示ssl证书校验失败

通常证书合法,直接使用下列代码即可进行http请求和下载。

OkHttpClient client = new OkHttpClient();

如果需要忽略ssl校验,可以自定义OkHttpClient,如下所示:

import okhttp3.OkHttpClient;
import javax.net.ssl.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

public class UnsafeOkHttpClient {
    public static OkHttpClient getUnsafeClient() {
        try {
            // 创建一个信任所有证书的 TrustManager
            final X509TrustManager trustManager = new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            };

            // 获取 SSLContext 并初始化
            SSLContext sslContext = SSLContext.getInstance("SSL");
            sslContext.init(null, new TrustManager[]{trustManager}, new java.security.SecureRandom());
            // 创建忽略证书的 SSLSocketFactory
            SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();

            // 构建 OkHttpClient
            OkHttpClient.Builder builder = new OkHttpClient.Builder();
            builder.sslSocketFactory(sslSocketFactory, trustManager);
            builder.hostnameVerifier((hostname, session) -> true); // 忽略主机名验证

            return builder.build();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

2、windows正常,但发布生产linux水印乱码

一般出现此类问题的根源在于linux环境不存在对应的字体文件

windows环境使用下列代码进行测试即可。

Graphics2D g2d = watermarkedImage.createGraphics();
g2d.drawImage(image, 0, 0, null);
// windows环境正常
g2d.setFont(new Font("微软雅黑", isItalic? Font.ITALIC : Font.PLAIN, 30));
g2d.setFont(derivedFont);

但如果需要指定特定的字体文件,可以使用如下写法。

Graphics2D g2d = watermarkedImage.createGraphics();
g2d.drawImage(image, 0, 0, null);

// linux 环境指定特定的字体文件
Font customFont = Font.createFont(Font.TRUETYPE_FONT, new File(fontFilePath));
int fontStyle = isItalic? Font.ITALIC : Font.PLAIN;
Font derivedFont = customFont.deriveFont(fontStyle, waterMarkFontSize);

g2d.setFont(derivedFont);

字体文件可以从windows系统的C:\Windows\Fonts中进行拷贝。

到此这篇关于Java实现根据图片url下载图片并动态添加水印的文章就介绍到这了,更多相关Java根据图片url下载内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Mybatis控制台打印SQL语句的两种实现方式

    Mybatis控制台打印SQL语句的两种实现方式

    在使用Mybatis开发时,由于可以动态拼接SQL,当动态SQL拼接块过多,直接从*mapper.xml中找出完整的SQL较难,此时,可以通过两种方法调试出SQL,方法一,将ibatislog4j运行级别调到DEBUG,在控制台打印出ibatis运行的SQL语句
    2024-10-10
  • Java杂谈之代码重构的方法多长才算长

    Java杂谈之代码重构的方法多长才算长

    关于代码重构的理解:在不改变软件系统/模块所具备的功能特性的前提下,遵循/利用某种规则,使其内部结构趋于完善。其在软件生命周期中的价值体现主要在于可维护性和可扩展性
    2021-10-10
  • 详解在idea 中使用Mybatis Generator逆向工程生成代码

    详解在idea 中使用Mybatis Generator逆向工程生成代码

    这篇文章主要介绍了在idea 中使用Mybatis Generator逆向工程生成代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • IDEA设置JVM运行参数的方法步骤

    IDEA设置JVM运行参数的方法步骤

    这篇文章主要介绍了IDEA设置JVM运行参数的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • SpringBoot使用spring.factories加载默认配置的实现代码

    SpringBoot使用spring.factories加载默认配置的实现代码

    在日常开发过程中,发布一些产品或者框架时,会遇到某些功能需要一些配置才能正常运行,这时我们需要的提供默认配置项,同时用户也能覆盖进行个性化
    2024-06-06
  • springboot项目打docker镜像实例(入门级)

    springboot项目打docker镜像实例(入门级)

    最近做个项目,我们想把自己的程序打包成镜像,并运行在docker容器中,本文主要介绍了springboot项目打docker镜像实例,具有一定的参考价值,感兴趣的可以了解一下
    2024-06-06
  • 详解使用spring cloud config来统一管理配置文件

    详解使用spring cloud config来统一管理配置文件

    这篇文章主要介绍了详解使用spring cloud config来统一管理配置文件,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-12-12
  • 升级springboot3之自动配置导入失效问题及解决

    升级springboot3之自动配置导入失效问题及解决

    这篇文章主要介绍了升级springboot3之自动配置导入失效问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • java冒泡排序简单实例

    java冒泡排序简单实例

    本文主要介绍了JSONjava冒泡排序实例与思路分析。具有一定的参考价值,下面跟着小编一起来看下吧
    2017-01-01
  • springboot 中异步任务,定时任务,邮件任务详解

    springboot 中异步任务,定时任务,邮件任务详解

    这篇文章主要介绍了springboot 与异步任务,定时任务,邮件任务,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-09-09

最新评论