Android上的GIF图片缩放库的开发

文章详细介绍了在Android平台下实现GIF图片缩放的三种方法,包括自定义Java版本的GIF解码器和编码器、使用Android上的ImageMagick库和GIFlib库。经过对比分析,作者最终选择了Java版本的GIF解码器和编码器,并深入探讨了其在处理透明区域问题时的改进方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在做Android上面的GIF图片的缩放的开发,Android原生的框架并不支持这个功能,使用BitmapFactory去解码GIF图片也只是把GIF图片的第一帧解码到Bitmap中而已。

经过一定的调研搜索,我确定了有三种可行的方法:

1. 使用Java版本的GIF解码器和编码器将GIF中的每一帧都解码出来,然后缩放,再编码到新的GIF文件中。

2. 使用Android上面的ImageMagick库,https://github.com/paulasiimwe/Android-ImageMagick

3. 使用GIFlib库


Android版本的ImageMagick库很大,有好几兆,而且我试了一下用它提供的接口进行GIF的缩放是没有效果的,而且接口设计得也很不优雅,注释写的也很费解,最后果断放弃了这个方法。也考虑过将ImageMagick中与GIF相关的模块抠出来单独编译,以使库的体积减小,但是我尝试了一下,需要去摸清楚ImagMagick的框架和代码,大家也知道去阅读别人的代码,始终是一个吃力的活。


至于GIFlib库,貌似Android系统就是用的这个库,但是好像没有人做GIFlib的应用层移植,所以这个方法我也放弃了。


关于第一种方案,我在Github上面找到了Java版本的GIF解码器和编码器,初步尝试了将每一帧解码出来-》缩放-》编码的方法,初步的结论是可行,但是有一个致命的问题,就是重新编码后的图片的透明区域没有了。


开始时,我以为如果GIF图片没有透明区域(变成了有色的,通常情况下是黑色),那这个问题就不会被发现了,可是我想错了,动画的GIF图片中的非首帧通常情况下都是有透明区域的,因为如果某一帧的某个区域现对于前面一帧是没有变化的话,那么这一帧的这个区域就是透明的,透明区域会在GIF图片中大量存在,所以这个透明区域的问题一定得解决。


三种方案如何抉择?将自己有限的时间精力投入到哪一种方案会使问题得以解决?我的分析是第一种和第二种方案的代码量大,而且需要自己去读懂别人的代码,这是一个很吃力的活,需要投入大量的精力和时间,而且在阅读的过程中自己动手去做一些小demo进行验证的可能性是很小的。而第三种方案,Java版本的解码器和编码器的代码量小,如果加上自己去研究GIF的文件结构(http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp),再加上自己在这个过程中不断地写一些小demo进行验证,自己的积极性会被不断地提高,相信问题会很快解决,所以最终我选择了第三种方案。


首先要说一下GIF的透明区域的实现原理,GIF不像PNG或者WEBP那样有自己的alpha通道,GIF是通过指定颜色表(通常有256中颜色)中的某一个颜色为透明色,那么图片中只要有某个像素的颜色为这个值,那么在绘制的时候这个像素就会被绘制为透明的,所以在这种情况下,GIF能够用的颜色就少了一种,只有255中颜色了。


所以了解了这个原理,那么来看看Java版本的编码器下面这段代码:

protected void analyzePixels() {
	    int len = pixels.length;
	    int nPix = len / 3;
	    indexedPixels = new byte[nPix];
	    NeuQuant nq = new NeuQuant(pixels, len, sample);
	    // initialize quantizer
	    colorTab = nq.process(); // create reduced palette
	    // convert map from BGR to RGB
	    for (int i = 0; i < colorTab.length; i += 3) {
	      byte temp = colorTab[i];
	      colorTab[i] = colorTab[i + 2];
	      colorTab[i + 2] = temp;
	      usedEntry[i / 3] = false;
	    }
	    // map image pixels to new palette
	    int k = 0;
	    for (int i = 0; i < nPix; i++) {
	      int index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
	      usedEntry[index] = true;
	      indexedPixels[i] = (byte) index;
	    }
	    pixels = null;
	    colorDepth = 8;
	    palSize = 7;
	    // get closest match to transparent color if specified
	    if (transparent != -1) {
	      transIndex = findClosest(transparent);
	    }
	  }
这段代码所做的事情就是生成颜色表,并将图像的像素点的颜色值与颜色表进行索引,transparent就是编码器的使用者设置的透明色,也就是说,只要某个像素的颜色值为transparent的值,那么这个点就是透明的。代码中findClosest这个方法是有问题的,找到一个最接近transparent的值的index作为transindex,那就是说非常有可能找的颜色值并不是transparent,而是一个接近transparent的值,那就是说在这样的情况下原本应该为透明的地方,却没有成为透明,修改如下:

	/**
	 * Analyzes image colors and creates color map.
	 */
	protected void analyzePixels() {
		int len = pixels.length;
		int nPix = len / 3;
		indexedPixels = new byte[nPix];
		NeuQuant nq = new NeuQuant(pixels, len, sample);
		// initialize quantizer
		colorTab = nq.process(); // create reduced palette
		// convert map from BGR to RGB
		for (int i = 0; i < colorTab.length; i += 3) {
			byte temp = colorTab[i];
			colorTab[i] = colorTab[i + 2];
			colorTab[i + 2] = temp;
			usedEntry[i / 3] = false;
		}
		// map image pixels to new palette
		int k = 0;
		for (int i = 0; i < nPix; i++) {
			int index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff,
					pixels[k++] & 0xff);
			usedEntry[index] = true;
			indexedPixels[i] = (byte) index;
		}
		pixels = null;
		colorDepth = 8;
		palSize = 7;

		if (transparent != -1) {
			transIndex = nq.map((transparent >> 0) & 0xff,
					(transparent >> 8) & 0xff, (transparent >> 16) & 0xff);
		}
	}

从nq中直接map,因为传进来的transparent值是从decoder中取出来的(我已经在decoder中加了相应的代码),所以不用担心transparent值会被其他非透明像素点使用。而且在接口设计上,setTransparent这个值的传入参数transparent本来就应该从图片中取出来,因为你并不知道哪些颜色是没有被使用的,除非你写出相应的代码去判断。


编码器的修改主要在于,透明色的处理上面,在解码器上面也有一些修改,主要包括:

1. 增加获取loopCount的接口,loopCount参数指示动画的播放模式,是否重复等等

2. 增加获取透明色的接口,这里需要注意的是,在图像被加载为Bitmap后,如果config是ARGB8888的话,对于透明的像素,RGB的值在解码的时候是被忽略掉了的,所以ARGB都为0。

3. 获取每一帧的dispose参数的方法,getDisposalMethod,dispose参数决定下一帧在绘制时,对上一帧的处理方法,是留下上一帧呢,还是全部抹掉



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值