在上次完成压缩图片的方法封装之后,
测试小伙伴发现一个问题,仍然有部分图片是压缩失败的??
在各方搜索之下发现:谷歌的webp格式的图片不会随着文件后缀而改变,也就是说,可能他下载的png/jpg 等等格式的图片内核仍然是webp(可以用记事本打开看看或者notepad++)
这就导致:java自带的ImageIO.read( InputStream ) 读取到的的是会返回一个null,无法对这部分图片处理;
webp格式图片资料:WebP是google开发的一种旨在加快图片加载速度的图片格式。图片压缩体积大约只有JPEG的2/3,并能节省大量的服务器带宽资源和数据空间。是现代图像格式,提供了优越的无损和有损压缩的图片在网络上。使用WebP,网站管理员和web开发人员可以创建更小、更丰富的图像,使网页更快。
WebP无损的png图像小26%。WebP有损图像是25 - 34%小于等效SSIM质量指数可比JPEG图像
无损WebP支持透明(也称为alpha通道)的成本只有22%额外的字节。对有损压缩RGB压缩情况下是可以接受的,有损WebP还支持透明度,通常提供3×PNG相比较小的文件大小。
但是!!!!
但是!!!由于Webp格式推出比较晚, Jdk 内置的图片编解码库对此并不支持(所以返回null)。因此,需要在处理图片之前,先检验图片的真实格式(不是通过后缀去解析)
代码如下,最后附上参考大佬的链接:
public static String imgType(InputStream inputStream) throws IOException {
// 读取文件前几位
byte[] fileHeader = new byte[4];
int read = inputStream.read(fileHeader, 0, fileHeader.length);
inputStream.close();
// 转为十六进制字符串
String header = ByteUtil.bytes2Hex(fileHeader);
if (header.contains("FFD8FF")) {
return "jpg";
} else if (header.contains("89504E47")) {
return "png";
} else if (header.contains("47494638")) {
return "gif";
} else if (header.contains("424D")) {
return "bmp";
} else if (header.contains("52494646")) {
return "webp";
} else if (header.contains("49492A00")) {
return "tif";
} else {
return "unknown";
}
}
public static String bytes2Hex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(b & 0xff);
sb.append(hex.length() == 2 ? hex : ("0"+hex));
}
return sb.toString();}
原文链接:https://blog.csdn.net/Apoca_lypse/article/details/106723876
所以,完整处理流程:
-
下载github中处理webp格式的jar包,四方打听0.1.0版本是最好的:
-
使用maven命令引入自己的仓库
mvn install:install-file -Dfile=E:\Downloads\webp-imageio-core-0.1.0.jar -DgroupId=org.sejda.imageio -DartifactId=webp-imageio-core -Dversion=0.1.0 -Dpackaging=jar
解析(来源于通义千问):
这条命令是在命令行中使用Apache Maven工具来将一个本地的jar包安装到Maven本地仓库中。命令的具体解释如下:
- mvn: 这是Maven命令行工具的执行命令。
- install:install-file: 这是一个Maven插件的目标,用于安装指定的文件到Maven仓库。在这里主要用于将一个外部的jar包安装到本地仓库中,使得在项目的构建过程中可以通过Maven坐标来引用它。
命令后面的参数是对要安装的jar包的相关信息的定义:
- -Dfile=E:\Downloads\webp-imageio-core-0.1.0.jar: 定义了要安装的jar包文件的路径和文件名。这意味着Maven会从E盘的Downloads目录下的webp-imageio-core-0.1.0.jar文件读取jar包内容。
- -DgroupId=org.sejda.imageio: 指定了jar包在Maven坐标系中的Group ID。Group ID是用来组织相关的项目和模块的一个命名空间。
- -DartifactId=webp-imageio-core: 指定了jar包在Maven坐标系中的Artifact ID。Artifact ID是项目构建产生的主要工件的唯一标识符,通常对应于一个 jar 包的名字。
- -Dversion=0.1.0: 指定了jar包的版本号。
- -Dpackaging=jar: 表明此工件打包的形式是jar。在Maven中,packaging可以是jar、war、ear等多种形式,这里指明它是jar格式的包。
执行这条命令后,Maven会将指定的jar包安装到本地仓库中,并且可以用 <groupId>:<artifactId>:<version> 这样的Maven坐标在其他项目中引用它。
-
例如,在其他项目的pom.xml文件中,可以这样添加依赖:
<dependency>
<groupId>org.sejda.imageio</groupId>
<artifactId>webp-imageio-core</artifactId>
<version>0.1.0</version>
</dependency>
</dependencies>
-
示例代码如下:
fileService.createFile是我自己的上传方法
/**
* 压缩图片
* @param uploadReqVO
* @return FileDO
* @throws Exception
*/
@PostMapping("/compressImage")
@PermitAll
public CommonResult<FileDO> compressImage(FileUploadReqVO uploadReqVO) throws Exception {
MultipartFile file = uploadReqVO.getFile();
String fileName = file.getOriginalFilename();
String fileSuffix = FileUtils.getFileNameSuffix(fileName);
if (StrUtil.isNotEmpty(fileSuffix) && StrUtil.containsAny(fileSuffix, new CharSequence[]{"bmp", "gif", "jpg", "jpeg", "png"})) {
// 验证文件是否实际上是WebP格式
if (imgType(file.getInputStream()).equals("webp")) {
// 处理WebP格式的逻辑,例如使用webp-imageio库
return CommonResult.success(compressPictureByMultipartFile(file));
} else {
// 如果不是WebP,按原逻辑处理
return CommonResult.success(compressImage(file)); // 假设压缩质量为80%
// 假设压缩质量为80%
}
// 调用压缩方法
} else {
log.error("当前仅支持:bmp、gif、jpg、jpeg、png等格式图片压缩");
return CommonResult.error(500, "当前支持压缩的图片格式:bmp、gif、jpg、jpeg、png");
}
}
private static final double targetSize = 2 * 1024 * 1024; // 目标大小(2MB)
public FileDO compressImage(MultipartFile file) throws IOException {
//如果小于500kb直接返回
long originalSize = file.getSize();
if (originalSize <= targetSize) {
return this.fileService.createFile(0L,file.getOriginalFilename(), null, file.getBytes(), false, file.getInputStream());
}
// 初始质量估计,考虑一个基础比例
double quality = Math.max(0.1, Math.min(1.0, targetSize / originalSize));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 第一次压缩
compress(file, quality, outputStream);
// 如果第一次压缩后的图片仍大于2MB,则继续压缩
while (outputStream.size() > targetSize && quality > 0.05) { // 防止压缩质量过低
quality -= 0.05; // 逐步降低质量
outputStream.reset(); // 重置输出流以便重新压缩
compress(file, quality, outputStream);
}
byte[] bytes = outputStream.toByteArray();
return this.fileService.createFile(0L,file.getOriginalFilename(), null, bytes, false, file.getInputStream());
}
/**
* 压缩图片
*
* @param inputFile
* @param quality
* @param outputStream
* @throws IOException
*/
private void compress(MultipartFile inputFile, double quality, ByteArrayOutputStream outputStream) throws IOException {
Thumbnails.of(inputFile.getInputStream())
.scale(1.0) // 保持原始尺寸
.outputQuality(quality)
.outputFormat("jpg")
.toOutputStream(outputStream);
}
/**
* 将MultipartFile对象转换为File对象并保存在临时目录下。
*
* @param multipartFile 从HTTP请求中接收到的MultipartFile对象
* @return 转换后的File对象,存储在临时目录下
* @throws IOException 在转换过程中出现I/O错误时抛出异常
*/
public File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
// 获取文件原始名称
String fileName = multipartFile.getOriginalFilename();
// 创建一个临时目录(默认为系统的临时目录)
File tempDir = new File(System.getProperty("java.io.tmpdir"));
// 创建一个临时文件,文件名与原始名称相同,扩展名保留
File tempFile = null;
if (fileName != null) {
tempFile = File.createTempFile(fileName, "", tempDir);
// 将MultipartFile的内容写入临时文件
multipartFile.transferTo(tempFile);
}
return tempFile;
}
/**
* 压缩图片方法,接受一个MultipartFile对象作为输入,将其转换为压缩后的图片,并以FileDO对象返回。
*
* @param file 用户上传的MultipartFile对象
* @return 压缩后的图片信息封装在FileDO对象中
* @throws IOException 图片读取、压缩或写入过程中发生错误时抛出异常
*/
public FileDO compressPictureByMultipartFile(MultipartFile file) throws IOException {
// 记录日志,显示MultipartFile对象的输入流信息
log.info("file.getInputStream(){}", file.getInputStream());
// 获取WebP图像读取器
ImageReader reader = ImageIO.getImageReadersByMIMEType("image/webp").next();
// 配置WebP解码参数
WebPReadParam readParam = new WebPReadParam();
readParam.setBypassFiltering(true);
// 设置读取器的输入源为从MultipartFile转换而来的File对象
reader.setInput(new FileImageInputStream(convertMultipartFileToFile(file)));
// 解码WebP图片
BufferedImage image = reader.read(0, readParam);
// 初始化压缩质量及输出流
float quality = 0.8f;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 循环调整压缩质量直至文件大小小于2MB
do {
baos.reset(); // 清空输出流以便重写压缩数据
ImageWriter imgWrier = ImageIO.getImageWritersByFormatName("jpg").next();
ImageWriteParam imgWriteParams = new JPEGImageWriteParam(null);
imgWriteParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
imgWriteParams.setCompressionQuality(quality);
imgWriteParams.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
imgWrier.setOutput(ImageIO.createImageOutputStream(baos));
imgWrier.write(null, new IIOImage(image, null, null), imgWriteParams);
imgWrier.dispose(); // 关闭ImageWriter以释放资源
byte[] compressedBytes = baos.toByteArray();
if (compressedBytes.length < 2 * 1024 * 1024) { // 判断压缩后大小是否小于2MB
break;
} else {
quality -= 0.1f; // 若大于2MB则减小压缩质量
}
} while (quality >= 0.1f); // 保证最低质量不低于0.1
// 假设fileService.createFile方法用于处理压缩后的字节数组并返回FileDO对象
return fileService.createFile(0L, file.getOriginalFilename(), null, baos.toByteArray(), false, null);
}
/**
* 根据文件输入流的前几个字节识别图片文件类型。
*
* @param inputStream 文件输入流
* @return 图片文件类型(如"jpg"、"png"、"gif"等)
* @throws IOException 在读取文件头时发生错误时抛出异常
*/
public String imgType(InputStream inputStream) throws IOException {
// 读取文件头的前4个字节
byte[] fileHeader = new byte[4];
int read = inputStream.read(fileHeader, 0, fileHeader.length);
inputStream.close();
// 将字节转换为十六进制字符串
String header = bytes2Hex(fileHeader);
// 根据文件头判断图片类型
if (header.contains("FFD8FF")) {
return "jpg";
} else if (header.contains("89504E47")) {
return "png";
} else if (header.contains("47494638")) {
return "gif";
} else if (header.contains("424D")) {
return "bmp";
} else if (header.contains("52494646")) {
return "webp";
} else if (header.contains("49492A00")) {
return "tif";
} else {
return "unknown";
}
}
/**
* 将字节数组转换为十六进制字符串。
*
* @param bytes 字节数组
* @return 十六进制表示的字符串
*/
public String bytes2Hex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(b & 0xff);
sb.append(hex.length() == 2 ? hex : ("0" + hex));
}
return sb.toString();
}
方法来源各方大佬的链接和资料,如果有没有指出的地方还望私聊我,侵删
也欢迎大佬们给出指正意见,谢谢各位!