学习目标:
NDK之加载89a版本的gif学习内容:
gif的版本有87a
,89a
这两种不同的编码方法,所以解码方式也有所不同。想知道你是什么版本的gif?简单
右键图片 ,文本打开,然后看下图
那今天先手敲一个89a
(现在都是89a了)
既然是从NDK的方向入手,当然得先创建一个Native工程,目录如下所示
cpp文件夹目录下,没有被框起来的是gifLib库
,你会问这些哪里来的?我告诉你,在下面,把里面的C/C++文件c v
里面的代码都是google工程师的产出,至于代码我就不看了,比较繁琐,有很多,有兴趣可以自行研究。接下来看以下我们在jni里的入口函数
#include <jni.h>
#include <string>
#include <android/bitmap.h>
#include <android/log.h>
//适用89a版本的gif
extern "C" {
#include "gif_lib.h"
}
#define argb(a, r, g, b) ( ((a) & 0xff) << 24 ) | ( ((b) & 0xff) << 16 ) | ( ((g) & 0xff) << 8 ) | ((r) & 0xff)
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
//gif播放信息实体类
struct GifBean {
//当前帧数
int current_frame;
//总帧数
int total_frame;
//每帧的播放时间
int *delays;
};
//绘制函数声明
void drawFrame(GifFileType *pType, AndroidBitmapInfo info, void *pVoid);
extern "C"
JNIEXPORT jlong JNICALL
Java_com_example_dymaicimagegif_GifUtil_startLoad(JNIEnv *env, jclass clazz, jstring path) {
const char *temPath = env->GetStringUTFChars(path, 0);
int error;
GifFileType *gifFileType = DGifOpenFileName(temPath, &error);
if ((error == D_GIF_ERR_OPEN_FAILED) || (error == D_GIF_ERR_NOT_ENOUGH_MEM)) {
return -1;
}
//初始化缓冲区(一次性把gif进行解码,然后输出解码后的数据)
DGifSlurp(gifFileType);
GifBean *gifBean = static_cast<GifBean *>(malloc(sizeof(GifBean)));
memset(gifBean, 0, sizeof(GifBean));
gifFileType->UserData = gifBean;
gifBean->delays = (int *) malloc(sizeof(int) * gifFileType->ImageCount);
memset(gifBean->delays, 0, sizeof(int) * gifFileType->ImageCount);
gifBean->current_frame = 0;
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]);
LOGD("时间 %d ",frame_delay);
gifBean->delays[i] = frame_delay;
}
}
//手动释放
env->ReleaseStringUTFChars(path, temPath);
return reinterpret_cast<jlong>(gifFileType);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_dymaicimagegif_GifUtil_getWidth(JNIEnv *env, jclass clazz, jlong gif_handler) {
GifFileType *gifFileType = reinterpret_cast<GifFileType *>(gif_handler);
return gifFileType->SWidth;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_dymaicimagegif_GifUtil_getHeight(JNIEnv *env, jclass clazz, jlong gif_handler) {
GifFileType *gifFileType = reinterpret_cast<GifFileType *>(gif_handler);
return gifFileType->SHeight;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_dymaicimagegif_GifUtil_update(JNIEnv *env, jclass clazz, jobject bitmap,
jlong gif_handler) {
AndroidBitmapInfo info;
AndroidBitmap_getInfo(env, bitmap, &info);
int width = info.width;
int height = info.height;
void *pixels;
AndroidBitmap_lockPixels(env, bitmap, &pixels);
GifFileType *gifFileType = reinterpret_cast<GifFileType *>(gif_handler);
drawFrame(gifFileType, info, pixels);
AndroidBitmap_unlockPixels(env, bitmap);
GifBean *gifBean = static_cast<GifBean *>(gifFileType->UserData);
gifBean->current_frame++;
if (gifBean->current_frame >= gifBean->total_frame - 1) {
gifBean->current_frame = 0;
}
return gifBean->delays[gifBean->current_frame];;
}
/**
* 绘制每一帧
* @param pType
* @param info
* @param pVoid
*/
void drawFrame(GifFileType *pType, AndroidBitmapInfo info, void *pVoid) {
GifBean *gifBean = static_cast<GifBean *>(pType->UserData);
SavedImage savedImage = pType->SavedImages[gifBean->current_frame];
//描述
GifImageDesc frameDesc = savedImage.ImageDesc;
//像素
GifByteType *frameRaster = savedImage.RasterBits;
ColorMapObject *colorMapObject = frameDesc.ColorMap;
GifColorType gifColorType;
int *px = (int *) pVoid;
int *head;
int pixelsIndex;
GifByteType gifByteType;
for (int i = frameDesc.Top; i < frameDesc.Top + frameDesc.Height; i++) {
head = px;
for (int j = frameDesc.Left; j < frameDesc.Left + frameDesc.Width; j++) {
pixelsIndex = (i - frameDesc.Top) * frameDesc.Width + (j - frameDesc.Left);
gifByteType = frameRaster[pixelsIndex];
if(colorMapObject == NULL){
//黑色
head[j] = argb(255,0,0,0);
}else{
gifColorType = colorMapObject->Colors[gifByteType];
head[j] = argb(255,gifColorType.Red,gifColorType.Green,gifColorType.Blue);
}
}
px = (int *) ((char *) px + info.stride);
}
}
接下来写java层的调用工具类
package com.example.dymaicimagegif;
import android.graphics.Bitmap;
/**
* @ Author: SunLife
* @ Create date: 2021/6/7 9:06
* @ Version: [1.0 2021/6/7]
* @ Description: tools for gif
*/
public class GifUtil {
private long gifHandler;
static {
System.loadLibrary("native-lib");
}
private GifUtil(long gifHandler) {
this.gifHandler = gifHandler;
}
public static GifUtil load(String path) {
long gifHandler = startLoad(path);
GifUtil instance = null;
if(gifHandler != -1){
instance = new GifUtil(gifHandler);
}
return instance;
}
//获取宽度
public int getWidth() {
return getWidth(gifHandler);
}
//获取高度
public int getHeight() {
return getHeight(gifHandler);
}
/**
* 刷新gif帧
* 无法显示,就显示黑图
* @param bitmap 存放的bitmap
* @return 下一帧的时间
*/
public int updateFrame(Bitmap bitmap) {
return update(bitmap, gifHandler);
}
//获取宽度
public static native int getWidth(long gifHandler);
//获取高度
public static native int getHeight(long gifHandler);
//开始加载
public static native long startLoad(String path);
//更新帧
public static native int update(Bitmap bitmap, long gifHandler);
}
接下来就是在activity调用了如下
package com.example.dymaicimagegif;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.graphics.Bitmap;
import android.media.Image;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import java.io.File;
public class MainActivity extends AppCompatActivity {
static GifUtil load;
static Bitmap bitmap;
static ImageView gifImageView;
GifHandler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new GifHandler();
gifImageView = findViewById(R.id.imageView);
}
public void ndkLoadGif(View view){
File file = new File(Environment.getExternalStorageDirectory(),"demo.gif");
load = GifUtil.load(file.getAbsolutePath());
int width = load.getWidth();
int height = load.getHeight();
Log.d("oicq","width:"+ width +" height:"+height);
bitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
gifImageView.setImageBitmap(bitmap);
int nextFrame = load.updateFrame(bitmap);
handler.sendEmptyMessageDelayed(1,nextFrame);
}
private static class GifHandler extends Handler{
@Override
public void handleMessage(@NonNull Message msg) {
//这一帧持续的时间
int delay = load.updateFrame(bitmap);
sendEmptyMessageDelayed(1,delay);
gifImageView.setImageBitmap(bitmap);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
}
我们通过jni实现不断的通过对bitmao进行绘制然后绘制到imageView上,实现gif播放,但是这只能满足gif89a版本的播放,所以会有适配老版本Gif(87a)问题,所以需要更加深入。
项目链接
下面这个github项目(别人的)就完美解决了上面只能满足gif89a不能满足gif87a
的问题,接下来会出一篇关于这个项目的详细注释的博客
github项目链接