美文网首页
NDK--实现gif图片播放

NDK--实现gif图片播放

作者: aruba | 来源:发表于2020-05-22 16:30 被阅读0次

    GIF是由CompuServe公司所推出的一种图形文件格式,安卓系统控件并不支持gif图片,如果将一个gif图片设置到ImageView上,它只会播放第一帧

    在Java层可以利用创建Movie实例,绘制每一帧图片来达到Gif动态效果。
    问题点:

    部分Gif图片不能自适应大小,
    播放速度比实际播放速度快,
    如果要显示的gif过大,还会出现OOM的问题。

    Glide框架对gif的支持是利用GifHelper,同样的也会产生这些问题,很明显在Java层做处理并不是特别棒。
    既然gif图片是CompuServe公司推出的,那么它必然有自己的加载方式:giflib,这个库由c编写,其中提供解析gif方法,在安卓源码中也含有这个库,位于\external目录下
    我们创建NDK工程,将这个库中文件拷贝到项目中,在gif_lib.h头文件中,定义了gif图片相应的结构体GifFileType,我们首先分析下这个数据结构
    typedef struct GifFileType {
        GifWord SWidth, SHeight;         /* 图片的宽,高 */
        GifWord SColorResolution;        /* How many colors can we generate? */
        GifWord SBackGroundColor;        /* Background color for virtual canvas */
        GifByteType AspectByte;      /* Used to compute pixel aspect ratio */
        ColorMapObject *SColorMap;       /* Global colormap, NULL if nonexistent. */
        int ImageCount;                  /* 帧数:一共多少张图片 */
        GifImageDesc Image;              /* Current image (low-level API) */
        SavedImage *SavedImages;         /* 图片数据数组:每一帧的图片数据 */
        int ExtensionBlockCount;         /* Count of extensions before image */
        ExtensionBlock *ExtensionBlocks; /* Extensions before image */    
        int Error;               /* Last error condition reported */
        void *UserData;                  /* 可以绑定我们自己的数据,类似于View的tag */
        void *Private;                   /* Don't mess with this! */
    } GifFileType;
    
    GifFileType结构体中,我们需要关注的:除了图片的宽高、帧数、自己绑定的数据外,还有一个结构体SavedImage,它储存了每一帧的图片数据。
    typedef struct SavedImage {
        GifImageDesc ImageDesc;          /* 图象标识符 */
        GifByteType *RasterBits;         /* 每个像素对应的压缩颜色 */
        int ExtensionBlockCount;         /* 扩展块个数 */
        ExtensionBlock *ExtensionBlocks; /* 扩展块数据 */
    } SavedImage;
    
    SavedImage 结构体中又含有大量的结构体,我们一一解析
    1.GifImageDesc 结构体:图像标识符,存储着显示图片内容的像素偏移量(一张图片宽高是100*100,但实际真正的显示内容可能只有50*50)
    typedef struct GifImageDesc {
        GifWord Left, Top, Width, Height;   /* 内容偏移量. */
        bool Interlace;                     /* Sequential/Interlaced lines. */
        ColorMapObject *ColorMap;           /* 解压的RGB值 */
    } GifImageDesc;
    
    typedef struct ColorMapObject {
        int ColorCount;
        int BitsPerPixel;
        bool SortFlag;
        GifColorType *Colors;    /* on malloc(3) heap */
    } ColorMapObject;
    
    typedef struct GifColorType {
        GifByteType Red, Green, Blue; /* 三原色 */
    } GifColorType;
    
    
    2.GifByteType :存储着每个像素对应的压缩RGB颜色(只包含内容的)
    typedef unsigned char GifByteType;
    
    3.ExtensionBlock 结构体:扩展块数据,分为4个

    图形控制扩展(Graphic Control Extension) 固定值0xF9
    作用:用来跟踪下一帧的信息和渲染形式

    注释扩展块 固定值0xFE
    作用 :可以用来记录图形、版权、描述等任何的非图形和控制的纯文本数据

    图形文本扩展块 固定值0x01
    作用:控制绘制的参数,比如左边界偏移量

    应用程序扩展 固定值 0xFF
    作用:这是提供给应用程序自己使用的,应用程序可以在这里定义自己的标识、
    信息。可以做到当前app所生成的gif只能由我这个app打开

    我们目前只需要关注:图形控制扩展(Graphic Control Extension) 即可,其中存储着每一帧的延时(每一帧播放的时长可能不同,这就是为什么使用Java实现会比真实gif播放快的原因)
    typedef struct ExtensionBlock {
        int ByteCount;
        GifByteType *Bytes; /* GifByteType就是char类型,之前用于存储三原色,这边第3个元素存储着延时时间的高8位,第二个元素存储着延时时间的低8位 */
        int Function;       /* The block function code */
    #define CONTINUE_EXT_FUNC_CODE    0x00    /* continuation subblock */
    #define COMMENT_EXT_FUNC_CODE     0xfe    /* comment */
    #define GRAPHICS_EXT_FUNC_CODE    0xf9    /* graphics control (GIF89) */
    #define PLAINTEXT_EXT_FUNC_CODE   0x01    /* plaintext */
    #define APPLICATION_EXT_FUNC_CODE 0xff    /* application block */
    } ExtensionBlock;
    
    需要注意的是:GifByteType就是char类型,之前用于存储三原色,这边用于存储延时时间:第3个元素存储着延时时间的高8位,第二个元素存储着延时时间的低8位
    到此,gif图片的结构体已经分析完毕
    gif结构体
    接下来编写相应的native代码,实现gif图的播放
    package com.aruba.gifapplication;
    
    import android.graphics.Bitmap;
    
    import java.io.FileDescriptor;
    
    public class GifHandler {
        private long gifAddr;
    
        static {
            System.loadLibrary("native-lib");
        }
    
        public GifHandler(FileDescriptor fileDescriptor, long offset) {
            this.gifAddr = loadFd(fileDescriptor, offset);
        }
    
        /**
         * 加载gif资源文件
         *
         * @param fileDescriptor
         * @param offset
         * @return
         */
        private native long loadFd(FileDescriptor fileDescriptor, long offset);
    
        /**
         * 获取图片的宽
         *
         * @param ndkGif
         * @return
         */
        public native int getWidth(long ndkGif);
    
        /**
         * 获取图片的高
         *
         * @param ndkGif
         * @return
         */
        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);
        }
    
        /**
         * 更新bitmap到下一帧
         *
         * @param bitmap
         * @return
         */
        public int updateFrame(Bitmap bitmap) {
            return updateFrame(gifAddr, bitmap);
        }
    }
    
    
    #include <jni.h>
    #include <string>
    #include "gif_lib.h"
    #include <android/log.h>
    #include <android/bitmap.h>
    #include <malloc.h>
    #include <string.h>
    #include <unistd.h>
    
    #define  LOG_TAG    "aruba"
    #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);
        }
    
    
    } ;
    
    int fileRead(GifFileType *gif, GifByteType *bytes, int size) {
        FILE *file = (FILE *) gif->UserData;
        return fread(bytes, 1, size, file);
    }
    
    JNIEXPORT jlong JNICALL
    Java_com_aruba_gifapplication_GifHandler_loadFd(JNIEnv *env, jobject instance,
                                                    jobject fileDescriptor, jlong offset) {
        jclass clz = env->GetObjectClass(fileDescriptor);
        jfieldID jfieldID = env->GetFieldID(clz, "descriptor", "I");
        jint oldFd = env->GetIntField(fileDescriptor, jfieldID);
        const int fd = dup(oldFd);
        lseek64(fd, offset, SEEK_SET);
        FILE *file = fdopen(fd, "rb");
        int err;
    
        //用系统函数打开一个gif文件   返回一个结构体,这个结构体为句柄
        GifFileType *gifFileType = DGifOpen(file, fileRead, &err);
    
        DGifSlurp(gifFileType);
        GifBean *gifBean = (GifBean *) malloc(sizeof(GifBean));
    
        //清空内存地址
        memset(gifBean, 0, sizeof(GifBean));
        //绑定tag
        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);
        return (jlong) gifFileType;
    }
    
    JNIEXPORT jint JNICALL
    Java_com_aruba_gifapplication_GifHandler_getWidth(JNIEnv *env, jobject instance, jlong ndkGif) {
        GifFileType *gifFileType = (GifFileType *) ndkGif;
        return gifFileType->SWidth;
    }
    
    JNIEXPORT jint JNICALL
    Java_com_aruba_gifapplication_GifHandler_getHeight(JNIEnv *env, jobject instance, jlong ndkGif) {
    
        GifFileType *gifFileType = (GifFileType *) ndkGif;
        return gifFileType->SHeight;
    
    }
    
    JNIEXPORT jint JNICALL
    Java_com_aruba_gifapplication_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];
    }
    
    
    }
    
    在CMakeLists中添加对bitmap的支持
    # For more information about using CMake with Android Studio, read the
    # documentation: https://d.android.com/studio/projects/add-native-code.html
    
    # Sets the minimum version of CMake required to build the native library.
    
    cmake_minimum_required(VERSION 3.4.1)
    
    # Creates and names a library, sets it as either STATIC
    # or SHARED, and provides the relative paths to its source code.
    # You can define multiple libraries, and CMake builds them for you.
    # Gradle automatically packages shared libraries with your APK.
    
    add_library( # Sets the name of the library.
            native-lib
    
            # Sets the library as a shared library.
            SHARED
    
            # Provides a relative path to your source file(s).
            native-lib.cpp
            dgif_lib.c
            gifalloc.c)
    
    find_library( # Sets the name of the path variable.
            jnigraphics-lib
    
            # Specifies the name of the NDK library that
            # you want CMake to locate.
            jnigraphics)
    
    # Searches for a specified prebuilt library and stores the path as a
    # variable. Because CMake includes system libraries in the search path by
    # default, you only need to specify the name of the public NDK library
    # you want to add. CMake verifies that the library exists before
    # completing its build.
    
    find_library( # Sets the name of the path variable.
            log-lib
    
            # Specifies the name of the NDK library that
            # you want CMake to locate.
            log)
    
    # Specifies libraries CMake should link to your target library. You
    # can link multiple libraries, such as libraries you define in this
    # build script, prebuilt third-party libraries, or system libraries.
    
    target_link_libraries( # Specifies the target library.
            native-lib
    
            # Links the target library to the log library
            # included in the NDK.
            ${log-lib}
            ${jnigraphics-lib})
    
    将gif图放入工程资源文件夹
    chi.gif
    在Activity中使用
    package com.aruba.gifapplication;
    
    import android.content.res.AssetFileDescriptor;
    import android.graphics.Bitmap;
    import android.os.Bundle;
    import android.os.Environment;
    import android.os.Handler;
    import android.os.Message;
    import android.support.v7.app.AppCompatActivity;
    import android.view.View;
    import android.widget.ImageView;
    
    import java.io.File;
    
    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) {
            AssetFileDescriptor assetFileDescriptor = getResources().openRawResourceFd(R.drawable.chi);
            gifHandler = new GifHandler(assetFileDescriptor.getFileDescriptor(), assetFileDescriptor.getStartOffset());
            //得到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加载.gif
    项目地址:https://gitee.com/aruba/GifApplication.git

    相关文章

      网友评论

          本文标题:NDK--实现gif图片播放

          本文链接:https://www.haomeiwen.com/subject/ydmfahtx.html