人间观察

人往往都是多面性的,一个人的时候是一个样子,一群人的时候是另一个样子。

声明

此篇文章只为记录和学习JNI以及了解GIF的解码原理。借鉴了网上的有关gif文章介绍和代码。如果是自己学习,建议自己敲一遍jni的代码,不要眼高手低。

一些开源的gif解码项目

本篇我们学习了解GIF的解码原理和熟悉jni即可。

利用Android 系统的源码来实现GIF的解码播放

Android 系统的源码支持gif的解码和生成gif文件,可以选择性的拷贝源文件和头文件,这里就直接全部拷贝了。

基本的实现流程:

在jni层中打开gif文件并加载得到gif的总播放帧数和每帧之间的延迟时间

在java层中创建ARGB_8888格式的bitmap(jni层是按照每像素4字节处理的)

在jni层获取步骤2中的bitmap对应的像素数据指针void**

解码gif的每帧的数据然后对步骤3中的像素数据指针进行赋值,像素格式还是abgr。这样java层调用imageView.setImageBitmap(bitmap);数据就是修改后的。

定时刷新执行步骤4即可,来达到循环播放gif

java代码:

package com.bj.gxz.gifjnidecode;

import android.graphics.Bitmap;

import android.os.Bundle;

import android.os.Environment;

import android.os.Handler;

import android.os.Looper;

import android.os.Message;

import android.util.Log;

import android.view.View;

import android.widget.ImageView;

import androidx.annotation.NonNull;

import androidx.appcompat.app.AppCompatActivity;

import java.io.File;

public class MainActivity extends AppCompatActivity {

private GifJni gifJni;

private ImageView imageView;

private Bitmap bitmap;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

imageView = findViewById(R.id.id_iv);

// File file = new File(Environment.getExternalStorageDirectory(), "demo.gif");

// java

// Glide.with( this )

// .load( file.getAbsolutePath() )

// .into( imageView );

// c实现,建议

// https://github.com/koral--/android-gif-drawable

// try {

// GifDrawable gifFromPath = new GifDrawable(file.getAbsolutePath());

// imageView.setImageDrawable(gifFromPath);

// } catch (IOException e) {

// e.printStackTrace();

// }

}

private final Handler handler = new Handler(Looper.getMainLooper()) {

@Override

public void handleMessage(@NonNull Message msg) {

super.handleMessage(msg);

updateFrame();

}

};

// Android源码中的,有些gif解码有些问题

public void ndkGif(View view) {

File file = new File(Environment.getExternalStorageDirectory(), "demo.gif");

gifJni = new GifJni(file.getAbsolutePath());

int width = gifJni.getWidth();

int height = gifJni.getHeight();

Log.d("GIF", "width:" + width);

Log.d("GIF", "height:" + height);

bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

Log.d("GIF", "bitmap:" + bitmap.getConfig().name());

updateFrame();

}

private void updateFrame() {

long cost = System.currentTimeMillis();

//下一帧的刷新时间

int delayShowTime = gifJni.updateFrame(bitmap);

cost = System.currentTimeMillis() - cost;

int realDelay = (int) (delayShowTime - cost);

// 真正的延时,需要减去更新一帧所消耗的时间

realDelay = Math.max(realDelay, 0);

Log.d("GIF", "realDelay:" + realDelay + ",cost:" + cost);

imageView.setImageBitmap(bitmap);

handler.sendEmptyMessageDelayed(0, realDelay);

}

@Override

protected void onDestroy() {

super.onDestroy();

handler.removeCallbacksAndMessages(null);

}

}

jni层的代码

public class GifJni {

static {

System.loadLibrary("native-lib");

}

// Convenience for JNI access

public long mNativePtr;

public GifJni(String path) {

this.mNativePtr = loadPath(path);

}

public int getWidth(){

return getWidth(mNativePtr);

}

public int getHeight(){

return getHeight(mNativePtr);

}

public int updateFrame(Bitmap bitmap){

return updateFrame(mNativePtr,bitmap);

}

//通过路径加载gif图片(这里使用的是本地图片,源码中的gif加载是支持流的格式的)

public native long loadPath(String path);

//获取gif的宽

public native int getWidth(long mNativePtr);

//获取gif的高

public native int getHeight(long mNativePtr);

//每隔一段时间刷新一次,返回的int值表示下次刷新的时间间隔

public native int updateFrame(long mNativePtr, Bitmap bitmap);

}

jni层的代码

#include

#include

#include

#include

#include "gif_lib.h"

#define LOG_TAG "GIF"

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)

#define argb(a, r, g, b) ( ((a) & 0xff) << 24 ) | ( ((b) & 0xff) << 16 ) | ( ((g) & 0xff) << 8 ) | ((r) & 0xff)

typedef struct GifBean {

// 当前帧

int current_frame;

// 总帧数

int total_frame;

// 延迟时间数组,长度不确定,根据gif帧数计算

int *delays;

} GifBean;

