Telegram-Android/TMessagesProj/jni/lottie.cpp

398 lines
15 KiB
C++
Raw Normal View History

2019-07-18 15:01:39 +02:00
#include <jni.h>
#include <android/bitmap.h>
#include <cstring>
#include <rlottie.h>
#include <lz4.h>
#include <unistd.h>
2019-12-31 14:08:08 +01:00
#include <condition_variable>
#include <atomic>
#include <thread>
2019-08-22 01:53:26 +02:00
#include <map>
2019-12-31 14:08:08 +01:00
#include <sys/stat.h>
#include <utime.h>
2020-09-30 15:48:47 +02:00
#include "tgnet/FileLog.h"
#include "tgnet/ConnectionsManager.h"
2019-07-18 15:01:39 +02:00
#include "c_utils.h"
extern "C" {
using namespace rlottie;
typedef struct LottieInfo {
~LottieInfo() {
if (decompressBuffer != nullptr) {
delete[]decompressBuffer;
decompressBuffer = nullptr;
}
}
std::unique_ptr<Animation> animation;
size_t frameCount = 0;
int32_t fps = 30;
bool precache = false;
bool createCache = false;
2019-12-31 14:08:08 +01:00
bool limitFps = false;
2019-07-18 15:01:39 +02:00
std::string path;
std::string cacheFile;
uint8_t *decompressBuffer = nullptr;
2019-12-31 14:08:08 +01:00
uint32_t decompressBufferSize = 0;
volatile uint32_t maxFrameSize = 0;
2019-07-18 15:01:39 +02:00
uint32_t imageSize = 0;
uint32_t fileOffset = 0;
bool nextFrameIsCacheFrame = false;
2019-12-31 14:08:08 +01:00
FILE *precacheFile = nullptr;
char *compressBuffer = nullptr;
const char *buffer = nullptr;
bool firstFrame = false;
int bufferSize;
int compressBound;
int firstFrameSize;
volatile uint32_t framesAvailableInCache = 0;
2019-07-18 15:01:39 +02:00
};
2020-09-30 15:48:47 +02:00
JNIEXPORT jlong Java_org_telegram_ui_Components_RLottieDrawable_create(JNIEnv *env, jclass clazz, jstring src, jint w, jint h, jintArray data, jboolean precache, jintArray colorReplacement, jboolean limitFps) {
2019-07-18 15:01:39 +02:00
LottieInfo *info = new LottieInfo();
2019-12-31 14:08:08 +01:00
std::map<int32_t, int32_t> *colors = nullptr;
2020-01-03 16:45:22 +01:00
int color = 0;
2019-08-22 01:53:26 +02:00
if (colorReplacement != nullptr) {
jint *arr = env->GetIntArrayElements(colorReplacement, 0);
if (arr != nullptr) {
jsize len = env->GetArrayLength(colorReplacement);
2019-12-31 14:08:08 +01:00
colors = new std::map<int32_t, int32_t>();
2019-08-22 01:53:26 +02:00
for (int32_t a = 0; a < len / 2; a++) {
2019-12-31 14:08:08 +01:00
(*colors)[arr[a * 2]] = arr[a * 2 + 1];
2020-01-03 16:45:22 +01:00
if (color == 0) {
color = arr[a * 2 + 1];
}
2019-08-22 01:53:26 +02:00
}
env->ReleaseIntArrayElements(colorReplacement, arr, 0);
}
}
2019-07-18 15:01:39 +02:00
char const *srcString = env->GetStringUTFChars(src, 0);
info->path = srcString;
2019-08-22 01:53:26 +02:00
info->animation = rlottie::Animation::loadFromFile(info->path, colors);
2019-07-18 15:01:39 +02:00
if (srcString != 0) {
env->ReleaseStringUTFChars(src, srcString);
}
if (info->animation == nullptr) {
delete info;
return 0;
}
info->frameCount = info->animation->totalFrame();
info->fps = (int) info->animation->frameRate();
2019-12-31 14:08:08 +01:00
info->limitFps = limitFps;
2019-07-18 15:01:39 +02:00
if (info->fps > 60 || info->frameCount > 600) {
delete info;
return 0;
}
info->precache = precache;
if (info->precache) {
info->cacheFile = info->path;
2019-12-31 14:08:08 +01:00
std::string::size_type index = info->cacheFile.find_last_of("/");
if (index != std::string::npos) {
std::string dir = info->cacheFile.substr(0, index) + "/acache";
mkdir(dir.c_str(), 0777);
info->cacheFile.insert(index, "/acache");
}
2020-01-03 16:45:22 +01:00
info->cacheFile += std::to_string(w) + "_" + std::to_string(h);
if (color != 0) {
info->cacheFile += "_" + std::to_string(color);
}
2019-12-31 14:08:08 +01:00
if (limitFps) {
2020-01-03 16:45:22 +01:00
info->cacheFile += ".s.cache";
2019-12-31 14:08:08 +01:00
} else {
2020-01-03 16:45:22 +01:00
info->cacheFile += ".cache";
2019-12-31 14:08:08 +01:00
}
2019-07-18 15:01:39 +02:00
FILE *precacheFile = fopen(info->cacheFile.c_str(), "r+");
if (precacheFile == nullptr) {
info->createCache = true;
} else {
uint8_t temp;
size_t read = fread(&temp, sizeof(uint8_t), 1, precacheFile);
info->createCache = read != 1 || temp == 0;
if (!info->createCache) {
2019-12-31 14:08:08 +01:00
uint32_t maxFrameSize;
fread(&maxFrameSize, sizeof(uint32_t), 1, precacheFile);
info->maxFrameSize = maxFrameSize;
2019-07-18 15:01:39 +02:00
fread(&(info->imageSize), sizeof(uint32_t), 1, precacheFile);
info->fileOffset = 9;
2019-12-31 14:08:08 +01:00
utimensat(0, info->cacheFile.c_str(), NULL, 0);
2019-07-18 15:01:39 +02:00
}
fclose(precacheFile);
}
}
jint *dataArr = env->GetIntArrayElements(data, 0);
if (dataArr != nullptr) {
dataArr[0] = (jint) info->frameCount;
dataArr[1] = (jint) info->animation->frameRate();
dataArr[2] = info->createCache ? 1 : 0;
env->ReleaseIntArrayElements(data, dataArr, 0);
}
return (jlong) (intptr_t) info;
}
2020-09-30 15:48:47 +02:00
JNIEXPORT jlong Java_org_telegram_ui_Components_RLottieDrawable_createWithJson(JNIEnv *env, jclass clazz, jstring json, jstring name, jintArray data, jintArray colorReplacement) {
2019-12-31 14:08:08 +01:00
std::map<int32_t, int32_t> *colors = nullptr;
if (colorReplacement != nullptr) {
jint *arr = env->GetIntArrayElements(colorReplacement, 0);
if (arr != nullptr) {
jsize len = env->GetArrayLength(colorReplacement);
colors = new std::map<int32_t, int32_t>();
for (int32_t a = 0; a < len / 2; a++) {
(*colors)[arr[a * 2]] = arr[a * 2 + 1];
}
env->ReleaseIntArrayElements(colorReplacement, arr, 0);
}
}
2019-07-18 15:01:39 +02:00
LottieInfo *info = new LottieInfo();
char const *jsonString = env->GetStringUTFChars(json, 0);
char const *nameString = env->GetStringUTFChars(name, 0);
2019-12-31 14:08:08 +01:00
info->animation = rlottie::Animation::loadFromData(jsonString, nameString, colors);
2019-07-18 15:01:39 +02:00
if (jsonString != 0) {
env->ReleaseStringUTFChars(json, jsonString);
}
if (nameString != 0) {
env->ReleaseStringUTFChars(name, nameString);
}
if (info->animation == nullptr) {
delete info;
return 0;
}
info->frameCount = info->animation->totalFrame();
info->fps = (int) info->animation->frameRate();
jint *dataArr = env->GetIntArrayElements(data, 0);
if (dataArr != nullptr) {
dataArr[0] = (int) info->frameCount;
dataArr[1] = (int) info->animation->frameRate();
dataArr[2] = 0;
env->ReleaseIntArrayElements(data, dataArr, 0);
}
return (jlong) (intptr_t) info;
}
2020-09-30 15:48:47 +02:00
JNIEXPORT void Java_org_telegram_ui_Components_RLottieDrawable_destroy(JNIEnv *env, jclass clazz, jlong ptr) {
if (!ptr) {
2019-07-18 15:01:39 +02:00
return;
}
LottieInfo *info = (LottieInfo *) (intptr_t) ptr;
delete info;
}
2020-09-30 15:48:47 +02:00
JNIEXPORT void Java_org_telegram_ui_Components_RLottieDrawable_setLayerColor(JNIEnv *env, jclass clazz, jlong ptr, jstring layer, jint color) {
if (!ptr || layer == nullptr) {
2019-07-18 15:01:39 +02:00
return;
}
LottieInfo *info = (LottieInfo *) (intptr_t) ptr;
char const *layerString = env->GetStringUTFChars(layer, 0);
info->animation->setValue<Property::Color>(layerString, Color(((color) & 0xff) / 255.0f, ((color >> 8) & 0xff) / 255.0f, ((color >> 16) & 0xff) / 255.0f));
if (layerString != 0) {
env->ReleaseStringUTFChars(layer, layerString);
}
}
2020-09-30 15:48:47 +02:00
JNIEXPORT void Java_org_telegram_ui_Components_RLottieDrawable_replaceColors(JNIEnv *env, jclass clazz, jlong ptr, jintArray colorReplacement) {
if (!ptr || colorReplacement == nullptr) {
2019-12-31 14:08:08 +01:00
return;
}
LottieInfo *info = (LottieInfo *) (intptr_t) ptr;
jint *arr = env->GetIntArrayElements(colorReplacement, 0);
if (arr != nullptr) {
jsize len = env->GetArrayLength(colorReplacement);
for (int32_t a = 0; a < len / 2; a++) {
(*info->animation->colorMap)[arr[a * 2]] = arr[a * 2 + 1];
}
info->animation->resetCurrentFrame();
env->ReleaseIntArrayElements(colorReplacement, arr, 0);
}
}
bool cacheWriteThreadCreated{false};
LottieInfo *cacheWriteThreadTask{nullptr};
bool cacheWriteThreadDone{false};
std::thread worker;
std::mutex cacheMutex;
std::condition_variable cacheCv;
std::mutex cacheDoneMutex;
std::condition_variable cacheDoneCv;
std::atomic<bool> frameReady{false};
void CacheWriteThreadProc() {
while (!cacheWriteThreadDone) {
std::unique_lock<std::mutex> lk(cacheMutex);
cacheCv.wait(lk, [] { return frameReady.load(); });
std::lock_guard<std::mutex> lg(cacheDoneMutex);
LottieInfo *task;
if (cacheWriteThreadTask != nullptr) {
task = cacheWriteThreadTask;
cacheWriteThreadTask = nullptr;
} else {
task = nullptr;
}
lk.unlock();
if (task != nullptr) {
uint32_t size = (uint32_t) LZ4_compress_default(task->buffer, task->compressBuffer, task->bufferSize, task->compressBound);
if (task->firstFrame) {
task->firstFrameSize = size;
task->fileOffset = 9 + sizeof(uint32_t) + task->firstFrameSize;
}
task->maxFrameSize = MAX(task->maxFrameSize, size);
fwrite(&size, sizeof(uint32_t), 1, task->precacheFile);
fwrite(task->compressBuffer, sizeof(uint8_t), size, task->precacheFile);
fflush(task->precacheFile);
fsync(fileno(task->precacheFile));
task->framesAvailableInCache++;
}
frameReady = false;
cacheDoneCv.notify_one();
}
}
2020-09-30 15:48:47 +02:00
JNIEXPORT void Java_org_telegram_ui_Components_RLottieDrawable_createCache(JNIEnv *env, jclass clazz, jlong ptr, jint w, jint h) {
2019-12-31 14:08:08 +01:00
if (ptr == NULL) {
2019-07-18 15:01:39 +02:00
return;
}
LottieInfo *info = (LottieInfo *) (intptr_t) ptr;
2019-12-31 14:08:08 +01:00
FILE *cacheFile = fopen(info->cacheFile.c_str(), "r+");
if (cacheFile != nullptr) {
2019-07-18 15:01:39 +02:00
uint8_t temp;
2019-12-31 14:08:08 +01:00
size_t read = fread(&temp, sizeof(uint8_t), 1, cacheFile);
fclose(cacheFile);
2019-07-18 15:01:39 +02:00
if (read == 1 && temp != 0) {
return;
}
}
2019-12-31 14:08:08 +01:00
if (!cacheWriteThreadCreated) {
cacheWriteThreadCreated = true;
worker = std::thread(CacheWriteThreadProc);
}
2019-07-18 15:01:39 +02:00
2019-12-31 14:08:08 +01:00
if (info->nextFrameIsCacheFrame && info->createCache && info->frameCount != 0) {
info->precacheFile = fopen(info->cacheFile.c_str(), "w+");
if (info->precacheFile != nullptr) {
fseek(info->precacheFile, info->fileOffset = 9, SEEK_SET);
2019-07-18 15:01:39 +02:00
info->maxFrameSize = 0;
2019-12-31 14:08:08 +01:00
info->bufferSize = w * h * 4;
info->imageSize = (uint32_t) w * h * 4;
info->compressBound = LZ4_compressBound(info->bufferSize);
info->compressBuffer = new char[info->compressBound];
uint8_t *firstBuffer = new uint8_t[info->bufferSize];
uint8_t *secondBuffer = new uint8_t[info->bufferSize];
//long time = ConnectionsManager::getInstance(0).getCurrentTimeMonotonicMillis();
Surface surface1((uint32_t *) firstBuffer, (size_t) w, (size_t) h, (size_t) w * 4);
Surface surface2((uint32_t *) secondBuffer, (size_t) w, (size_t) h, (size_t) w * 4);
int framesPerUpdate = !info->limitFps || info->fps < 60 ? 1 : 2;
int num = 0;
2019-07-18 15:01:39 +02:00
for (size_t a = 0; a < info->frameCount; a += framesPerUpdate) {
2019-12-31 14:08:08 +01:00
Surface &surfaceToRender = num % 2 == 0 ? surface1 : surface2;
num++;
2020-10-30 11:26:29 +01:00
info->animation->renderSync(a, surfaceToRender, true);
2019-12-31 14:08:08 +01:00
if (a != 0) {
std::unique_lock<std::mutex> lk(cacheDoneMutex);
cacheDoneCv.wait(lk, [] { return !frameReady.load(); });
2019-07-18 15:01:39 +02:00
}
2019-12-31 14:08:08 +01:00
std::lock_guard<std::mutex> lg(cacheMutex);
cacheWriteThreadTask = info;
info->firstFrame = a == 0;
info->buffer = (const char *) surfaceToRender.buffer();
frameReady = true;
cacheCv.notify_one();
2019-07-18 15:01:39 +02:00
}
2019-12-31 14:08:08 +01:00
std::unique_lock<std::mutex> lk(cacheDoneMutex);
cacheDoneCv.wait(lk, [] { return !frameReady.load(); });
//DEBUG_D("sticker time = %d", (int) (ConnectionsManager::getInstance(0).getCurrentTimeMonotonicMillis() - time));
delete[] info->compressBuffer;
2020-10-30 11:26:29 +01:00
delete[] firstBuffer;
2019-12-31 14:08:08 +01:00
delete[] secondBuffer;
fseek(info->precacheFile, 0, SEEK_SET);
2019-07-18 15:01:39 +02:00
uint8_t byte = 1;
2019-12-31 14:08:08 +01:00
fwrite(&byte, sizeof(uint8_t), 1, info->precacheFile);
uint32_t maxFrameSize = info->maxFrameSize;
fwrite(&maxFrameSize, sizeof(uint32_t), 1, info->precacheFile);
fwrite(&info->imageSize, sizeof(uint32_t), 1, info->precacheFile);
fflush(info->precacheFile);
fsync(fileno(info->precacheFile));
2019-07-18 15:01:39 +02:00
info->createCache = false;
2019-12-31 14:08:08 +01:00
fclose(info->precacheFile);
2019-07-18 15:01:39 +02:00
}
}
}
2020-10-30 11:26:29 +01:00
JNIEXPORT jint Java_org_telegram_ui_Components_RLottieDrawable_getFrame(JNIEnv *env, jclass clazz, jlong ptr, jint frame, jobject bitmap, jint w, jint h, jint stride, jboolean clear) {
2020-09-30 15:48:47 +02:00
if (!ptr || bitmap == nullptr) {
2019-07-18 15:01:39 +02:00
return 0;
}
LottieInfo *info = (LottieInfo *) (intptr_t) ptr;
2019-12-31 14:08:08 +01:00
int framesPerUpdate = !info->limitFps || info->fps < 60 ? 1 : 2;
int framesAvailableInCache = info->framesAvailableInCache;
if (info->createCache && info->precache && frame > 0) {
if (frame / framesPerUpdate >= framesAvailableInCache) {
return -1;
}
}
2019-07-18 15:01:39 +02:00
void *pixels;
if (AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0) {
bool loadedFromCache = false;
2019-12-31 14:08:08 +01:00
uint32_t maxFrameSize = info->maxFrameSize;
if (info->precache && (!info->createCache || frame > 0) && w * 4 == stride && maxFrameSize <= w * h * 4 && info->imageSize == w * h * 4) {
2019-07-18 15:01:39 +02:00
FILE *precacheFile = fopen(info->cacheFile.c_str(), "r");
if (precacheFile != nullptr) {
2019-12-31 14:08:08 +01:00
if (info->decompressBuffer != nullptr && info->decompressBufferSize < maxFrameSize) {
delete[] info->decompressBuffer;
info->decompressBuffer = nullptr;
}
2019-07-18 15:01:39 +02:00
if (info->decompressBuffer == nullptr) {
2019-12-31 14:08:08 +01:00
info->decompressBufferSize = maxFrameSize;
if (info->createCache) {
info->decompressBufferSize += 10000;
}
info->decompressBuffer = new uint8_t[info->decompressBufferSize];
2019-07-18 15:01:39 +02:00
}
2019-12-31 14:08:08 +01:00
fseek(precacheFile, info->fileOffset, SEEK_SET);
2019-07-18 15:01:39 +02:00
uint32_t frameSize;
fread(&frameSize, sizeof(uint32_t), 1, precacheFile);
2019-12-31 14:08:08 +01:00
if (frameSize > 0 && frameSize <= info->decompressBufferSize) {
2019-07-18 15:01:39 +02:00
fread(info->decompressBuffer, sizeof(uint8_t), frameSize, precacheFile);
info->fileOffset += 4 + frameSize;
LZ4_decompress_safe((const char *) info->decompressBuffer, (char *) pixels, frameSize, w * h * 4);
loadedFromCache = true;
}
fclose(precacheFile);
if (frame + framesPerUpdate >= info->frameCount) {
info->fileOffset = 9;
}
}
}
2019-12-31 14:08:08 +01:00
2019-07-18 15:01:39 +02:00
if (!loadedFromCache) {
2019-12-31 14:08:08 +01:00
if (!info->nextFrameIsCacheFrame || !info->precache) {
Surface surface((uint32_t *) pixels, (size_t) w, (size_t) h, (size_t) stride);
2020-10-30 11:26:29 +01:00
info->animation->renderSync((size_t) frame, surface, clear);
2019-12-31 14:08:08 +01:00
info->nextFrameIsCacheFrame = true;
}
2019-07-18 15:01:39 +02:00
}
AndroidBitmap_unlockPixels(env, bitmap);
}
return frame;
}
}