diff --git a/README.md b/README.md index a7ca47c..13e3f27 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ Idea from: [square/gifencoder](https://github.com/square/gifencoder) BurstLinker is a simple C++ GIF encode library. -## Android +## Download -### Download +### Android Gradle: @@ -16,13 +16,13 @@ Gradle: implementation 'com.bilibili:burst-linker:latest-version' ``` -### Build Environment +#### Build Environment -Android Studio 3.1.4 +Android Studio 3.2.1 -NDK r17 +NDK r18 -### Basic usage +#### Basic usage ``` java int delayMs = 1000; @@ -48,7 +48,7 @@ try { } ``` -### Enable RenderScript Support +#### Enable RenderScript Support > This is an untested experimental feature @@ -57,7 +57,7 @@ try { 3. Uncomment the line 64 of the /lib/CMakeLists.txt 4. Run -## Linux & Mac +### Linux & Mac 1. Install [CMake](http://www.cmake.org/) - Mac `brew install cmake` @@ -73,7 +73,7 @@ try { - `./BurstLinker 1000 1.jpg 2.jpg 3.jpg` - See out.gif -## Windows +### Windows 1. Install [Microsoft Visual Studio](https://www.visualstudio.com/) & [CMake](http://www.cmake.org/) @@ -89,7 +89,69 @@ try { - `BurstLinker.exe 1000 1.jpg 2.jpg 3.jpg` - See out.gif -# Thanks +## Samples + +### Different quantizers & ditherers + +- Original + +![original](screenshot/lenna-original.png) + +- Uniform + No + +![uniform](screenshot/uniform.gif) + +- MedianCut + No + +![media-cut](screenshot/media-cut.gif) + +- KMeans + No + +![k-means](screenshot/k-means.gif) + +- Random + No + +![random](screenshot/random.gif) + +- Octree + No + +![octree](screenshot/octree.gif) + +- NeuQuant - 10 + No + +![neu-quant-10](screenshot/neu-quant-10.gif) + +- NeuQuant - 1 + No + +![neu-quant-1](screenshot/neu-quant-1.gif) + +- Octree + M2 + +![uniform](screenshot/octree-m2.gif) + +- Octree + Bayer + +![octree-bayer](screenshot/octree-bayer.gif) + +- Octree + FloydSteinberg + +![octree-floyd-steinberg](screenshot/octree-floyd-steinberg.gif) + +### Encoding with transparent image + +- Original + +![bilibili](screenshot/bilibili.png) + +- Octree + No + Default (ARGB.a != 0) + +![bilibili-octree](screenshot/bilibili-octree-default.gif) + +- Octree + No + Ignore translucency (ARGB.a == 255) + +![bilibili-octree](screenshot/bilibili-octree-ignore.gif) + +## Thanks [square/gifencoder](https://github.com/square/gifencoder) @@ -107,7 +169,7 @@ try { [progschj/ThreadPool](https://github.com/progschj/ThreadPool) -# License +## License ``` Copyright 2018 Bilibili diff --git a/android/build.gradle b/android/build.gradle index 1a99a3b..bd3892a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:3.1.4' - classpath 'com.novoda:bintray-release:0.8.0' + classpath 'com.novoda:bintray-release:0.8.1' } } @@ -34,6 +34,6 @@ task clean(type: Delete) { } ext { - burstLinkerVer = '0.0.9' + burstLinkerVer = '0.0.11' libs = ['burstLinkerVer': "com.bilibili:$burstLinkerVer"] } diff --git a/android/lib/CMakeLists.txt b/android/lib/CMakeLists.txt index 5eb6277..670e4b9 100644 --- a/android/lib/CMakeLists.txt +++ b/android/lib/CMakeLists.txt @@ -14,30 +14,30 @@ cmake_minimum_required(VERSION 3.4.1) # link_directories(${ANDROID_NDK}/toolchains/renderscript/prebuilt/${ANDROID_HOST_TAG}/platform/${ANDROID_SYSROOT_ABI}) -set(srcdir ../../src) +set(src_dir ../../src) set(SOURCE_FILES - ${srcdir}/GifEncoder.cpp - ${srcdir}/GifBlockWriter.cpp - ${srcdir}/KDTree.cpp - ${srcdir}/LzwEncoder.cpp - ${srcdir}/Logger.cpp - - ${srcdir}/NoDitherer.cpp - ${srcdir}/BayerDitherer.cpp - ${srcdir}/M2Ditherer.cpp - ${srcdir}/FloydSteinbergDitherer.cpp - - ${srcdir}/OctreeQuantizer.cpp - ${srcdir}/UniformQuantizer.cpp - ${srcdir}/KMeansQuantizer.cpp - ${srcdir}/MedianCutQuantizer.cpp - ${srcdir}/RandomQuantizer.cpp - ${srcdir}/NeuQuant.cpp - ${srcdir}/NeuQuantQuantizer.cpp - - # ${srcdir}/GifAnalyzer.h - ${srcdir}/ThreadPool.h + ${src_dir}/GifEncoder.cpp + ${src_dir}/GifBlockWriter.cpp + ${src_dir}/KDTree.cpp + ${src_dir}/LzwEncoder.cpp + ${src_dir}/Logger.cpp + + ${src_dir}/NoDitherer.cpp + ${src_dir}/BayerDitherer.cpp + ${src_dir}/M2Ditherer.cpp + ${src_dir}/FloydSteinbergDitherer.cpp + + ${src_dir}/OctreeQuantizer.cpp + ${src_dir}/UniformQuantizer.cpp + ${src_dir}/KMeansQuantizer.cpp + ${src_dir}/MedianCutQuantizer.cpp + ${src_dir}/RandomQuantizer.cpp + ${src_dir}/NeuQuant.cpp + ${src_dir}/NeuQuantQuantizer.cpp + + # ${src_dir}/GifAnalyzer.h + ${src_dir}/ThreadPool.h src/main/cpp/BurstLinker.cpp # src/main/cpp/DithererWithRs.cpp diff --git a/android/lib/build.gradle b/android/lib/build.gradle index 0607b6a..cd0e6f4 100644 --- a/android/lib/build.gradle +++ b/android/lib/build.gradle @@ -6,11 +6,11 @@ ext { } android { - compileSdkVersion 27 - buildToolsVersion '27.0.3' + compileSdkVersion 28 + buildToolsVersion '28.0.3' defaultConfig { minSdkVersion 14 - targetSdkVersion 27 + targetSdkVersion 28 versionCode 1 versionName VERSION_NAME.toString() testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -25,7 +25,7 @@ android { // arguments "-DANDROID_ARM_NEON=TRUE" // arguments "-DANDROID_ABI=armeabi-v7a with NEON" arguments "-DANDROID_STL=c++_static" - cppFlags "-std=c++11 -fno-rtti -fno-exceptions" + cppFlags "-std=c++14 -fno-rtti -fno-exceptions" } } } diff --git a/android/lib/src/main/cpp/BayerDithererWithRs.h b/android/lib/src/main/cpp/BayerDithererWithRs.h index 5e24ac8..c880cc5 100644 --- a/android/lib/src/main/cpp/BayerDithererWithRs.h +++ b/android/lib/src/main/cpp/BayerDithererWithRs.h @@ -10,7 +10,6 @@ #include using namespace android::RSC; -using namespace std; class BayerDithererWithRs : public DithererWithRs { diff --git a/android/lib/src/main/cpp/BurstLinker.cpp b/android/lib/src/main/cpp/BurstLinker.cpp index e98ae24..489e526 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 "../../../../../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) using namespace std; using namespace blk; 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 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) { GifEncoder *gifEncoder = (GifEncoder *) handle; gifEncoder->debugLog = debug; } JNIEXPORT jstring JNICALL Java_com_bilibili_burstlinker_BurstLinker_jniConnect(JNIEnv *env, jobject, jlong handle, jint quantizerType, jint ditherType, jint left, jint top, jint delay, jstring rsCacheDir, jobject jBitmap) { if (jBitmap == nullptr) { return env->NewStringUTF("WTF!!! jBitmap == null"); } auto *gifEncoder = (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; uint32_t *dst = new uint32_t[imageSize]; int32_t stride = 0; 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[k] = RGB565_B(v) << 16 | RGB565_G(v) << 8 | RGB565_R(v); } delete[] tmp; } else if (androidBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888) { stride = width * 4; int32_t pixelsCount = stride * height; memcpy(dst, src, static_cast(pixelsCount)); AndroidBitmap_unlockPixels(env, jBitmap); } else { return env->NewStringUTF("bitmap's format is't RGB_565 or RGBA_8888"); } vector out; gifEncoder->addImage(dst, delay, static_cast(quantizerType), static_cast(ditherType), (uint16_t) left, (uint16_t) top, out); if (out.size() <= 0) { return env->NewStringUTF("gifEncoder add image out arrays is empty"); } delete[] dst; gifEncoder->flush(out); return nullptr; } JNIEXPORT jstring JNICALL Java_com_bilibili_burstlinker_BurstLinker_jniConnectArray(JNIEnv *env, jobject, jlong handle, jint quantizerType, jint ditherType, jint left, jint top, jint delay, jstring rsCacheDir, jobjectArray jBitmapArray) { auto *gifEncoder = (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); } 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("WTF!!! jBitmap == 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; uint32_t *dst = new uint32_t[imageSize]; int32_t stride = 0; 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[k] = RGB565_B(v) << 16 | RGB565_G(v) << 8 | RGB565_R(v); } delete[] tmp; } else if (androidBitmapInfo.format == ANDROID_BITMAP_FORMAT_RGBA_8888) { stride = width * 4; int32_t pixelsCount = stride * height; memcpy(dst, src, static_cast(pixelsCount)); AndroidBitmap_unlockPixels(env, jBitmap); } else { env->DeleteLocalRef(jBitmap); return env->NewStringUTF("bitmap's format is't RGB_565 or RGBA_8888"); } env->DeleteLocalRef(jBitmap); auto result = gifEncoder->threadPool->enqueue([=]() { vector out; gifEncoder->addImage(dst, delay, static_cast(quantizerType), static_cast(ditherType), (uint16_t) left, (uint16_t) top, out); delete[] dst; return out; }); tasks.emplace_back(move(result)); } for (auto &task : tasks) { 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) { GifEncoder *gifEncoder = (GifEncoder *) handle; 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) 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; gifEncoder->finishEncoding(); delete gifEncoder; } #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/android/lib/src/main/cpp/DisableDithererWithRs.h b/android/lib/src/main/cpp/DisableDithererWithRs.h index 51ab1d8..c16f2e8 100644 --- a/android/lib/src/main/cpp/DisableDithererWithRs.h +++ b/android/lib/src/main/cpp/DisableDithererWithRs.h @@ -11,7 +11,6 @@ #include using namespace android::RSC; -using namespace std; class DisableDithererWithRs : public DithererWithRs { diff --git a/android/lib/src/main/cpp/NoDithererWithRs.h b/android/lib/src/main/cpp/NoDithererWithRs.h index 514c765..8e866a0 100644 --- a/android/lib/src/main/cpp/NoDithererWithRs.h +++ b/android/lib/src/main/cpp/NoDithererWithRs.h @@ -11,7 +11,6 @@ #include using namespace android::RSC; -using namespace std; class NoDithererWithRs : public DithererWithRs { diff --git a/android/lib/src/main/java/com/bilibili/burstlinker/BurstLinker.java b/android/lib/src/main/java/com/bilibili/burstlinker/BurstLinker.java index 701a85b..1972ec1 100644 --- a/android/lib/src/main/java/com/bilibili/burstlinker/BurstLinker.java +++ b/android/lib/src/main/java/com/bilibili/burstlinker/BurstLinker.java @@ -32,6 +32,8 @@ public class BurstLinker { private String mUseRenderScript; + private int mIgnoreTranslucency = 0; + private int mWidth; private int mHeight; private int mThreadCount; @@ -41,10 +43,10 @@ public class BurstLinker { private native long jniDebugLog(long gifEncoder, boolean debug); - private native String jniConnect(long gifEncoder, int quantizerType, int ditherType, + private native String jniConnect(long gifEncoder, int quantizerType, int ditherType, int ignoreTranslucency, int left, int top, int delayMs, String cacheDir, Bitmap bitmap); - private native String jniConnectArray(long gifEncoder, int quantizerType, int ditherType, + private native String jniConnectArray(long gifEncoder, int quantizerType, int ditherType, int ignoreTranslucency, int left, int top, int delayMs, String cacheDir, Bitmap[] bitmaps); private native void jniRelease(long gifEncoder); @@ -70,7 +72,7 @@ public void init(int width, int height, String path, int loopCount, Context cont } /** - * @param loopCount 0 repeat forever + * @param loopCount 0 = repeat forever * @param context use for enable renderScript */ private void init(int width, int height, String path, int loopCount, int threadCount, Context context) throws GifEncodeException { @@ -99,7 +101,7 @@ private void init(int width, int height, String path, int loopCount, int threadC mThreadCount = threadCount; mNative = jniInit(path, width, height, loopCount, threadCount); if (mNative == 0) { - throw new GifEncodeException("init aborted!!! WTF"); + throw new GifEncodeException("miao miao miao !!! init aborted"); } } @@ -107,6 +109,10 @@ public void debugLog(boolean debug) { jniDebugLog(mNative, debug); } + public void setIgnoreTranslucency(boolean ignoreTranslucency) { + mIgnoreTranslucency = ignoreTranslucency ? 1 : 0; + } + public void connect(Bitmap bitmap, int quantizerType, int ditherType, int delayMs) throws GifEncodeException { connect(bitmap, quantizerType, ditherType, 0, 0, delayMs); } @@ -116,7 +122,7 @@ public void connect(Bitmap bitmap, int quantizerType, int ditherType, int delayM */ public void connect(Bitmap bitmap, int quantizerType, int ditherType, int left, int top, int delayMs) throws GifEncodeException { if (mNative == 0) { - throw new GifEncodeException("please first initialization"); + throw new GifEncodeException("please initialize first"); } if (bitmap == null) { throw new GifEncodeException("bitmap is null"); @@ -127,18 +133,18 @@ public void connect(Bitmap bitmap, int quantizerType, int ditherType, int left, if (left + bitmap.getWidth() > mWidth || top + bitmap.getHeight() > mHeight) { throw new GifEncodeException("image does not fit in screen"); } - String nativeMessage = jniConnect(mNative, quantizerType, ditherType, left, top, + String nativeMessage = jniConnect(mNative, quantizerType, ditherType, mIgnoreTranslucency, left, top, delayMs, mUseRenderScript, bitmap); if (!TextUtils.isEmpty(nativeMessage)) { throw new GifEncodeException("native -> " + nativeMessage); } } - public void connectArray(List bitmaps, int quantizerType, int ditherType, int delayMs) throws GifEncodeException { - connectArray(bitmaps, quantizerType, ditherType, 0, 0, delayMs); + public void connect(List bitmaps, int quantizerType, int ditherType, int delayMs) throws GifEncodeException { + connect(bitmaps, quantizerType, ditherType, 0, 0, delayMs); } - public void connectArray(List bitmaps, int quantizerType, int ditherType, int left, int top, int delayMs) throws GifEncodeException { + public void connect(List bitmaps, int quantizerType, int ditherType, int left, int top, int delayMs) throws GifEncodeException { if (mNative == 0) { throw new GifEncodeException("please first initialization"); } @@ -156,7 +162,7 @@ public void connectArray(List bitmaps, int quantizerType, int ditherType String nativeMessage; if (mThreadCount <= 1) { for (Bitmap bitmap : bitmaps) { - nativeMessage = jniConnect(mNative, quantizerType, ditherType, left, top, + nativeMessage = jniConnect(mNative, quantizerType, ditherType, mIgnoreTranslucency, left, top, delayMs, mUseRenderScript, bitmap); if (!TextUtils.isEmpty(nativeMessage)) { throw new GifEncodeException("native -> " + nativeMessage); @@ -166,9 +172,9 @@ public void connectArray(List bitmaps, int quantizerType, int ditherType Bitmap[] bitmapArray; int bitmapSize = bitmaps.size(); if (mThreadCount > bitmapSize) { - bitmapArray = bitmaps.toArray(new Bitmap[bitmapSize]); - nativeMessage = jniConnectArray(mNative, quantizerType, ditherType, left, top, - delayMs, mUseRenderScript, bitmapArray); + bitmapArray = bitmaps.toArray(new Bitmap[0]); + nativeMessage = jniConnectArray(mNative, quantizerType, ditherType, mIgnoreTranslucency, + left, top, delayMs, mUseRenderScript, bitmapArray); if (!TextUtils.isEmpty(nativeMessage)) { throw new GifEncodeException("native -> " + nativeMessage); } @@ -185,9 +191,9 @@ public void connectArray(List bitmaps, int quantizerType, int ditherType end = start + remain; } List processBitmaps = bitmaps.subList(start, end); - bitmapArray = processBitmaps.toArray(new Bitmap[processBitmaps.size()]); - nativeMessage = jniConnectArray(mNative, quantizerType, ditherType, left, top, - delayMs, mUseRenderScript, bitmapArray); + bitmapArray = processBitmaps.toArray(new Bitmap[0]); + nativeMessage = jniConnectArray(mNative, quantizerType, ditherType, mIgnoreTranslucency, + left, top, delayMs, mUseRenderScript, bitmapArray); if (!TextUtils.isEmpty(nativeMessage)) { throw new GifEncodeException("native -> " + nativeMessage); } @@ -197,7 +203,10 @@ public void connectArray(List bitmaps, int quantizerType, int ditherType } public void release() { - jniRelease(mNative); + if (mNative == 0) { + return; + } +// jniRelease(mNative); mNative = 0; } } diff --git a/android/sample/build.gradle b/android/sample/build.gradle index 7e9a6b3..35dc53d 100644 --- a/android/sample/build.gradle +++ b/android/sample/build.gradle @@ -3,13 +3,13 @@ apply plugin: 'com.android.application' def libVersion = rootProject.ext.burstLinkerVer android { - compileSdkVersion 27 - buildToolsVersion '27.0.3' + compileSdkVersion 28 + buildToolsVersion '28.0.3' defaultConfig { applicationId "com.bilibili.burstlinker.sample" minSdkVersion 14 - targetSdkVersion 27 + targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -33,7 +33,7 @@ dependencies { }) testImplementation 'junit:junit:4.12' - implementation 'com.android.support:appcompat-v7:27.0.2' + implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.github.bumptech.glide:glide:4.4.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.4.0' implementation project(path: ':lib') 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 ef56db5..09be191 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 @@ -43,7 +43,7 @@ protected void onCreate(Bundle savedInstanceState) { mEncodeTv = findViewById(R.id.text); String dstFile = "result.gif"; - mFilePath = getCacheDir() + File.separator + dstFile; + mFilePath = getExternalCacheDir() + File.separator + dstFile; mEncodeTv.setOnClickListener(v -> { mEncodeTv.setEnabled(false); @@ -66,13 +66,14 @@ private void encodeGIF() { Exception exception = null; try { burstLinker.init(width, height, mFilePath, 0, CPU_COUNT); + burstLinker.debugLog(true); if (true) { List bitmaps = new ArrayList<>(); bitmaps.add(bitmap); bitmaps.add(bitmap); bitmaps.add(bitmap); size = bitmaps.size(); - burstLinker.connectArray(bitmaps, BurstLinker.OCTREE_QUANTIZER, + burstLinker.connect(bitmaps, BurstLinker.OCTREE_QUANTIZER, BurstLinker.NO_DITHER, 0, 0, delayMs); } else { Bitmap colorBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); @@ -113,9 +114,6 @@ private void encodeGIF() { }); } - /** - * it is recommended to use ARGB_8888 - */ private Bitmap loadBitmap(int resource) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_8888; diff --git a/android/sample/src/main/res/drawable-nodpi/bilibili.png b/android/sample/src/main/res/drawable-nodpi/bilibili.png new file mode 100644 index 0000000..f7ace48 Binary files /dev/null and b/android/sample/src/main/res/drawable-nodpi/bilibili.png differ diff --git a/example/Main.cpp b/example/Main.cpp index 79aa1be..993dc4e 100644 --- a/example/Main.cpp +++ b/example/Main.cpp @@ -9,19 +9,17 @@ #include "../third_part/stb_image.h" - -using namespace std; using namespace blk; long long currentTimeMs() { - chrono::time_point tp = chrono::time_point_cast( - chrono::system_clock::now()); - auto tmp = chrono::duration_cast(tp.time_since_epoch()); + std::chrono::time_point tp = std::chrono::time_point_cast( + std::chrono::system_clock::now()); + auto tmp = std::chrono::duration_cast(tp.time_since_epoch()); auto timestamp = tmp.count(); return timestamp; } -void addImage(const char *fileName, uint32_t width, uint32_t height, int delay, BurstLinker &burstLinker, +void addImage(const char *fileName, uint32_t width, uint32_t height, uint32_t delay, BurstLinker &burstLinker, QuantizerType quantizerType, DitherType ditherType) { uint32_t imageSize = width * height; if (imageSize < width || imageSize < height) { @@ -30,12 +28,13 @@ void addImage(const char *fileName, uint32_t width, uint32_t height, int delay, int w, h, n; unsigned char *data = stbi_load(fileName, &w, &h, &n, 0); if (w != width || h != height) { - cout << "Image is not the same width or height" << endl; + std::cout << "Image is not the same width or height" << std::endl; return; } - auto imagePixel = new uint32_t[imageSize]; + std::vector image; + image.reserve(imageSize); uint32_t index = 0; - uint32_t pixelIndex = 0; + int a = 255; int r = 0; int g = 0; int b = 0; @@ -49,149 +48,119 @@ void addImage(const char *fileName, uint32_t width, uint32_t height, int delay, r = data[index++]; g = data[index++]; b = data[index++]; - index++; + a = data[index++]; } else { return; } - int bgr = b << 16 | g << 8 | r; - imagePixel[pixelIndex++] = static_cast(bgr); + image.push_back(a << 24 | b << 16 | g << 8 | r); } } stbi_image_free(data); - burstLinker.connect(imagePixel, delay, quantizerType, ditherType, 0, 0); + int ignoreTranslucency = 0; + int enableTransparency = (n == 4 ? 1 : 0); + int transparencyOption = ignoreTranslucency << 8 | enableTransparency; + + std::vector> images; + images.emplace_back(image); + images.emplace_back(image); + burstLinker.connect(images, delay, quantizerType, ditherType, transparencyOption, 0, 0); } void -addImage(int r, int g, int b, uint32_t width, uint32_t height, int delay, BurstLinker &burstLinker, +addImage(int r, int g, int b, uint32_t width, uint32_t height, uint32_t delay, BurstLinker &burstLinker, QuantizerType quantizerType, DitherType ditherType) { uint32_t imageSize = width * height; if (imageSize < width || imageSize < height) { return; } - auto *imagePixel = new uint32_t[imageSize]; - int pixelIndex = 0; + std::vector imagePixel; + imagePixel.reserve(imageSize); for (uint32_t i = 0; i < width; i++) { for (uint32_t j = 0; j < height; j++) { - int rgb = b << 16 | g << 8 | r; - imagePixel[pixelIndex++] = static_cast(rgb); + imagePixel.push_back(b << 16 | g << 8 | r); } } - burstLinker.connect(imagePixel, delay, quantizerType, ditherType, 0, 0); + int ignoreTranslucency = 1; + int enableTransparency = 0; + int transparencyOption = ignoreTranslucency << 8 | enableTransparency; + burstLinker.connect(imagePixel, delay, quantizerType, ditherType, transparencyOption, 0, 0); } int main(int argc, char *argv[]) { - const char *fileName = "1.jpg"; - + const char *fileName = "../screenshot/lenna-original.png"; int width, height, n; - unsigned char *data = stbi_load("1.jpg", &width, &height, &n, 0); - + unsigned char *data = stbi_load(fileName, &width, &height, &n, 0); if (!data) { - cout << "Image load failed" << endl; + std::cout << "Image load failed" << std::endl; stbi_image_free(data); return 0; } - int imageSize = width * height; if (width >= 65536 || height >= 65536) { - cout << "Image is too large " << imageSize << endl; + std::cout << "Image is too large width = " << width << "height = " << height << std::endl; stbi_image_free(data); return 0; } BurstLinker burstLinker; if (!burstLinker.init("out.gif", width, height, 0, 4)) { - cout << "GifEncoder init fail" << endl; + std::cout << "GifEncoder init fail" << std::endl; stbi_image_free(data); return 0; } - int delay = 1000; - + uint32_t delay = 1000; long long currentTime = currentTimeMs(); + bool colorTest = false; - addImage(fileName, width, height, delay, burstLinker, QuantizerType::Uniform, DitherType::NO); -// addImage(255, 255, 255, width, height, delay burstLinker,QuantizerType::Uniform, DitherType::Bayer); - + addImage(fileName, width, height, delay, burstLinker, QuantizerType::Uniform, DitherType::No); + if (colorTest) { + addImage(255, 255, 255, width, height, delay, burstLinker, QuantizerType::Uniform, DitherType::Bayer); + } long long diff = currentTimeMs() - currentTime; currentTime = currentTimeMs(); - cout << "Uniform " << diff << "ms" << endl; - - addImage(fileName, width, height, delay, burstLinker, QuantizerType::MedianCut, DitherType::NO); -// addImage(255, 255, 255, width, height,delay, burstLinker, QuantizerType::MedianCut, DitherType::M2); + std::cout << "Uniform " << diff << "ms" << std::endl; + addImage(fileName, width, height, delay, burstLinker, QuantizerType::MedianCut, DitherType::Bayer); + if (colorTest) { + addImage(255, 255, 255, width, height, delay, burstLinker, QuantizerType::MedianCut, DitherType::M2); + } diff = currentTimeMs() - currentTime; currentTime = currentTimeMs(); - cout << "MedianCut " << diff << "ms" << endl; - - addImage(fileName, width, height, delay, burstLinker, QuantizerType::KMeans, DitherType::NO); -// addImage(255, 255, 255, width, height, delay,burstLinker, QuantizerType::KMeans, DitherType::M2); + std::cout << "MedianCut " << diff << "ms" << std::endl; + addImage(fileName, width, height, delay, burstLinker, QuantizerType::KMeans, DitherType::M2); + if (colorTest) { + addImage(255, 255, 255, width, height, delay, burstLinker, QuantizerType::KMeans, DitherType::M2); + } diff = currentTimeMs() - currentTime; currentTime = currentTimeMs(); - cout << "KMeans " << diff << "ms" << endl; - - addImage(fileName, width, height, delay, burstLinker, QuantizerType::Random, DitherType::NO); -// addImage(255, 255, 255, width, height,delay, burstLinker, QuantizerType::Random, DitherType::FloydSteinberg); + std::cout << "KMeans " << diff << "ms" << std::endl; + addImage(fileName, width, height, delay, burstLinker, QuantizerType::Random, DitherType::FloydSteinberg); + if (colorTest) { + addImage(255, 255, 255, width, height, delay, burstLinker, QuantizerType::Random, DitherType::FloydSteinberg); + } diff = currentTimeMs() - currentTime; currentTime = currentTimeMs(); - cout << "Random " << diff << "ms" << endl; - - addImage(fileName, width, height, delay, burstLinker, QuantizerType::Octree, DitherType::NO); -// addImage(255, 255, 255, width, height, delay,burstLinker, QuantizerType::Octree, DitherType::FloydSteinberg); + std::cout << "Random " << diff << "ms" << std::endl; + addImage(fileName, width, height, delay, burstLinker, QuantizerType::Octree, DitherType::No); + if (colorTest) { + addImage(255, 255, 255, width, height, delay, burstLinker, QuantizerType::Octree, DitherType::FloydSteinberg); + } diff = currentTimeMs() - currentTime; currentTime = currentTimeMs(); - cout << "Octree " << diff << "ms" << endl; - - addImage(fileName, width, height, delay, burstLinker, QuantizerType::NeuQuant, DitherType::NO); -// addImage(255, 255, 255, width, height,delay, burstLinker, QuantizerType::NeuQuant, DitherType::Bayer); + std::cout << "Octree " << diff << "ms" << std::endl; + addImage(fileName, width, height, delay, burstLinker, QuantizerType::NeuQuant, DitherType::FloydSteinberg); + if (colorTest) { + addImage(255, 255, 255, width, height, delay, burstLinker, QuantizerType::NeuQuant, DitherType::Bayer); + } diff = currentTimeMs() - currentTime; - cout << "NeuQuant -10 " << diff << "ms" << endl; - -// vector imagePixels; -// for (int i = startPosition; i < argc; ++i) { -// const char *processFileName = fileName; -// fipImage processImage; -// processImage.load(processFileName); -// size_t processWidth = processImage.getWidth(); -// size_t processHeight = processImage.getHeight(); -// if (processWidth != width || processHeight != height) { -// cout << "Image is not the same width or height" << endl; -// releaseImage(processImage, true); -// return 0; -// } -// if (imageSize < width || imageSize < height) { -// cout << "C6386" << endl; -// releaseImage(processImage, true); -// return 0; -// } -// auto *imagePixel = new uint32_t[imageSize]; -// memset(imagePixel, 0, imageSize * sizeof(uint32_t)); -// int pixelIndex = 0; -// RGBQUAD color{}; -// for (uint32_t i = 0; i < height; i++) { -// for (uint32_t j = 0; j < width; j++) { -// processImage.getPixelColor(j, height - 1 - i, &color); -// int r = color.rgbRed & 0xFF; -// int g = color.rgbGreen & 0xFF; -// int b = color.rgbBlue & 0xFF; -// int bgr = b << 16 | g << 8 | r; -// imagePixel[pixelIndex++] = static_cast(bgr); -// } -// } -// processImage.clear(); -// FreeImage_Unload(processImage); -// imagePixels.emplace_back(imagePixel); -// } -// QuantizerType quantizerType = QuantizerType::NeuQuant; -// DitherType ditherType = DitherType::FloydSteinberg; -// burstLinker.connect(imagePixels, delay, quantizerType, ditherType, 0, 0); + std::cout << "NeuQuant -10 " << diff << "ms" << std::endl; burstLinker.release(); - stbi_image_free(data); // burstLinker.analyzerGifInfo("out.gif"); - return 0; } diff --git a/screenshot/bilibili-octree-default.gif b/screenshot/bilibili-octree-default.gif new file mode 100644 index 0000000..bdb7128 Binary files /dev/null and b/screenshot/bilibili-octree-default.gif differ diff --git a/screenshot/bilibili-octree-ignore.gif b/screenshot/bilibili-octree-ignore.gif new file mode 100644 index 0000000..d73bef5 Binary files /dev/null and b/screenshot/bilibili-octree-ignore.gif differ diff --git a/screenshot/bilibili.png b/screenshot/bilibili.png new file mode 100644 index 0000000..f7ace48 Binary files /dev/null and b/screenshot/bilibili.png differ diff --git a/screenshot/k-means.gif b/screenshot/k-means.gif new file mode 100644 index 0000000..10c4e3c Binary files /dev/null and b/screenshot/k-means.gif differ diff --git a/screenshot/lenna-original.png b/screenshot/lenna-original.png new file mode 100644 index 0000000..59ef68a Binary files /dev/null and b/screenshot/lenna-original.png differ diff --git a/screenshot/media-cut.gif b/screenshot/media-cut.gif new file mode 100644 index 0000000..e4a49f3 Binary files /dev/null and b/screenshot/media-cut.gif differ diff --git a/screenshot/neu-quant-1.gif b/screenshot/neu-quant-1.gif new file mode 100644 index 0000000..0fb4b9a Binary files /dev/null and b/screenshot/neu-quant-1.gif differ diff --git a/screenshot/neu-quant-10.gif b/screenshot/neu-quant-10.gif new file mode 100644 index 0000000..175af70 Binary files /dev/null and b/screenshot/neu-quant-10.gif differ diff --git a/screenshot/octree-bayer.gif b/screenshot/octree-bayer.gif new file mode 100644 index 0000000..5c2b5fa Binary files /dev/null and b/screenshot/octree-bayer.gif differ diff --git a/screenshot/octree-floyd-steinberg.gif b/screenshot/octree-floyd-steinberg.gif new file mode 100644 index 0000000..2e23ea5 Binary files /dev/null and b/screenshot/octree-floyd-steinberg.gif differ diff --git a/screenshot/octree-m2.gif b/screenshot/octree-m2.gif new file mode 100644 index 0000000..edcff4b Binary files /dev/null and b/screenshot/octree-m2.gif differ diff --git a/screenshot/octree.gif b/screenshot/octree.gif new file mode 100644 index 0000000..bc6b409 Binary files /dev/null and b/screenshot/octree.gif differ diff --git a/screenshot/random.gif b/screenshot/random.gif new file mode 100644 index 0000000..d830cdb Binary files /dev/null and b/screenshot/random.gif differ diff --git a/screenshot/uniform.gif b/screenshot/uniform.gif new file mode 100644 index 0000000..f41ebe8 Binary files /dev/null and b/screenshot/uniform.gif differ diff --git a/src/BayerDitherer.cpp b/src/BayerDitherer.cpp index 2f8d6c8..d9dbec8 100644 --- a/src/BayerDitherer.cpp +++ b/src/BayerDitherer.cpp @@ -2,37 +2,47 @@ // Created by succlz123 on 2017/10/26. // +#include #include "BayerDitherer.h" #include "KDTree.h" -using namespace std; using namespace blk; -void BayerDitherer::dither(RGB *originPixels, uint16_t width, uint16_t height, - RGB quantizerPixels[], int32_t quantizerSize, - uint8_t *colorIndices) { +void BayerDitherer::dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) { int bayer[64]; const int delta = 1 << (5 - bayerScale); for (int i = 0; i < 64; i++) { bayer[i] = (bayerDitherValue(i) >> 1) - delta; } + + size_t totalSize = width * height; + size_t size = origin.size(); KDTree kdTree; KDTree::Node rootNode; - kdTree.createKDTree(&rootNode, quantizerPixels, 0, quantizerSize - 1, 0); - RGB target; - uint8_t lastIndex = 0; - int position = 0; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - auto rgb = originPixels[position]; + size_t quantizeSize = quantize.size(); + auto end = static_cast(quantizeSize - 1); + auto transparentColorIndex = static_cast(quantizeSize + 1); + kdTree.createKDTree(&rootNode, quantize, 0, end, 0); + int count = 0; + for (int i = 0; i < size; ++count) { + auto rgb = origin[i]; + if (rgb.unTranpsparentIndex == count) { + ++i; + int x = i % width; + auto y = static_cast(std::ceil(i / width)); int offset = (bayer[((y & 7) << 3) | (x & 7)]); - target.r = static_cast((min(255, max(0, rgb.r + offset)))); - target.g = static_cast((min(255, max(0, rgb.g + offset)))); - target.b = static_cast((min(255, max(0, rgb.b + offset)))); - kdTree.searchNNNoBacktracking(&rootNode, target, -1); - lastIndex = kdTree.nearest.index; - colorIndices[position] = lastIndex; - position++; + auto r = static_cast(std::min(255, std::max(0, rgb.r + offset))); + auto g = static_cast(std::min(255, std::max(0, rgb.g + offset))); + auto b = static_cast(std::min(255, std::max(0, rgb.b + offset))); + kdTree.searchNoBacktracking(&rootNode, r, g, b, -1); + colorIndices[count] = kdTree.nearest.index; + } else { + colorIndices[count] = transparentColorIndex; + } + } + if (count < totalSize) { + for (int i = count; i < totalSize; ++i) { + colorIndices[i] = transparentColorIndex; } } kdTree.freeKDTree(&rootNode); diff --git a/src/BayerDitherer.h b/src/BayerDitherer.h index 0dbbfc3..b3568d1 100644 --- a/src/BayerDitherer.h +++ b/src/BayerDitherer.h @@ -13,10 +13,7 @@ namespace blk { public: - void - dither(RGB *originPixels, uint16_t width, uint16_t height, - RGB quantizerPixels[], int32_t quantizerSize, - uint8_t *colorIndices) override; + void dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) override; }; } diff --git a/src/BurstLinker.cpp b/src/BurstLinker.cpp index 33cffe0..f38ebfa 100644 --- a/src/BurstLinker.cpp +++ b/src/BurstLinker.cpp @@ -7,44 +7,36 @@ using namespace blk; -BurstLinker::~BurstLinker() { - delete gifEncoder; -} - bool BurstLinker::init(const char *path, uint16_t width, uint16_t height, uint32_t loopCount, uint32_t threadNum) { - gifEncoder = new GifEncoder(); + gifEncoder = std::make_unique(); return gifEncoder->init(path, width, height, 0, threadNum); } -bool BurstLinker::connect(uint32_t *imagePixel, uint32_t delay, - QuantizerType quantizerType, DitherType ditherType, +bool BurstLinker::connect(std::vector &image, uint32_t delay, + QuantizerType quantizerType, DitherType ditherType, int32_t transparencyOption, uint16_t left, uint16_t top) { if (gifEncoder == nullptr) { return false; } std::vector content; - gifEncoder->addImage(imagePixel, delay, quantizerType, - ditherType, left, top, content); - delete[] imagePixel; + gifEncoder->addImage(image, delay, quantizerType, ditherType, transparencyOption, left, top, content); gifEncoder->flush(content); return true; } -bool BurstLinker::connect(std::vector imagePixels, uint32_t delay, - QuantizerType quantizerType, DitherType ditherType, +bool BurstLinker::connect(std::vector> &images, uint32_t delay, + QuantizerType quantizerType, DitherType ditherType, int32_t transparencyOption, uint16_t left, uint16_t top) { if (gifEncoder == nullptr) { return false; } - size_t size = imagePixels.size(); + size_t size = images.size(); std::vector>> tasks; for (int k = 0; k < size; ++k) { - auto result = gifEncoder->threadPool->enqueue([=]() { + auto result = gifEncoder->threadPool->enqueue([=, &images]() { std::vector content; - uint32_t *imagePixel = imagePixels[k]; - gifEncoder->addImage(imagePixel, delay, quantizerType, - ditherType, left, top, content); - delete[] imagePixel; + auto image = images[k]; + gifEncoder->addImage(image, delay, quantizerType, ditherType, transparencyOption, left, top, content); return content; }); tasks.emplace_back(std::move(result)); diff --git a/src/BurstLinker.h b/src/BurstLinker.h index 61a26a6..fc4fc65 100644 --- a/src/BurstLinker.h +++ b/src/BurstLinker.h @@ -14,17 +14,15 @@ namespace blk { public: - ~BurstLinker(); - bool init(const char *path, uint16_t width, uint16_t height, uint32_t loopCount, uint32_t threadNum); - bool connect(uint32_t *imagePixels, uint32_t delay, - QuantizerType quantizerType, DitherType ditherType, + bool connect(std::vector &image, uint32_t delay, + QuantizerType quantizerType, DitherType ditherType, int32_t transparencyOption, uint16_t left, uint16_t top); - bool connect(std::vector imagePixels, uint32_t delay, - QuantizerType quantizerType, DitherType ditherType, + bool connect(std::vector> &images, uint32_t delay, + QuantizerType quantizerType, DitherType ditherType, int32_t transparencyOption, uint16_t left, uint16_t top); void release(); @@ -33,7 +31,7 @@ namespace blk { private: - GifEncoder *gifEncoder = nullptr; + std::unique_ptr gifEncoder; }; diff --git a/src/ColorQuantizer.h b/src/ColorQuantizer.h index 3ae77e5..51d660c 100644 --- a/src/ColorQuantizer.h +++ b/src/ColorQuantizer.h @@ -19,8 +19,7 @@ namespace blk { virtual ~ColorQuantizer() = default;; - virtual int32_t - quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCount, RGB out[])= 0; + virtual int32_t quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out)= 0; protected: diff --git a/src/Ditherer.h b/src/Ditherer.h index 91101ee..932024d 100644 --- a/src/Ditherer.h +++ b/src/Ditherer.h @@ -25,12 +25,13 @@ namespace blk { // only for bayer int bayerScale = 1; + uint16_t width; + + uint16_t height; + virtual ~Ditherer() = default; - virtual void - dither(RGB *originPixels, uint16_t width, uint16_t height, - RGB quantizerPixels[], int32_t quantizerSize, - uint8_t *colorIndices) = 0; + virtual void dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) = 0; }; diff --git a/src/FloydSteinbergDitherer.cpp b/src/FloydSteinbergDitherer.cpp index d094750..5dc51e1 100644 --- a/src/FloydSteinbergDitherer.cpp +++ b/src/FloydSteinbergDitherer.cpp @@ -6,7 +6,6 @@ #include "FloydSteinbergDitherer.h" #include "KDTree.h" -using namespace std; using namespace blk; static const int8_t ERROR_COMPONENT_SIZE = 4; @@ -14,14 +13,14 @@ static const float ERROR_COMPONENT_DELTA_X[] = {1.0f, -1.0f, 0.0f, 1.0f}; static const float ERROR_COMPONENT_DELTA_Y[] = {0.0f, 1.0f, 1.0f, 1.0f}; static const float ERROR_COMPONENT_FACTION[] = {7.0f / 16.0f, 3.0f / 16.0f, 5.0f / 16.0f, 1.0f / 16.0f}; -void FloydSteinbergDitherer::dither(RGB *originPixels, uint16_t width, uint16_t height, - RGB quantizerPixels[], int32_t quantizerSize, - uint8_t *colorIndices) { +void FloydSteinbergDitherer::dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) { int32_t totalSize = width * height; KDTree kdTree; KDTree::Node rootNode; - kdTree.createKDTree(&rootNode, quantizerPixels, 0, quantizerSize - 1, 0); - RGB target; + size_t quantizeSize = quantize.size(); + auto end = static_cast(quantizeSize - 1); + kdTree.createKDTree(&rootNode, quantize, 0, end, 0); + uint8_t nearestCentroidR = 0; uint8_t nearestCentroidG = 0; uint8_t nearestCentroidB = 0; @@ -34,7 +33,7 @@ void FloydSteinbergDitherer::dither(RGB *originPixels, uint16_t width, uint16_t int position = 0; for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { - auto rgb = originPixels[position]; + auto rgb = origin[position]; r = rgb.r; g = rgb.g; b = rgb.b; @@ -42,19 +41,16 @@ void FloydSteinbergDitherer::dither(RGB *originPixels, uint16_t width, uint16_t lastR = r; lastG = g; lastB = b; - target.r = r; - target.g = g; - target.b = b; - kdTree.searchNNNoBacktracking(&rootNode, target, -1); + kdTree.searchNoBacktracking(&rootNode, r, g, b, -1); } nearestCentroidR = kdTree.nearest.r; nearestCentroidG = kdTree.nearest.g; nearestCentroidB = kdTree.nearest.b; - originPixels[position].r = nearestCentroidR; - originPixels[position].g = nearestCentroidG; - originPixels[position].b = nearestCentroidB; + origin[position].r = nearestCentroidR; + origin[position].g = nearestCentroidG; + origin[position].b = nearestCentroidB; position++; @@ -72,22 +68,22 @@ void FloydSteinbergDitherer::dither(RGB *originPixels, uint16_t width, uint16_t float errorComponentB = errorB * ERROR_COMPONENT_FACTION[directionId]; int ditherPosition = siblingY * width + siblingX; - auto siblingRgb = originPixels[ditherPosition]; + auto siblingRgb = origin[ditherPosition]; auto siblingR = static_cast(siblingRgb.r + errorComponentR); auto siblingG = static_cast(siblingRgb.g + errorComponentG); auto siblingB = static_cast(siblingRgb.b + errorComponentB); - originPixels[ditherPosition].r = static_cast(min(255, max(0, siblingR))); - originPixels[ditherPosition].g = static_cast(min(255, max(0, siblingG))); - originPixels[ditherPosition].b = static_cast(min(255, max(0, siblingB))); + origin[ditherPosition].r = static_cast(std::min(255, std::max(0, siblingR))); + origin[ditherPosition].g = static_cast(std::min(255, std::max(0, siblingG))); + origin[ditherPosition].b = static_cast(std::min(255, std::max(0, siblingB))); } } } } for (int i = 0; i < totalSize; ++i) { - auto rgb = originPixels[i]; - kdTree.searchNNNoBacktracking(&rootNode, rgb, -1); + auto rgb = origin[i]; + kdTree.searchNoBacktracking(&rootNode, rgb.r, rgb.g, rgb.b, -1); colorIndices[i] = kdTree.nearest.index; } kdTree.freeKDTree(&rootNode); diff --git a/src/FloydSteinbergDitherer.h b/src/FloydSteinbergDitherer.h index 073fad0..6cf5d09 100644 --- a/src/FloydSteinbergDitherer.h +++ b/src/FloydSteinbergDitherer.h @@ -13,10 +13,7 @@ namespace blk { public: - void - dither(RGB *originPixels, uint16_t width, uint16_t height, - RGB quantizerPixels[], int32_t quantizerSize, - uint8_t *colorIndices) override; + void dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) override; }; diff --git a/src/GifAnalyzer.cpp b/src/GifAnalyzer.cpp index 2ed5eb1..a81bee2 100644 --- a/src/GifAnalyzer.cpp +++ b/src/GifAnalyzer.cpp @@ -8,7 +8,6 @@ #include "GifAnalyzer.h" #include "Logger.h" -using namespace std; using namespace blk; static const size_t BUF_SIZE = 1024; diff --git a/src/GifBlockWriter.cpp b/src/GifBlockWriter.cpp index 84aabb5..c7b6610 100644 --- a/src/GifBlockWriter.cpp +++ b/src/GifBlockWriter.cpp @@ -5,24 +5,22 @@ #include #include "GifBlockWriter.h" -using namespace std; using namespace blk; -void GifBlockWriter::writeHeaderBlock(ofstream &file) { +void GifBlockWriter::writeHeaderBlock(std::ofstream &file) { file.write("GIF89a", 6); } static const uint32_t GLOBAL_COLOR_TABLE_FLAG = 1 << 7; static const uint32_t LSD_SORT_FLAG = 1 << 3; -void -GifBlockWriter::writeLogicalScreenDescriptorBlock(ofstream &file, int32_t logicalScreenWidth, - int32_t logicalScreenHeight, - bool globalColorTable, int32_t colorResolution, - bool sort, - int32_t globalColorTableSize, - int32_t backgroundColorIndex, - int32_t pixelAspectRatio) { +void GifBlockWriter::writeLogicalScreenDescriptorBlock(std::ofstream &file, int32_t logicalScreenWidth, + int32_t logicalScreenHeight, + bool globalColorTable, int32_t colorResolution, + bool sort, + int32_t globalColorTableSize, + int32_t backgroundColorIndex, + int32_t pixelAspectRatio) { file.write((char *) (&logicalScreenWidth), 2); file.write((char *) (&logicalScreenHeight), 2); @@ -47,7 +45,7 @@ static const uint8_t SUB_BLOCK_SIZE = 0x03; static const uint8_t SUB_BLOCK_ID = 0x01; // Application Extension -void GifBlockWriter::writeNetscapeLoopingExtensionBlock(ofstream &file, uint32_t loopCount) { +void GifBlockWriter::writeNetscapeLoopingExtensionBlock(std::ofstream &file, uint32_t loopCount) { file.write((char *) (&EXTENSION_INTRODUCER), 1); file.write((char *) (&APPLICATION_EXTENSION), 1); // 8+3 @@ -67,45 +65,28 @@ static const uint32_t TRANSPARENT_COLOR_FLAG = 1; static const uint8_t GRAPHICS_CONTROL_LABEL = 0xF9; static const uint8_t GRAPHICS_CONTROL_EXTENSION_BLOCK_SIZE = 0x04; -void GifBlockWriter::writeGraphicsControlExtensionBlock(vector &content, int32_t disposalMethod, - bool userInput, - bool transparentColor, - int32_t delayCentiseconds, - int32_t transparentColorIndex) { -// file.write((char *) (&EXTENSION_INTRODUCER), 1); -// file.write((char *) (&GRAPHICS_CONTROL_LABEL), 1); -// file.write((char *) (&GRAPHICS_CONTROL_EXTENSION_BLOCK_SIZE), 1); -// -// uint8_t packed = (disposalMethod << 3 -// | (userInput ? USER_INPUT_FLAG : 0) -// | (transparentColor ? TRANSPARENT_COLOR_FLAG : 0)); -// file.write((char *) (&packed), 1); -// auto delay1 = static_cast(delayCentiseconds & 0xFF); -// auto delay2 = static_cast(delayCentiseconds >> 8); -// file.write((char *) (&delay1), 1); -// file.write((char *) (&delay2), 1); -// file.write((char *) (&transparentColorIndex), 1); -// file.write((char *) (&BLOCK_TERMINATOR), 1); - - content.emplace_back(EXTENSION_INTRODUCER); - content.emplace_back(GRAPHICS_CONTROL_LABEL); - content.emplace_back(GRAPHICS_CONTROL_EXTENSION_BLOCK_SIZE); +void GifBlockWriter::writeGraphicsControlExtensionBlock(std::vector &content, int32_t disposalMethod, + bool userInput, bool transparentColor, + int32_t delayCentiseconds, int32_t transparentColorIndex) { + content.push_back(EXTENSION_INTRODUCER); + content.push_back(GRAPHICS_CONTROL_LABEL); + content.push_back(GRAPHICS_CONTROL_EXTENSION_BLOCK_SIZE); auto packed = static_cast( disposalMethod << 3 | (userInput ? USER_INPUT_FLAG : 0) | (transparentColor ? TRANSPARENT_COLOR_FLAG : 0) ); - content.emplace_back(packed); + content.push_back(packed); auto delay1 = static_cast(delayCentiseconds & 0xFF); auto delay2 = static_cast(delayCentiseconds >> 8); - content.emplace_back(delay1); - content.emplace_back(delay2); + content.push_back(delay1); + content.push_back(delay2); - content.emplace_back(transparentColorIndex); - content.emplace_back(BLOCK_TERMINATOR); + content.push_back(transparentColorIndex); + content.push_back(BLOCK_TERMINATOR); } static const uint8_t IMAGE_SEPARATOR = 0x2C; @@ -113,39 +94,27 @@ static const uint32_t LOCAL_COLOR_TABLE_FLAG = 1 << 7; static const uint32_t INTERLACE_FLAG = 1 << 6; static const uint32_t ID_SORT_FLAG = 1 << 5; -void -GifBlockWriter::writeImageDescriptorBlock(vector &content, uint16_t imageLeft, uint16_t imageTop, - uint16_t imageWidth, - uint16_t imageHeight, - bool localColorTable, bool interlace, bool sort, - int localColorTableSize) { - content.emplace_back(IMAGE_SEPARATOR); - content.emplace_back(imageLeft & 0xFF); - content.emplace_back(imageLeft >> 8); - content.emplace_back(imageTop & 0xFF); - content.emplace_back(imageTop >> 8); - content.emplace_back(imageWidth & 0xFF); - content.emplace_back(imageWidth >> 8); - content.emplace_back(imageHeight & 0xFF); - content.emplace_back(imageHeight >> 8); +void GifBlockWriter::writeImageDescriptorBlock(std::vector &content, uint16_t imageLeft, uint16_t imageTop, + uint16_t imageWidth, + uint16_t imageHeight, + bool localColorTable, bool interlace, bool sort, + int localColorTableSize) { + content.push_back(IMAGE_SEPARATOR); + content.push_back(imageLeft & 0xFF); + content.push_back(imageLeft >> 8); + content.push_back(imageTop & 0xFF); + content.push_back(imageTop >> 8); + content.push_back(imageWidth & 0xFF); + content.push_back(imageWidth >> 8); + content.push_back(imageHeight & 0xFF); + content.push_back(imageHeight >> 8); auto packed = static_cast( (localColorTable ? LOCAL_COLOR_TABLE_FLAG : 0) | (interlace ? INTERLACE_FLAG : 0) | (sort ? ID_SORT_FLAG : 0) | localColorTableSize ); - content.emplace_back(packed); -// file.write((const char *) (&IMAGE_SEPARATOR), 1); -// file.write((const char *) (&imageLeft), 2); -// file.write((const char *) (&imageTop), 2); -// file.write((const char *) (&imageWidth), 2); -// file.write((const char *) (&imageHeight), 2); -// uint8_t packed = ((localColorTable ? LOCAL_COLOR_TABLE_FLAG : 0) -// | (interlace ? INTERLACE_FLAG : 0) -// | (sort ? ID_SORT_FLAG : 0) -// | localColorTableSize); -// file.write((const char *) (&packed), 1); - + content.push_back(packed); } int32_t GifBlockWriter::paddedSize(int32_t size) { @@ -160,32 +129,36 @@ int32_t GifBlockWriter::paddedSize(int32_t size) { return n; } -void GifBlockWriter::writeColorTable(vector &content, RGB quantizerPixels[], - int quantizerSize, int paddedSize) { - int32_t unpaddedSize = quantizerSize; - for (int k = 0; k < quantizerSize; k++) { - content.emplace_back(quantizerPixels[k].r); - content.emplace_back(quantizerPixels[k].g); - content.emplace_back(quantizerPixels[k].b); -// file.write((char *) (&r), 1); -// file.write((char *) (&g), 1); -// file.write((char *) (&b), 1); +void GifBlockWriter::writeColorTableEntity(std::vector &content, const std::vector &quantize, + int paddedSize) { + size_t quantizeSize = quantize.size(); + for (size_t k = 0; k < quantizeSize; k++) { + content.push_back(quantize[k].r); + content.push_back(quantize[k].g); + content.push_back(quantize[k].b); } - for (int32_t i = unpaddedSize; i < paddedSize; ++i) { -// file.write("\0\0\0", 3); - content.emplace_back('\0'); - content.emplace_back('\0'); - content.emplace_back('\0'); +} + +void GifBlockWriter::writeColorTableTransparency(std::vector &content, uint8_t r, uint8_t g, uint8_t b) { + content.push_back(r); + content.push_back(g); + content.push_back(b); +} + +void GifBlockWriter::writeColorTableUnpadded(std::vector &content, int unpaddedSize, int paddedSize) { + for (int i = unpaddedSize; i < paddedSize; ++i) { + content.push_back('\0'); + content.push_back('\0'); + content.push_back('\0'); } } -void -GifBlockWriter::writeImageDataBlock(ofstream &file, uint8_t colorDepth, list lzwData, - int lzwDataSize) { +void GifBlockWriter::writeImageDataBlock(std::ofstream &file, uint8_t colorDepth, + std::list lzw, int lzwSize) { file.write((const char *) &colorDepth, 1); int index = 0; - for (auto data : lzwData) { - int subBlockLength = min(lzwDataSize - index, 255); + for (auto data : lzw) { + int subBlockLength = std::min(lzwSize - index, 255); file.write((const char *) &subBlockLength, 1); file.write((const char *) data, subBlockLength); index += subBlockLength; @@ -195,8 +168,7 @@ GifBlockWriter::writeImageDataBlock(ofstream &file, uint8_t colorDepth, list &content, int32_t disposalMethod, bool userInput, - bool transparentColor, int32_t delayCentiseconds, - int32_t transparentColorIndex); + static void writeGraphicsControlExtensionBlock(std::vector &content, int32_t disposalMethod, + bool userInput, bool transparentColor, int32_t delayCentiseconds, + int32_t transparentColorIndex); static void writeImageDescriptorBlock(std::vector &content, uint16_t imageLeft, uint16_t imageTop, uint16_t imageWidth, @@ -40,12 +38,14 @@ namespace blk { static int32_t paddedSize(int32_t size); - static void writeColorTable(std::vector &content, RGB quantizerPixels[], int quantizerSize, - int paddedSize); - static void - writeImageDataBlock(std::ofstream &file, uint8_t colorDepth, std::list lzwData, - int lzwDataSize); + writeColorTableEntity(std::vector &content, const std::vector &quantize, int paddedSize); + + static void writeColorTableTransparency(std::vector &content, uint8_t r, uint8_t g, uint8_t b); + + static void writeColorTableUnpadded(std::vector &content, int unpaddedSize, int paddedSize); + + static void writeImageDataBlock(std::ofstream &file, uint8_t colorDepth, std::list lzw, int lzwSize); static void writeTerminator(std::ofstream &file); diff --git a/src/GifEncoder.cpp b/src/GifEncoder.cpp index 027b67c..a7eaab7 100644 --- a/src/GifEncoder.cpp +++ b/src/GifEncoder.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "GifEncoder.h" #include "GifBlockWriter.h" #include "Logger.h" @@ -21,7 +22,6 @@ #include "M2Ditherer.h" #include "NoDitherer.h" -using namespace std; using namespace blk; #if defined(__RenderScript__) @@ -58,13 +58,12 @@ GifEncoder::~GifEncoder() { } #endif outfile.close(); - delete threadPool; delete[] rsCacheDir; } bool GifEncoder::init(const char *path, uint16_t width, uint16_t height, uint32_t loopCount, uint32_t threadCount) { - outfile.open(path, ios::out | ios::binary); + outfile.open(path, std::ios::out | std::ios::binary); if (!outfile.is_open()) { return false; } @@ -78,76 +77,144 @@ bool GifEncoder::init(const char *path, uint16_t width, uint16_t height, uint32_ threadCount = 8; } if (threadCount > 1) { - threadPool = new ThreadPool(threadCount); + threadPool = std::make_unique(threadCount); } Logger::log(debugLog, "Image size is " + Logger::toString(width * height)); return true; } -vector GifEncoder::addImage(uint32_t *originalColors, uint32_t delay, - QuantizerType quantizerType, DitherType ditherType, - uint16_t left, uint16_t top, - vector &content) { - Logger::log(debugLog, "Get image pixel"); +std::vector GifEncoder::addImage(const std::vector &original, uint32_t delay, + QuantizerType qType, DitherType dType, + int32_t transparencyOption, uint16_t left, uint16_t top, + std::vector &content) { + Logger::log(debugLog, "Get image pixel " + Logger::toString(original.size())); - uint32_t pixelCount = screenWidth * screenHeight; - unique_ptr colorQuantizer; - string quantizerStr; - switch (quantizerType) { + uint32_t size = screenWidth * screenHeight; + std::unique_ptr colorQuantizer; + std::string quantizerStr; + switch (qType) { case QuantizerType::Uniform: - colorQuantizer.reset(new UniformQuantizer()); + colorQuantizer = std::make_unique(); quantizerStr = "UniformQuantizer"; break; case QuantizerType::MedianCut: - colorQuantizer.reset(new MedianCutQuantizer()); + colorQuantizer = std::make_unique(); quantizerStr = "MedianCutQuantizer"; break; case QuantizerType::KMeans: - colorQuantizer.reset(new KMeansQuantizer()); + colorQuantizer = std::make_unique(); quantizerStr = "KMeansQuantizer"; break; case QuantizerType::Random: - colorQuantizer.reset(new RandomQuantizer()); + colorQuantizer = std::make_unique(); quantizerStr = "RandomQuantizer"; break; case QuantizerType::Octree: - colorQuantizer.reset(new OctreeQuantizer()); + colorQuantizer = std::make_unique(); quantizerStr = "OctreeQuantizer"; break; case QuantizerType::NeuQuant: - colorQuantizer.reset(new NeuQuantQuantizer()); + colorQuantizer = std::make_unique(); quantizerStr = "NeuQuantQuantizer"; break; } - auto *pixels = reinterpret_cast(originalColors); + std::vector quantizeIn; + quantizeIn.reserve(size); + bool enableTransparentColor = ((transparencyOption & 0xff) == 1); + bool ignoreTranslucency = (((transparencyOption >> 8) & 0xff) == 1); + bool hasTransparentColor = false; + uint8_t a = 255; + uint8_t r; + uint8_t g; + uint8_t b; + for (uint32_t i = 0; i < size; i++) { + auto color = original[i]; + if (enableTransparentColor) { + a = static_cast((color >> 24) & 0xff); + if (!hasTransparentColor) { + if ((ignoreTranslucency && a != 255) || (!ignoreTranslucency && a == 0)) { + hasTransparentColor = true; + } + } + } + b = static_cast((color >> 16) & 0xff); + g = static_cast((color >> 8) & 0xff); + r = static_cast(color & 0xff); + if (a == 255 || (!ignoreTranslucency && a != 0)) { + quantizeIn.emplace_back(a, r, g, b, i); + } + } - RGB quantizerPixels[256]; - int32_t quantizerSize = 0; - if (pixelCount > 256) { - quantizerSize = colorQuantizer->quantize(pixels, pixelCount, 256, quantizerPixels); + std::vector quantizeOut; + quantizeOut.reserve(256); + int quantizeSize = 0; + if (size > 256) { + quantizeSize = colorQuantizer->quantize(quantizeIn, hasTransparentColor ? 255 : 256, + quantizeOut); } else { - quantizerSize = pixelCount; - memcpy(quantizerPixels, pixels, pixelCount * sizeof(RGB)); + quantizeSize = size; + quantizeOut.assign(quantizeIn.begin(), quantizeIn.end()); } - Logger::log(debugLog, quantizerStr + " size is " + Logger::toString(quantizerSize)); + Logger::log(debugLog, quantizerStr + " size is " + Logger::toString(quantizeSize)); - if (quantizerSize <= 0) { + if (quantizeSize <= 0) { return content; } - // int32_t paddedColorCount = GifBlockWriter::paddedSize(quantizerSize); + uint8_t transparentColorR = 0; + uint8_t transparentColorG = 0; + uint8_t transparentColorB = 0; + if (hasTransparentColor) { + std::mt19937 generator((uint32_t) time(nullptr)); + std::uniform_int_distribution sizeDis(0, size); + std::uniform_int_distribution rgbDis(0, 255); + int tryCount = 0; + while (tryCount < 12) { + uint32_t random = sizeDis(generator); + if (tryCount >= 6) { + transparentColorR = static_cast(rgbDis(generator)); + transparentColorG = static_cast(rgbDis(generator)); + transparentColorB = static_cast(rgbDis(generator)); + } else { + transparentColorR = quantizeIn[random].r; + transparentColorG = quantizeIn[random].g; + transparentColorB = quantizeIn[random].b; + } + int repeatCount = 0; + for (int i = 0; i < quantizeSize; i++) { + auto qColor = quantizeOut[i]; + if (transparentColorR == qColor.r && transparentColorG == qColor.g && + transparentColorB == qColor.b) { + break; + } else { + repeatCount++; + } + } + if (repeatCount == quantizeSize) { + break; + } + tryCount++; + } + if (dType == DitherType::FloydSteinberg) { + dType = DitherType::Bayer; + } + } + + // int32_t paddedColorCount = GifBlockWriter::paddedSize(quantizeSize); int32_t paddedColorCount = 256; - GifBlockWriter::writeGraphicsControlExtensionBlock(content, 0, false, false, delay / 10, 0); + auto transparentColorIndex = static_cast(quantizeSize + 1); + GifBlockWriter::writeGraphicsControlExtensionBlock(content, 0, false, hasTransparentColor, + delay / 10, + hasTransparentColor ? transparentColorIndex + : 0); GifBlockWriter::writeImageDescriptorBlock(content, left, top, screenWidth, screenHeight, true, - false, - false, + false, false, getColorTableSizeField(paddedColorCount)); - GifBlockWriter::writeColorTable(content, quantizerPixels, quantizerSize, paddedColorCount); + GifBlockWriter::writeColorTableEntity(content, quantizeOut, paddedColorCount); - unique_ptr ditherer; - unique_ptr colorIndices(new uint8_t[pixelCount]); - string dithererStr; + std::unique_ptr ditherer; + std::string dithererStr; #if defined(__RenderScript__) bool useRenderScript = false; @@ -161,8 +228,8 @@ vector GifEncoder::addImage(uint32_t *originalColors, uint32_t delay, useRenderScript = true; } } - switch (ditherType) { - case DitherType::NO: + switch (dType) { + case DitherType::No: if (useRenderScript) { ditherer.reset(new NoDithererWithRs()); dithererStr = "NoDithererWithRs"; @@ -192,32 +259,36 @@ vector GifEncoder::addImage(uint32_t *originalColors, uint32_t delay, break; } #else - switch (ditherType) { - case DitherType::NO: - ditherer.reset(new NoDitherer()); + switch (dType) { + case DitherType::No: + ditherer = std::make_unique(); dithererStr = "NoDitherer"; break; case DitherType::M2: - ditherer.reset(new M2Ditherer()); + ditherer = std::make_unique(); dithererStr = "M2Ditherer"; break; case DitherType::Bayer: - ditherer.reset(new BayerDitherer()); + ditherer = std::make_unique(); dithererStr = "BayerDitherer"; break; case DitherType::FloydSteinberg: - ditherer.reset(new FloydSteinbergDitherer()); + ditherer = std::make_unique(); dithererStr = "FloydSteinbergDitherer"; break; } #endif + ditherer->width = screenWidth; + ditherer->height = screenHeight; + auto colorIndices = new uint8_t[size]; + #if defined(__RenderScript__) if (useRenderScript) { static_cast(ditherer.get())->dither(pixels, screenWidth, screenHeight, quantizerPixels, quantizerSize, colorIndices.get(), rs); - } else if (quantizerType == QuantizerType::Octree && ditherType == DitherType::NO) { + } else if (qType == QuantizerType::Octree && dType == DitherType::No) { static_cast(colorQuantizer.get())->getColorIndices(pixels, colorIndices.get(), pixelCount, nullptr); @@ -226,25 +297,32 @@ vector GifEncoder::addImage(uint32_t *originalColors, uint32_t delay, colorIndices.get()); } #else - if (quantizerType == QuantizerType::Octree && ditherType == DitherType::NO) { - static_cast(colorQuantizer.get())->getColorIndices(pixels, - colorIndices.get(), - pixelCount, nullptr); + if (qType == QuantizerType::Octree && dType == DitherType::No && !hasTransparentColor) { + static_cast(colorQuantizer.get())->getColorIndices(quantizeIn, + colorIndices); } else { - ditherer->dither(pixels, screenWidth, screenHeight, quantizerPixels, quantizerSize, - colorIndices.get()); + ditherer->dither(quantizeIn, quantizeOut, colorIndices); } #endif + if (hasTransparentColor) { + GifBlockWriter::writeColorTableTransparency(content, transparentColorR, transparentColorG, + transparentColorB); + GifBlockWriter::writeColorTableUnpadded(content, transparentColorIndex, paddedColorCount); + } else { + GifBlockWriter::writeColorTableUnpadded(content, quantizeSize, paddedColorCount); + } + Logger::log(debugLog, dithererStr); LzwEncoder lzwEncoder(paddedColorCount); - lzwEncoder.encode(colorIndices.get(), screenWidth, screenHeight, content); + lzwEncoder.encode(colorIndices, screenWidth, screenHeight, content); + delete[] colorIndices; Logger::log(debugLog, "LZW encode"); return content; } -void GifEncoder::flush(vector &content) { +void GifEncoder::flush(const std::vector &content) { size_t size = content.size(); for (int i = 0; i < size; ++i) { outfile.write((char *) (&content[i]), 1); diff --git a/src/GifEncoder.h b/src/GifEncoder.h index b886dd8..7c396e7 100644 --- a/src/GifEncoder.h +++ b/src/GifEncoder.h @@ -13,19 +13,33 @@ namespace blk { - struct RGB { + struct ARGB { + uint8_t a = 0; uint8_t r = 0; uint8_t g = 0; uint8_t b = 0; uint8_t index = 0; - bool operator==(const RGB &rgb) const { + uint32_t unTranpsparentIndex = 0; + + bool operator==(const ARGB &rgb) const { return rgb.r == r && rgb.g == g && rgb.b == b; } - bool operator<(const RGB &rgb) const { + bool operator<(const ARGB &rgb) const { return (r + g + b) < (rgb.r + rgb.g + rgb.b); } + + ARGB(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b) {} + + ARGB(uint8_t r, uint8_t g, uint8_t b, uint8_t index) : r(r), g(g), b(b), index(index) {} + + ARGB(uint8_t a, uint8_t r, uint8_t g, uint8_t b, uint32_t unTranpsparentIndex) + : a(a), r(r), g(g), b(b), unTranpsparentIndex(unTranpsparentIndex) {} + + ARGB(uint8_t a, uint8_t r, uint8_t g, uint8_t b, uint8_t index, + uint32_t unTranpsparentIndex) + : a(a), r(r), g(g), b(b), index(index), unTranpsparentIndex(unTranpsparentIndex) {} }; struct Compare { @@ -33,7 +47,7 @@ namespace blk { explicit Compare(uint8_t split) : split(split) {}; - bool operator()(const RGB &a, const RGB &b) { + bool operator()(const ARGB a, const ARGB b) { switch (split) { case 0: default: @@ -56,7 +70,7 @@ namespace blk { }; enum class DitherType { - NO = 0, + No = 0, M2 = 1, Bayer = 2, FloydSteinberg = 3 @@ -74,20 +88,18 @@ namespace blk { const char *rsCacheDir = nullptr; - ThreadPool *threadPool = nullptr; + std::unique_ptr threadPool = nullptr; ~GifEncoder(); - bool - init(const char *path, uint16_t width, uint16_t height, uint32_t loopCount, - uint32_t threadCount); + bool init(const char *path, uint16_t width, uint16_t height, uint32_t loopCount, uint32_t threadCount); - std::vector - addImage(uint32_t *originalColors, uint32_t delay, - QuantizerType quantizerType, DitherType ditherType, - uint16_t left, uint16_t top, std::vector &content); + std::vector addImage(const std::vector &original, uint32_t delay, + QuantizerType qType, DitherType dType, + int32_t transparencyOption, uint16_t left, uint16_t top, + std::vector &content); - void flush(std::vector &content); + void flush(const std::vector &content); void finishEncoding(); diff --git a/src/KDTree.cpp b/src/KDTree.cpp index 14c36fd..034d847 100644 --- a/src/KDTree.cpp +++ b/src/KDTree.cpp @@ -5,25 +5,24 @@ #include #include "KDTree.h" -using namespace std; using namespace blk; // euclideanDistance // int distance = nr * nr + ng * ng + nb * nb; // manhattanDistance // int distance = abs(nr) + abs(ng) + abs(nb); -int calculateDist(KDTree::Node *node, RGB target) { +int calculateDist(KDTree::Node *node, uint8_t r, uint8_t g, uint8_t b) { int tmp = 0; - int diff = node->r - target.r; + int diff = node->r - r; tmp += (diff * diff) << 1; - diff = node->g - target.g; + diff = node->g - g; tmp += (diff * diff) << 2; - diff = node->b - target.b; + diff = node->b - b; tmp += (diff * diff) * 3; return tmp; } -uint8_t getDimension(RGB rgb[], int start, int end) { +uint8_t getDimension(const std::vector &rgb, int start, int end) { int dataSize = end - start + 1; if (dataSize <= 0) { return 0; @@ -68,7 +67,7 @@ uint8_t getDimension(RGB rgb[], int start, int end) { return dimension; } -void *KDTree::createKDTree(Node *node, RGB rgb[], int32_t start, int32_t end, uint8_t split) { +void *KDTree::createKDTree(Node *node, std::vector &rgb, int32_t start, int32_t end, uint8_t split) { int size = end - start + 1; if (size <= 0) { return nullptr; @@ -85,7 +84,7 @@ void *KDTree::createKDTree(Node *node, RGB rgb[], int32_t start, int32_t end, ui return node; } - sort(rgb + start, rgb + end, Compare(split)); + std::sort(rgb.begin() + start, rgb.begin() + end, Compare(split)); int splitSize = size / 2; int leftStart = start; @@ -113,41 +112,41 @@ void *KDTree::createKDTree(Node *node, RGB rgb[], int32_t start, int32_t end, ui return node; } -int KDTree::searchNNNoBacktracking(KDTree::Node *node, RGB target, int32_t dis) { +int KDTree::searchNoBacktracking(KDTree::Node *node, uint8_t r, uint8_t g, uint8_t b, int32_t dis) { if (node == nullptr) { return dis; } if (node->left == nullptr && node->right == nullptr) { if (dis < 0) { nearest = *node; - dis = calculateDist(node, target); + dis = calculateDist(node, r, g, b); return dis; } } bool comp = false; switch (node->split) { case 0: - comp = node->r <= target.r; + comp = node->r <= r; break; case 1: - comp = node->g <= target.g; + comp = node->g <= g; break; case 2: - comp = node->b <= target.b; + comp = node->b <= b; break; default: break; } if (comp) { - dis = searchNNNoBacktracking(node->left, target, dis); - int tmp = calculateDist(node, target); + dis = searchNoBacktracking(node->left, r, g, b, dis); + int tmp = calculateDist(node, r, g, b); if (tmp < dis || dis == -1) { nearest = *node; dis = tmp; } } else { - dis = searchNNNoBacktracking(node->right, target, dis); - int tmp = calculateDist(node, target); + dis = searchNoBacktracking(node->right, r, g, b, dis); + int tmp = calculateDist(node, r, g, b); if (tmp < dis || dis == -1) { nearest = *node; dis = tmp; diff --git a/src/KDTree.h b/src/KDTree.h index 52f8254..f088c3a 100644 --- a/src/KDTree.h +++ b/src/KDTree.h @@ -25,9 +25,9 @@ namespace blk { Node nearest; - void *createKDTree(Node *node, RGB rgb[], int32_t start, int32_t end, uint8_t split); + void *createKDTree(Node *node, std::vector &quantize, int32_t start, int32_t end, uint8_t split); - int searchNNNoBacktracking(Node *node, RGB target, int32_t dis); + int searchNoBacktracking(Node *node, uint8_t r, uint8_t g, uint8_t b, int32_t dis); void freeKDTree(Node *tree); diff --git a/src/KMeansQuantizer.cpp b/src/KMeansQuantizer.cpp index 5844ed4..cb04359 100644 --- a/src/KMeansQuantizer.cpp +++ b/src/KMeansQuantizer.cpp @@ -5,26 +5,25 @@ #include #include #include -#include #include "KMeansQuantizer.h" #include "KDTree.h" +#include "Logger.h" -using namespace std; using namespace blk; -int32_t -KMeansQuantizer::quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCount, RGB out[]) { - if (pixelCount == 0) { +int32_t KMeansQuantizer::quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) { + size_t pixelCount = in.size(); + if (pixelCount <= 0) { return 0; } // random initial center - mt19937 generator((uint32_t) time(nullptr)); - uniform_int_distribution dis(0, pixelCount); - set centroidsToRecompute; + std::mt19937 generator((uint32_t) time(nullptr)); + std::uniform_int_distribution dis(0, pixelCount); + std::set centroidsToRecompute; uint32_t randomCount = 0; while (centroidsToRecompute.size() < maxColorCount) { uint32_t random = dis(generator); - centroidsToRecompute.insert(pixels[random]); + centroidsToRecompute.insert(in[random]); if (randomCount++ > pixelCount) { break; } @@ -34,7 +33,7 @@ KMeansQuantizer::quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCou auto centroidSize = static_cast(centroidsToRecompute.size()); if (centroidSize < maxColorCount) { resultSize = centroidSize; - for (RGB color : centroidsToRecompute) { + for (ARGB color : centroidsToRecompute) { out[colorPaletteIndex].r = color.r; out[colorPaletteIndex].g = color.g; out[colorPaletteIndex].b = color.b; @@ -58,14 +57,14 @@ KMeansQuantizer::quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCou } // recursion - RGB rgb; int label = 0; int iterateNum = 0; int lastCost = 0; int currCost = 0; int unchanged = 0; bool loop = true; - auto *counts = new uint32_t[maxColorCount]; + std::vector counts; + counts.reserve(maxColorCount); auto **nextMeans = new uint32_t *[maxColorCount]; for (uint32_t i = 0; i < maxColorCount; i++) { nextMeans[i] = new uint32_t[3]; @@ -73,7 +72,7 @@ KMeansQuantizer::quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCou while (loop) { // init - memset(counts, 0, sizeof(int) * maxColorCount); + counts.assign(maxColorCount, 0); for (size_t i = 0; i < maxColorCount; i++) { memset(nextMeans[i], 0, sizeof(int) * 3); } @@ -83,19 +82,19 @@ KMeansQuantizer::quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCou // classification { KDTree kdTree; - unique_ptr datas(new RGB[pixelCount]); + std::vector datas; for (uint32_t l = 0; l < maxColorCount; ++l) { auto color = means[l]; - datas[l].r = static_cast(color[0]); - datas[l].g = static_cast(color[1]); - datas[l].b = static_cast(color[2]); - datas[l].index = static_cast(l); + auto r = static_cast(color[0]); + auto g = static_cast(color[1]); + auto b = static_cast(color[2]); + datas.emplace_back(r, g, b, l); } KDTree::Node rootNode; - kdTree.createKDTree(&rootNode, datas.get(), 0, maxColorCount - 1, 0); + kdTree.createKDTree(&rootNode, datas, 0, maxColorCount - 1, 0); for (uint32_t i = 0; i < pixelCount; i++) { - rgb = pixels[i]; - currCost += kdTree.searchNNNoBacktracking(&rootNode, rgb, -1); + auto rgb = in[i]; + currCost += kdTree.searchNoBacktracking(&rootNode, rgb.r, rgb.g, rgb.b, -1); label = kdTree.nearest.index; counts[label]++; nextMeans[label][0] += rgb.r; @@ -127,7 +126,7 @@ KMeansQuantizer::quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCou loop = false; } -// stringstream ss; +// std::stringstream ss; // ss << "iterateNum: " << iterateNum << " lastCost: " << lastCost << " currCost: " << currCost // << " diff:" << diff; // Logger::log(true, ss.str()); @@ -135,13 +134,12 @@ KMeansQuantizer::quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCou resultSize = maxColorCount; for (uint32_t l = 0; l < maxColorCount; ++l) { auto color = means[l]; - out[colorPaletteIndex].r = static_cast(color[0]); - out[colorPaletteIndex].g = static_cast(color[1]); - out[colorPaletteIndex].b = static_cast(color[2]); - out[colorPaletteIndex].index = static_cast(colorPaletteIndex); + auto r = static_cast(color[0]); + auto g = static_cast(color[1]); + auto b = static_cast(color[2]); + out.emplace_back(r, g, b, colorPaletteIndex); colorPaletteIndex++; } - delete[] counts; for (uint32_t i = 0; i < maxColorCount; i++) { delete[] means[i]; delete[] nextMeans[i]; diff --git a/src/KMeansQuantizer.h b/src/KMeansQuantizer.h index aed07ab..7a6f774 100644 --- a/src/KMeansQuantizer.h +++ b/src/KMeansQuantizer.h @@ -13,7 +13,7 @@ namespace blk { public: - int32_t quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCount, RGB out[]) override; + int32_t quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) override; }; diff --git a/src/Logger.cpp b/src/Logger.cpp index 372ac25..75f5d17 100644 --- a/src/Logger.cpp +++ b/src/Logger.cpp @@ -3,9 +3,6 @@ // #include -#include -#include -#include #include "Logger.h" @@ -23,7 +20,6 @@ #endif -using namespace std; using namespace blk; static long long currentTime = 0; @@ -32,14 +28,14 @@ static long long currentTimeMillis() { //struct timeval tv{}; //gettimeofday(&tv, nullptr); //return ((unsigned long long) tv.tv_sec * 1000 + (unsigned long long) tv.tv_usec / 1000); - chrono::time_point tp = chrono::time_point_cast( - chrono::system_clock::now()); - auto tmp = chrono::duration_cast(tp.time_since_epoch()); + std::chrono::time_point tp = std::chrono::time_point_cast( + std::chrono::system_clock::now()); + auto tmp = std::chrono::duration_cast(tp.time_since_epoch()); auto timestamp = tmp.count(); return timestamp; } -void Logger::log(bool show, string str) { +void Logger::log(bool show, std::string str) { if (!show) { return; } @@ -50,7 +46,7 @@ void Logger::log(bool show, string str) { #if defined(__RenderScript__) || defined(__AndroidLog__) LOGI("%s time : %dms", str.c_str(), (int) diff); #else - cout << str << " - time " << diff << "ms" << endl; + std::cout << str << " - time " << diff << "ms" << std::endl; #endif currentTime = currentTimeMillis(); } diff --git a/src/LzwEncoder.cpp b/src/LzwEncoder.cpp index 9e3d108..b4359ac 100644 --- a/src/LzwEncoder.cpp +++ b/src/LzwEncoder.cpp @@ -5,7 +5,6 @@ #include #include "LzwEncoder.h" -using namespace std; using namespace blk; static const int32_t MAX_STACK_SIZE = 4096; @@ -22,13 +21,14 @@ LzwEncoder::LzwEncoder(int32_t paddedColorCount) { numColors = paddedColorCount; current = new uint8_t[BLOCK_SIZE]; memset(current, 0, BLOCK_SIZE); - datas.emplace_back(current); + datas.push_back(current); pos = 0; remain = 8; } int getMinimumCodeSize(int numColors) { - int size = 2; // Cannot be smaller than 2. + // cannot be smaller than 2 + int size = 2; while (numColors > 1 << size) { ++size; } @@ -46,7 +46,7 @@ void LzwEncoder::writeBits(uint32_t src, int32_t bitNum) { if (pos == BLOCK_SIZE) { current = new uint8_t[BLOCK_SIZE]; memset(current, 0, BLOCK_SIZE); - datas.emplace_back(current); + datas.push_back(current); pos = 0; } } else { @@ -57,46 +57,32 @@ void LzwEncoder::writeBits(uint32_t src, int32_t bitNum) { } } -//int BitWritingBlock::write(ofstream &file, uint8_t minimumCodeSize) { -// int total = 0; -// for (auto data : datas) { -// uint8_t size = data == current ? (remain == 0 ? pos : pos + 1) : BLOCK_SIZE; -// total = total + size; -// } -// GifBlockWriter::writeImageDataBlock(file, minimumCodeSize, datas, total); -// return total; -//} - static const uint8_t BLOCK_TERMINATOR = 0x00; static const uint8_t BLOCK_MIN_CODE_SIZE = 0x8; -int LzwEncoder::write(vector &content, uint8_t minimumCodeSize) { -// file.write((const char *) &BLOCK_MIN_CODE_SIZE, 1); - content.emplace_back(minimumCodeSize); +int LzwEncoder::write(std::vector &content, uint8_t minimumCodeSize) { + content.push_back(minimumCodeSize); int size; int total = 0; for (auto block : datas) { size = block == current ? (remain == 0 ? pos : pos + 1) : BLOCK_SIZE; total = total + size; -// file.write((const char *) &size, 1); -// file.write((const char *) block, size); - content.emplace_back(size); + content.push_back(size); for (int i = 0; i < size; ++i) { - content.emplace_back(block[i]); + content.push_back(block[i]); } } -// file.write((char *) (&BLOCK_TERMINATOR), 1); - content.emplace_back(BLOCK_TERMINATOR); + content.push_back(BLOCK_TERMINATOR); return total; } -void LzwEncoder::encode(uint8_t indices[], uint16_t width, uint16_t height, vector &content) { +void LzwEncoder::encode(uint8_t indices[], uint16_t width, uint16_t height, std::vector &content) { uint8_t *endPixels = indices + width * height; uint8_t dataSize = 8; uint32_t codeSize = dataSize + 1; uint32_t codeMask = (1 << codeSize) - 1; - vector lzwInfoHolder; + std::vector lzwInfoHolder; lzwInfoHolder.resize(MAX_STACK_SIZE * BYTE_NUM); uint16_t *lzwInfos = &lzwInfoHolder[0]; uint32_t clearCode = 1 << dataSize; diff --git a/src/M2Ditherer.cpp b/src/M2Ditherer.cpp index 505d70f..8a9e305 100644 --- a/src/M2Ditherer.cpp +++ b/src/M2Ditherer.cpp @@ -2,10 +2,10 @@ // Created by succlz123 on 2017/10/26. // +#include #include "M2Ditherer.h" #include "KDTree.h" -using namespace std; using namespace blk; static const uint8_t matrix4x4[4][4] = { @@ -15,26 +15,35 @@ static const uint8_t matrix4x4[4][4] = { {15, 7, 13, 5} }; -void M2Ditherer::dither(RGB *originPixels, uint16_t width, uint16_t height, - RGB quantizerPixels[], int32_t quantizerSize, - uint8_t *colorIndices) { +void M2Ditherer::dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) { + size_t totalSize = width * height; + size_t size = origin.size(); KDTree kdTree; KDTree::Node rootNode; - kdTree.createKDTree(&rootNode, quantizerPixels, 0, quantizerSize - 1, 0); - RGB target; - uint8_t lastIndex = 0; - int position = 0; - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - auto rgb = originPixels[position]; + size_t quantizeSize = quantize.size(); + auto end = static_cast(quantizeSize - 1); + auto transparentColorIndex = static_cast(quantizeSize + 1); + kdTree.createKDTree(&rootNode, quantize, 0, end, 0); + int count = 0; + for (int i = 0; i < size; ++count) { + auto rgb = origin[i]; + if (rgb.unTranpsparentIndex == count) { + ++i; + int x = i % width; + auto y = static_cast(std::ceil(i / width)); int offset = (matrix4x4[(x & 3)][y & 3]); - target.r = static_cast((min(255, max(0, rgb.r + offset)))); - target.g = static_cast((min(255, max(0, rgb.g + offset)))); - target.b = static_cast((min(255, max(0, rgb.b + offset)))); - kdTree.searchNNNoBacktracking(&rootNode, target, -1); - lastIndex = kdTree.nearest.index; - colorIndices[position] = lastIndex; - position++; + auto r = static_cast(std::min(255, std::max(0, rgb.r + offset))); + auto g = static_cast(std::min(255, std::max(0, rgb.g + offset))); + auto b = static_cast(std::min(255, std::max(0, rgb.b + offset))); + kdTree.searchNoBacktracking(&rootNode, r, g, b, -1); + colorIndices[count] = kdTree.nearest.index; + } else { + colorIndices[count] = transparentColorIndex; + } + } + if (count < totalSize) { + for (int i = count; i < totalSize; ++i) { + colorIndices[i] = transparentColorIndex; } } kdTree.freeKDTree(&rootNode); diff --git a/src/M2Ditherer.h b/src/M2Ditherer.h index f1449c8..210a7e9 100644 --- a/src/M2Ditherer.h +++ b/src/M2Ditherer.h @@ -13,10 +13,7 @@ namespace blk { public: - void - dither(RGB *originPixels, uint16_t width, uint16_t height, - RGB quantizerPixels[], int32_t quantizerSize, - uint8_t *colorIndices) override; + void dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) override; }; diff --git a/src/Main.cpp b/src/Main.cpp index 7c1ef99..1c1b0f9 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -7,22 +7,21 @@ #define STB_IMAGE_IMPLEMENTATION -#include "../thirdpart/stb_image.h" +#include "../third_part/stb_image.h" -using namespace std; using namespace blk; long long currentTimeMs() { - chrono::time_point tp = chrono::time_point_cast( - chrono::system_clock::now()); - auto tmp = chrono::duration_cast(tp.time_since_epoch()); + std::chrono::time_point tp = std::chrono::time_point_cast( + std::chrono::system_clock::now()); + auto tmp = std::chrono::duration_cast(tp.time_since_epoch()); auto timestamp = tmp.count(); return timestamp; } int main(int argc, char *argv[]) { QuantizerType quantizerType = QuantizerType::Octree; - DitherType ditherType = DitherType::NO; + DitherType ditherType = DitherType::No; int delay = 0; const char *fileName = nullptr; @@ -50,7 +49,7 @@ int main(int argc, char *argv[]) { } char *dt = argv[startPosition]; if (!strcmp(dt, "-d0")) { - ditherType = DitherType::NO; + ditherType = DitherType::No; startPosition++; } else if (!strcmp(dt, "-d1")) { ditherType = DitherType::M2; @@ -64,65 +63,66 @@ int main(int argc, char *argv[]) { } delay = atol(argv[startPosition]); if (delay <= 0) { - cout << "Delay time is too short" << endl; + std::cout << "Delay time is too short" << std::endl; return 0; } startPosition++; fileName = argv[startPosition]; } else { - cout << "Missing input parameters" << endl; + std::cout << "Missing input parameters" << std::endl; return 0; } int width, height, n; unsigned char *data = stbi_load(fileName, &width, &height, &n, 0); if (!data) { - cout << "Image load failed" << endl; + std::cout << "Image load failed" << std::endl; stbi_image_free(data); return 0; } int imageSize = width * height; if (width >= 65536 || height >= 65536) { - cout << "Image is too large " << width * height << endl; + std::cout << "Image is too large " << width * height << std::endl; stbi_image_free(data); return 0; } BurstLinker burstLinker; if (!burstLinker.init("out.gif", width, height, 0, 4)) { - cout << "GifEncoder init fail" << endl; + std::cout << "GifEncoder init fail" << std::endl; stbi_image_free(data); return 0; } long long currentTime = currentTimeMs(); - cout << "Start" << endl; + std::cout << "Start" << std::endl; - vector imagePixels; + std::vector> tasks; + int enableTransparency = 0; for (int i = startPosition; i < argc; ++i) { const char *processFileName = argv[i]; int processWidth, processHeight, processN; unsigned char *processImage = stbi_load(processFileName, &processWidth, &processHeight, &processN, 0); if (!processImage) { - cout << "Image load failed " << processFileName << endl; + std::cout << "Image load failed " << processFileName << std::endl; stbi_image_free(processImage); return 0; } if (processWidth != width || processHeight != height) { - cout << "Image is not the same width or height " << processFileName << endl; + std::cout << "Image is not the same width or height " << processFileName << std::endl; stbi_image_free(processImage); return 0; } if (imageSize < width || imageSize < height) { - cout << "C6386 " << processFileName << endl; + std::cout << "C6386 " << processFileName << std::endl; stbi_image_free(processImage); return 0; } - auto *imagePixel = new uint32_t[imageSize]; - memset(imagePixel, 0, imageSize * sizeof(uint32_t)); - int pixelIndex = 0; + std::vector image; + image.reserve(width * height); int index = 0; + int a = 255; int r = 0; int g = 0; int b = 0; @@ -133,26 +133,29 @@ int main(int argc, char *argv[]) { g = processImage[index++]; b = processImage[index++]; } else if (n == 4) { - r = processImage[index++]; - g = processImage[index++]; - b = processImage[index++]; - index++; + r = data[index++]; + g = data[index++]; + b = data[index++]; + a = data[index++]; } else { - cout << "Unsupported images" << endl; + std::cout << "Unsupported images" << std::endl; return 0; } - int bgr = b << 16 | g << 8 | r; - imagePixel[pixelIndex++] = static_cast(bgr); + if (enableTransparency == 0) { + enableTransparency = (n == 4 ? 1 : 0); + } + image.push_back(a << 24 | b << 16 | g << 8 | r); } } stbi_image_free(processImage); - imagePixels.emplace_back(imagePixel); + tasks.push_back(image); } - - burstLinker.connect(imagePixels, delay, quantizerType, ditherType, 0, 0); + int ignoreTranslucency = 0; + int transparencyOption = ignoreTranslucency << 8 | enableTransparency; + burstLinker.connect(tasks, delay, quantizerType, ditherType, transparencyOption, 0, 0); long long diff = currentTimeMs() - currentTime; - cout << "End " << diff << "ms" << endl; + std::cout << "End " << diff << "ms" << std::endl; burstLinker.release(); stbi_image_free(data); diff --git a/src/MedianCutQuantizer.cpp b/src/MedianCutQuantizer.cpp index e73fe7f..7342c04 100644 --- a/src/MedianCutQuantizer.cpp +++ b/src/MedianCutQuantizer.cpp @@ -5,10 +5,8 @@ #include #include #include -#include #include "MedianCutQuantizer.h" -using namespace std; using namespace blk; struct Cluster { @@ -17,13 +15,13 @@ struct Cluster { int pixelSize = 0; int componentWithLargestSpread = 0; - static bool cmpR(const RGB &i, const RGB &j) { return (i.r < j.r); } + static bool cmpR(const ARGB &i, const ARGB &j) { return (i.r < j.r); } - static bool cmpG(const RGB &i, const RGB &j) { return (i.g < j.g); } + static bool cmpG(const ARGB &i, const ARGB &j) { return (i.g < j.g); } - static bool cmpB(const RGB &i, const RGB &j) { return (i.b < j.b); } + static bool cmpB(const ARGB &i, const ARGB &j) { return (i.b < j.b); } - bool (*cmp[3])(const RGB &, const RGB &) = {cmpR, cmpG, cmpB}; + bool (*cmp[3])(const ARGB &, const ARGB &) = {cmpR, cmpG, cmpB}; bool operator<(const Cluster &cluster) const { return (pixelSize < cluster.pixelSize); @@ -35,7 +33,7 @@ struct Cluster { pixelSize = end - start; } - int getComponentRSpread(RGB *pixels) { + int getComponentRSpread(std::vector &pixels) { uint8_t minCount = 0; uint8_t maxCount = 0; for (int i = start; i < end; i++) { @@ -46,7 +44,7 @@ struct Cluster { return maxCount - minCount; } - int getComponentGSpread(RGB *pixels) { + int getComponentGSpread(std::vector &pixels) { uint8_t minCount = 0; uint8_t maxCount = 0; for (int i = start; i < end; i++) { @@ -57,7 +55,7 @@ struct Cluster { return maxCount - minCount; } - int getComponentBSpread(RGB *pixels) { + int getComponentBSpread(std::vector &pixels) { uint8_t minCount = 0; uint8_t maxCount = 0; for (int i = start; i < end; i++) { @@ -68,7 +66,7 @@ struct Cluster { return maxCount - minCount; } - void calculateSpread(RGB *pixels) { + void calculateSpread(std::vector &pixels) { int largestSpread = -1; int componentSpread = getComponentRSpread(pixels); if (componentSpread > largestSpread) { @@ -86,85 +84,25 @@ struct Cluster { } } - bool split(RGB *pixels, Cluster *cluster1, Cluster *cluster2) { + bool split(std::vector &pixels, Cluster *cluster1, Cluster *cluster2) { if (pixelSize < 2) { return false; } - -// std::sort(pixels + start, pixels + end, cmp[componentWithLargestSpread]); - switch (componentWithLargestSpread) { - case 0: - countingSortR(pixels, start, end); - break; - case 1: - countingSortG(pixels, start, end); - break; - case 2: - countingSortB(pixels, start, end); - break; - default: - break; - } - + std::sort(pixels.begin() + start, pixels.begin() + end, cmp[componentWithLargestSpread]); int medianIndex = (pixelSize + 1) / 2; cluster1->setStartAndEnd(start, start + medianIndex); cluster2->setStartAndEnd(start + medianIndex, end); return true; } - - void countingSortR(RGB *a, int start, int end) { - vector c[256]; - for (int i = start; i < end; i++) { - RGB rgb = a[i]; - int count = rgb.r; - c[count].push_back(rgb); - } - int index = 0; - for (const auto &res : c) { - for (const auto out : res) { - a[index++] = out; - } - } - } - - void countingSortG(RGB *a, int start, int end) { - vector c[256]; - for (int i = start; i < end; i++) { - RGB rgb = a[i]; - int count = rgb.g; - c[count].push_back(rgb); - } - int index = 0; - for (const auto &res : c) { - for (const auto out : res) { - a[index++] = out; - } - } - } - - void countingSortB(RGB *a, int start, int end) { - vector c[256]; - for (int i = start; i < end; i++) { - RGB rgb = a[i]; - int count = rgb.b; - c[count].push_back(rgb); - } - int index = 0; - for (const auto &res : c) { - for (const auto out : res) { - a[index++] = out; - } - } - } }; -int32_t MedianCutQuantizer::quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCount, RGB out[]) { +int32_t MedianCutQuantizer::quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) { + size_t pixelCount = in.size(); std::priority_queue clusters; Cluster cluster; cluster.setStartAndEnd(0, pixelCount); clusters.push(cluster); - auto *sortPixels = new RGB[pixelCount]; - memcpy(sortPixels, pixels, pixelCount * sizeof(RGB)); + std::vector sortPixels(in); for (uint32_t k = 0; k < maxColorCount - 1; ++k) { Cluster top, cluster1, cluster2; top = clusters.top(); @@ -192,12 +130,11 @@ int32_t MedianCutQuantizer::quantize(RGB *pixels, uint32_t pixelCount, uint32_t sumG += sortPixels[j].g; sumB += sortPixels[j].b; } - out[index].r = static_cast(sumR / top.pixelSize); - out[index].g = static_cast(sumG / top.pixelSize); - out[index].b = static_cast(sumB / top.pixelSize); - out[index].index = static_cast(index); + auto r = static_cast(sumR / top.pixelSize); + auto g = static_cast(sumG / top.pixelSize); + auto b = static_cast(sumB / top.pixelSize); + out.emplace_back(r, g, b, index); index++; } - delete[] sortPixels; return resultSize; } diff --git a/src/MedianCutQuantizer.h b/src/MedianCutQuantizer.h index 02bb63e..3209ac3 100644 --- a/src/MedianCutQuantizer.h +++ b/src/MedianCutQuantizer.h @@ -13,7 +13,7 @@ namespace blk { public: - int32_t quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCount, RGB out[]) override; + int32_t quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) override; }; diff --git a/src/NeuQuant.cpp b/src/NeuQuant.cpp index cd42afc..1a4b2c7 100644 --- a/src/NeuQuant.cpp +++ b/src/NeuQuant.cpp @@ -63,17 +63,20 @@ void NeuQuant::unbiasnet() { } /* Output colour dither */ -int NeuQuant::getColourMap(RGB out[]) { +int NeuQuant::getColourMap(std::vector &out, uint32_t maxColorCount) { int index[netsize]; for (int i = 0; i < netsize; i++) { index[network[i][3]] = i; } int k = 0; for (int j : index) { - out[k].r = static_cast(network[j][0]); - out[k].g = static_cast(network[j][1]); - out[k].b = static_cast(network[j][2]); - out[k].index = static_cast(k); + if (k >= maxColorCount) { + return k; + } + auto r = static_cast(network[j][0]); + auto g = static_cast(network[j][1]); + auto b = static_cast(network[j][2]); + out.emplace_back(r, g, b, k); k++; } return k; diff --git a/src/NeuQuant.h b/src/NeuQuant.h index c844e04..1c60463 100644 --- a/src/NeuQuant.h +++ b/src/NeuQuant.h @@ -95,7 +95,7 @@ namespace blk { void unbiasnet(); // Output colour dither - int getColourMap(RGB colorPalette[]); + int getColourMap(std::vector &out, uint32_t maxColorCount); // Insertion sort of network and building of netindex[0..255] (to do after unbias) void inxbuild(); diff --git a/src/NeuQuantQuantizer.cpp b/src/NeuQuantQuantizer.cpp index ab52af7..a35aa69 100644 --- a/src/NeuQuantQuantizer.cpp +++ b/src/NeuQuantQuantizer.cpp @@ -7,12 +7,21 @@ using namespace blk; -int32_t -NeuQuantQuantizer::quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCount, RGB out[]) { +int32_t NeuQuantQuantizer::quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) { NeuQuant neuQuant; - neuQuant.initnet(reinterpret_cast(pixels), pixelCount * 3, sample); + size_t size = in.size(); + auto pixels = new uint8_t[size * 3]; + int index = 0; + for (int i = 0; i < size; ++i) { + auto inColor = in[i]; + pixels[index++] = inColor.r; + pixels[index++] = inColor.g; + pixels[index++] = inColor.b; + } + neuQuant.initnet(pixels, size * 3, sample); neuQuant.learn(); neuQuant.unbiasnet(); - resultSize = neuQuant.getColourMap(out); + resultSize = neuQuant.getColourMap(out, maxColorCount); + delete[] pixels; return resultSize; } diff --git a/src/NeuQuantQuantizer.h b/src/NeuQuantQuantizer.h index 61316e9..9549611 100644 --- a/src/NeuQuantQuantizer.h +++ b/src/NeuQuantQuantizer.h @@ -13,7 +13,7 @@ namespace blk { public: - int32_t quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCount, RGB out[]) override; + int32_t quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) override; }; diff --git a/src/NoDitherer.cpp b/src/NoDitherer.cpp index ec964fc..0f22c23 100644 --- a/src/NoDitherer.cpp +++ b/src/NoDitherer.cpp @@ -3,36 +3,36 @@ // #include +#include #include "NoDitherer.h" #include "KDTree.h" using namespace blk; -void -NoDitherer::dither(RGB *originPixels, uint16_t width, uint16_t height, - RGB quantizerPixels[], int32_t quantizerSize, - uint8_t *colorIndices) { - size_t size = width * height; +void NoDitherer::dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) { + size_t totalSize = width * height; + size_t size = origin.size(); + size_t quantizeSize = quantize.size(); -// int32_t maxCentroidDistance = 255 * 255 * 255; +// int maxCentroidDistance = 255 * 255 * 255; // for (int i = 0; i < size; ++i) { -// auto rgb = originPixels[i]; +// auto rgb = origin[i]; // uint8_t r = rgb.r; // uint8_t g = rgb.g; // uint8_t b = rgb.b; -// int nearestCentroidDistance = maxCentroidDistance; +// double nearestCentroidDistance = maxCentroidDistance; // int nearestIndices = 0; -// for (int k = 0; k < quantizerSize; ++k) { -// uint8_t qr = quantizerPixels[k].r; -// uint8_t qg = quantizerPixels[k].g; -// uint8_t qb = quantizerPixels[k].b; -// int16_t nr = r - qr; -// int16_t ng = g - qg; -// int16_t nb = b - qb; -// int32_t distance = 2 * nr * nr + 4 * ng * ng + 3 * nb * nb; +// for (int k = 0; k < quantizeSize; ++k) { +// uint8_t qr = quantize[k].r; +// uint8_t qg = quantize[k].g; +// uint8_t qb = quantize[k].b; +// int16_t nr = qr - r; +// int16_t ng = qg - g; +// int16_t nb = qb - b; +// auto distance = nr * nr + ng * ng + nb * nb; // if (distance < nearestCentroidDistance) { // nearestCentroidDistance = distance; -// nearestIndices = quantizerPixels[k].index; +// nearestIndices = quantize[k].index; // }; // } // colorIndices[i] = static_cast(nearestIndices); @@ -40,21 +40,34 @@ NoDitherer::dither(RGB *originPixels, uint16_t width, uint16_t height, KDTree kdTree; KDTree::Node rootNode; - kdTree.createKDTree(&rootNode, quantizerPixels, 0, quantizerSize - 1, 0); + auto end = static_cast(quantizeSize - 1); + auto transparentColorIndex = static_cast(quantizeSize + 1); + kdTree.createKDTree(&rootNode, quantize, 0, end, 0); uint16_t lastR = 256; uint16_t lastG = 256; uint16_t lastB = 256; uint8_t lastIndex = 0; - for (int i = 0; i < size; ++i) { - auto rgb = originPixels[i]; - if (!(lastR == rgb.r && lastG == rgb.g && lastB == rgb.b)) { - lastR = rgb.r; - lastG = rgb.g; - lastB = rgb.b; - kdTree.searchNNNoBacktracking(&rootNode, rgb, -1); - lastIndex = kdTree.nearest.index; + int count = 0; + for (int i = 0; i < size; ++count) { + auto rgb = origin[i]; + if (rgb.unTranpsparentIndex == count) { + ++i; + if (!(lastR == rgb.r && lastG == rgb.g && lastB == rgb.b)) { + lastR = rgb.r; + lastG = rgb.g; + lastB = rgb.b; + kdTree.searchNoBacktracking(&rootNode, rgb.r, rgb.g, rgb.b, -1); + lastIndex = kdTree.nearest.index; + } + colorIndices[count] = lastIndex; + } else { + colorIndices[count] = transparentColorIndex; + } + } + if (count < totalSize) { + for (int i = count; i < totalSize; ++i) { + colorIndices[i] = transparentColorIndex; } - colorIndices[i] = lastIndex; } kdTree.freeKDTree(&rootNode); } diff --git a/src/NoDitherer.h b/src/NoDitherer.h index e68369e..5a59620 100644 --- a/src/NoDitherer.h +++ b/src/NoDitherer.h @@ -13,10 +13,7 @@ namespace blk { public: - void - dither(RGB *originPixels, uint16_t width, uint16_t height, - RGB quantizerPixels[], int32_t quantizerSize, - uint8_t *colorIndices) override; + void dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) override; }; diff --git a/src/OctreeQuantizer.cpp b/src/OctreeQuantizer.cpp index b048b22..e1c9342 100644 --- a/src/OctreeQuantizer.cpp +++ b/src/OctreeQuantizer.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include "OctreeQuantizer.h" using namespace blk; @@ -19,11 +18,9 @@ void OctreeQuantizer::reduceTree() { uint32_t blueSum = 0; uint32_t pixelCount = 0; - // Find the deepest level containing at least one reducible node for (i = 7; (i > 0) && (nodeList[i] == nullptr); i--) { }; - // Reduce the node most recently added to the list at level i Node *tmpNode = nodeList[i]; nodeList[i] = tmpNode->next; @@ -62,27 +59,27 @@ OctreeQuantizer::Node *OctreeQuantizer::createNode(int inLevel) { return node; } -bool OctreeQuantizer::addColor(Node *node, uint32_t r, uint32_t g, uint32_t b, int level) { +bool OctreeQuantizer::addColor(Node **node, uint32_t r, uint32_t g, uint32_t b, int level) { int index, shift; - if (node == nullptr) { - node = createNode(level); + if (*node == nullptr) { + *node = createNode(level); } - if (node == nullptr) { + if (*node == nullptr) { return false; } - if (node->isLeaf) { - node->pixelCount++; - node->rSum += r; - node->gSum += g; - node->bSum += b; + if ((*node)->isLeaf) { + (*node)->pixelCount++; + (*node)->rSum += r; + (*node)->gSum += g; + (*node)->bSum += b; } else { shift = 7 - level; index = (((r & mask[level]) >> shift) << 2) | (((g & mask[level]) >> shift) << 1) | ((b & mask[level]) >> shift); - if (!addColor(node->child[index], r, g, b, level + 1)) { + if (!addColor(&((*node)->child[index]), r, g, b, level + 1)) { return false; } } @@ -107,7 +104,7 @@ int32_t OctreeQuantizer::getColorIndex(uint8_t r, uint8_t g, uint8_t b) const { return currentTree->colorIndex; } -void OctreeQuantizer::getColorPalette(Node *tree, int32_t &inIndex, RGB out[]) { +void OctreeQuantizer::getColorPalette(Node *tree, int32_t &inIndex, std::vector &out) { if (tree == nullptr) { return; } @@ -119,10 +116,10 @@ void OctreeQuantizer::getColorPalette(Node *tree, int32_t &inIndex, RGB out[]) { tree->pixelCount = 1; } tree->colorIndex = static_cast(inIndex); - out[inIndex].r = static_cast(tree->rSum); - out[inIndex].g = static_cast(tree->gSum); - out[inIndex].b = static_cast(tree->bSum); - out[inIndex].index = static_cast(inIndex); + auto r = static_cast(tree->rSum); + auto g = static_cast(tree->gSum); + auto b = static_cast(tree->bSum); + out.emplace_back(r, g, b, inIndex); inIndex++; } else { for (auto &i : tree->child) { @@ -151,27 +148,24 @@ OctreeQuantizer::~OctreeQuantizer() { leafCount = 0; } -int32_t -OctreeQuantizer::quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCount, RGB out[]) { +int32_t OctreeQuantizer::quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) { leafCount = 0; - Node *node = nullptr; + size_t pixelCount = in.size(); for (int i = 0; i < pixelCount; ++i) { - auto color = pixels[i]; - if (!addColor(node, color.r, color.g, color.b, 0)) { + auto color = in[i]; + if (!addColor(&octree, color.r, color.g, color.b, 0)) { return 0; } while (leafCount > maxColorCount) { reduceTree(); } } - octree = node; - getColorPalette(node, resultSize, out); + getColorPalette(octree, resultSize, out); return resultSize; } -void -OctreeQuantizer::getColorIndices(RGB pixels[], uint8_t *out, uint32_t size, - int (*getOffset)(int, int)) { +void OctreeQuantizer::getColorIndices(const std::vector &pixels, uint8_t *out) { + size_t size = pixels.size(); int lastR = 256; int lastG = 256; int lastB = 256; @@ -181,12 +175,6 @@ OctreeQuantizer::getColorIndices(RGB pixels[], uint8_t *out, uint32_t size, uint8_t r = color.r; uint8_t g = color.g; uint8_t b = color.b; -// if (getOffset) { -// int offset = getOffset(j, color); -// r = (std::min(255, std::max(0, r + offset))); -// g = (std::min(255, std::max(0, g + offset))); -// b = (std::min(255, std::max(0, b + offset))); -// } if (!(lastR == r && lastG == g && lastB == b)) { lastR = r; lastG = g; diff --git a/src/OctreeQuantizer.h b/src/OctreeQuantizer.h index 25bc69c..3ae0abc 100644 --- a/src/OctreeQuantizer.h +++ b/src/OctreeQuantizer.h @@ -28,23 +28,23 @@ namespace blk { ~OctreeQuantizer() override; - int32_t quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCount, RGB out[]) override; + int32_t quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) override; int32_t getColorIndex(uint8_t r, uint8_t g, uint8_t b) const; - void getColorIndices(RGB pixels[], uint8_t *out, uint32_t size, int (*getOffset)(int, int)); + void getColorIndices(const std::vector &pixels, uint8_t *out); void freeTree(Node *&tree); protected: - bool addColor(Node *node, uint32_t r, uint32_t g, uint32_t b, int level); + bool addColor(Node **node, uint32_t r, uint32_t g, uint32_t b, int level); Node *createNode(int inLevel); void reduceTree(); - void getColorPalette(Node *tree, int32_t &inIndex, RGB out[]); + void getColorPalette(Node *tree, int32_t &inIndex, std::vector &out); size_t leafCount = 0; diff --git a/src/RandomQuantizer.cpp b/src/RandomQuantizer.cpp index 1970011..6173910 100644 --- a/src/RandomQuantizer.cpp +++ b/src/RandomQuantizer.cpp @@ -5,27 +5,24 @@ #include #include "RandomQuantizer.h" -using namespace std; using namespace blk; -int32_t RandomQuantizer::quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCount, RGB out[]) { - mt19937 generator((uint32_t) time(nullptr)); - uniform_int_distribution dis(0, pixelCount); - set randomColor; +int32_t RandomQuantizer::quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) { + size_t pixelCount = in.size(); + std::mt19937 generator((uint32_t) time(nullptr)); + std::uniform_int_distribution dis(0, pixelCount); + std::set randomColor; uint32_t index = 0; - uint32_t maxCount = pixelCount / 4; + size_t maxCount = pixelCount / 4; while (randomColor.size() < maxColorCount && index < maxCount) { index++; - uint32_t random = dis(generator); - randomColor.insert(pixels[random]); + auto rColor = in[dis(generator)]; + randomColor.emplace(rColor.r, rColor.g, rColor.b); } resultSize = static_cast(randomColor.size()); - int colorPaletteIndex = 0; - for (RGB color:randomColor) { - out[colorPaletteIndex].r = color.r; - out[colorPaletteIndex].g = color.g; - out[colorPaletteIndex].b = color.b; - out[colorPaletteIndex].index = static_cast(colorPaletteIndex); + uint8_t colorPaletteIndex = 0; + for (ARGB color:randomColor) { + out.emplace_back(color.r, color.g, color.b, colorPaletteIndex); colorPaletteIndex++; } return resultSize; diff --git a/src/RandomQuantizer.h b/src/RandomQuantizer.h index a6ed955..202453c 100644 --- a/src/RandomQuantizer.h +++ b/src/RandomQuantizer.h @@ -13,7 +13,7 @@ namespace blk { public: - int32_t quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCount, RGB out[]) override; + int32_t quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) override; }; diff --git a/src/UniformQuantizer.cpp b/src/UniformQuantizer.cpp index a25f3a6..e95937f 100644 --- a/src/UniformQuantizer.cpp +++ b/src/UniformQuantizer.cpp @@ -7,35 +7,30 @@ #include #include "UniformQuantizer.h" -using namespace std; using namespace blk; -int32_t -UniformQuantizer::quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCount, RGB out[]) { +int32_t UniformQuantizer::quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) { uint32_t index = 0; - auto baseSegments = static_cast(pow(maxColorCount, 1.0 / 3.0)); + auto baseSegments = static_cast(pow(maxColorCount, 1.0 / 3.0)); int32_t redSegments = baseSegments; int32_t greenSegments = baseSegments; int32_t blueSegments = baseSegments; - - // see if we can add an extra segment to one or two channels. if (redSegments * (greenSegments + 1) * blueSegments <= maxColorCount) { ++greenSegments; } if ((redSegments + 1) * greenSegments * blueSegments <= maxColorCount) { ++redSegments; } - for (size_t redSegment = 0; redSegment < redSegments; ++redSegment) { for (size_t greenSegment = 0; greenSegment < greenSegments; ++greenSegment) { for (size_t blueSegment = 0; blueSegment < blueSegments; ++blueSegment) { - double r = redSegment / (redSegments - 1.0); - double g = greenSegment / (greenSegments - 1.0); - double b = blueSegment / (blueSegments - 1.0); - out[index].r = static_cast(r * 255); - out[index].g = static_cast(g * 255); - out[index].b = static_cast(b * 255); - out[index].index = static_cast(index); + double dr = redSegment / (redSegments - 1.0); + double dg = greenSegment / (greenSegments - 1.0); + double db = blueSegment / (blueSegments - 1.0); + auto r = static_cast(dr * 255); + auto g = static_cast(dg * 255); + auto b = static_cast(db * 255); + out.emplace_back(r, g, b, index); index++; } } diff --git a/src/UniformQuantizer.h b/src/UniformQuantizer.h index 760e2bd..583c192 100644 --- a/src/UniformQuantizer.h +++ b/src/UniformQuantizer.h @@ -13,7 +13,7 @@ namespace blk { public: - int32_t quantize(RGB *pixels, uint32_t pixelCount, uint32_t maxColorCount, RGB out[]) override; + int32_t quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) override; }; }