png图片压缩后黑底问题解决

news2024/11/28 19:43:26

问题背景

使用thumbnail对图片进行压缩,偶然会发现对png图片出现黑底的情况如下:

压缩前

 

压缩后

 

问题解决

对网上搜到的解决方法主要有两种:

1.指定png输出

JAVA - Get black background when uploading PNG image - Stack Overflow

一句话解决Thumbnails缩略图工具PNG透明背景缩放后变黑问题_thumbnails背景变黑_applebomb的博客-CSDN博客

但是这样有个问题就是要指定大小,可以参考

Java压缩图片util,可等比例宽高不失真压缩,也可直接指定压缩后的宽高_xiaoxiansweety的博客-CSDN博客 做等比例,不过代码稍微复杂点

2.重绘图,用白底重绘

用白的背景重画,就会有类似如下问题:左边为原图,右边为重绘后的图

 

解决方案

看thumbnail的tofile方法可以看到

 

看fileImageLink,可以看到getExtension()

那么是怎么确定输出文件的后缀呢?获取.后面的内容 

 问题到这基本就清晰了,黑底的原因是png压缩过程中alpha通道值没了,用黑色补充,为什么alpha通道会丢失呢?因为按jpg处理了,所以我们只需要处理png时,识别到真实png,保证其后缀名为.png即可。

步骤如下:

  1. 读取文件头

判断文件类型为jpg还是png,对后缀名不对的,强行补充.png等

  1. 一行压缩

 Thumbnails.of(oriFile).scale(compressFactor).toFile(finalFileName);

完整代码:

package com.htsc.project.common;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.text.DecimalFormat;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;

/**
 * @author 020152
 * @date 2023/5/9
 */
@Service
@Slf4j
public class ImageCompress {
    private static final Integer COMPRESSED = 1;
    /**
     * 支持的文件的头,key:文件头,value:支持的文件后缀,以逗号分割
     * {"FFD8FF":"jpg,jpeg","89504E47":"png"}
     */
    @Value(value = "#{${image.compress.fileHeader:{\"FFD8FF\":\"jpg,jpeg\",\"89504E47\":\"png\"}}}")
    private Map<String, String> compressFileHeaders;

    /**
     * 是否压缩图片,0不压缩,1压缩
     */
    @Value(value = "${image.compressed:0}")
    private Integer compressed;

    /**
     * png图片开始压缩的大小,对较小的png不压缩,防止无用功
     */
    @Value(value = "${image.compress.minSize:100000}")
    private Long minCompressedSize;

    /**
     * 太大的png压缩必要不大,可能是无用功
     */
    @Value(value = "${image.compress.maxSize:10000000}")
    private Long maxCompressedSize;

    /**
     * 压缩率,默认0.9
     */
    @Value(value = "${image.compressFactor:0.9}")
    private Double compressFactor;

    @Autowired
    private Config config;

    @PostConstruct
    public void init() {
        log.info("compressHeaders:{} compressed{}compressFactor:{}", compressFileHeaders, compressed, compressFactor);
        log.info(Constant.LOG_DEVMODESTR + config.getDevMode());
    }

