848 lines
26 KiB
C
848 lines
26 KiB
C
//thanks to https://github.com/koral--/android-gif-drawable
|
|
/*
|
|
MIT License
|
|
Copyright (c)
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
|
|
// Copyright (c) 2011 Google Inc. All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are
|
|
// met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above
|
|
// copyright notice, this list of conditions and the following disclaimer
|
|
// in the documentation and/or other materials provided with the
|
|
// distribution.
|
|
// * Neither the name of Google Inc. nor the names of its
|
|
// contributors may be used to endorse or promote products derived from
|
|
// this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
The GIFLIB distribution is Copyright (c) 1997 Eric S. Raymond
|
|
*/
|
|
|
|
#include <jni.h>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
#include <limits.h>
|
|
#include "gif.h"
|
|
#include "giflib/gif_lib.h"
|
|
|
|
#define D_GIF_ERR_NO_FRAMES 1000
|
|
#define D_GIF_ERR_INVALID_SCR_DIMS 1001
|
|
#define D_GIF_ERR_INVALID_IMG_DIMS 1002
|
|
#define D_GIF_ERR_IMG_NOT_CONFINED 1003
|
|
|
|
typedef struct {
|
|
uint8_t blue;
|
|
uint8_t green;
|
|
uint8_t red;
|
|
uint8_t alpha;
|
|
} argb;
|
|
|
|
typedef struct {
|
|
unsigned int duration;
|
|
int transpIndex;
|
|
unsigned char disposalMethod;
|
|
} FrameInfo;
|
|
|
|
typedef struct {
|
|
GifFileType *gifFilePtr;
|
|
unsigned long lastFrameReaminder;
|
|
unsigned long nextStartTime;
|
|
int currentIndex;
|
|
unsigned int lastDrawIndex;
|
|
FrameInfo *infos;
|
|
argb *backupPtr;
|
|
int startPos;
|
|
unsigned char *rasterBits;
|
|
char *comment;
|
|
unsigned short loopCount;
|
|
int currentLoop;
|
|
jfloat speedFactor;
|
|
} GifInfo;
|
|
|
|
static ColorMapObject *defaultCmap = NULL;
|
|
|
|
static ColorMapObject *genDefColorMap(void) {
|
|
ColorMapObject *cmap = GifMakeMapObject(256, NULL);
|
|
if (cmap != NULL) {
|
|
int iColor;
|
|
for (iColor = 0; iColor < 256; iColor++) {
|
|
cmap->Colors[iColor].Red = (GifByteType) iColor;
|
|
cmap->Colors[iColor].Green = (GifByteType) iColor;
|
|
cmap->Colors[iColor].Blue = (GifByteType) iColor;
|
|
}
|
|
}
|
|
return cmap;
|
|
}
|
|
|
|
jint gifOnJNILoad(JavaVM *vm, void *reserved, JNIEnv *env) {
|
|
defaultCmap = genDefColorMap();
|
|
if (defaultCmap == NULL) {
|
|
return -1;
|
|
}
|
|
return JNI_VERSION_1_6;
|
|
}
|
|
|
|
void gifOnJNIUnload(JavaVM *vm, void *reserved) {
|
|
GifFreeMapObject(defaultCmap);
|
|
}
|
|
|
|
static int fileReadFunc(GifFileType *gif, GifByteType *bytes, int size) {
|
|
FILE *file = (FILE *)gif->UserData;
|
|
return fread(bytes, 1, size, file);
|
|
}
|
|
|
|
static int fileRewindFun(GifInfo *info) {
|
|
return fseek(info->gifFilePtr->UserData, info->startPos, SEEK_SET);
|
|
}
|
|
|
|
static unsigned long getRealTime() {
|
|
struct timespec ts;
|
|
const clockid_t id = CLOCK_MONOTONIC;
|
|
if (id != (clockid_t) - 1 && clock_gettime(id, &ts) != -1) {
|
|
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void cleanUp(GifInfo *info) {
|
|
if (info->backupPtr) {
|
|
free(info->backupPtr);
|
|
info->backupPtr = NULL;
|
|
}
|
|
if (info->infos) {
|
|
free(info->infos);
|
|
info->infos = NULL;
|
|
}
|
|
if (info->rasterBits) {
|
|
free(info->rasterBits);
|
|
info->rasterBits = NULL;
|
|
}
|
|
if (info->comment) {
|
|
free(info->comment);
|
|
info->comment = NULL;
|
|
}
|
|
|
|
GifFileType *GifFile = info->gifFilePtr;
|
|
if (GifFile->SColorMap == defaultCmap) {
|
|
GifFile->SColorMap = NULL;
|
|
}
|
|
if (GifFile->SavedImages != NULL) {
|
|
SavedImage *sp;
|
|
for (sp = GifFile->SavedImages; sp < GifFile->SavedImages + GifFile->ImageCount; sp++) {
|
|
if (sp->ImageDesc.ColorMap != NULL) {
|
|
GifFreeMapObject(sp->ImageDesc.ColorMap);
|
|
sp->ImageDesc.ColorMap = NULL;
|
|
}
|
|
}
|
|
free(GifFile->SavedImages);
|
|
GifFile->SavedImages = NULL;
|
|
}
|
|
DGifCloseFile(GifFile);
|
|
free(info);
|
|
}
|
|
|
|
static int getComment(GifByteType *Bytes, char **cmt) {
|
|
unsigned int len = (unsigned int) Bytes[0];
|
|
unsigned int offset = *cmt != NULL ? strlen(*cmt) : 0;
|
|
char *ret = realloc(*cmt, (len + offset + 1) * sizeof(char));
|
|
if (ret != NULL) {
|
|
memcpy(ret + offset, &Bytes[1], len);
|
|
ret[len + offset] = 0;
|
|
*cmt = ret;
|
|
return GIF_OK;
|
|
}
|
|
return GIF_ERROR;
|
|
}
|
|
|
|
static void packARGB32(argb *pixel, GifByteType alpha, GifByteType red, GifByteType green, GifByteType blue) {
|
|
pixel->alpha = alpha;
|
|
pixel->red = red;
|
|
pixel->green = green;
|
|
pixel->blue = blue;
|
|
}
|
|
|
|
static void getColorFromTable(int idx, argb *dst, const ColorMapObject *cmap) {
|
|
int colIdx = (idx >= cmap->ColorCount) ? 0 : idx;
|
|
GifColorType *col = &cmap->Colors[colIdx];
|
|
packARGB32(dst, 0xFF, col->Red, col->Green, col->Blue);
|
|
}
|
|
|
|
static void eraseColor(argb *bm, int w, int h, argb color) {
|
|
int i;
|
|
for (i = 0; i < w * h; i++) {
|
|
*(bm + i) = color;
|
|
}
|
|
}
|
|
|
|
static inline bool setupBackupBmp(GifInfo *info, short transpIndex) {
|
|
GifFileType *fGIF = info->gifFilePtr;
|
|
info->backupPtr = calloc(fGIF->SWidth * fGIF->SHeight, sizeof(argb));
|
|
if (!info->backupPtr) {
|
|
info->gifFilePtr->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
|
|
return false;
|
|
}
|
|
argb paintingColor;
|
|
if (transpIndex == -1) {
|
|
getColorFromTable(fGIF->SBackGroundColor, &paintingColor, fGIF->SColorMap);
|
|
} else {
|
|
packARGB32(&paintingColor, 0, 0, 0, 0);
|
|
}
|
|
eraseColor(info->backupPtr, fGIF->SWidth, fGIF->SHeight, paintingColor);
|
|
return true;
|
|
}
|
|
|
|
static int readExtensions(int ExtFunction, GifByteType *ExtData, GifInfo *info) {
|
|
if (ExtData == NULL) {
|
|
return GIF_OK;
|
|
}
|
|
if (ExtFunction == GRAPHICS_EXT_FUNC_CODE && ExtData[0] == 4) {
|
|
FrameInfo *fi = &info->infos[info->gifFilePtr->ImageCount];
|
|
fi->transpIndex = -1;
|
|
char *b = (char*) ExtData + 1;
|
|
short delay = ((b[2] << 8) | b[1]);
|
|
fi->duration = delay > 1 ? delay * 10 : 100;
|
|
fi->disposalMethod = ((b[0] >> 2) & 7);
|
|
if (ExtData[1] & 1) {
|
|
fi->transpIndex = 0xff & b[3];
|
|
}
|
|
if (fi->disposalMethod == 3 && info->backupPtr == NULL) {
|
|
if (!setupBackupBmp(info, fi->transpIndex)) {
|
|
return GIF_ERROR;
|
|
}
|
|
}
|
|
} else if (ExtFunction == COMMENT_EXT_FUNC_CODE) {
|
|
if (getComment(ExtData, &info->comment) == GIF_ERROR) {
|
|
info->gifFilePtr->Error = D_GIF_ERR_NOT_ENOUGH_MEM;
|
|
return GIF_ERROR;
|
|
}
|
|
} else if (ExtFunction == APPLICATION_EXT_FUNC_CODE && ExtData[0] == 11) {
|
|
if (strncmp("NETSCAPE2.0", &ExtData[1], 11) == 0 || strncmp("ANIMEXTS1.0", &ExtData[1], 11) == 0) {
|
|
if (DGifGetExtensionNext(info->gifFilePtr, &ExtData, &ExtFunction) == GIF_ERROR) {
|
|
return GIF_ERROR;
|
|
}
|
|
if (ExtFunction == APPLICATION_EXT_FUNC_CODE && ExtData[0] == 3 && ExtData[1] == 1) {
|
|
info->loopCount = (unsigned short) (ExtData[2] + (ExtData[3] << 8));
|
|
}
|
|
}
|
|
}
|
|
return GIF_OK;
|
|
}
|
|
|
|
static int DDGifSlurp(GifFileType *GifFile, GifInfo* info, bool shouldDecode) {
|
|
GifRecordType RecordType;
|
|
GifByteType *ExtData;
|
|
int codeSize;
|
|
int ExtFunction;
|
|
size_t ImageSize;
|
|
do {
|
|
if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) {
|
|
return (GIF_ERROR);
|
|
}
|
|
switch (RecordType) {
|
|
case IMAGE_DESC_RECORD_TYPE:
|
|
|
|
if (DGifGetImageDesc(GifFile, !shouldDecode) == GIF_ERROR) {
|
|
return (GIF_ERROR);
|
|
}
|
|
int i = shouldDecode ? info->currentIndex : GifFile->ImageCount - 1;
|
|
SavedImage *sp = &GifFile->SavedImages[i];
|
|
ImageSize = sp->ImageDesc.Width * sp->ImageDesc.Height;
|
|
|
|
if (sp->ImageDesc.Width < 1 || sp->ImageDesc.Height < 1 || ImageSize > (SIZE_MAX / sizeof(GifPixelType))) {
|
|
GifFile->Error = D_GIF_ERR_INVALID_IMG_DIMS;
|
|
return GIF_ERROR;
|
|
}
|
|
if (sp->ImageDesc.Width > GifFile->SWidth || sp->ImageDesc.Height > GifFile->SHeight) {
|
|
GifFile->Error = D_GIF_ERR_IMG_NOT_CONFINED;
|
|
return GIF_ERROR;
|
|
}
|
|
if (shouldDecode) {
|
|
sp->RasterBits = info->rasterBits;
|
|
if (sp->ImageDesc.Interlace) {
|
|
int i, j;
|
|
int InterlacedOffset[] = { 0, 4, 2, 1 };
|
|
int InterlacedJumps[] = { 8, 8, 4, 2 };
|
|
for (i = 0; i < 4; i++) {
|
|
for (j = InterlacedOffset[i]; j < sp->ImageDesc.Height; j += InterlacedJumps[i]) {
|
|
if (DGifGetLine(GifFile, sp->RasterBits + j * sp->ImageDesc.Width, sp->ImageDesc.Width) == GIF_ERROR) {
|
|
return GIF_ERROR;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (DGifGetLine(GifFile, sp->RasterBits, ImageSize) == GIF_ERROR) {
|
|
return (GIF_ERROR);
|
|
}
|
|
}
|
|
if (info->currentIndex >= GifFile->ImageCount - 1) {
|
|
if (info->loopCount > 0) {
|
|
info->currentLoop++;
|
|
}
|
|
if (fileRewindFun(info) != 0) {
|
|
info->gifFilePtr->Error = D_GIF_ERR_READ_FAILED;
|
|
return GIF_ERROR;
|
|
}
|
|
}
|
|
return GIF_OK;
|
|
} else {
|
|
if (DGifGetCode(GifFile, &codeSize, &ExtData) == GIF_ERROR) {
|
|
return (GIF_ERROR);
|
|
}
|
|
while (ExtData != NULL) {
|
|
if (DGifGetCodeNext(GifFile, &ExtData) == GIF_ERROR) {
|
|
return (GIF_ERROR);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case EXTENSION_RECORD_TYPE:
|
|
if (DGifGetExtension(GifFile, &ExtFunction, &ExtData) == GIF_ERROR) {
|
|
return (GIF_ERROR);
|
|
}
|
|
|
|
if (!shouldDecode) {
|
|
FrameInfo *tmpInfos = realloc(info->infos, (GifFile->ImageCount + 1) * sizeof(FrameInfo));
|
|
if (tmpInfos == NULL) {
|
|
return GIF_ERROR;
|
|
}
|
|
info->infos = tmpInfos;
|
|
if (readExtensions(ExtFunction, ExtData, info) == GIF_ERROR) {
|
|
return GIF_ERROR;
|
|
}
|
|
}
|
|
while (ExtData != NULL) {
|
|
if (DGifGetExtensionNext(GifFile, &ExtData, &ExtFunction) == GIF_ERROR) {
|
|
return (GIF_ERROR);
|
|
}
|
|
if (!shouldDecode) {
|
|
if (readExtensions(ExtFunction, ExtData, info) == GIF_ERROR) {
|
|
return GIF_ERROR;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case TERMINATE_RECORD_TYPE:
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
} while (RecordType != TERMINATE_RECORD_TYPE);
|
|
bool ok = true;
|
|
if (shouldDecode) {
|
|
ok = (fileRewindFun(info) == 0);
|
|
}
|
|
if (ok) {
|
|
return (GIF_OK);
|
|
} else {
|
|
info->gifFilePtr->Error = D_GIF_ERR_READ_FAILED;
|
|
return (GIF_ERROR);
|
|
}
|
|
}
|
|
|
|
static void copyLine(argb *dst, const unsigned char *src, const ColorMapObject *cmap, int transparent, int width) {
|
|
for (; width > 0; width--, src++, dst++) {
|
|
if (*src != transparent) {
|
|
getColorFromTable(*src, dst, cmap);
|
|
}
|
|
}
|
|
}
|
|
|
|
static argb *getAddr(argb *bm, int width, int left, int top) {
|
|
return bm + top * width + left;
|
|
}
|
|
|
|
static void blitNormal(argb *bm, int width, int height, const SavedImage *frame, const ColorMapObject *cmap, int transparent) {
|
|
const unsigned char* src = (unsigned char*) frame->RasterBits;
|
|
argb *dst = getAddr(bm, width, frame->ImageDesc.Left, frame->ImageDesc.Top);
|
|
GifWord copyWidth = frame->ImageDesc.Width;
|
|
if (frame->ImageDesc.Left + copyWidth > width) {
|
|
copyWidth = width - frame->ImageDesc.Left;
|
|
}
|
|
|
|
GifWord copyHeight = frame->ImageDesc.Height;
|
|
if (frame->ImageDesc.Top + copyHeight > height) {
|
|
copyHeight = height - frame->ImageDesc.Top;
|
|
}
|
|
|
|
for (; copyHeight > 0; copyHeight--) {
|
|
copyLine(dst, src, cmap, transparent, copyWidth);
|
|
src += frame->ImageDesc.Width;
|
|
dst += width;
|
|
}
|
|
}
|
|
|
|
static void fillRect(argb *bm, int bmWidth, int bmHeight, GifWord left, GifWord top, GifWord width, GifWord height, argb col) {
|
|
uint32_t* dst = (uint32_t*) getAddr(bm, bmWidth, left, top);
|
|
GifWord copyWidth = width;
|
|
if (left + copyWidth > bmWidth) {
|
|
copyWidth = bmWidth - left;
|
|
}
|
|
|
|
GifWord copyHeight = height;
|
|
if (top + copyHeight > bmHeight) {
|
|
copyHeight = bmHeight - top;
|
|
}
|
|
uint32_t* pColor = (uint32_t *) (&col);
|
|
for (; copyHeight > 0; copyHeight--) {
|
|
memset(dst, *pColor, copyWidth * sizeof(argb));
|
|
dst += bmWidth;
|
|
}
|
|
}
|
|
|
|
static void drawFrame(argb *bm, int bmWidth, int bmHeight, const SavedImage *frame, const ColorMapObject *cmap, short transpIndex) {
|
|
if (frame->ImageDesc.ColorMap != NULL) {
|
|
cmap = frame->ImageDesc.ColorMap;
|
|
if (cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
|
|
cmap = defaultCmap;
|
|
}
|
|
}
|
|
blitNormal(bm, bmWidth, bmHeight, frame, cmap, transpIndex);
|
|
}
|
|
|
|
static bool checkIfCover(const SavedImage *target, const SavedImage *covered) {
|
|
if (target->ImageDesc.Left <= covered->ImageDesc.Left
|
|
&& covered->ImageDesc.Left + covered->ImageDesc.Width
|
|
<= target->ImageDesc.Left + target->ImageDesc.Width
|
|
&& target->ImageDesc.Top <= covered->ImageDesc.Top
|
|
&& covered->ImageDesc.Top + covered->ImageDesc.Height
|
|
<= target->ImageDesc.Top + target->ImageDesc.Height) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static inline void disposeFrameIfNeeded(argb *bm, GifInfo *info, unsigned int idx) {
|
|
argb* backup = info->backupPtr;
|
|
argb color;
|
|
packARGB32(&color, 0, 0, 0, 0);
|
|
GifFileType *fGif = info->gifFilePtr;
|
|
SavedImage* cur = &fGif->SavedImages[idx - 1];
|
|
SavedImage* next = &fGif->SavedImages[idx];
|
|
bool curTrans = info->infos[idx - 1].transpIndex != -1;
|
|
int curDisposal = info->infos[idx - 1].disposalMethod;
|
|
bool nextTrans = info->infos[idx].transpIndex != -1;
|
|
int nextDisposal = info->infos[idx].disposalMethod;
|
|
argb *tmp;
|
|
if ((curDisposal == 2 || curDisposal == 3) && (nextTrans || !checkIfCover(next, cur))) {
|
|
switch (curDisposal) {
|
|
case 2:
|
|
|
|
fillRect(bm, fGif->SWidth, fGif->SHeight, cur->ImageDesc.Left, cur->ImageDesc.Top, cur->ImageDesc.Width, cur->ImageDesc.Height, color);
|
|
break;
|
|
|
|
case 3:
|
|
tmp = bm;
|
|
bm = backup;
|
|
backup = tmp;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nextDisposal == 3) {
|
|
memcpy(backup, bm, fGif->SWidth * fGif->SHeight * sizeof(argb));
|
|
}
|
|
}
|
|
|
|
static void reset(GifInfo *info) {
|
|
if (fileRewindFun(info) != 0) {
|
|
return;
|
|
}
|
|
info->nextStartTime = 0;
|
|
info->currentLoop = -1;
|
|
info->currentIndex = -1;
|
|
}
|
|
|
|
static void getBitmap(argb *bm, GifInfo *info) {
|
|
GifFileType* fGIF = info->gifFilePtr;
|
|
|
|
argb paintingColor;
|
|
int i = info->currentIndex;
|
|
if (DDGifSlurp(fGIF, info, true) == GIF_ERROR) {
|
|
return;
|
|
}
|
|
SavedImage* cur = &fGIF->SavedImages[i];
|
|
int transpIndex = info->infos[i].transpIndex;
|
|
if (i == 0) {
|
|
if (transpIndex == -1) {
|
|
getColorFromTable(fGIF->SBackGroundColor, &paintingColor, fGIF->SColorMap);
|
|
} else {
|
|
packARGB32(&paintingColor, 0, 0, 0, 0);
|
|
}
|
|
eraseColor(bm, fGIF->SWidth, fGIF->SHeight, paintingColor);
|
|
} else {
|
|
disposeFrameIfNeeded(bm, info, i);
|
|
}
|
|
drawFrame(bm, fGIF->SWidth, fGIF->SHeight, cur, fGIF->SColorMap, transpIndex);
|
|
}
|
|
|
|
static void setMetaData(int width, int height, int ImageCount, int errorCode, JNIEnv *env, jintArray metaData) {
|
|
jint *const ints = (*env)->GetIntArrayElements(env, metaData, 0);
|
|
if (ints == NULL) {
|
|
return;
|
|
}
|
|
ints[0] = width;
|
|
ints[1] = height;
|
|
ints[2] = ImageCount;
|
|
ints[3] = errorCode;
|
|
(*env)->ReleaseIntArrayElements(env, metaData, ints, 0);
|
|
}
|
|
|
|
static jint open(GifFileType *GifFileIn, int Error, int startPos, JNIEnv *env, jintArray metaData) {
|
|
if (startPos < 0) {
|
|
Error = D_GIF_ERR_NOT_READABLE;
|
|
DGifCloseFile(GifFileIn);
|
|
}
|
|
if (Error != 0 || GifFileIn == NULL) {
|
|
setMetaData(0, 0, 0, Error, env, metaData);
|
|
return (jint) NULL;
|
|
}
|
|
int width = GifFileIn->SWidth, height = GifFileIn->SHeight;
|
|
unsigned int wxh = width * height;
|
|
if (wxh < 1 || wxh > INT_MAX) {
|
|
DGifCloseFile(GifFileIn);
|
|
setMetaData(width, height, 0, D_GIF_ERR_INVALID_SCR_DIMS, env, metaData);
|
|
return (jint) NULL;
|
|
}
|
|
GifInfo *info = malloc(sizeof(GifInfo));
|
|
if (info == NULL) {
|
|
DGifCloseFile(GifFileIn);
|
|
setMetaData(width, height, 0, D_GIF_ERR_NOT_ENOUGH_MEM, env, metaData);
|
|
return (jint) NULL;
|
|
}
|
|
info->gifFilePtr = GifFileIn;
|
|
info->startPos = startPos;
|
|
info->currentIndex = -1;
|
|
info->nextStartTime = 0;
|
|
info->lastFrameReaminder = ULONG_MAX;
|
|
info->comment = NULL;
|
|
info->loopCount = 0;
|
|
info->currentLoop = -1;
|
|
info->speedFactor = 1.0;
|
|
info->rasterBits = calloc(GifFileIn->SHeight * GifFileIn->SWidth, sizeof(GifPixelType));
|
|
info->infos = malloc(sizeof(FrameInfo));
|
|
info->backupPtr = NULL;
|
|
|
|
if (info->rasterBits == NULL || info->infos == NULL) {
|
|
cleanUp(info);
|
|
setMetaData(width, height, 0, D_GIF_ERR_NOT_ENOUGH_MEM, env, metaData);
|
|
return (jint) NULL;
|
|
}
|
|
info->infos->duration = 0;
|
|
info->infos->disposalMethod = 0;
|
|
info->infos->transpIndex = -1;
|
|
if (GifFileIn->SColorMap == NULL || GifFileIn->SColorMap->ColorCount != (1 << GifFileIn->SColorMap->BitsPerPixel)) {
|
|
GifFreeMapObject(GifFileIn->SColorMap);
|
|
GifFileIn->SColorMap = defaultCmap;
|
|
}
|
|
|
|
DDGifSlurp(GifFileIn, info, false);
|
|
|
|
int imgCount = GifFileIn->ImageCount;
|
|
|
|
if (imgCount < 1) {
|
|
Error = D_GIF_ERR_NO_FRAMES;
|
|
}
|
|
if (fileRewindFun(info) != 0) {
|
|
Error = D_GIF_ERR_READ_FAILED;
|
|
}
|
|
if (Error != 0) {
|
|
cleanUp(info);
|
|
}
|
|
setMetaData(width, height, imgCount, Error, env, metaData);
|
|
return (jint)(Error == 0 ? info : NULL);
|
|
}
|
|
|
|
JNIEXPORT jlong JNICALL Java_org_telegram_ui_Components_GifDrawable_getAllocationByteCount(JNIEnv *env, jclass class, jobject gifInfo) {
|
|
GifInfo *info = (GifInfo *)gifInfo;
|
|
if (info == NULL) {
|
|
return 0;
|
|
}
|
|
unsigned int pxCount = info->gifFilePtr->SWidth + info->gifFilePtr->SHeight;
|
|
jlong sum = pxCount * sizeof(char);
|
|
if (info->backupPtr != NULL) {
|
|
sum += pxCount * sizeof(argb);
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_org_telegram_ui_Components_GifDrawable_reset(JNIEnv *env, jclass class, jobject gifInfo) {
|
|
GifInfo *info = (GifInfo *)gifInfo;
|
|
if (info == NULL) {
|
|
return;
|
|
}
|
|
reset(info);
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_org_telegram_ui_Components_GifDrawable_setSpeedFactor(JNIEnv *env, jclass class, jobject gifInfo, jfloat factor) {
|
|
GifInfo *info = (GifInfo *)gifInfo;
|
|
if (info == NULL) {
|
|
return;
|
|
}
|
|
info->speedFactor = factor;
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_org_telegram_ui_Components_GifDrawable_seekToTime(JNIEnv *env, jclass class, jobject gifInfo, jint desiredPos, jintArray jPixels) {
|
|
GifInfo *info = (GifInfo *)gifInfo;
|
|
if (info == NULL || jPixels == NULL) {
|
|
return;
|
|
}
|
|
int imgCount = info->gifFilePtr->ImageCount;
|
|
if (imgCount <= 1) {
|
|
return;
|
|
}
|
|
|
|
unsigned long sum = 0;
|
|
int i;
|
|
for (i = 0; i < imgCount; i++) {
|
|
unsigned long newSum = sum + info->infos[i].duration;
|
|
if (newSum >= desiredPos) {
|
|
break;
|
|
}
|
|
sum = newSum;
|
|
}
|
|
if (i < info->currentIndex) {
|
|
return;
|
|
}
|
|
|
|
unsigned long lastFrameRemainder = desiredPos - sum;
|
|
if (i == imgCount - 1 && lastFrameRemainder > info->infos[i].duration) {
|
|
lastFrameRemainder = info->infos[i].duration;
|
|
}
|
|
if (i > info->currentIndex) {
|
|
jint *const pixels = (*env)->GetIntArrayElements(env, jPixels, 0);
|
|
if (pixels == NULL) {
|
|
return;
|
|
}
|
|
while (info->currentIndex <= i) {
|
|
info->currentIndex++;
|
|
getBitmap((argb*) pixels, info);
|
|
}
|
|
(*env)->ReleaseIntArrayElements(env, jPixels, pixels, 0);
|
|
}
|
|
info->lastFrameReaminder = lastFrameRemainder;
|
|
|
|
if (info->speedFactor == 1.0) {
|
|
info->nextStartTime = getRealTime() + lastFrameRemainder;
|
|
} else {
|
|
info->nextStartTime = getRealTime() + lastFrameRemainder * info->speedFactor;
|
|
}
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_org_telegram_ui_Components_GifDrawable_seekToFrame(JNIEnv *env, jclass class, jobject gifInfo, jint desiredIdx, jintArray jPixels) {
|
|
GifInfo *info = (GifInfo *)gifInfo;
|
|
if (info == NULL|| jPixels==NULL) {
|
|
return;
|
|
}
|
|
if (desiredIdx <= info->currentIndex) {
|
|
return;
|
|
}
|
|
|
|
int imgCount = info->gifFilePtr->ImageCount;
|
|
if (imgCount <= 1) {
|
|
return;
|
|
}
|
|
|
|
jint *const pixels = (*env)->GetIntArrayElements(env, jPixels, 0);
|
|
if (pixels == NULL) {
|
|
return;
|
|
}
|
|
|
|
info->lastFrameReaminder = 0;
|
|
if (desiredIdx >= imgCount) {
|
|
desiredIdx = imgCount - 1;
|
|
}
|
|
|
|
while (info->currentIndex < desiredIdx) {
|
|
info->currentIndex++;
|
|
getBitmap((argb *) pixels, info);
|
|
}
|
|
(*env)->ReleaseIntArrayElements(env, jPixels, pixels, 0);
|
|
if (info->speedFactor == 1.0) {
|
|
info->nextStartTime = getRealTime() + info->infos[info->currentIndex].duration;
|
|
} else {
|
|
info->nextStartTime = getRealTime() + info->infos[info->currentIndex].duration * info->speedFactor;
|
|
}
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_org_telegram_ui_Components_GifDrawable_renderFrame(JNIEnv *env, jclass class, jintArray jPixels, jobject gifInfo, jintArray metaData) {
|
|
GifInfo *info = (GifInfo *)gifInfo;
|
|
if (info == NULL || jPixels == NULL) {
|
|
return;
|
|
}
|
|
bool needRedraw = false;
|
|
unsigned long rt = getRealTime();
|
|
|
|
if (rt >= info->nextStartTime && info->currentLoop < info->loopCount) {
|
|
if (++info->currentIndex >= info->gifFilePtr->ImageCount) {
|
|
info->currentIndex = 0;
|
|
}
|
|
needRedraw = true;
|
|
}
|
|
jint *const rawMetaData = (*env)->GetIntArrayElements(env, metaData, 0);
|
|
if (rawMetaData == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (needRedraw) {
|
|
jint *const pixels = (*env)->GetIntArrayElements(env, jPixels, 0);
|
|
if (pixels == NULL) {
|
|
(*env)->ReleaseIntArrayElements(env, metaData, rawMetaData, 0);
|
|
return;
|
|
}
|
|
getBitmap((argb *)pixels, info);
|
|
rawMetaData[3] = info->gifFilePtr->Error;
|
|
|
|
(*env)->ReleaseIntArrayElements(env, jPixels, pixels, 0);
|
|
unsigned int scaledDuration = info->infos[info->currentIndex].duration;
|
|
if (info->speedFactor != 1.0) {
|
|
scaledDuration /= info->speedFactor;
|
|
if (scaledDuration<=0) {
|
|
scaledDuration=1;
|
|
} else if (scaledDuration > INT_MAX) {
|
|
scaledDuration = INT_MAX;
|
|
}
|
|
}
|
|
info->nextStartTime = rt + scaledDuration;
|
|
rawMetaData[4] = scaledDuration;
|
|
} else {
|
|
long delay = info->nextStartTime-rt;
|
|
if (delay < 0) {
|
|
rawMetaData[4] = -1;
|
|
} else {
|
|
rawMetaData[4] = (int) delay;
|
|
}
|
|
}
|
|
(*env)->ReleaseIntArrayElements(env, metaData, rawMetaData, 0);
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_org_telegram_ui_Components_GifDrawable_free(JNIEnv *env, jclass class, jobject gifInfo) {
|
|
if (gifInfo == NULL) {
|
|
return;
|
|
}
|
|
GifInfo *info = (GifInfo *)gifInfo;
|
|
FILE *file = info->gifFilePtr->UserData;
|
|
if (file) {
|
|
fclose(file);
|
|
}
|
|
info->gifFilePtr->UserData = NULL;
|
|
cleanUp(info);
|
|
}
|
|
|
|
JNIEXPORT jstring JNICALL Java_org_telegram_ui_Components_GifDrawable_getComment(JNIEnv *env, jclass class, jobject gifInfo) {
|
|
if (gifInfo == NULL) {
|
|
return NULL;
|
|
}
|
|
GifInfo *info = (GifInfo *)gifInfo;
|
|
return (*env)->NewStringUTF(env, info->comment);
|
|
}
|
|
|
|
JNIEXPORT jint JNICALL Java_org_telegram_ui_Components_GifDrawable_getLoopCount(JNIEnv *env, jclass class, jobject gifInfo) {
|
|
if (gifInfo == NULL) {
|
|
return 0;
|
|
}
|
|
return ((GifInfo *)gifInfo)->loopCount;
|
|
}
|
|
|
|
JNIEXPORT jint JNICALL Java_org_telegram_ui_Components_GifDrawable_getDuration(JNIEnv *env, jclass class, jobject gifInfo) {
|
|
GifInfo *info = (GifInfo *)gifInfo;
|
|
if (info == NULL) {
|
|
return 0;
|
|
}
|
|
int i;
|
|
unsigned long sum = 0;
|
|
for (i = 0; i < info->gifFilePtr->ImageCount; i++) {
|
|
sum += info->infos[i].duration;
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
JNIEXPORT jint JNICALL Java_org_telegram_ui_Components_GifDrawable_getCurrentPosition(JNIEnv *env, jclass class, jobject gifInfo) {
|
|
GifInfo *info = (GifInfo *)gifInfo;
|
|
if (info == NULL) {
|
|
return 0;
|
|
}
|
|
int idx = info->currentIndex;
|
|
if (idx < 0 || info->gifFilePtr->ImageCount <= 1) {
|
|
return 0;
|
|
}
|
|
int i;
|
|
unsigned int sum = 0;
|
|
for (i = 0; i < idx; i++) {
|
|
sum += info->infos[i].duration;
|
|
}
|
|
unsigned long remainder = info->lastFrameReaminder == ULONG_MAX ? getRealTime() - info->nextStartTime : info->lastFrameReaminder;
|
|
return (int) (sum + remainder);
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_org_telegram_ui_Components_GifDrawable_saveRemainder(JNIEnv *env, jclass class, jobject gifInfo) {
|
|
GifInfo *info = (GifInfo *)gifInfo;
|
|
if (info == NULL) {
|
|
return;
|
|
}
|
|
info->lastFrameReaminder = getRealTime() - info->nextStartTime;
|
|
}
|
|
|
|
JNIEXPORT void JNICALL Java_org_telegram_ui_Components_GifDrawable_restoreRemainder(JNIEnv *env, jclass class, jobject gifInfo) {
|
|
GifInfo *info = (GifInfo *)gifInfo;
|
|
if (info == NULL || info->lastFrameReaminder == ULONG_MAX) {
|
|
return;
|
|
}
|
|
info->nextStartTime = getRealTime() + info->lastFrameReaminder;
|
|
info->lastFrameReaminder = ULONG_MAX;
|
|
}
|
|
|
|
JNIEXPORT jint JNICALL Java_org_telegram_ui_Components_GifDrawable_openFile(JNIEnv *env, jclass class, jintArray metaData, jstring jfname) {
|
|
if (jfname == NULL) {
|
|
setMetaData(0, 0, 0, D_GIF_ERR_OPEN_FAILED, env, metaData);
|
|
return (jint) NULL;
|
|
}
|
|
|
|
const char *const fname = (*env)->GetStringUTFChars(env, jfname, 0);
|
|
FILE *file = fopen(fname, "rb");
|
|
(*env)->ReleaseStringUTFChars(env, jfname, fname);
|
|
if (file == NULL) {
|
|
setMetaData(0, 0, 0, D_GIF_ERR_OPEN_FAILED, env, metaData);
|
|
return (jint) NULL;
|
|
}
|
|
int Error = 0;
|
|
GifFileType *GifFileIn = DGifOpen(file, &fileReadFunc, &Error);
|
|
return open(GifFileIn, Error, ftell(file), env, metaData);
|
|
}
|