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

发布时间:2026/9/9 12:18:42

浅谈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/9/9 12:18:42

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

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

2026/9/9 12:13:41

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

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

2026/9/9 12:13:41

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

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

2026/9/9 13:19:15

终端AI代理opencode实战指南:开源模型无关,从安装到高效落地

最近AI编程助手这个圈子越来越热闹了。以前大家说的都是Copilot、Cursor这种IDE里的自动补全,现在风向明显变了:终端里的AI代理(agent)成了新宠,能直接接管整个项目,自己读代码、改文件、跑命令。而最近热度…

2026/9/9 13:19:15

Ruffle Flash 播放器完整指南:五分钟唤醒你的 SWF 老回忆

Ruffle Flash 播放器完整指南:五分钟唤醒你的 SWF 老回忆 【免费下载链接】ruffle A Flash Player emulator written in Rust 项目地址: https://gitcode.com/GitHub_Trending/ru/ruffle 你玩过的 Flash 老游戏,如今打开 SWF 文件只剩一片空白&am…

2026/9/9 13:19:15

基于MATLAB的BP神经网络股票价格预测系统实战

做股票价格预测这件事,很多人一上来就想着堆模型、上LSTM、搞Transformer,说实话有点本末倒置。我在MATLAB里用反向传播神经网络(BPNN)做了一套完整的价格预测小系统,带GUI界面,代码全部开源可跑&#xff0…

2026/9/9 13:14:14

从收藏到掌握:用技能地图和刻意练习把知识变成能力

去年整理收藏夹和网盘时,我面对过一个尴尬的事实:攒了三百多个教程、买过十几门课,笔记软件里躺着上千条摘抄。但当别人问起“你擅长什么”的时候,我居然答不上来。收藏的东西很多,真正变成 skills 的却很少。这件事促…

2026/9/9 13:11:35

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/8 7:15:15

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/8 7:15:10

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/9 0:00:48

MHS模型硬件标准:让大模型像调用软件一样控制物理设备

让Claude真正看着显微镜说“这个细胞形态不太对”,或者让大模型自己调一版机械臂的运动轨迹,这事儿听上去已经很接近科幻片了。但你真上手试一次就会发现,模型不缺智商,缺的是一个能插进显微镜、机械臂、激光控制器里的“通用插座…

2026/9/9 0:00:48

AI五大核心方向详解:从机器学习到大模型,零基础转行选哪条?

会有人告诉我,他想转行学AI,但打开招聘网站一看直接傻眼:机器学习、深度学习、自然语言处理、计算机视觉、大模型应用……满屏都是这些词,好像每个都会一点,又好像每个都离自己很远。还有人上来就问“学Python还是学Ja…

2026/9/9 0:00:49

从50行最小循环到生产级AI引擎:工程化改造全解析

直接说干货。这一章我写的不是那种"hello world跑通某个模型"的教程,而是把AI引擎当做一个真正要上线、要被人调用、要扛流量的系统来聊。从最初只有50行的最小循环,到能够承载生产流量的AI引擎,中间差的不是代码量,而是…

2026/9/7 16:23:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/9 10:21:54

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

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

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

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