    /**
     * 压缩图片
     *
     * @param oriFile  原始文件
     * @param fileName 文件名
     * @param random   uuid随机
     * @return 压缩后的文件
     */
    public File compress(File oriFile, String fileName, String random) {
        if (!COMPRESSED.equals(compressed)) {
            return oriFile;
        }
        try (InputStream inputStream = Files.newInputStream(oriFile.toPath())) {
            byte[] bytes = new byte[10];
            inputStream.read(bytes, 0, bytes.length);
            // 校验文件头信息,防止txthtml等文件
            String fileHeader = bytesToHex(bytes);
            String matchExt = compressFileHeaders.keySet().stream().filter(fileHeader::startsWith).findAny().orElse(null);
            if (matchExt == null) {
                // 非图片文件,直接返回
                return oriFile;
            }
            String suffix, prefix;
            if (fileName.lastIndexOf(".") == -1) {
                suffix = "." + compressFileHeaders.get(matchExt);
                if (suffix.contains(",")) {
                    suffix = ".jpg";
                }
                prefix = fileName;
            } else {
                suffix = fileName.substring(fileName.lastIndexOf("."));
                prefix = fileName.substring(0, fileName.lastIndexOf("."));
            }
            String finalFileName = prefix + suffix;
            // 后缀名和真实文件类型不匹配,如本身是png文件但是后缀为jpg,或反之
            if (!compressFileHeaders.get(matchExt).contains(suffix.substring(1))) {
                // 实际是jpg文件,但是用了png后缀,在toFile使用jpg压缩,保证压缩率,但是不修改目标文件后缀名
                if (!suffix.contains("jpg")) {
                    finalFileName = prefix + ".jpg";
                }
                // 实际是png文件,但是用jpg后缀,在toFile使用jpg压缩可能出现黑底
                if ("png".equals(compressFileHeaders.get(matchExt))) {
                    finalFileName = prefix + ".png";
                }
            }
            finalFileName = random + "_" + finalFileName;
            File result = compressFile(matchExt, oriFile, finalFileName);
            long oriLength = oriFile.length();
            long length = result.length();
            Double cut = (oriLength - length) / (double) oriLength;
            DecimalFormat df = new DecimalFormat("#.##%");
            String cutStr = df.format(cut);
            log.info("fileName:{}, finalFileName:{} , 压缩前:{}, 压缩后:{}, 节省:{}", fileName, finalFileName, oriLength, length, cutStr);
            // 压缩失败=0;压缩负优化;png原图大小不在压缩范围
            if (length == 0 || length > oriLength || result.equals(oriFile)) {
                return oriFile;
            }
            oriFile.delete();
            return result;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 字节数组转Hex
     *
     * @param bytes 字节数组
     * @return Hex
     */
    public String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        if (bytes != null) {
            for (byte aByte : bytes) {
                String hex = byteToHex(aByte);
                sb.append(hex);
            }
        }
        return sb.toString();
    }

    /**
     * Byte字节转Hex
     *
     * @param b 字节
     * @return Hex
     */
    public String byteToHex(byte b) {
        String hexString = Integer.toHexString(b & 0xFF);
        //由于十六进制是由0~9A~F来表示1~16,所以如果Byte转换成Hex后如果是<16,就会是一个字符(比如A=10),通常是使用两个字符来表示16进制位的,
        //假如一个字符的话,遇到字符串11,这到底是1个字节,还是11两个字节,容易混淆,如果是补0,那么11补充后就是010111就表示纯粹的11
        if (hexString.length() < 2) {
            hexString = 0 + hexString;
        }
        return hexString.toUpperCase();
    }

    private File compressFile(String matchExt, File oriFile, String finalFileName) throws IOException {
        if (compressFileHeaders.get(matchExt).contains("jpg")) {
            return compressJpg(oriFile, finalFileName);
        } else {
            return compressPng(oriFile, finalFileName);
        }
    }

    /**
     * 实际可以和compressJpg合并为一个方法,考虑后期png可能单独处理,暂时保留
     */
    private File compressPng(File oriFile, String finalFileName) throws IOException {
        if (oriFile.length() < minCompressedSize || oriFile.length() > maxCompressedSize) {
            return oriFile;
        }
        Thumbnails.of(oriFile).scale(compressFactor).toFile(finalFileName);
        return new File(finalFileName);
    }

    private File compressJpg(File oriFile, String finalFileName) throws IOException {
        Thumbnails.of(oriFile).scale(compressFactor).toFile(finalFileName);
        return new File(finalFileName);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据好找工作么?前景如何

大数据好不好找工作不是一概而论的&#xff0c;要根据你个人的学历情况&#xff0c;掌握技能程度&#xff0c;所在城市招聘需求&#xff0c;甚至是你的面试能力和简历是否突出优势有关。 但是毋庸置疑的是&#xff0c;大数据目前的发展前景还是相当优秀的。 我们知道&#xf…

什么是测试开发,聊聊我对测试开发的看法

目录 前 还没来阿里之前&#xff0c;我对测开的看法 多数人眼中的测试开发 来了阿里之后&#xff0c;对测开看法有了转变 阿里的测开是干嘛的 测试平台的好处和不足 我对测试平台的看法 测试平台是测开必需品吗&#xff1f; 实际项目中用不到测试平台&#xff0c;有必要…

GIT | 日常命令查阅表

最近公司代码管控比较乱&#xff0c;有天就利用分支进行了代码梳理&#xff0c;当时就遇到一些困惑&#xff0c;抽空就把git 再学了一下。 以前我是用git命令的&#xff0c;但是敲命令对我来说还是有点麻烦&#xff08;主要是git 功力不够&#xff09;&#xff0c;看到有同事用…

jmeter性能测试技巧(欢迎提问,不定时更新)

问题1 如何在大并发测试下&#xff0c;让登录或者后续接口只执行一次&#xff1f; 分析 2023Jmeter性能测试项目实战教程&#xff0c;十年测试大佬手把手教你做性能&#xff01;_哔哩哔哩_bilibili2023Jmeter性能测试项目实战教程&#xff0c;十年测试大佬手把手教你做性能&…

SQL综合案例之电商漏斗转化分析,pv,uv及

漏斗模型示例&#xff1a; 不同的业务场景有不同的业务路径 : 有先后顺序, 事件可以出现多次 注册转化漏斗 : 启动APP --> APP注册页面--->注册结果 -->提交订单-->支付成功 搜购转化漏斗 : 搜索商品--> 点击商品--->加入购物车-->提交订单-->支付成功…

Scrum中可以有测试人员吗?

作者 | Federico Toledo Scrum 是将质量融入产品开发和创建敏捷团队的宝贵框架。测试人员如何适应这一切&#xff1f;让我们深入研究这篇文章。 毫无疑问&#xff0c;Scrum是在任何环境中为团队寻求业务敏捷性&#xff0c;以及应对不同复杂挑战的最重要工具之一。 正如《福布斯…

在pycharm里安装pytorch环境-GPU版

1、安装Anaconda 在官网下载安装&#xff1a;https://www.anaconda.com/download 2、安装pycharm https://www.jetbrains.com/pycharm/download/#sectionwindows 使用社区版即可。 3、检查conda环境 按winr&#xff0c;输入cmd回车打开命令窗 在命令窗内输入conda 环境无问…

小学课后兴趣班选课平台的设计与实现(ASP.NET,SQLServer)

系统功能模块设计 中小学课后兴趣班选课平台包括前台功能模块和后台功能模块&#xff1a;前台功能模块是给会员使用的功能模块&#xff0c;在前台功能模块中会员可以实现在线注册&#xff0c;登录&#xff0c;查看发布的新闻资讯信息&#xff0c;查看教师&#xff0c;在线留言&…

软件测试的 20 个误区

软件测试中常遇到的 20 个误区&#xff0c;争取能给想从事软件测试的小伙伴一点启发。 1、测试人员不需要了解软件开发知识 抛开自动化测试&#xff0c;测试开发等&#xff0c;这些是必须要学习软件开发知识。功能测试和接口测试等还是需要软件开发知识的&#xff0c;例如新建…

电动汽车、车载充电器及其过流保护电路介绍

摘要&#xff1a;本文通过比亚迪公司的专利了解电动汽车、车载充电器及其过流保护电路&#xff0c;其中&#xff0c;车载充电器包括AC/DC变换器和DC/DC变换器&#xff0c;AC/DC变换器和DC/DC变换器均采用光耦驱动的SiC开关管&#xff0c;过流保护电路包括&#xff1a;电流检测单…

ChatGPT与网络安全

文章目录 一、“AI用于攻击”二、“AI用于安全&#xff08;防御&#xff09;”三、“AI的防御”四、“AI被攻击” ChatGPT作为基于生成式预训练模型&#xff08;GPT&#xff09;的聊天机器人&#xff0c;其核心技术是自然语言处理&#xff08;NLP&#xff09;。随着NLP技术的不…

OS实战笔记(9)-- 构建二级引导器

Grub内核映像格式 Grub工作的时候&#xff0c;需要一个内核映像文件&#xff0c;其中包括了二级引导器模块、内核模块、图片和字库等。这些不同的文件都被放到了一个映像文件中&#xff0c;为了Grub能够正常加载&#xff0c;需要一个预先定义好的格式&#xff0c;以便Grub能解析…

代码实现 ResNet 详解

零、ResNet的介绍 ResNet代码&#xff08;含详细的使用说明&#xff09;&#xff1a; https://github.com/GarsonWw/resnet-garson.git 当谈到深度学习中的卷积神经网络时&#xff0c;ResNet&#xff08;Residual Network&#xff09;是一个备受赞誉且引人注目的架构。ResNet…

最全的mysql编码集问题排查

用navicate导入一个json文件数据的时候&#xff0c;发现中文有一些是乱码的&#xff0c;查了很多资料&#xff0c;发现mysql、navicate编码集都没问题&#xff0c;包括导入流程&#xff0c;那是什么原因呢&#xff1f;想着难道是电脑的编码集影响了&#xff1f;于是调整以后&am…

【C语言】结构体——我就是秩序的创建者!(结构体数组、结构体指针、嵌套、匿名、字面量、伸缩型数组、链式结构)

一、结构体基础1.1 声明和定义1.1 初始化和赋值1.3 访问结构体成员 二、结构体数组2.1 定义和初始化2.2 访问 三、结构体的嵌套五、指向结构体的指针六、向函数传递结构体6.1 只传递结构体成员6.2 传递结构体指针6.3 传递结构体 七、结构体的其他特性——不容小觑7.1 结构体的大…

硬盘数据丢失怎么办?一招轻松恢复硬盘数据!

硬盘应该是最为常用的数据存储设备了&#xff0c;它为电脑等设备提供巨大的存储空间。我们在平时的工作和学习中也经常会使用硬盘来存储数据&#xff0c;很多用户会将多年搜集到的资料存到电脑硬盘里。 硬盘上的文件&#xff0c;不论是工作文档还是照片、视频&#xff0c;对用…

(四)ArcGIS空间数据的转换与处理——数据结构转换

ArcGIS空间数据的转换与处理——数据转换 空间数据的来源很多&#xff0c;如地图、工程图、规划图、航空与遥感影像等&#xff0c;因此空间数据也有多种格式。根据应用需要&#xff0c;需对数据进行格式转换&#xff0c;不同数据结构间的转换主要包括矢量数据到栅格数据的转换…

Guava的骚操作,大大提升了我摸鱼的时间

以面向对象思想处理字符串对基本类型进行支持总结 概述 1、Guava是google公司开发的一款Java类库扩展工具包&#xff0c;包括新的集合类型&#xff08;如 multimap 和 multiset&#xff09;、不可变集合、图形库&#xff0c;以及用于缓存、并发、I/O等实用程序。使用这些API一…

内存溢出导致的Full GC异常

背景 线上服务GC耗时过长&#xff0c;普遍10s&#xff0c;此外GC后&#xff0c;内存回收不多 问题一 通过查询gc日志可以发现&#xff0c;CMS进行垃圾回收的时候报concurrent mode failure错误&#xff0c;该错误是因为CMS进行垃圾回收的时候&#xff0c;新生代进行GC产生的对象…

JupyterLab(Jupyter Notebook)安装与使用

文章目录 前言安装JupyterLab切换中文语言JupyterLab desktop 使用演示其它补充后记 前言 目前在看《程序员数学&#xff1a;用Python学透线性代数和微积分》这个书&#xff0c;它里面的代码是在Jupyter中编写的&#xff0c;所以也安装下用用。 JupyterLab是一个可以同时编写…