前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >利用Android系统源码中giflib实现播放gif文件

利用Android系统源码中giflib实现播放gif文件

原创
作者头像
包子388321
修改2020-07-01 17:46:29
1.9K0
修改2020-07-01 17:46:29
举报
文章被收录于专栏:包子的书架包子的书架

前言

目前市场上流行的图片框架都是可以很好的处理gif图片,像glide是通过Java层来处理gif的展示,但是Java层来处gif的展示,始终会存在OOM的风险。今天学习了一下Android系统源码中拓展源码的giflib加载gif。

探讨一下.jpeg
探讨一下.jpeg

开始扯犊子

想要实现gif的一帧一帧的播放,必须要了解一下gif的大体结构,关于gif格式图片的详细解析 ,这边大体介绍一下。

gif的结构介绍

GIF是Compuserve公司开发的图形文件格式,一共有两个版本87a和89a,现在市场上大部分使用的都是89a的版本,89a版本相对87a多了数据扩展块,也正是我们实现需要用到的内容。下图:列出gif大体文件构造元素。

GIF文件结构图.png
GIF文件结构图.png

由图我们可以看出:

GIF文件结构:文件头,数据流,结束器。

GIF文件内部是按块划分的,包括控制块( Control Block )和数据块(Data Sub-blocks)两种。

数据流包含:逻辑屏幕标识,全局颜色列表,符图像块,图形控制拓展,应用程序拓展等。

  • 思考 一个gif有很多帧的文件图片,每一帧图片的展示需要通过绘制图片的像素和每一个像素的颜色值,以及还需要知道每一帧到下一帧的展示的时间。
  • 设计 1、利用giflib的DGIFOpenFileName以文件流的形式加载gif文件,获取到gif的图片结构包括:图片的宽高,像素,颜色表,像素帧,gif中的图片帧数,每一帧数需要播放的时间(距离下一帧图像出现的时间)。 2、创建GIFBean的结构体,用来保存:第一帧到下一帧的计算每一帧的延迟时间,gif图片中总共的图片帧数,当前播放的是哪一帧。 3、通过绘制每一帧的像素来达到展示,利用每一帧延迟时间对下一帧的图像进行播放。 4、绘制中,需要遍历每一帧的图像的像素,并从gif中的颜色表中取值,然后对像素进行填色。
    gif内部结构草图.png
    gif内部结构草图.png
敲代码.jpg
敲代码.jpg
Android系统源码的giflib介绍

个人这边以Android8.1的系统源码的giflib来讲解 \android-8.1.0_r1\external\giflib

  • GifFileType结构体
代码语言:txt
复制
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;
  • 涉及到的结构体
代码语言:txt
复制
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文件结构的一些信息。

撸起袖子开始搬砖
  • 首先加载gif文件图片,获取图片的基本信息 giflib有两种加载方式:以文件流方式打开gif文件和以文件句柄方式 这边直接采用文件流方式
代码语言:txt
复制
const char * path = env->GetStringUTFChars(path_, NULL);
int error;
//以文件流来打开gif文件,读取gif文件信息,并且返回一个结构体
GifFileType *gifFileType = DGifOpenFileName(path, &error);
//将整个GIF读入内核,挂起它所有的状态信息 GifFileType指针
DGifSlurp(gifFileType);

创建一个我们自己的结构体用来存储:

  1. 每一帧的延迟时间
  2. 总的图像帧数
  3. 当前的播放帧位置
代码语言:txt
复制
typedef struct GifBean{
    int current_frame;
    int total_frame;
    int *delays;    /* 每一帧的延迟时间都有可能不一样, 所以列表形式存储 */
} GifBean;
  • 接着计算每一帧的延迟时间(每一帧的延迟时间都有可能不一样)和存储所有的帧数
代码语言:txt
复制
 //给自定义的结构体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;
  • 获取gif的宽高
代码语言:txt
复制
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;
}
  • 绘制每一帧图像
代码语言:txt
复制
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];
}
  • 绘制每一帧的像素
代码语言:txt
复制
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 表示是每行字节数的数量
    }
}
  • 在Java中的调用和显示
代码语言:txt
复制
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图片播放了。

活不过今晚了.gif
活不过今晚了.gif

参考文献

https://blog.csdn.net/wzy198852/article/details/17266507

https://www.cnblogs.com/xiaoxiaoboke/archive/2012/02/13/2349770.html

https://www.liangzl.com/get-article-detail-163059.html

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 开始扯犊子
    • gif的结构介绍
      • Android系统源码的giflib介绍
        • 撸起袖子开始搬砖
        • 参考文献
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档