基于Java和Spring:实现图片压缩、WebP格式转换与水印添加

应用业务场景

大多数公司通常会拥有面向企业端(B端)和消费者端(C端)的应用,如淘宝IOS/Android端和PC端。对于C端产品而言,往往具备访问量大、数据量庞大的特点,例如类似淘宝的商品详情页,其中包含大量且尺寸较大的图片。在这种前提下,为了确保良好的用户体验,需要在保证图片质量的基础上,尽可能地压缩图片的体积。

这种情况下,网络带宽往往是一种有限资源。因此,为了确保页面加载速度和用户体验,对图片进行有效的压缩是至关重要的。通过优化图片压缩算法,可以在减少图片体积的同时尽量保持其视觉质量。这种优化可以通过使用现代的压缩技术(如WebP格式)以及使用适当的分辨率和压缩比例来实现。这样既可以降低页面加载时间,又能够提供高质量的图片展示,从而提升用户体验。

本篇文章将基于JDK 17 + Spring 3.0webp-imageio实现将图片压缩、转换为WebP格式,并添加文字、图片水印。

什么是WebP

WebP官网

WebP是一种由Google开发的现代图像格式,旨在提供更高的压缩率和更好的图像质量,相比于传统的JPEG、PNG等格式,能够显著减少图像文件的大小。WebP图像通常具有更小的文件大小,因此可以加快网页加载速度,节省带宽和用户的流量消耗。

WebP图像支持有损压缩和无损压缩两种模式。在有损压缩模式下,WebP图像通常能够实现较高的压缩比,而在无损压缩模式下,WebP图像则可以保留原始图像的质量而实现较小的文件体积。

WebP格式的优势主要包括:

  1. 更高的压缩率: WebP图像通常比JPEG图像具有更小的文件大小,同时保持相近甚至更好的图像质量。
  2. 更快的加载速度: 由于文件大小更小,WebP图像能够更快地加载,从而改善网页加载性能,提升用户体验。
  3. 广泛的兼容性: WebP图像在现代浏览器中得到了广泛支持,包括Chrome、Firefox、Edge等浏览器,同时也可以通过Polyfill等方式在不支持WebP的浏览器上进行兼容性处理。

WebP格式已经成为常用的图像格式之一,特别是对于需要大量图片展示的网站,如电子商务平台、社交媒体和新闻网站等。


使用第三方库处理图片

第三方库名称:webp-imageio

maven坐标:

xml 复制代码
<!-- https://mvnrepository.com/artifact/org.sejda.imageio/webp-imageio -->
<dependency>
    <groupId>org.sejda.imageio</groupId>
    <artifactId>webp-imageio</artifactId>
    <version>0.1.6</version>
</dependency>

maven库地址:

WebP ImageIO

使用webp-imageio DEMO

java 复制代码
/**
 * Author:      微信公众号:种棵代码技术树
 * Date:        2024/2/24
 * Description: webp-imageio 处理图片
 * Version: 1.0
 */
public class PicWebPTest {
	public static void main(String[] args) throws IOException {
		LocalDateTime startTime = LocalDateTime.now();
        //添加图片水印
		addPictureWatermarkAndConvert("your_master_picture_path",
                                      "your_output_picture_path",
                        			  "your_water_image_path");

        //添加文字水印
		addWatermarkAndConvert("your_master_picture_path", 
                               "your_output_picture_path",
                               "种棵代码技术树");

        //计算压缩率
		double compressionRatio = calculateCompressionRatio("your_master_picture_path",
				"your_output_picture_path");
		LocalDateTime endTime = LocalDateTime.now();
		Duration duration = Duration.between(startTime, endTime);
		long secondsDifference = Math.abs(duration.getSeconds());
		System.out.println("Time: " + secondsDifference + "s");
		System.out.println("Compression ratio: " + compressionRatio + "%");
	}

	/**
	 * 添加图片水印
	 *
	 * @param input              原图位置
	 * @param output             输出图片位置
	 * @param watermarkImagePath 水印图片位置
	 * @throws IOException IO异常
	 */
	public static void addPictureWatermarkAndConvert(String input, String output, String watermarkImagePath) throws IOException {
		// 读取原始图片
		File inputFile = new File(input);
		BufferedImage image = ImageIO.read(inputFile);

		// 创建一个带有水印的新图片
		BufferedImage watermarkedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
		Graphics2D graphics = watermarkedImage.createGraphics();
		graphics.drawImage(image, 0, 0, null);

		// 加载水印图片
		File watermarkFile = new File(watermarkImagePath);
		BufferedImage watermarkImage = ImageIO.read(watermarkFile);

		// 计算水印图片的大小,使其适应原图大小
		int watermarkWidth = (int) (image.getWidth() * 0.125); 
		int watermarkHeight = watermarkWidth * watermarkImage.getHeight() / watermarkImage.getWidth();
		BufferedImage scaledWatermarkImage = new BufferedImage(watermarkWidth, watermarkHeight, BufferedImage.TYPE_INT_ARGB);
		Graphics2D watermarkGraphics = scaledWatermarkImage.createGraphics();
		watermarkGraphics.drawImage(watermarkImage, 0, 0, watermarkWidth, watermarkHeight, null);
		watermarkGraphics.dispose();

		// 设置水印位置
		int x = watermarkedImage.getWidth() - watermarkWidth - 10;
		int y = watermarkedImage.getHeight() - watermarkHeight - 10;

		// 添加水印
		graphics.drawImage(scaledWatermarkImage, x, y, null);

		// 输出带有水印的图片
		File outputFile = new File(output);
		ImageIO.write(watermarkedImage, "webp", outputFile);

		// 释放资源
		graphics.dispose();
	}


