目前市场上流行的图片框架都是可以很好的处理gif图片,像glide是通过Java层来处理gif的展示,但是Java层来处gif的展示,始终会存在OOM的风险。今天学习了一下Android系统源码中拓展源码的giflib加载gif。
想要实现gif的一帧一帧的播放,必须要了解一下gif的大体结构,关于gif格式图片的详细解析 ,这边大体介绍一下。
GIF是Compuserve公司开发的图形文件格式,一共有两个版本87a和89a,现在市场上大部分使用的都是89a的版本,89a版本相对87a多了数据扩展块,也正是我们实现需要用到的内容。下图:列出gif大体文件构造元素。
由图我们可以看出:
GIF文件结构:文件头,数据流,结束器。
GIF文件内部是按块划分的,包括控制块( Control Block )和数据块(Data Sub-blocks)两种。
数据流包含:逻辑屏幕标识,全局颜色列表,符图像块,图形控制拓展,应用程序拓展等。
个人这边以Android8.1的系统源码的giflib来讲解 \android-8.1.0_r1\external\giflib
typedef int GifWord;
typedef struct GifFileType {
GifWord SWidth, SHeight; /* gif的逻辑宽高 */
GifWord SColorResolution; /* gif文件需要生成的多少种颜色,最大不会超过255 */
GifWord SBackGroundColor; /* 画布的背景颜色 */
GifByteType AspectByte; /* 用来计算宽高比 */
ColorMapObject *SColorMap; /* 颜色列表,如果局部颜色列表为空,这边表示的是全局颜色列表,否则是局部颜色列表 */
int ImageCount; /* gif的图像帧数 */
GifImageDesc Image; /* 当前帧图像 */
SavedImage *SavedImages; /*用来存储已经读取过得图像数据 */
int ExtensionBlockCount; /* 表示图像扩展数据块数量 */
ExtensionBlock *ExtensionBlocks; /* 用来存储图像的数据块 */
int Error; /* 错误码 */
void *UserData; /* 用来存储开发者的数据,类似view设置tag */
void *Private; /* Don't mess with this! */
} GifFileType;
typedef unsigned char GifByteType;
typedef int GifWord;
typedef struct GifColorType {
GifByteType Red, Green, Blue; /* rgb 三种色 */
} GifColorType;
//表示颜色表的结构体,用来存储每一个像素的rgb颜色
typedef struct ColorMapObject {
int ColorCount;
int BitsPerPixel;
bool SortFlag;
GifColorType *Colors; /* on malloc(3) heap */
} ColorMapObject;
// 图像的基本参数,宽高,颜色表,gif存储的是顺序或者是交错的方式
typedef struct GifImageDesc {
GifWord Left, Top, Width, Height; /* Current image dimensions. */
bool Interlace; /* Sequential/Interlaced lines. */
ColorMapObject *ColorMap; /* The local color map */
} GifImageDesc;
// 数据块: 字节数,字节表,块函数编码,表示是哪一种数据块(图形控制扩展,图形文本扩展等)
typedef struct ExtensionBlock {
int ByteCount;
GifByteType *Bytes; /* on malloc(3) heap */
int Function; /* The block function code */
#define CONTINUE_EXT_FUNC_CODE 0x00 /* continuation subblock */
#define COMMENT_EXT_FUNC_CODE 0xfe /* 注释扩展*/
#define GRAPHICS_EXT_FUNC_CODE 0xf9 /* 图形控制扩展(89a版本才有的) */
#define PLAINTEXT_EXT_FUNC_CODE 0x01 /* 图形文本扩展*/
#define APPLICATION_EXT_FUNC_CODE 0xff /* 应用程序扩展 */
} ExtensionBlock;
上面介绍的结构体,都是涉及到gif文件结构的一些信息。
const char * path = env->GetStringUTFChars(path_, NULL);
int error;
//以文件流来打开gif文件,读取gif文件信息,并且返回一个结构体
GifFileType *gifFileType = DGifOpenFileName(path, &error);
//将整个GIF读入内核,挂起它所有的状态信息 GifFileType指针
DGifSlurp(gifFileType);
创建一个我们自己的结构体用来存储:
typedef struct GifBean{
int current_frame;
int total_frame;
int *delays; /* 每一帧的延迟时间都有可能不一样, 所以列表形式存储 */
} GifBean;
//给自定义的结构体GifBean分配空间
GifBean *gifBean = (GifBean*)(malloc(sizeof(GifBean)));
//初始化,清空内存
memset(gifBean, 0, sizeof(GifBean));
//将我们创建的gifbean的数据指针传到GifFileType的用户数据指针变量
gifFileType->UserData = gifBean;
//给gif所有帧播放的时间,分配内存空间
gifBean->delays = (int *)malloc(sizeof(int) * gifFileType->ImageCount);
//清空初始化分配好的内存
memset(gifBean->delays, 0, sizeof(int) * gifFileType->ImageCount);
//存储gif文件一共有多少帧
gifBean->total_frame = gifFileType->ImageCount;
//定义拓展块结构体
ExtensionBlock *extensionBlock;
for (int i = 0; i < gifFileType->ImageCount; ++i) {
// SaveImages变量用来存储已经读取过得图像数据。
// 获取每一帧图像的资源
SavedImage savedImage = gifFileType->SavedImages[i];
// 遍历当前这一帧的图片中的拓展块的数据
for (int j = 0; j < savedImage.ExtensionBlockCount; ++j) {
//拿到图形拓展的内容
if (savedImage.ExtensionBlocks[j].Function == GRAPHICS_EXT_FUNC_CODE) {
extensionBlock = &savedImage.ExtensionBlocks[j];
break;
}
}
if (extensionBlock) {
//拿到图形拓展块后,计算每一帧的播放时长
int frame_delay = 10 * (extensionBlock->Bytes[2] << 8 | extensionBlock->Bytes[1]);
LOGE("时间 %d ",frame_delay);
gifBean->delays[i] = frame_delay;
}
}
LOGE("gif 长度大小 %d ",gifFileType->ImageCount);
env->ReleaseStringUTFChars(path_, path);
//到这做了两件事:一是计算gif图片中,每一帧图片的播放时长,二是保持总共多少帧图片
//返回结构体的首地址
return (jlong) gifFileType;
JNIEXPORT jint JNICALL
Java_com_jason_ndk_gifdemo_GifHandler_getGifWidth(JNIEnv *env, jobject thiz, jlong gif_point) {
GifFileType *gifFileType = (GifFileType *) gif_point;
//返回gif的宽度
return gifFileType->SWidth;
}
JNIEXPORT jint JNICALL
Java_com_jason_ndk_gifdemo_GifHandler_getGifHeight(JNIEnv *env, jobject thiz, jlong gif_point) {
GifFileType *gifFileType = (GifFileType *) gif_point;
//返回gif的高度
return gifFileType->SHeight;
}
JNIEXPORT jint JNICALL
Java_com_jason_ndk_gifdemo_GifHandler_updateFrame(JNIEnv *env, jobject thiz, jlong gif_point,
jobject bitmap) {
GifFileType *gifFileType = (GifFileType *) gif_point;
//拿出我们所保持在GifFileType的GIFBean数据
GifBean *gifBean = (GifBean *)gifFileType->UserData;
AndroidBitmapInfo info;
//代表一幅图片的像素数组
void *pixel;
//获取bitmap的图片信息
AndroidBitmap_getInfo(env, bitmap, &info);
//锁定bitmap
AndroidBitmap_lockPixels(env, bitmap, &pixel);
//绘制每一帧
drawFrame(gifFileType, gifBean, info, pixel);
//播放完成之后,循环到下一帧
gifBean->current_frame += 1;
LOGE("当前帧 %d ",gifBean->current_frame);
if (gifBean->current_frame >= gifBean->total_frame-1) {
//当前播放到最后一帧,重置为第一帧,开始重新播放
gifBean->current_frame = 0;
LOGE("重新过来 %d ",gifBean->current_frame);
}
//接触锁定bitmap
AndroidBitmap_unlockPixels(env, bitmap);
return gifBean->delays[gifBean->current_frame];
}
void drawFrame(GifFileType *gifFileType, GifBean *gifBean, AndroidBitmapInfo info, void *pixels) {
//播放底层代码
//拿到当前帧
SavedImage savedImage = gifFileType->SavedImages[gifBean->current_frame];
//
GifImageDesc frameInfo = savedImage.ImageDesc;
//整幅图片的首地址
int* px = (int *)pixels;
//每一行的首地址
int *line;
//其中一个像素的位置 不是指针 在颜色表中的索引
int pointPixel;
//gif的字节类型
GifByteType gifByteType;
//gif图片的像素颜色类型
GifColorType gifColorType;
//获取gif图片的颜色表
ColorMapObject* colorMapObject=frameInfo.ColorMap;
//计算像素,对每一个像素进行颜色填充
//px = px + info.stride * frameInfo.Top; //会直接被向上去值,px是int,stride是char,位数不同
px = (int *) ((char*)px + info.stride * frameInfo.Top);
for (int y = frameInfo.Top; y < frameInfo.Top + frameInfo.Height; ++y) {
//更新行数
line = px;
for (int x = frameInfo.Left; x < frameInfo.Left + frameInfo.Width; ++x) {
pointPixel = (y - frameInfo.Top) * frameInfo.Width + (x - frameInfo.Left);
//获取gif这个像素点的颜色类型,是red,green,blue中的一个,GifByteType 本身是无符号的字符 unsigned char
gifByteType = savedImage.RasterBits[pointPixel];
//获取到当前像素的颜色RGB的结构体
gifColorType = colorMapObject->Colors[gifByteType];
line[x] = argb(255,gifColorType.Red, gifColorType.Green, gifColorType.Blue);
}
//像素位置更新
px = (int *) ((char*)px + info.stride); //info.stride 表示是每行字节数的数量
}
}
public void load(ImageView imageView, final String path) {
imageViewWeakReference = new WeakReference<ImageView>(imageView);
new Thread() {
@Override
public void run() {
load(path);
}
}.start();
}
private void load(String path) {
File file = new File(path);
if(!file.exists()) {
System.err.println("not found gif file");
return;
}
GifHandler gifHandler = new GifHandler(file.getAbsolutePath());
int width = gifHandler.getWidth();
int height = gifHandler.getHeight();
Bitmap bitmap= Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
int nextFrame = gifHandler.updateFrame(bitmap);
handler.sendEmptyMessageDelayed(1,nextFrame);
}
private Handler handler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
ImageView gifIv = imageViewWeakReference.get();
if(gifIv == null) {
return;
}
int mNextFrame = gifHandler.updateFrame(bitmap);
handler.sendEmptyMessageDelayed(1,mNextFrame);
gifIv.setImageBitmap(bitmap);
}
};
最后运行就可以看到gif图片播放了。
https://blog.csdn.net/wzy198852/article/details/17266507
https://www.cnblogs.com/xiaoxiaoboke/archive/2012/02/13/2349770.html
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。