浅谈Thumbnails压缩gif图片质量的实现方式

发布时间:2026/10/29 10:55:38

浅谈Thumbnails压缩gif图片质量的实现方式 Thumbnails是一个比较大众的图片处理工具类似的工具还有hutool可以对图片进行裁剪、缩放、旋转、格式转换、水印等。然而它只提供单张图片的压缩对于gif的压缩却是需要我们自己去处理。 下面是一段压缩图片质量的代码public static BufferedImage compressPic(BufferedImage frame,float scale) throws Exception{ return Thumbnails.of(frame).outputFormat(jpg).scale(scale).outputQuality(scale).asBufferedImage(); }其中scale是对图片进行缩放的比例传入一个0到1之间的浮点数大于1表示放大outputQuality代表输出图片的质量值在0到1之间但是我在实际压缩图片的使用中outputQuality这个参数并不生效图片最终的质量只受到scale影响不清楚是何原因可能是因为这个outputQuality取值和图片字节大小有联系。官方提供了一个根据文件大小动态调整压缩率的计算方式如下/** * 自动调节精度(经验数值) * * param size 源图片大小 * return 图片压缩质量比 */ private static double getAccuracy(long size) { double accuracy; if (size 900) { accuracy 0.85; } else if (size 2047) { accuracy 0.6; } else if (size 3275) { accuracy 0.44; } else { accuracy 0.4; } return accuracy; }通过动态调整压缩比例重复几次压缩可以实现将单张图片压缩到指定大小一下代码如下/** * 根据指定大小压缩图片 * * param imageBytes 源图片字节数组 * param desFileSize 指定图片大小单位kb * return 压缩质量后的图片字节数组 */ public static byte[] compressPicForScale(byte[] imageBytes, long desFileSize,String prefix) throws Exception{ long d desFileSize * 1024; if (imageBytes null || imageBytes.length 0 || imageBytes.length d) { return imageBytes; } long srcSize imageBytes.length; double accuracy getAccuracy(srcSize / 1024); while (imageBytes.length d) { ByteArrayInputStream inputStream new ByteArrayInputStream(imageBytes); ByteArrayOutputStream outputStream new ByteArrayOutputStream(imageBytes.length); BufferedImage read ImageIO.read(inputStream); Thumbnails.of(read) .scale(accuracy) .outputQuality(accuracy) .outputFormat(prefix) .toOutputStream(outputStream); imageBytes outputStream.toByteArray(); } return imageBytes; }这种方式在压缩单张图片时没有问题然而压缩gif时并不适用因为gif是由多张图片组成的每一张都是GIF的一个帧每帧之间有一个间隔播放时间类似于视频所以我们要压缩gif有两个方向去实现1.压缩每张图片的质量我们可以读取到gif的每一帧然后对每一帧都进行压缩最后再将压缩后的帧按顺序拼接起来组成新的gif原则上新的gif占用的空间会减少。然而存在一个问题我们使用上面的代码去压缩时由于每一帧图片大小不一致压缩的比例也不一样无法事先确定图片宽高导致压缩出来的每帧的宽高都不一致这样就导致gif没法看了。因此我们不能将图片帧按大小压缩而是应该直接进行缩放比例这样也能压缩质量保证每张图片宽高一致。2.减少帧的数量一般比较大的图片都是帧数非常多通过减少帧数也能显著降低图片大小可以对帧数进行一个采样只选出固定数量的帧数进行压缩然后拼接成新的gif这样的方式其实我们在日常生活中有时也能看到gif变成了一帧一帧不连续的动图。采样的方式有很多种顺序采样或者均匀采样随机采样等等。对于缩放比例我们可以根据图片宽高进行动态计算public static float getScare(int width,int height){ int n Math.max(width,height); float rate; //大于450像素 if(n 450){ //缩放到450*0.290像素 rate 0.2f; }else if(n 400){ rate 0.26f; }else if(n 300){ rate 0.3f; }else if(n 200){ rate 0.4f; }else if(n 100){ rate 0.6f; }else if(n 80){ rate 0.7f; }else{ rate 0.8f; } return rate; }gif生成使用到了如下依赖dependency groupIdcom.madgag/groupId artifactIdanimated-gif-lib/artifactId version1.4/version /dependency具体压缩gif代码如下public static byte[] compressGif(byte[] data) throws Exception{ ByteArrayInputStream stream new ByteArrayInputStream(data); ByteArrayOutputStream out new ByteArrayOutputStream(); GifDecoder decoder new GifDecoder(); decoder.read(stream); int cnt decoder.getFrameCount(); //如果只有一帧直接压缩到20kb if(cnt 1){ return compressPicForScale(data, 20, jpg); } int width decoder.getImage().getWidth(); int height decoder.getImage().getHeight(); float scale getScare(width,height); width (int) (width*scale); height (int) (height*scale); AnimatedGifEncoder e new AnimatedGifEncoder(); // 设置生成图片大小 e.setSize(width, height); //保存到数组 e.start(out); //重复次数 0表示无限重复 默认不重复 e.setRepeat(0); //进行采样 BufferedImage[] fs getFrames(decoder); for (BufferedImage f : fs) { if (fs.length 1) { //设置延迟 int delay; if (fs.length 5) { delay 200; } else if (fs.length 4) { delay 400; } else { delay 1000; } e.setDelay(delay); } BufferedImage image compressPic(f, scale); e.addFrame(image); } e.finish(); return out.toByteArray(); }采样代码private static BufferedImage[] getFrames(GifDecoder decoder){ int cnt decoder.getFrameCount(); //我这里只采了5帧 int max 5; if(cnt max){ BufferedImage[] r new BufferedImage[cnt]; for (int i 0; i cnt; i) { r[i] decoder.getFrame(i); } return r; }else if(cnt max*2){ BufferedImage[] r new BufferedImage[max]; for (int i 0; i max; i) { r[i] decoder.getFrame(i); } return r; }else{ BufferedImage[] r new BufferedImage[max]; int sec cnt/max; int n 0; for (int i 0; i cnt n max; isec) { r[n] decoder.getFrame(i); n; } return r; } }调用方式public static void main(String[] args) throws Exception{ byte[] d FileUtils.readFileToByteArray(new File(D:\\zhou\\111.gif)); byte[] compress compress(d,111.gif); if(compress.length d){ //压缩后体积是减小的才保存 FileUtils.writeByteArrayToFile(new File(D:\\zhou\\222.gif),compress); } }
延伸阅读

更多相关文章

2026/10/21 10:59:36

工业读码器选型实战指南:从场景到参数全面解析

1. 选型真不是看参数表那么简单,先理清应用场景再谈方案能提出“工业扫码器设备前期选型可以提供方案吗”这个问题的人,多半已经被市面上五花八门的读码器型号搞得有点晕了。我接触过不少做自动化集成的朋友,他们经常拿着一个需求跑来问我&am…

2026/10/25 14:25:40

基于NSGA-II的峰谷电价下电动汽车充电负荷多目标优化实践

这两年做电动汽车充电负荷优化,和电网侧、用户侧的人聊了不少,发现大家不约而同盯上了同一个问题:电动车越来越多,大家下班回家就充电,高峰和高峰撞在一起,变压器扛不住,电费也难看。单纯靠电网…

2026/10/25 3:22:10

别让年度总结变成流水账:一套从复盘到规划的闭环方法

1. 别急着写规划,先学会复盘每年到这个时候,朋友圈里全是各种年度总结模板,很多人打开文档就开始回忆这一年干了什么,然后对着空白的规划页发呆。我干了这些年个人管理和效率工具折腾,最深的体会是:年度总结…

2026/10/29 10:54:58

SpringBoot 统一返回结果封装 + 全局异常 + 参数校验

前言做SpringBoot后端接口开发,几乎每个项目都会遇到这几个痛点:Controller返回格式不统一,有的返回String、有的返回Map,前端对接成本高;异常直接抛出,错误堆栈信息返回前端,存在安全风险&…

2026/10/29 10:54:58

Java 重写 Override 与重载 Overload 区别、访问修饰符、static 关键字

前言Java 面向对象面试有一套连环组合题:重载 & 重写、访问修饰符、static 关键字。不少人的误区:以为返回值不同就可以构成重载;以为静态方法可以重写;分不清 protected 在不同包子类的访问权限;搞混静态代码块、…

2026/10/29 10:54:58

iloader:iPhone 侧载 SideStore 与导入 IPA,全程不碰命令行

iloader:iPhone 侧载 SideStore 与导入 IPA,全程不碰命令行 【免费下载链接】iloader User friendly sideloader 项目地址: https://gitcode.com/GitHub_Trending/iloa/iloader 用其他工具装 SideStore,装完往往还要把 rppairing、loc…

2026/10/29 10:54:58

计算机服务巡检

一、先一句话总览物理核:CPU真实硬件核心;逻辑核:超线程(SMT)虚拟出来的核心;频率:核心每秒运算次数;进程:程序运行的独立资源容器;线程:进程里面…

2026/10/29 10:49:57

yunerDesk—API包装,服务器运维,数据推送,同步

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/28 14:42:12

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/28 15:28:56

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/28 3:32:29

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/29 0:03:42

学生选课系统DFD图绘制指南:边界划分与分层校验实战

简介:文档以顶层与第一层DFD图为核心,完整刻画学生选课系统的逻辑模型,面向软件工程课程设计、系统分析学习及毕设前期建模人群。顶层图界定系统边界,外部实体包括管理员、教师和学生三类角色;第一层图进一步细化用户登…

2026/10/29 0:03:42

JIRA敏捷落地:Scrum角色权限、Sprint与燃尽图避坑指南

简介:围绕“基于JIRA的敏捷开发项目管理”这一主题,面向产品负责人、Scrum Master及研发测试成员,梳理Scrum角色分工、迭代流程与JIRA工具配置的对应关系,适合初、中级团队导入敏捷管理时参考。压缩包共1个docx文件,大…

2026/10/29 0:03:42

SEMI S8人机工程验收实战:从条款拆解到现场实测的完整指南

简介:面向半导体设备设计、制造与运维工程师及安全合规人员,这份资料聚焦SEMI标准体系下的设备安全规范,重点收录SEMI S8-0308《半导体制造设备人体工程学工程安全指南》。内容围绕设备设计原则、错误与事故预防、人体工程学适配三大主线展开…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