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.2=90像素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><groupId>com.madgag</groupId><artifactId>animated-gif-lib</artifactId><version>1.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();//如果只有一帧,直接压缩到20kbif(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; i+=sec) {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);}}

浅谈Thumbnails压缩gif图片质量的实现方式相关推荐

  1. 浅谈常用的几种web攻击方式以及解决办法

    身在互联网的时候,web在给我们带来便利的同时,有些人也在盯着这些便利,因此出现了攻击网站的现象.所以我们在开发的时候,要注意这些容易被攻击的地方,以及做好防御的措施,下面将介绍一些这些 常见的攻击手 ...

  2. java 对象之间转换_浅谈java对象之间相互转化的多种方式

    浅谈java对象之间相互转化的多种方式,对象,属性,参数,赋值,不支持 浅谈java对象之间相互转化的多种方式 易采站长站,站长之家为您整理了浅谈java对象之间相互转化的多种方式的相关内容. 第一种 ...

  3. 浅谈模型压缩之量化、剪枝、权重共享

    之前陆陆续续看了许多模型压缩相关的文章,自己业务中也接触过一些相关的客户,今天周末没事做,把模型压缩相关的内容整理一下做个分享.可能更多地从科普的角度去介绍,因为我也不是专业做这方面技术的研究. 首先 ...

  4. 浅谈ffmpeg 压缩视频

    1 首选需要安装ffmpeg 安装ffmpeg Linux 宝塔面板安装FFMpeg和编码库 yum install https://download1.rpmfusion.org/free/el/r ...

  5. 浅谈MySQL中客户端与服务端连接方式

    一.前言 前面五篇文章给大家介绍了如何安装数据库到一条SQL在服务端需要经历那些步骤才能够解析完成,相信大家对数据库也有了初步的了解,但俗话说的好"纸上谈兵不如躬行实践",前面学习 ...

  6. 浅谈蓝牙耳机的听诊器效应、佩戴方式、蓝牙耳机类型

    随着科学技术的飞速发展,越来越多的新思维.新技术开始切切实实地影响着我们的生活.耳机领域也随着近几年技术的更新换代开始了一场庞大的革命即无线蓝牙耳机取代有线耳机.相信大家虽然可能正在使用无线蓝牙耳机但 ...

  7. 以【优酷】为例,浅谈电信级多媒体运营平台的实现方式(1)

    当前,流媒体视频技术已经应用到了各个领域,像 Youtube.优酷.爱奇艺.腾讯视频.抖音.花椒直播.一直播.学而思网校.TutorABC等等,这些不同行业的视频运营平台均获得了极大成功,然而这些平台 ...

  8. java线程池和线程实例化_浅谈Java 线程池原理及使用方式

    一.简介 什么是线程池? 池的概念大家也许都有所听闻,池就是相当于一个容器,里面有许许多多的东西你可以即拿即用.java中有线程池.连接池等等.线程池就是在系统启动或者实例化池时创建一些空闲的线程,等 ...

  9. 浅谈四种常见的agv导航方式及各自的优缺点

    了解过AGV小车的朋友都知道,agv小车也叫作自动搬运车.搬运机器人等,agv小车主要是通过电磁.光学或其它自动导引装置,能够实现自主规划线路自动行驶的一个过程,是一种具有安全性高以及拥有各种搭载功能 ...

最新文章

  1. 如何使用Sublime Text 2重新格式化HTML代码?
  2. LeetCode Perfect Squares
  3. ActionT和FuncT委托
  4. vim cheat-sheet
  5. .html()和.text()及.val()的区别
  6. 对一次通过CISSP考试的建议
  7. 进击的UI-------------------RAC
  8. java数据存在ie中_[Java教程]解决在IE中获取数据的缓存问题,运行环境为node.js
  9. ArcSDE常用操作命令
  10. echart 三维可视化地图_Echarts三维坐标系
  11. taskmgr(任务管理器)无法出来的解决办法
  12. 不用远程软件,校园网电脑之间如何远程连接
  13. php 计算工资,php计算税后工资的方法_PHP
  14. 谁将成为人工智能行业的“领头羊”?
  15. html中canvas动画游戏显示,【Fes】基于canvas的前端动画/游戏入门(一)
  16. 陳三甲网络笔记:赚钱路上,一些人生思考 陳三甲网络笔记
  17. 使用git强行切换分支
  18. 树莓派4B-Python-控制DS18B20(温度传感器)
  19. Flutter Text组件 文字的对齐、数字和字母对齐中文
  20. 零基础!!最全计算机三级网络技术备考指南!!

热门文章

  1. 厦门大学437社会工作实务考研参考书目
  2. Android Navigation与BottomNavigationView实现底部导航栏
  3. 简单电脑版微信双开方法多开bat分身代码教程
  4. Productivity Power Tools工具
  5. hbuilder怎么做登录界面_HBuilder如何安装和使用?(教程)
  6. 自定义控件其实很简单 五
  7. UILabel根据字数计算高度,宽度,行数
  8. SAP现金管理(Cash Management)的常见问题
  9. Flash与服务器通信简介
  10. [NOI 1997] 积木游戏(dp)