一 gif编码原理
图形控制扩展块(Graphic Control Extension)
- 固定值:0xF9
- 作用:用来跟踪下一帧的信息和渲染形式
注释扩展块
- 固定值0xFE
- 作用 :可以用来记录图形、版权、描述等任何的非图形和控制的纯文本数据
图形文本扩展块
- 固定值0x01
- 作用:控制绘制的参数,比如左边界偏移量
应用程序扩展
- 固定值 0xFF
- 作用:这是提供给应用程序自己使用的,应用程序可以在这里定义自己的标识、信息。可以做到当前app所生成的gif只能由我这个app打开
a r g b 一个像素 4 个字节
a 24位 r 《16 位 g <位 b
二 Android常见的gif播放方式
Java方式: Movie类
创建Movie实例,绘制每一帧图片来达到Gif动态效果。
缺点: 部分Gif图片不能自适应大小,播放速度比实际播放速度快,如果要显示的gif过大,还会出现OOM的问题。
GifView
GifView --》GifHelper gif文件 --》编码—》解析 --》播放
Glide 也是采用GifHelper的方式
缺点:OOM的问题
三 利用系统源码实现gif播放
绘制原理
- 利用像素算法遍历绘制图形
优点:占用内存小
1 实现步骤
- 加载gif图片
- 获取gif图片的信息,包括 宽,高 帧数 每帧播放时长 当前播放的位置
- 每一帧播放
2 主要代码
第一步 引入安卓系统加载cif图片的c文件
第二步 JNI加载解析git图片
native-lib.cpp
#include <jni.h>
#include <string>
#include "gif_lib.h"
#include <android/log.h>
#include <android/bitmap.h>
#include <malloc.h>
#define LOG_TAG "david"
#define argb(a,r,g,b) ( ((a) & 0xff) << 24 ) | ( ((b) & 0xff) << 16 ) | ( ((g) & 0xff) << 8 ) | ((r) & 0xff)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
typedef struct GifBean{
int current_frame;
int total_frame;
int *dealys;
} GifBean;
extern "C"{
//绘制一张图片
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;
GifByteType gifByteType;
GifColorType gifColorType;
ColorMapObject* colorMapObject=frameInfo.ColorMap;
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);
gifByteType = savedImage.RasterBits[pointPixel];
gifColorType = colorMapObject->Colors[gifByteType];
line[x] = argb(255,gifColorType.Red, gifColorType.Green, gifColorType.Blue);
}
px = (int *) ((char*)px + info.stride);
}
}
JNIEXPORT jlong JNICALL
Java_com_dongnao_gifplayerdemo_GifHandler_loadPath(JNIEnv *env, jobject instance, jstring path_) {
const char *path = env->GetStringUTFChars(path_, 0);
int err;
//用系统函数打开一个gif文件 返回一个结构体,这个结构体为句柄
GifFileType * gifFileType=DGifOpenFileName(path, &err);
DGifSlurp(gifFileType);
GifBean *gifBean = (GifBean *) malloc(sizeof(GifBean));
// 清空内存地址
memset(gifBean, 0, sizeof(GifBean));
gifFileType->UserData=gifBean;
gifBean->dealys = (int *) malloc(sizeof(int) * gifFileType->ImageCount);
memset(gifBean->dealys, 0, sizeof(int) * gifFileType->ImageCount);
gifBean->total_frame = gifFileType->ImageCount;
ExtensionBlock* ext;
for (int i = 0; i < gifFileType->ImageCount; ++i) {
SavedImage frame = gifFileType->SavedImages[i];
for (int j = 0; j < frame.ExtensionBlockCount; ++j) {
if (frame.ExtensionBlocks[j].Function == GRAPHICS_EXT_FUNC_CODE) {
ext = &frame.ExtensionBlocks[j];
break;
}
}
if (ext) {
int frame_delay = 10 * (ext->Bytes[2] << 8 | ext->Bytes[1]);
LOGE("时间 %d ",frame_delay);
gifBean->dealys[i] = frame_delay;
}
}
LOGE("gif 长度大小 %d ",gifFileType->ImageCount);
env->ReleaseStringUTFChars(path_, path);
return (jlong) gifFileType;
}
JNIEXPORT jint JNICALL
Java_com_dongnao_gifplayerdemo_GifHandler_getWidth(JNIEnv *env, jobject instance, jlong ndkGif) {
GifFileType* gifFileType= (GifFileType *) ndkGif;
return gifFileType->SWidth;
}
JNIEXPORT jint JNICALL
Java_com_dongnao_gifplayerdemo_GifHandler_getHeight(JNIEnv *env, jobject instance, jlong ndkGif) {
GifFileType* gifFileType= (GifFileType *) ndkGif;
return gifFileType->SHeight;
}
JNIEXPORT jint JNICALL
Java_com_dongnao_gifplayerdemo_GifHandler_updateFrame(JNIEnv *env, jobject instance, jlong ndkGif,
jobject bitmap) {
//强转代表gif图片的结构体
GifFileType *gifFileType= (GifFileType *)ndkGif;
GifBean * gifBean= (GifBean *) gifFileType->UserData;
AndroidBitmapInfo info;
//代表一幅图片的像素数组
void *pixels;
AndroidBitmap_getInfo(env,bitmap,&info);
//锁定bitmap 一幅图片--》二维 数组 ===一个二维数组
AndroidBitmap_lockPixels(env,bitmap,&pixels);
// TODO
drawFrame(gifFileType, gifBean, info, pixels);
//播放完成之后 循环到下一帧
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);
}
AndroidBitmap_unlockPixels(env, bitmap);
return gifBean->dealys[gifBean->current_frame];
}
}
第3步, 调用显示
public class GifHandler {
private long gifAddr;
public GifHandler(String path) {
this.gifAddr = loadPath(path);
}
static {
System.loadLibrary("native-lib");
}
private native long loadPath(String path);
public native int getWidth(long ndkGif);
public native int getHeight(long ndkGif);
public native int updateFrame(long ndkGif, Bitmap bitmap);
public int getWidth() {
return getWidth(gifAddr);
}
public int getHeight() {
return getHeight(gifAddr);
}
public int updateFrame(Bitmap bitmap) {
return updateFrame(gifAddr,bitmap);
}
}
public class MainActivity extends AppCompatActivity {
Bitmap bitmap;
GifHandler gifHandler;
ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView= (ImageView) findViewById(R.id.image);
}
public void ndkLoadGif(View view) {
File file=new File(Environment.getExternalStorageDirectory(),"demo.gif");
gifHandler = new GifHandler(file.getAbsolutePath());
Log.i("tuch", "ndkLoadGif: "+file.getAbsolutePath());
//得到gif width height 生成Bitmap
int width=gifHandler.getWidth();
int height=gifHandler.getHeight();
bitmap= Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
int nextFrame=gifHandler.updateFrame(bitmap);
handler.sendEmptyMessageDelayed(1,nextFrame);
}
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
int mNextFrame=gifHandler.updateFrame(bitmap);
handler.sendEmptyMessageDelayed(1,mNextFrame);
imageView.setImageBitmap(bitmap);
}
};
}
注意:要把显示的gif图片导入到手机对应的加载目录