void drawFrame(GifFileType *pType, GifBean *pBean, AndroidBitmapInfo info, void *pVoid);

extern "C"

JNIEXPORT jint JNICALL

Java_com_bj_gxz_gifjnidecode_GifJni_getWidth(JNIEnv *env, jobject thiz, jlong native_address_ptr) {

GifFileType *gifFileType = reinterpret_cast(native_address_ptr);

return gifFileType->SWidth;

}

extern "C"

JNIEXPORT jint JNICALL

Java_com_bj_gxz_gifjnidecode_GifJni_getHeight(JNIEnv *env, jobject thiz, jlong native_address_ptr) {

GifFileType *gifFileType = reinterpret_cast(native_address_ptr);

return gifFileType->SHeight;

}

extern "C"

JNIEXPORT jlong JNICALL

Java_com_bj_gxz_gifjnidecode_GifJni_loadPath(JNIEnv *env, jobject thiz, jstring path) {

const char *c_path = (char *) env->GetStringUTFChars(path, 0);

int error;

// 打开gif文件

GifFileType *gifFileType = DGifOpenFileName(c_path, &error);

DGifSlurp(gifFileType);

// 自定义一个bean类,来存储当前播放帧,总帧数,播放延迟的数组,并把bean对象与gifFileType绑定

GifBean *gifBean = (GifBean *) malloc(sizeof(GifBean));

memset(gifBean, 0, sizeof(GifBean));

//初始化当前帧和总帧数

gifBean->current_frame = 0;

gifBean->total_frame = gifFileType->ImageCount;

gifBean->delays = (int *) (malloc(sizeof(int) * gifFileType->ImageCount));

memset(gifBean->delays, 0, sizeof(int) * gifFileType->ImageCount);

// 把自己定义的数据结构保存到UserData,便于其它方法的获取使用

gifFileType->UserData = gifBean;

ExtensionBlock *extensionBlock;

//遍历每一帧

for (int i = 0; i < gifFileType->ImageCount; i++) {

//遍历每一帧中的扩展块(度娘Gif编码)

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];

}

}

//获取延迟时间,extensionBlock的第二,三个元素一起存放延迟时间低8位和高8位向左偏移8位,进行或运算

//乘10因为编码的时间单位是1/100秒 乘10换算为毫秒

if (extensionBlock) {

int frame_delay = 10 * (extensionBlock->Bytes[1] | extensionBlock->Bytes[2] << 8);

gifBean->delays[i] = frame_delay;

// LOGD("delays[%d]=%d", i, frame_delay);

}

}

env->ReleaseStringUTFChars(path, c_path);

return reinterpret_cast(gifFileType);

}

// 直接修改bitmap的像素

void drawFrame(GifFileType *gifFileType, GifBean *gifBean, AndroidBitmapInfo info, void *pixels) {

// 先拿到当前帧

SavedImage savedImage = gifFileType->SavedImages[gifBean->current_frame];

GifImageDesc imageDesc = savedImage.ImageDesc;

// 获取color map

//字典,存放的是gif压缩rgb数据

ColorMapObject *colorMap = imageDesc.ColorMap;

//部分图片某些帧的ColorMapObject取到为null

if (colorMap == NULL) {

colorMap = gifFileType->SColorMap;

}

GifByteType gifByteType;

int pointPixels;

// bitmap的像素的指针

int *px = (int *) pixels;

// 在jni c层中真实存储是 A B G R

int bitmapLineStart; // bitmap每一行的首地址

for (int y = imageDesc.Top; y < imageDesc.Top + imageDesc.Height; y++) {

// 更新行的首地址

bitmapLineStart = y * info.width;

for (int x = imageDesc.Left; x < imageDesc.Left + imageDesc.Width; x++) {

// 拿到一个坐标的位置索引 --> 数据

pointPixels = (y - imageDesc.Top) * imageDesc.Width + (x - imageDesc.Left);

// 解压 gif中为了节省内存rgb采用lzw压缩,所以取rgb信息需要解压

// 通过index拿到的是一个压缩数据

gifByteType = savedImage.RasterBits[pointPixels];

// 拿到真正的rgb

GifColorType gifColorType = colorMap->Colors[gifByteType];

// 转成一个int值,并赋值给对应的像素点

px[bitmapLineStart + x] =

argb(255, gifColorType.Red, gifColorType.Green, gifColorType.Blue);

}

}

}

extern "C"

JNIEXPORT jint JNICALL

Java_com_bj_gxz_gifjnidecode_GifJni_updateFrame(JNIEnv *env, jobject thiz, jlong native_address_ptr,

jobject bitmap) {

GifFileType *gifFileType = reinterpret_cast(native_address_ptr);

GifBean *gifBean = (GifBean *) gifFileType->UserData;

AndroidBitmapInfo info;

AndroidBitmap_getInfo(env, bitmap, &info);

void *addrPtr;

AndroidBitmap_lockPixels(env, bitmap, &addrPtr);

drawFrame(gifFileType, gifBean, info, addrPtr);

// 循环播放

gifBean->current_frame += 1;

if (gifBean->current_frame >= gifBean->total_frame - 1) {

gifBean->current_frame = 0;

}

AndroidBitmap_unlockPixels(env, bitmap);

// 把下一帧的延迟时间返回上去

return gifBean->delays[gifBean->current_frame];

}

