本系列文章以koral实现的GIF文件native渲染为根据,解读实现的大致过程以及关键代码分析,github地址:https://github.com/koral–/android-gif-drawable。
由前文中纹理贴图native实现可知,若想实现图像的OpenGL渲染(主要glTexImage2D函数),首先需要解析出该图像文件(当时PNG为例)的图像数据以及其它相关信息,然后利用native OpenGL API实现,而GIF文件的渲染过程稍微麻烦一些,因为涉及到多帧图像的渲染,具体过程可以大致分为以下几个步骤。
(1)创建解析GIF文件的句柄
利用GIFLIB解析GIF文件,把相关信息保存在GifInfo结构体中,并将该结构体的指针(long)返回给JAVA层,作为访问GIF文件这些信息的句柄。从JAVA层到GIFLIB的大致实现流程如下:
GifInfo结构体:
struct GifInfo {
void (*destructor)(GifInfo *, JNIEnv *);
GifFileType *gifFilePtr;
GifWord originalWidth, originalHeight;
uint_fast16_t sampleSize;
long long lastFrameRemainder;
long long nextStartTime;
uint_fast32_t currentIndex;
GraphicsControlBlock *controlBlock;
argb *backupPtr;
long long startPos;
unsigned char *rasterBits;
uint_fast32_t rasterSize;
char *comment;
uint_fast16_t loopCount;
uint_fast16_t currentLoop;
RewindFunc rewindFunction;
jfloat speedFactor;
int32_t stride;
jlong sourceLength;
bool isOpaque;
void *frameBufferDescriptor;
};
解析GIF文件相关信息的关键代码:
/******************************************************************************
This routine should be called before any other DGif calls. Note that
this routine is called automatically from DGif file open routines.
******************************************************************************/
int
DGifGetScreenDesc(GifFileType *GifFile) {
// bool SortFlag;
GifByteType Buf[3];
// GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private;
// if (!IS_READABLE(Private)) {
// /* This file was NOT open for reading: */
// GifFile->Error = D_GIF_ERR_NOT_READABLE;
// return GIF_ERROR;
// }
/* Put the screen descriptor into the file: */
if (DGifGetWord(GifFile, &GifFile->SWidth) == GIF_ERROR ||
DGifGetWord(GifFile, &GifFile->SHeight) == GIF_ERROR)
return GIF_ERROR;
if (((GifFilePrivateType *) GifFile->Private)->Read(GifFile, Buf, 3) != 3) {
GifFile->Error = D_GIF_ERR_READ_FAILED;
GifFreeMapObject(GifFile->SColorMap);
GifFile->SColorMap = NULL;
return GIF_ERROR;
}
// GifFile->SColorResolution = (((Buf[0] & 0x70) + 1) >> 4) + 1;
// SortFlag = (Buf[0] & 0x08) != 0;
uint_fast8_t BitsPerPixel = (uint_fast8_t) ((Buf[0] & 0x07) + 1);
GifFile->SBackGroundColor = Buf[1];
// GifFile->AspectByte = Buf[2];
if (Buf[0] & 0x80) { /* Do we have global color map? */
uint_fast16_t i;
GifFile->SColorMap = GifMakeMapObject(BitsPerPixel, NULL);
if (GifFile->SColorMap == NULL) {
GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
return GIF_ERROR;
}
/* Get the global color map: */
// GifFile->SColorMap->SortFlag = SortFlag;
for (i = 0; i < GifFile->SColorMap->ColorCount; i++) {
if (((GifFilePrivateType *) GifFile->Private)->Read(GifFile, Buf, 3) != 3) {
GifFreeMapObject(GifFile->SColorMap);
GifFile->SColorMap = NULL;
GifFile->Error = D_GIF_ERR_READ_FAILED;
return GIF_ERROR;
}
GifFile->SColorMap->Colors[i].Red = Buf[0];
GifFile->SColorMap->Colors[i].Green = Buf[1];
GifFile->SColorMap->Colors[i].Blue = Buf[2];
}
} else {
GifFile->SColorMap = NULL;
}
return GIF_OK;
}
(2)读取每帧的图像数据
由于GIF文件是多帧的,所以GIF文件的渲染需要根据每帧的持续时间来获取不同帧图像来渲染,大致流程如下图:
getBitmap用于获取当前帧的图像数据,并保存在GifInfo结构的frameBufferDescriptor所指向的TexImageDescriptor结构中frameBuffer。
TexImageDescriptor结构:
typedef struct {
struct pollfd eventPollFd;
void *frameBuffer;
pthread_mutex_t renderMutex;
pthread_t slurpThread;
} TexImageDescriptor;
关键代码如下:
static void *slurp(void *pVoidInfo) {
GifInfo *info = pVoidInfo;
while (true) {
long renderStartTime = getRealTime();
// reset gif info
DDGifSlurp(info, true, false);
TexImageDescriptor *texImageDescriptor = info->frameBufferDescriptor;
pthread_mutex_lock(&texImageDescriptor->renderMutex);
if (info->currentIndex == 0) {
prepareCanvas(texImageDescriptor->frameBuffer, info);
}
const uint_fast32_t frameDuration = getBitmap(texImageDescriptor->frameBuffer, info);
pthread_mutex_unlock(&texImageDescriptor->renderMutex);
const long long invalidationDelayMillis = calculateInvalidationDelay(info, renderStartTime, frameDuration);
int pollResult = poll(&texImageDescriptor->eventPollFd, 1, (int) invalidationDelayMillis);
DDGifSlurpTest(info, true, false);
eventfd_t eventValue;
if (pollResult < 0) {
throwException(getEnv(), RUNTIME_EXCEPTION_ERRNO, "Could not poll on eventfd ");
break;
} else if (pollResult > 0) {
const int readResult = TEMP_FAILURE_RETRY(eventfd_read(texImageDescriptor->eventPollFd.fd, &eventValue));
if (readResult != 0) {
throwException(getEnv(), RUNTIME_EXCEPTION_ERRNO, "Could not read from eventfd ");
}
break;
}
}
DetachCurrentThread();
return NULL;
}
(3)实现指定和替换纹理
在前面过程中,我们可以拿到GIF文件的相关信息以及当前所需绘制的帧图像,这样再实现纹理的指定和替换就很简单了,代码如下:
1.纹理指定
Java_pl_droidsonroids_gif_GifInfoHandle_glTexImage2D(JNIEnv *__unused unused, jclass __unused handleClass, jlong gifInfo, jint target, jint level) {
GifInfo *info = (GifInfo *) (intptr_t) gifInfo;
if (info == NULL || info->frameBufferDescriptor == NULL) {
return;
}
const GLsizei width = (const GLsizei) info->gifFilePtr->SWidth;
const GLsizei height = (const GLsizei) info->gifFilePtr->SHeight;
TexImageDescriptor *descriptor = info->frameBufferDescriptor;
void *const pixels = descriptor->frameBuffer;
pthread_mutex_lock(&descriptor->renderMutex);
glTexImage2D((GLenum) target, level, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
pthread_mutex_unlock(&descriptor->renderMutex);
}
2、纹理替换或修改
Java_pl_droidsonroids_gif_GifInfoHandle_glTexSubImage2D(JNIEnv *__unused env, jclass __unused handleClass, jlong gifInfo, jint target, jint level) {
GifInfo *info = (GifInfo *) (intptr_t) gifInfo;
if (info == NULL || info->frameBufferDescriptor == NULL) {
return;
}
const GLsizei width = (const GLsizei) info->gifFilePtr->SWidth;
const GLsizei height = (const GLsizei) info->gifFilePtr->SHeight;
TexImageDescriptor *descriptor = info->frameBufferDescriptor;
void *const pixels = descriptor->frameBuffer;
pthread_mutex_lock(&descriptor->renderMutex);
glTexSubImage2D((GLenum) target, level, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
pthread_mutex_unlock(&descriptor->renderMutex);
}
由于整个实现过程都在native中,基本上不申请JAVA堆内存,因此不会出现OOM问题,但是显然不适宜列表加载很多GIF文件,因为频繁申请和释放native内存,会产生大量的内存碎片。
该博客基于koral的android-gif-drawable库,深入解析Android中使用GIFLIB通过native层进行GIF文件渲染的过程。文章详细介绍了如何创建GIF解析句柄、读取每帧图像数据以及实现OpenGL中的纹理指定与替换。虽然避免了JAVA层的OOM问题,但大量GIF加载可能产生内存碎片。
670

被折叠的 条评论
为什么被折叠?