	/**
	 * 给图片添加文字水印
	 *
	 * @param originInput         原图地址
	 * @param output        处理后的图片位置
	 * @param watermarkText 水印文字
	 * @throws IOException IO异常
	 */
	public static void addWatermarkAndConvert(String originInput, String output, String watermarkText) throws IOException {
		// 读取原始图片
		File inputFile = new File(originInput);
		BufferedImage image = ImageIO.read(inputFile);

		// 创建一个带有水印的新图片
		BufferedImage watermarkedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
		Graphics2D graphics = watermarkedImage.createGraphics();
		graphics.drawImage(image, 0, 0, null);

		// 计算水印字体大小
		addTextWatermark(watermarkText, image, graphics, watermarkedImage);

		// 输出带有水印的图片
		File outputFile = new File(output);
		ImageIO.write(watermarkedImage, "webp", outputFile);

		// 释放资源
		graphics.dispose();
	}

	/**
	 * 给原图添加文字水印
	 *
	 * @param watermarkText    水印文字
	 * @param image            图片
	 * @param graphics         图形
	 * @param watermarkedImage 水印图
	 */
	private static void addTextWatermark(String watermarkText, BufferedImage image, Graphics2D graphics, BufferedImage watermarkedImage) {
		int fontSize = calculateFontSize(image);
		System.out.println("fontSize: " + fontSize);

		// 设置水印文本样式
		Font font = new Font("Arial", Font.BOLD, fontSize);
        // 设置水印文本颜色和透明度
		Color color = new Color(255, 255, 255, 128); 

		// 添加水印
		graphics.setColor(color);
		graphics.setFont(font);
		graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		graphics.drawString(watermarkText, 10, watermarkedImage.getHeight() - 10);
	}

	/**
	 * 计算水印文字大小
	 *
	 * @param image 图片
	 * @return 文字字号
	 */
	private static int calculateFontSize(BufferedImage image) {
		// 获取图片的高度
		int imageHeight = image.getHeight();
		// 计算字体大小,可以根据需要调整比例系数,最小字体12
		return Math.max(imageHeight / 20, 12);
	}


	/**
	 * 计算图片压缩率
	 *
	 * @param originalFilePath   原图路径
	 * @param compressedFilePath 压缩后的图片路径
	 * @return 压缩率
	 */
	public static double calculateCompressionRatio(String originalFilePath, String compressedFilePath) {
		File originalFile = new File(originalFilePath);
		File compressedFile = new File(compressedFilePath);

		long originalFileSize = originalFile.length();
		long compressedFileSize = compressedFile.length();
		return ((double) originalFileSize - compressedFileSize) / originalFileSize * 100;
	}

}

1.关于文件位置的获取

本地文件图片位置:

举个例子,我将图片放在resource目录下的image中,可以使用IDEA的Copy Path/Reference功能获取。

避免大家在测试过程中出现:先帝创业未半而中道崩殂的悲惨事件。

关于图片名称、水印信息配置调整

由于本Demo是以测试webp-imageio功能性和效果为目的,所以处理后的图片名称、水印位置、大小、样式等均可自定义或作为参数传入,以实现个人需求。

功能点

  • 图片压缩:压缩率49%-97%
  • 压缩时间:1-7秒
  • 水印:支持文字水印、图片水印

测试数据

测试平台

  • 系统:Windows 11
  • CPU:AMD R5 4600H
  • 内存:16G
  • 硬盘:三星PM981A

测试样张

文字水印-压缩前后图片信息对比

压缩结果:

图片水印-压缩前后图片信息对比

测试样张来源:

侵权请联系删除。

后续内容文章持续更新中...

近期发布。


关于我

👋🏻你好,我是Debug.c。微信公众号:种棵代码技术树 的维护者,一个跨专业自学Java,对技术保持热爱的bug猿,同样也是在某二线城市打拼四年余的Java Coder。

🏆在掘金、CSDN、公众号我将分享我最近学习的内容、踩过的坑以及自己对技术的理解。

📞如果您对我感兴趣,请联系我。

若有收获,就点个赞吧,喜欢原图请私信我。

相关推荐
276695829227 分钟前
海关 瑞数 后缀分析 rs
java·python·rs·瑞数·海关·瑞数后缀·后缀生成
呼Lu噜32 分钟前
WPF-遵循MVVM框架创建图表的显示【保姆级】
前端·后端·wpf
bing_15836 分钟前
为什么选择 Spring Boot? 它是如何简化单个微服务的创建、配置和部署的?
spring boot·后端·微服务
小臭希38 分钟前
Java——琐碎知识点一
java·开发语言
学c真好玩1 小时前
Django创建的应用目录详细解释以及如何操作数据库自动创建表
后端·python·django
Asthenia04121 小时前
GenericObjectPool——重用你的对象
后端
Piper蛋窝1 小时前
Go 1.18 相比 Go 1.17 有哪些值得注意的改动?
后端
excel1 小时前
招幕技术人员
前端·javascript·后端
盖世英雄酱581362 小时前
什么是MCP
后端·程序员