效果图

录屏的原因,效果显得不异常,真实正常。

demo.gif

源码

注意下读写sd卡的权限

参考网上的有关文章

android gif快手 源码,Android-JNI开发系列《十一》实践-利用Android C源码实现GIF图片的播放...相关推荐

  1. Cocos2dx游戏开发系列笔记9:android手机上运行《战神传说》,并解决横竖屏即分辨率自适应问题

    转载:http://blog.csdn.net/iamlazybone/article/details/17191539 懒骨头(http://blog.csdn.net/iamlazybone  Q ...

  2. Cocos2dx游戏开发系列笔记9:android手机上运行《战神传说》,并解决横竖屏即分辨率自适应...

    2019独角兽企业重金招聘Python工程师标准>>> 上节说到cygwin下成功编译出so文件,下面我们要把游戏运行在android上. 开始干活! 其实步骤可以参考 Cocos2 ...

  3. 抖音seo源码 短视频seo源码二次开发,怎么使用抖音seo源码,视频seo源码私有化部署?

    抖音seo源码 短视频seo源码二次开发,怎么使用抖音seo源码,短视频seo源码私有化部署? 抖音seo源码 短视频seo源码二次开发,怎么使用抖音seo源码,短视频seo源码私有化部署到本地.首先 ...

  4. vscode中安装webpack_leaflet-webpack 入门开发系列一初探篇(附源码下载)

    前言 leaflet-webpack 入门开发系列环境知识点了解: node 安装包下载 webpack 打包管理工具需要依赖 node 环境,所以 node 安装包必须安装,上面链接是官网下载地址w ...

  5. 安卓源码,安卓开发,跑步打卡项目app源码

    安卓源码,安卓开发,跑步打卡项目app源码,包括源码和简单文档 YID:6975644491117436

  6. 安卓源码,安卓开发,跑步打卡项目app源码,包括源码和简单文档

    安卓源码,安卓开发,跑步打卡项目app源码,包括源码和简单文档 ID:6975644491117436Robergean

  7. 安卓源码,安卓开发,跑步打卡项目app源码,包括源码和简单文 档

    安卓源码,安卓开发,跑步打卡项目app源码,包括源码和简单文 档

  8. 【Android从零单排系列十一】《Android视图控件——日历、日期、时间选择控件》

    目录 一.日历.日期.时间组件基本介绍 二.几种常见的控件类型 1.CalendarView –日历控件 2. DatePicker –日期选择控件 3.TimePicker –时间选择控件 4.Ch ...

  9. Android-JNI开发系列《十一》实践-利用Android C源码实现GIF图片的播放

    人间观察 人往往都是多面性的,一个人的时候是一个样子,一群人的时候是另一个样子. 声明 此篇文章只为记录和学习JNI以及了解GIF的解码原理.借鉴了网上的有关gif文章介绍和代码.如果是自己学习,建议 ...

最新文章

  1. python编程从入门到实践读书笔记-《Python编程:从入门到实践》项目部分读书笔记(二)...
  2. Java并发编程——线程池初步
  3. inner/left/right/full join on
  4. GitHubPage博客搭建学习专栏
  5. 前端学习的开源实战项目及其源码
  6. springboot毕业设计管理系统(带论文)
  7. 萤火虫小程序_“萤火虫课堂”开课了
  8. 【制作】基于51单片机的蓝牙遥控小车方案
  9. Windows错误系统配置提权之系统服务权限配置错误 (二)
  10. 九鼎实际控制人投资观!
  11. 【lifelines中文wiki】生存分析简介
  12. 零基础学习PS——#photoshop# 的167个技能!
  13. Python+request 将获取的url和接口响应时间(timeout)写入到Excel中《八》
  14. 百度,谷歌,360,神马,必应,搜狗搜索引擎网站链接提交入口
  15. 如何编写高质量的易语言代码?
  16. [网络收集]JS刷新页面总和!多种JS刷新页面代码!
  17. ffmpeg如何实现MP3转码g711a,
  18. DOA定位算法源码程序
  19. Python3网络爬虫教程8——有道在线翻译项目(JS加密)
  20. 【Golang之路】——slice总结

热门文章

  1. linux iostat
  2. 【黑马程序员】 学习笔记 - Java基础
  3. 2020年最新主板型号排行榜
  4. 分页工具PageHelper的使用
  5. Documents and settings文件夹探密
  6. Clean Code之封装:把野兽关进笼子
  7. 6. Nginx虚拟主机
  8. 使用memset初始化数组
  9. Thinkphp对接百度云对象存储 BOS【实现网页版的百度云盘】
  10. [转载]清末烟台邮局旧影