From b527b5c1a3cfdd82f0396332724d36363c4649d5 Mon Sep 17 00:00:00 2001 From: succlz123 Date: Mon, 17 Jun 2019 04:27:34 +0800 Subject: [PATCH] v0.0.13: fix image generation error when using "Bitmap.Config.RGB_565" --- README.md | 30 +++++++++++-------- android/build.gradle | 4 +-- .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/lib/build.gradle | 1 - android/lib/src/main/cpp/BurstLinker.cpp | 2 +- android/sample/build.gradle | 3 -- .../burstlinker/sample/MainActivity.java | 12 ++++---- src/Logger.cpp | 2 +- src/Main.cpp | 10 +++---- 9 files changed, 34 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 13e3f27..7be057a 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ implementation 'com.bilibili:burst-linker:latest-version' #### Build Environment -Android Studio 3.2.1 +Android Studio 3.4.1 -NDK r18 +NDK r20 #### Basic usage @@ -50,12 +50,12 @@ try { #### Enable RenderScript Support -> This is an untested experimental feature +> This is an untested feature. -1. Git branch `/feature/render-script` -2. Gradle sync, It will generate the required ScriptC_*.cpp -3. Uncomment the line 64 of the /lib/CMakeLists.txt -4. Run +1. Choose the Git branch "/feature/render-script". +2. Sync Project with Gradle Files, It will generate the required file named "ScriptC_*.cpp". +3. Uncomment the line 64 of the "/lib/CMakeLists.txt". +4. Try this function. ### Linux & Mac @@ -71,7 +71,7 @@ try { 3. Run - `./BurstLinker 1000 1.jpg 2.jpg 3.jpg` - - See out.gif + - See the "out.gif" ### Windows @@ -81,13 +81,13 @@ try { - `cd /BurstLinker` - `mkdir cmake-build-debug; cd cmake-build-debug` - `cmake ..` - - Open BurstLinker.sln + - Open the "BurstLinker.sln" - Solution Explorer -> BurstLinker -> Build 3. Run - `cd Debug` - `BurstLinker.exe 1000 1.jpg 2.jpg 3.jpg` - - See out.gif + - See the "out.gif" ## Samples @@ -137,17 +137,21 @@ try { ![octree-floyd-steinberg](screenshot/octree-floyd-steinberg.gif) -### Encoding with transparent image +### Encodes images with transparent channels - Original ![bilibili](screenshot/bilibili.png) -- Octree + No + Default (ARGB.a != 0) +- Octree + No + Default + + Display all Alpha channels greater than 0. (ARGB.a != 0) ![bilibili-octree](screenshot/bilibili-octree-default.gif) -- Octree + No + Ignore translucency (ARGB.a == 255) +- Octree + No + Ignored translucency + + Display only the Alpha channels equal to 255. (ARGB.a == 255) ![bilibili-octree](screenshot/bilibili-octree-ignore.gif) diff --git a/android/build.gradle b/android/build.gradle index 26ac886..efefc85 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,8 +6,8 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.4' - classpath 'com.novoda:bintray-release:0.8.1' + classpath 'com.android.tools.build:gradle:3.4.1' + classpath 'com.novoda:bintray-release:0.9.1' } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 0a8f6fa..714f7d4 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/android/lib/build.gradle b/android/lib/build.gradle index cd0e6f4..1a79dcc 100644 --- a/android/lib/build.gradle +++ b/android/lib/build.gradle @@ -21,7 +21,6 @@ android { externalNativeBuild { cmake { -// abiFilters 'armeabi-v7a', 'x86' // arguments "-DANDROID_ARM_NEON=TRUE" // arguments "-DANDROID_ABI=armeabi-v7a with NEON" arguments "-DANDROID_STL=c++_static" diff --git a/android/lib/src/main/cpp/BurstLinker.cpp b/android/lib/src/main/cpp/BurstLinker.cpp index e7dd3a9..7038f8c 100644 --- a/android/lib/src/main/cpp/BurstLinker.cpp +++ b/android/lib/src/main/cpp/BurstLinker.cpp @@ -1 +1 @@ -// // Created by succlz123 on 17-9-5. // #include #include #include #include #include #include "../../../../../src/GifEncoder.h" #include "../../../../../src/Logger.h" #include "../../../../../src/ThreadPool.h" #ifdef __cplusplus extern "C" { #endif #define RGB565_R(p) ((((p) & 0xF800) >> 11) << 3) #define RGB565_G(p) ((((p) & 0x7E0) >> 5) << 2) #define RGB565_B(p) (((p) & 0x1F) << 3) JNIEXPORT jlong JNICALL Java_com_bilibili_burstlinker_BurstLinker_jniInit(JNIEnv *env, jobject, jstring path, jint width, jint height, jint loopCount, jint threadCount) { const char *pathStr = env->GetStringUTFChars(path, nullptr); if (pathStr == nullptr) { return 0; } auto *gifEncoder = new blk::GifEncoder(); bool success = gifEncoder->init(pathStr, (uint16_t) width, (uint16_t) height, (uint32_t) loopCount, (uint32_t) threadCount); env->ReleaseStringUTFChars(path, pathStr); if (success) { return (jlong) gifEncoder; } else { delete gifEncoder; return 0; } } JNIEXPORT void JNICALL Java_com_bilibili_burstlinker_BurstLinker_jniDebugLog(JNIEnv *env, jobject, jlong handle, jboolean debug) { blk::GifEncoder *gifEncoder = (blk::GifEncoder *) handle; gifEncoder->debugLog = debug; } JNIEXPORT jstring JNICALL Java_com_bilibili_burstlinker_BurstLinker_jniConnect(JNIEnv *env, jobject, jlong handle, jint quantizerType, jint ditherType, jint ignoreTranslucency, jint left, jint top, jint delay, jstring rsCacheDir, jobject jBitmap) { if (jBitmap == nullptr) { return env->NewStringUTF("jBitmap is null"); } auto *gifEncoder = (blk::GifEncoder *) handle; AndroidBitmapInfo androidBitmapInfo; if (AndroidBitmap_getInfo(env, jBitmap, &androidBitmapInfo) < 0) { return env->NewStringUTF("call AndroidBitmap_getInfo failed"); } void *src = nullptr; if (AndroidBitmap_lockPixels(env, jBitmap, &src) < 0) { return env->NewStringUTF("call AndroidBitmap_lockPixels failed"); } char *rsCacheDirStr = nullptr; if (rsCacheDir != nullptr) { const char *tmp = env->GetStringUTFChars(rsCacheDir, nullptr); rsCacheDirStr = new char[strlen(tmp)]; strcpy(rsCacheDirStr, tmp); gifEncoder->rsCacheDir = rsCacheDirStr; env->ReleaseStringUTFChars(rsCacheDir, tmp); } uint16_t width = gifEncoder->screenWidth; uint16_t height = gifEncoder->screenHeight; uint32_t imageSize = width * height; std::vector dst(imageSize); int32_t stride = 0; int enableTransparency; if (androidBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGB_565) { stride = width * 2; int32_t pixelsCount = stride * height; uint16_t *tmp = new uint16_t[imageSize]; memcpy(tmp, src, static_cast(pixelsCount)); AndroidBitmap_unlockPixels(env, jBitmap); for (int k = 0; k < imageSize; ++k) { uint16_t v = tmp[k]; dst.push_back(RGB565_B(v) << 16 | RGB565_G(v) << 8 | RGB565_R(v)); } delete[] tmp; enableTransparency = 0; } else if (androidBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888) { stride = width * 4; int32_t pixelsCount = stride * height; memcpy((void *) &dst[0], src, static_cast(pixelsCount)); AndroidBitmap_unlockPixels(env, jBitmap); enableTransparency = 1; } else { return env->NewStringUTF("bitmap's format is't RGB_565 or RGBA_8888"); } int transparencyOption = (ignoreTranslucency << 8) | enableTransparency; std::vector out; gifEncoder->addImage(dst, delay, static_cast(quantizerType), static_cast(ditherType), transparencyOption, (uint16_t) left, (uint16_t) top, out); if (out.size() <= 0) { return env->NewStringUTF("gifEncoder add image out arrays is empty"); } gifEncoder->flush(out); return nullptr; } JNIEXPORT jstring JNICALL Java_com_bilibili_burstlinker_BurstLinker_jniConnectArray(JNIEnv *env, jobject, jlong handle, jint quantizerType, jint ditherType, jint ignoreTranslucency, jint left, jint top, jint delay, jstring rsCacheDir, jobjectArray jBitmapArray) { auto *gifEncoder = (blk::GifEncoder *) handle; char *rsCacheDirStr = nullptr; if (rsCacheDir != nullptr) { const char *tmp = env->GetStringUTFChars(rsCacheDir, nullptr); rsCacheDirStr = new char[strlen(tmp)]; strcpy(rsCacheDirStr, tmp); gifEncoder->rsCacheDir = rsCacheDirStr; env->ReleaseStringUTFChars(rsCacheDir, tmp); } std::vector>> tasks; jsize count = env->GetArrayLength(jBitmapArray); for (int i = 0; i < count; i++) { jobject jBitmap = env->GetObjectArrayElement(jBitmapArray, i); if (jBitmap == nullptr) { return env->NewStringUTF("jBitmap is null"); } AndroidBitmapInfo androidBitmapInfo; if (AndroidBitmap_getInfo(env, jBitmap, &androidBitmapInfo) < 0) { env->DeleteLocalRef(jBitmap); return env->NewStringUTF("call AndroidBitmap_getInfo failed"); } void *src = nullptr; if (AndroidBitmap_lockPixels(env, jBitmap, &src) < 0) { env->DeleteLocalRef(jBitmap); return env->NewStringUTF("call AndroidBitmap_lockPixels failed"); } uint16_t width = gifEncoder->screenWidth; uint16_t height = gifEncoder->screenHeight; uint32_t imageSize = width * height; std::vector dst(imageSize); int32_t stride = 0; int enableTransparency; if (androidBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGB_565) { stride = width * 2; int32_t pixelsCount = stride * height; uint16_t *tmp = new uint16_t[imageSize]; memcpy(tmp, src, static_cast(pixelsCount)); AndroidBitmap_unlockPixels(env, jBitmap); for (int k = 0; k < imageSize; ++k) { uint16_t v = tmp[k]; dst.push_back(RGB565_B(v) << 16 | RGB565_G(v) << 8 | RGB565_R(v)); } delete[] tmp; enableTransparency = 0; } else if (androidBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888) { stride = width * 4; int32_t pixelsCount = stride * height; memcpy((void *) &dst[0], src, static_cast(pixelsCount)); AndroidBitmap_unlockPixels(env, jBitmap); enableTransparency = 1; } else { env->DeleteLocalRef(jBitmap); return env->NewStringUTF("bitmap's format is't RGB_565 or RGBA_8888"); } env->DeleteLocalRef(jBitmap); int transparencyOption = (ignoreTranslucency << 8) | enableTransparency; auto result = gifEncoder->threadPool->enqueue([=]() { std::vector out; gifEncoder->addImage(dst, delay, static_cast(quantizerType), static_cast(ditherType), transparencyOption, (uint16_t) left, (uint16_t) top, out); return out; }); tasks.emplace_back(std::move(result)); } for (auto &task : tasks) { std::vector out = task.get(); if (out.size() <= 0) { return env->NewStringUTF("gifEncoder add image out arrays is empty"); } gifEncoder->flush(out); } return nullptr; } JNIEXPORT void JNICALL Java_com_bilibili_burstlinker_BurstLinker_jniRelease(JNIEnv *env, jobject, jlong handle) { blk::GifEncoder *gifEncoder = (blk::GifEncoder *) handle; if (gifEncoder != nullptr) { gifEncoder->finishEncoding(); delete gifEncoder; } } #ifdef __cplusplus } #endif \ No newline at end of file +// // Created by succlz123 on 17-9-5. // #include #include #include #include #include #include "../../../../../src/GifEncoder.h" #include "../../../../../src/Logger.h" #include "../../../../../src/ThreadPool.h" #ifdef __cplusplus extern "C" { #endif #define RGB565_R(p) ((((p) & 0xF800) >> 11) << 3) #define RGB565_G(p) ((((p) & 0x7E0) >> 5) << 2) #define RGB565_B(p) (((p) & 0x1F) << 3) #define MAKE_BGR(b, g, r) (((b) << 16) | ((g) << 8) | (r)) JNIEXPORT jlong JNICALL Java_com_bilibili_burstlinker_BurstLinker_jniInit(JNIEnv *env, jobject, jstring path, jint width, jint height, jint loopCount, jint threadCount) { const char *pathStr = env->GetStringUTFChars(path, nullptr); if (pathStr == nullptr) { return 0; } auto *gifEncoder = new blk::GifEncoder(); bool success = gifEncoder->init(pathStr, (uint16_t) width, (uint16_t) height, (uint32_t) loopCount, (uint32_t) threadCount); env->ReleaseStringUTFChars(path, pathStr); if (success) { return (jlong) gifEncoder; } else { delete gifEncoder; return 0; } } JNIEXPORT void JNICALL Java_com_bilibili_burstlinker_BurstLinker_jniDebugLog(JNIEnv *env, jobject, jlong handle, jboolean debug) { auto *gifEncoder = (blk::GifEncoder *) handle; gifEncoder->debugLog = debug; } JNIEXPORT jstring JNICALL Java_com_bilibili_burstlinker_BurstLinker_jniConnect(JNIEnv *env, jobject, jlong handle, jint quantizerType, jint ditherType, jint ignoreTranslucency, jint left, jint top, jint delay, jstring rsCacheDir, jobject jBitmap) { if (jBitmap == nullptr) { return env->NewStringUTF("jBitmap is null"); } auto *gifEncoder = (blk::GifEncoder *) handle; AndroidBitmapInfo androidBitmapInfo; if (AndroidBitmap_getInfo(env, jBitmap, &androidBitmapInfo) < 0) { return env->NewStringUTF("call AndroidBitmap_getInfo failed"); } void *src = nullptr; if (AndroidBitmap_lockPixels(env, jBitmap, &src) < 0) { return env->NewStringUTF("call AndroidBitmap_lockPixels failed"); } char *rsCacheDirStr = nullptr; if (rsCacheDir != nullptr) { const char *tmp = env->GetStringUTFChars(rsCacheDir, nullptr); rsCacheDirStr = new char[strlen(tmp)]; strcpy(rsCacheDirStr, tmp); gifEncoder->rsCacheDir = rsCacheDirStr; env->ReleaseStringUTFChars(rsCacheDir, tmp); } uint16_t width = gifEncoder->screenWidth; uint16_t height = gifEncoder->screenHeight; uint32_t imageSize = width * height; std::vector dst(imageSize); int8_t enableTransparency = 0; bool validFormat = true; if (androidBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGB_565) { for (int k = 0; k < imageSize; ++k) { uint16_t v = *(((uint16_t *) src) + k); dst[k] = static_cast(MAKE_BGR(RGB565_B(v), RGB565_G(v), RGB565_R(v))); } } else if (androidBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888) { memcpy((void *) &dst[0], src, imageSize * 4); enableTransparency = 1; } else { validFormat = false; } AndroidBitmap_unlockPixels(env, jBitmap); if (!validFormat) { return env->NewStringUTF("bitmap's format is't RGB_565 or RGBA_8888"); } int32_t transparencyOption = (ignoreTranslucency << 8) | enableTransparency; std::vector out; gifEncoder->addImage(dst, static_cast(delay), static_cast(quantizerType), static_cast(ditherType), transparencyOption, (uint16_t) left, (uint16_t) top, out); if (out.empty()) { return env->NewStringUTF("gifEncoder add image out arrays is empty"); } gifEncoder->flush(out); return nullptr; } JNIEXPORT jstring JNICALL Java_com_bilibili_burstlinker_BurstLinker_jniConnectArray(JNIEnv *env, jobject, jlong handle, jint quantizerType, jint ditherType, jint ignoreTranslucency, jint left, jint top, jint delay, jstring rsCacheDir, jobjectArray jBitmapArray) { auto *gifEncoder = (blk::GifEncoder *) handle; char *rsCacheDirStr = nullptr; if (rsCacheDir != nullptr) { const char *tmp = env->GetStringUTFChars(rsCacheDir, nullptr); rsCacheDirStr = new char[strlen(tmp)]; strcpy(rsCacheDirStr, tmp); gifEncoder->rsCacheDir = rsCacheDirStr; env->ReleaseStringUTFChars(rsCacheDir, tmp); } std::vector>> tasks; jsize count = env->GetArrayLength(jBitmapArray); for (int i = 0; i < count; i++) { jobject jBitmap = env->GetObjectArrayElement(jBitmapArray, i); if (jBitmap == nullptr) { return env->NewStringUTF("jBitmap is null"); } AndroidBitmapInfo androidBitmapInfo; if (AndroidBitmap_getInfo(env, jBitmap, &androidBitmapInfo) < 0) { env->DeleteLocalRef(jBitmap); return env->NewStringUTF("call AndroidBitmap_getInfo failed"); } void *src = nullptr; if (AndroidBitmap_lockPixels(env, jBitmap, &src) < 0) { env->DeleteLocalRef(jBitmap); return env->NewStringUTF("call AndroidBitmap_lockPixels failed"); } uint16_t width = gifEncoder->screenWidth; uint16_t height = gifEncoder->screenHeight; uint32_t imageSize = width * height; std::vector dst(imageSize); int8_t enableTransparency = 0; bool validFormat = true; if (androidBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGB_565) { for (int k = 0; k < imageSize; ++k) { uint16_t v = *(((uint16_t *) src) + k); dst[k] = static_cast(MAKE_BGR(RGB565_B(v), RGB565_G(v), RGB565_R(v))); } } else if (androidBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888) { memcpy((void *) &dst[0], src, imageSize * 4); enableTransparency = 1; } else { validFormat = false; } AndroidBitmap_unlockPixels(env, jBitmap); env->DeleteLocalRef(jBitmap); if (!validFormat) { return env->NewStringUTF("bitmap's format is't RGB_565 or RGBA_8888"); } int32_t transparencyOption = (ignoreTranslucency << 8) | enableTransparency; auto result = gifEncoder->threadPool->enqueue([=]() { std::vector out; gifEncoder->addImage(dst, static_cast(delay), static_cast(quantizerType), static_cast(ditherType), transparencyOption, (uint16_t) left, (uint16_t) top, out); return out; }); tasks.emplace_back(std::move(result)); } for (auto &task : tasks) { std::vector out = task.get(); if (out.empty()) { return env->NewStringUTF("gifEncoder add image out arrays is empty"); } gifEncoder->flush(out); } return nullptr; } JNIEXPORT void JNICALL Java_com_bilibili_burstlinker_BurstLinker_jniRelease(JNIEnv *env, jobject, jlong handle) { auto *gifEncoder = (blk::GifEncoder *) handle; if (gifEncoder != nullptr) { gifEncoder->finishEncoding(); delete gifEncoder; } } #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/android/sample/build.gradle b/android/sample/build.gradle index 35dc53d..d106148 100644 --- a/android/sample/build.gradle +++ b/android/sample/build.gradle @@ -28,9 +28,6 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', { - exclude group: 'com.android.support', module: 'support-annotations' - }) testImplementation 'junit:junit:4.12' implementation 'com.android.support:appcompat-v7:28.0.0' diff --git a/android/sample/src/main/java/com/bilibili/burstlinker/sample/MainActivity.java b/android/sample/src/main/java/com/bilibili/burstlinker/sample/MainActivity.java index 09be191..7957745 100644 --- a/android/sample/src/main/java/com/bilibili/burstlinker/sample/MainActivity.java +++ b/android/sample/src/main/java/com/bilibili/burstlinker/sample/MainActivity.java @@ -25,7 +25,7 @@ * Created by succlz123 on 2017/9/7. */ public class MainActivity extends AppCompatActivity { - private static final String TAG = "gif"; + private static final String TAG = "JAVA_BURSTLINKER"; private ImageView mDisplayImg; private TextView mTimeTv; @@ -57,7 +57,7 @@ private void encodeGIF() { final Context context = MainActivity.this; final Bitmap bitmap = loadBitmap(R.drawable.tcr); - int size = 0; + int count = 0; int width = bitmap.getWidth(); int height = bitmap.getHeight(); final int delayMs = 1000; @@ -67,12 +67,13 @@ private void encodeGIF() { try { burstLinker.init(width, height, mFilePath, 0, CPU_COUNT); burstLinker.debugLog(true); + // select one to test if (true) { List bitmaps = new ArrayList<>(); bitmaps.add(bitmap); bitmaps.add(bitmap); bitmaps.add(bitmap); - size = bitmaps.size(); + count = bitmaps.size(); burstLinker.connect(bitmaps, BurstLinker.OCTREE_QUANTIZER, BurstLinker.NO_DITHER, 0, 0, delayMs); } else { @@ -83,7 +84,7 @@ private void encodeGIF() { for (int color : colors) { p.setColor(color); canvas.drawRect(0, 0, width, height, p); - size++; + count++; burstLinker.connect(colorBitmap, BurstLinker.OCTREE_QUANTIZER, BurstLinker.NO_DITHER, 0, 0, delayMs); } @@ -105,7 +106,8 @@ private void encodeGIF() { file.delete(); } } else { - mText = "width " + width + " height " + height + " size " + size + " time " + diff + "ms"; + mText = + "width: " + width + " height: " + height + " count: " + count + " time: " + diff + "ms"; runOnUiThread(() -> Glide.with(context).load(mFilePath).into(mDisplayImg)); } runOnUiThread(() -> { diff --git a/src/Logger.cpp b/src/Logger.cpp index 75f5d17..5f98973 100644 --- a/src/Logger.cpp +++ b/src/Logger.cpp @@ -10,7 +10,7 @@ #include -#define LOG_TAG "BURSTLINKER" +#define LOG_TAG "JNI_BURSTLINKER" #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG,__VA_ARGS__) #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , LOG_TAG,__VA_ARGS__) diff --git a/src/Main.cpp b/src/Main.cpp index 1c1b0f9..3bfd633 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -109,7 +109,7 @@ int main(int argc, char *argv[]) { return 0; } if (processWidth != width || processHeight != height) { - std::cout << "Image is not the same width or height " << processFileName << std::endl; + std::cout << "The height and width of the front and back images do not match " << processFileName << std::endl; stbi_image_free(processImage); return 0; } @@ -133,10 +133,10 @@ int main(int argc, char *argv[]) { g = processImage[index++]; b = processImage[index++]; } else if (n == 4) { - r = data[index++]; - g = data[index++]; - b = data[index++]; - a = data[index++]; + r = processImage[index++]; + g = processImage[index++]; + b = processImage[index++]; + a = processImage[index++]; } else { std::cout << "Unsupported images" << std::endl; return 0;