├── android
├── sample
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── drawable-nodpi
│ │ │ │ ├── tcr.jpg
│ │ │ │ └── bilibili.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ └── layout
│ │ │ │ └── activity_main.xml
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── com
│ │ │ └── bilibili
│ │ │ └── burstlinker
│ │ │ └── sample
│ │ │ └── MainActivity.java
│ ├── proguard-rules.pro
│ └── build.gradle
├── settings.gradle
├── lib
│ ├── .gitignore
│ ├── src
│ │ └── main
│ │ │ ├── cpp
│ │ │ ├── DithererWithRs.cpp
│ │ │ ├── DisableDithererWithRs.h
│ │ │ ├── BayerDithererWithRs.h
│ │ │ ├── NoDithererWithRs.h
│ │ │ ├── DithererWithRs.h
│ │ │ ├── DisableDithererWithRs.cpp
│ │ │ ├── NoDithererWithRs.cpp
│ │ │ ├── BayerDithererWithRs.cpp
│ │ │ └── BurstLinker.cpp
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── com
│ │ │ └── bilibili
│ │ │ └── burstlinker
│ │ │ ├── GifEncodeException.java
│ │ │ └── BurstLinker.java
│ ├── proguard-rules.pro
│ ├── build.gradle
│ └── CMakeLists.txt
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradle.properties
├── .gitignore
├── upload.gradle
├── build.gradle
├── gradlew.bat
└── gradlew
├── .gitattributes
├── screenshot
├── octree.gif
├── random.gif
├── bilibili.png
├── k-means.gif
├── uniform.gif
├── media-cut.gif
├── neu-quant-1.gif
├── octree-m2.gif
├── neu-quant-10.gif
├── octree-bayer.gif
├── lenna-original.png
├── bilibili-octree-default.gif
├── bilibili-octree-ignore.gif
└── octree-floyd-steinberg.gif
├── .gitignore
├── src
├── GifAnalyzer.h
├── M2Ditherer.h
├── NoDitherer.h
├── BayerDitherer.h
├── KMeansQuantizer.h
├── UniformQuantizer.h
├── RandomQuantizer.h
├── NeuQuantQuantizer.h
├── FloydSteinbergDitherer.h
├── MedianCutQuantizer.h
├── Logger.h
├── ColorQuantizer.h
├── NeuQuantQuantizer.cpp
├── KDTree.h
├── Ditherer.h
├── LzwEncoder.h
├── RandomQuantizer.cpp
├── BurstLinker.h
├── OctreeQuantizer.h
├── UniformQuantizer.cpp
├── Logger.cpp
├── M2Ditherer.cpp
├── BayerDitherer.cpp
├── BurstLinker.cpp
├── GifBlockWriter.h
├── NoDitherer.cpp
├── GifEncoder.h
├── ThreadPool.h
├── FloydSteinbergDitherer.cpp
├── LzwEncoder.cpp
├── KDTree.cpp
├── MedianCutQuantizer.cpp
├── KMeansQuantizer.cpp
├── OctreeQuantizer.cpp
├── NeuQuant.h
├── Main.cpp
├── GifBlockWriter.cpp
├── GifAnalyzer.cpp
├── NeuQuant.cpp
└── GifEncoder.cpp
├── CMakeLists.txt
├── README.md
├── example
└── Main.cpp
└── LICENSE
/android/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':lib', ':sample'
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.c linguist-language=C++
2 | *.h linguist-language=C++
--------------------------------------------------------------------------------
/screenshot/octree.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/screenshot/octree.gif
--------------------------------------------------------------------------------
/screenshot/random.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/screenshot/random.gif
--------------------------------------------------------------------------------
/screenshot/bilibili.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/screenshot/bilibili.png
--------------------------------------------------------------------------------
/screenshot/k-means.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/screenshot/k-means.gif
--------------------------------------------------------------------------------
/screenshot/uniform.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/screenshot/uniform.gif
--------------------------------------------------------------------------------
/screenshot/media-cut.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/screenshot/media-cut.gif
--------------------------------------------------------------------------------
/screenshot/neu-quant-1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/screenshot/neu-quant-1.gif
--------------------------------------------------------------------------------
/screenshot/octree-m2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/screenshot/octree-m2.gif
--------------------------------------------------------------------------------
/screenshot/neu-quant-10.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/screenshot/neu-quant-10.gif
--------------------------------------------------------------------------------
/screenshot/octree-bayer.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/screenshot/octree-bayer.gif
--------------------------------------------------------------------------------
/screenshot/lenna-original.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/screenshot/lenna-original.png
--------------------------------------------------------------------------------
/android/lib/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | .externalNativeBuild/
3 | /src/main/cpp/cmake-build-debug/
4 | /src/main/cpp/.idea/
5 |
--------------------------------------------------------------------------------
/android/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | BurstLinker
3 |
4 |
--------------------------------------------------------------------------------
/screenshot/bilibili-octree-default.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/screenshot/bilibili-octree-default.gif
--------------------------------------------------------------------------------
/screenshot/bilibili-octree-ignore.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/screenshot/bilibili-octree-ignore.gif
--------------------------------------------------------------------------------
/screenshot/octree-floyd-steinberg.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/screenshot/octree-floyd-steinberg.gif
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/android/lib/src/main/cpp/DithererWithRs.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/11/10.
3 | //
4 |
5 | #include "DithererWithRs.h"
6 |
--------------------------------------------------------------------------------
/android/sample/src/main/res/drawable-nodpi/tcr.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/android/sample/src/main/res/drawable-nodpi/tcr.jpg
--------------------------------------------------------------------------------
/android/sample/src/main/res/drawable-nodpi/bilibili.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/android/sample/src/main/res/drawable-nodpi/bilibili.png
--------------------------------------------------------------------------------
/android/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bilibili/BurstLinker/HEAD/android/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/lib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | ## Project-wide Gradle settings.
2 | org.gradle.jvmargs=-Xmx5120m -XX:MaxPermSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
3 | org.gradle.daemon=true
4 | org.gradle.parallel=true
5 |
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle/
2 | /Android/local.properties
3 | .idea/
4 | .DS_Store
5 | build/
6 | /Android/build
7 | /.idea
8 | /.gradle
9 | captures/
10 | *.class
11 | *.iml
12 | .externalNativeBuild/
13 | CMakeFiles/
14 | .vs/
--------------------------------------------------------------------------------
/android/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle/
2 | /local.properties
3 | local.properties
4 | .idea/
5 | .DS_Store
6 | build/
7 | /Android/build
8 | /.idea
9 | /.gradle
10 | captures/
11 | *.class
12 | *.iml
13 | .externalNativeBuild/
14 | CMakeFiles/
15 | .vs/
16 | cmake-build-debug/
17 | CMakeSettings.json
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Aug 07 14:39:55 CST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/android/upload.gradle:
--------------------------------------------------------------------------------
1 | publish {
2 | userOrg = 'succlz123'
3 | groupId = 'com.bilibili'
4 | artifactId = 'burst-linker'
5 | publishVersion = rootProject.ext.burstLinkerVer
6 | desc = 'BurstLinker is a simple C++ GIF encode library.'
7 | website = 'https://github.com/Bilibili/BurstLinker'
8 | }
9 |
--------------------------------------------------------------------------------
/android/lib/src/main/java/com/bilibili/burstlinker/GifEncodeException.java:
--------------------------------------------------------------------------------
1 | package com.bilibili.burstlinker;
2 |
3 | /**
4 | * Created by succlz123 on 2017/9/7.
5 | */
6 | public class GifEncodeException extends Exception {
7 |
8 | public GifEncodeException(String message) {
9 | super(message);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/GifAnalyzer.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/11/4.
3 | //
4 |
5 | #ifndef BURSTLINKER_GIFANALYZER_H
6 | #define BURSTLINKER_GIFANALYZER_H
7 |
8 | namespace blk {
9 |
10 | class GifAnalyzer {
11 |
12 | public:
13 |
14 | void showGifInfo(const char *path);
15 |
16 | };
17 |
18 | }
19 |
20 | #endif //BURSTLINKER_GIFANALYZER_H
21 |
--------------------------------------------------------------------------------
/android/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/M2Ditherer.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-9-19.
3 | //
4 |
5 | #ifndef BURSTLINKER_M2DITHERER_H
6 | #define BURSTLINKER_M2DITHERER_H
7 |
8 | #include "Ditherer.h"
9 |
10 | namespace blk {
11 |
12 | class M2Ditherer : public Ditherer {
13 |
14 | public:
15 |
16 | void dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) override;
17 |
18 | };
19 |
20 | }
21 |
22 | #endif //BURSTLINKER_M2DITHERER_H
23 |
--------------------------------------------------------------------------------
/src/NoDitherer.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/11/15.
3 | //
4 |
5 | #ifndef BURSTLINKER_NODITHERER_H
6 | #define BURSTLINKER_NODITHERER_H
7 |
8 | #include "Ditherer.h"
9 |
10 | namespace blk {
11 |
12 | class NoDitherer : public Ditherer {
13 |
14 | public:
15 |
16 | void dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) override;
17 |
18 | };
19 |
20 | }
21 |
22 | #endif //BURSTLINKER_NODITHERER_H
23 |
--------------------------------------------------------------------------------
/src/BayerDitherer.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/10/26.
3 | //
4 |
5 | #ifndef BURSTLINKER_BAYERDITHERER_H
6 | #define BURSTLINKER_BAYERDITHERER_H
7 |
8 | #include "Ditherer.h"
9 |
10 | namespace blk {
11 |
12 | class BayerDitherer : public Ditherer {
13 |
14 | public:
15 |
16 | void dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) override;
17 | };
18 |
19 | }
20 |
21 | #endif //BURSTLINKER_BAYERDITHERER_H
22 |
--------------------------------------------------------------------------------
/src/KMeansQuantizer.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-10-9.
3 | //
4 |
5 | #ifndef BURSTLINKER_KMEANSQUANTIZER_H
6 | #define BURSTLINKER_KMEANSQUANTIZER_H
7 |
8 | #include "ColorQuantizer.h"
9 |
10 | namespace blk {
11 |
12 | class KMeansQuantizer : public ColorQuantizer {
13 |
14 | public:
15 |
16 | int32_t quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) override;
17 |
18 | };
19 |
20 | }
21 |
22 | #endif //BURSTLINKER_KMEANSQUANTIZER_H
23 |
--------------------------------------------------------------------------------
/src/UniformQuantizer.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-9-12.
3 | //
4 |
5 | #ifndef BURSTLINKER_UNIFORMQUANTIZER_H
6 | #define BURSTLINKER_UNIFORMQUANTIZER_H
7 |
8 | #include "ColorQuantizer.h"
9 |
10 | namespace blk {
11 |
12 | class UniformQuantizer : public ColorQuantizer {
13 |
14 | public:
15 |
16 | int32_t quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) override;
17 | };
18 |
19 | }
20 |
21 | #endif //BURSTLINKER_UNIFORMQUANTIZER_H
22 |
--------------------------------------------------------------------------------
/src/RandomQuantizer.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/11/5.
3 | //
4 |
5 | #ifndef BURSTLINKER_RANDOMQUANTIZER_H
6 | #define BURSTLINKER_RANDOMQUANTIZER_H
7 |
8 | #include "ColorQuantizer.h"
9 |
10 | namespace blk {
11 |
12 | class RandomQuantizer : public ColorQuantizer {
13 |
14 | public:
15 |
16 | int32_t quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) override;
17 |
18 | };
19 |
20 | }
21 |
22 | #endif //BURSTLINKER_RANDOMQUANTIZER_H
23 |
--------------------------------------------------------------------------------
/src/NeuQuantQuantizer.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/11/5.
3 | //
4 |
5 | #ifndef BURSTLINKER_NEUQUANTQUANTIZER_H
6 | #define BURSTLINKER_NEUQUANTQUANTIZER_H
7 |
8 | #include "ColorQuantizer.h"
9 |
10 | namespace blk {
11 |
12 | class NeuQuantQuantizer : public ColorQuantizer {
13 |
14 | public:
15 |
16 | int32_t quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) override;
17 |
18 | };
19 |
20 | }
21 |
22 | #endif //BURSTLINKER_NEUQUANTQUANTIZER_H
23 |
--------------------------------------------------------------------------------
/src/FloydSteinbergDitherer.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-9-19.
3 | //
4 |
5 | #ifndef BURSTLINKER_FLOYDSTEINBERGDITHERER_H
6 | #define BURSTLINKER_FLOYDSTEINBERGDITHERER_H
7 |
8 | #include "Ditherer.h"
9 |
10 | namespace blk {
11 |
12 | class FloydSteinbergDitherer : public Ditherer {
13 |
14 | public:
15 |
16 | void dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) override;
17 |
18 | };
19 |
20 | }
21 |
22 | #endif //BURSTLINKER_FLOYDSTEINBERGDITHERER_H
23 |
--------------------------------------------------------------------------------
/src/MedianCutQuantizer.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/10/14.
3 | //
4 |
5 | #ifndef BURSTLINKER_MEDIANCUTQUANTIZER_H
6 | #define BURSTLINKER_MEDIANCUTQUANTIZER_H
7 |
8 | #include "ColorQuantizer.h"
9 |
10 | namespace blk {
11 |
12 | class MedianCutQuantizer : public ColorQuantizer {
13 |
14 | public:
15 |
16 | int32_t quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) override;
17 |
18 | };
19 |
20 | }
21 |
22 | #endif //BURSTLINKER_MEDIANCUTQUANTIZER_H
23 |
--------------------------------------------------------------------------------
/src/Logger.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-9-30.
3 | //
4 |
5 | #ifndef BURSTLINKER_GIFLOGGER_H
6 | #define BURSTLINKER_GIFLOGGER_H
7 |
8 | #include
9 | #include
10 | #include
11 |
12 | namespace blk {
13 |
14 | class Logger {
15 |
16 | public:
17 |
18 | static void log(bool show, std::string str);
19 |
20 | template
21 | static std::string toString(T value) {
22 | std::ostringstream os;
23 | os << value;
24 | return os.str();
25 | }
26 | };
27 |
28 | }
29 |
30 | #endif //BURSTLINKER_GIFLOGGER_H
31 |
--------------------------------------------------------------------------------
/src/ColorQuantizer.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-9-11.
3 | //
4 |
5 | #ifndef BURSTLINKER_COLORQUANTIZER_H
6 | #define BURSTLINKER_COLORQUANTIZER_H
7 |
8 | #include
9 | #include
10 | #include "GifEncoder.h"
11 |
12 | namespace blk {
13 |
14 | class ColorQuantizer {
15 |
16 | public:
17 |
18 | int32_t resultSize = 0;
19 |
20 | virtual ~ColorQuantizer() = default;;
21 |
22 | virtual int32_t quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out)= 0;
23 |
24 | protected:
25 |
26 | int sample = 10; // for NeuQuant
27 |
28 | };
29 |
30 | }
31 |
32 | #endif //BURSTLINKER_COLORQUANTIZER_H
33 |
--------------------------------------------------------------------------------
/android/lib/src/main/cpp/DisableDithererWithRs.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/10/5.
3 | //
4 |
5 | #ifndef BURSTLINKER_DISABLEDITHERERWITHRS_H
6 | #define BURSTLINKER_DISABLEDITHERERWITHRS_H
7 |
8 | #include "../../../../../src/Ditherer.h"
9 | #include "DithererWithRs.h"
10 | #include
11 | #include
12 |
13 | using namespace android::RSC;
14 |
15 | class DisableDithererWithRs : public DithererWithRs {
16 |
17 | public:
18 |
19 | void dither(uint32_t *originalColors, int width, int height, unsigned char *quantizerColors,
20 | int quantizerSize, sp rs) override;
21 |
22 | };
23 |
24 |
25 | #endif //BURSTLINKER_DISABLEDITHERERWITHRS_H
26 |
--------------------------------------------------------------------------------
/android/lib/src/main/cpp/BayerDithererWithRs.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/11/15.
3 | //
4 |
5 | #ifndef BURSTLINKER_BAYERDITHERERWITHRS_H
6 | #define BURSTLINKER_BAYERDITHERERWITHRS_H
7 |
8 | #include "../../../../../src/Ditherer.h"
9 | #include "DithererWithRs.h"
10 | #include
11 |
12 | using namespace android::RSC;
13 |
14 | class BayerDithererWithRs : public DithererWithRs {
15 |
16 | public:
17 |
18 | void dither(blk::RGB *originPixels, uint16_t width, uint16_t height,
19 | blk::RGB quantizerPixels[], int32_t quantizerSize,
20 | uint8_t *colorIndices, sp rs) override;
21 |
22 | };
23 |
24 |
25 | #endif //BURSTLINKER_BAYERDITHERERWITHRS_H
26 |
--------------------------------------------------------------------------------
/android/lib/src/main/cpp/NoDithererWithRs.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/10/5.
3 | //
4 |
5 | #ifndef BURSTLINKER_DISABLEDITHERERWITHRS_H
6 | #define BURSTLINKER_DISABLEDITHERERWITHRS_H
7 |
8 | #include "../../../../../src/Ditherer.h"
9 | #include "DithererWithRs.h"
10 | #include
11 | #include
12 |
13 | using namespace android::RSC;
14 |
15 | class NoDithererWithRs : public DithererWithRs {
16 |
17 | public:
18 |
19 | void dither(blk::RGB *originPixels, uint16_t width, uint16_t height,
20 | blk::RGB quantizerPixels[], int32_t quantizerSize,
21 | uint8_t *colorIndices, sp rs) override;
22 |
23 | };
24 |
25 |
26 | #endif //BURSTLINKER_DISABLEDITHERERWITHRS_H
27 |
--------------------------------------------------------------------------------
/android/lib/src/main/cpp/DithererWithRs.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/11/10.
3 | //
4 |
5 | #ifndef BURSTLINKER_DITHERERWITHRS_H
6 | #define BURSTLINKER_DITHERERWITHRS_H
7 |
8 | #include "../../../../../src/Ditherer.h"
9 | #include
10 |
11 | class DithererWithRs : public blk::Ditherer {
12 |
13 | public:
14 |
15 | void
16 | dither(blk::RGB *originPixels, uint16_t width, uint16_t height,
17 | blk::RGB quantizerPixels[], int32_t quantizerSize,
18 | uint8_t *colorIndices) {};
19 |
20 | virtual void
21 | dither(blk::RGB *originPixels, uint16_t width, uint16_t height,
22 | blk::RGB quantizerPixels[], int32_t quantizerSize,
23 | uint8_t *colorIndices, android::RSC::sp rs)=0;
24 |
25 | };
26 |
27 |
28 | #endif //BURSTLINKER_DITHERERWITHRS_H
29 |
--------------------------------------------------------------------------------
/src/NeuQuantQuantizer.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/11/5.
3 | //
4 |
5 | #include "NeuQuantQuantizer.h"
6 | #include "NeuQuant.h"
7 |
8 | using namespace blk;
9 |
10 | int32_t NeuQuantQuantizer::quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) {
11 | NeuQuant neuQuant;
12 | size_t size = in.size();
13 | auto pixels = new uint8_t[size * 3];
14 | int index = 0;
15 | for (int i = 0; i < size; ++i) {
16 | auto inColor = in[i];
17 | pixels[index++] = inColor.r;
18 | pixels[index++] = inColor.g;
19 | pixels[index++] = inColor.b;
20 | }
21 | neuQuant.initnet(pixels, size * 3, sample);
22 | neuQuant.learn();
23 | neuQuant.unbiasnet();
24 | resultSize = neuQuant.getColourMap(out, maxColorCount);
25 | delete[] pixels;
26 | return resultSize;
27 | }
28 |
--------------------------------------------------------------------------------
/src/KDTree.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/12/28.
3 | //
4 |
5 | #ifndef BURSTLINKER_KDTREE_H
6 | #define BURSTLINKER_KDTREE_H
7 |
8 | #include "GifEncoder.h"
9 |
10 | namespace blk {
11 |
12 | class KDTree {
13 |
14 | public:
15 |
16 | struct Node {
17 | uint8_t r = 0;
18 | uint8_t g = 0;
19 | uint8_t b = 0;
20 | uint8_t index = 0;
21 | uint8_t split = 0;
22 | Node *left = nullptr;
23 | Node *right = nullptr;
24 | };
25 |
26 | Node nearest;
27 |
28 | void *createKDTree(Node *node, std::vector &quantize, int32_t start, int32_t end, uint8_t split);
29 |
30 | int searchNoBacktracking(Node *node, uint8_t r, uint8_t g, uint8_t b, int32_t dis);
31 |
32 | void freeKDTree(Node *tree);
33 |
34 | };
35 |
36 | }
37 |
38 | #endif //BURSTLINKER_KDTREE_H
39 |
--------------------------------------------------------------------------------
/src/Ditherer.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-9-19.
3 | //
4 |
5 | #ifndef BURSTLINKER_DITHERER_H
6 | #define BURSTLINKER_DITHERER_H
7 |
8 | #include
9 | #include "ColorQuantizer.h"
10 |
11 | namespace blk {
12 |
13 | class Ditherer {
14 |
15 | public:
16 |
17 | // ffmpeg vf_paletteuse.c
18 | static int bayerDitherValue(int p) {
19 | const int q = p ^(p >> 3);
20 | return (p & 4) >> 2 | (q & 4) >> 1 \
21 | | (p & 2) << 1 | (q & 2) << 2 \
22 | | (p & 1) << 4 | (q & 1) << 5;
23 | }
24 |
25 | // only for bayer
26 | int bayerScale = 1;
27 |
28 | uint16_t width = 0;
29 |
30 | uint16_t height = 0;
31 |
32 | virtual ~Ditherer() = default;
33 |
34 | virtual void dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) = 0;
35 |
36 | };
37 |
38 | }
39 |
40 | #endif //BURSTLINKER_DITHERER_H
41 |
--------------------------------------------------------------------------------
/src/LzwEncoder.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-9-9.
3 | //
4 |
5 | #ifndef BURSTLINKER_LZWENCODER_H
6 | #define BURSTLINKER_LZWENCODER_H
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | namespace blk {
14 |
15 | class LzwEncoder {
16 |
17 | public:
18 |
19 | explicit LzwEncoder(int32_t paddedColorCount);
20 |
21 | ~LzwEncoder();
22 |
23 | void encode(uint8_t indices[], uint16_t width, uint16_t height, std::vector &content);
24 |
25 | private:
26 |
27 | std::list datas;
28 |
29 | uint8_t *current;
30 |
31 | int pos;
32 |
33 | int remain;
34 |
35 | int32_t numColors;
36 |
37 | void writeBits(uint32_t src, int32_t bit);
38 |
39 | int write(std::vector &content, uint8_t minimumCodeSize);
40 | };
41 |
42 | }
43 |
44 | #endif //BURSTLINKER_LZWENCODER_H
45 |
--------------------------------------------------------------------------------
/android/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/android/lib/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/succlz123/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/android/sample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/succlz123/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/src/RandomQuantizer.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/11/5.
3 | //
4 |
5 | #include
6 | #include "RandomQuantizer.h"
7 |
8 | using namespace blk;
9 |
10 | int32_t RandomQuantizer::quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) {
11 | size_t pixelCount = in.size();
12 | std::mt19937 generator((uint32_t) time(nullptr));
13 | std::uniform_int_distribution dis(0, pixelCount);
14 | std::set randomColor;
15 | uint32_t index = 0;
16 | size_t maxCount = pixelCount / 4;
17 | while (randomColor.size() < maxColorCount && index < maxCount) {
18 | index++;
19 | auto rColor = in[dis(generator)];
20 | randomColor.emplace(rColor.r, rColor.g, rColor.b);
21 | }
22 | resultSize = static_cast(randomColor.size());
23 | uint8_t colorPaletteIndex = 0;
24 | for (ARGB color:randomColor) {
25 | out.emplace_back(color.r, color.g, color.b, colorPaletteIndex);
26 | colorPaletteIndex++;
27 | }
28 | return resultSize;
29 | }
30 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.BaseExtension
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | google()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:3.4.1'
10 | classpath 'com.novoda:bintray-release:0.9.1'
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | jcenter()
17 | google()
18 | }
19 | afterEvaluate {
20 | def android = project.extensions.findByName('android') as BaseExtension
21 | android?.compileOptions {
22 | sourceCompatibility JavaVersion.VERSION_1_8
23 | targetCompatibility JavaVersion.VERSION_1_8
24 | }
25 | }
26 | tasks.withType(Javadoc) {
27 | options.addStringOption('Xdoclint:none', '-quiet')
28 | options.addStringOption('encoding', 'UTF-8')
29 | }
30 | }
31 |
32 | task clean(type: Delete) {
33 | delete rootProject.buildDir
34 | }
35 |
36 | ext {
37 | burstLinkerVer = '0.0.12'
38 | libs = ['burstLinkerVer': "com.bilibili:$burstLinkerVer"]
39 | }
40 |
--------------------------------------------------------------------------------
/src/BurstLinker.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/11/30.
3 | //
4 |
5 | #ifndef BURSTLINKER_BURSTLINKER_H
6 | #define BURSTLINKER_BURSTLINKER_H
7 |
8 | #include
9 | #include "GifEncoder.h"
10 |
11 | namespace blk {
12 |
13 | class BurstLinker {
14 |
15 | public:
16 |
17 | bool init(const char *path, uint16_t width, uint16_t height, uint32_t loopCount,
18 | uint32_t threadNum);
19 |
20 | bool connect(std::vector &image, uint32_t delay,
21 | QuantizerType quantizerType, DitherType ditherType, int32_t transparencyOption,
22 | uint16_t left, uint16_t top);
23 |
24 | bool connect(std::vector> &images, uint32_t delay,
25 | QuantizerType quantizerType, DitherType ditherType, int32_t transparencyOption,
26 | uint16_t left, uint16_t top);
27 |
28 | void release();
29 |
30 | void analyzerGifInfo(const char *path);
31 |
32 | private:
33 |
34 | std::unique_ptr gifEncoder;
35 |
36 | };
37 |
38 | }
39 |
40 | #endif //BURSTLINKER_BURSTLINKER_H
41 |
--------------------------------------------------------------------------------
/android/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
23 |
24 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/android/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | def libVersion = rootProject.ext.burstLinkerVer
4 |
5 | android {
6 | compileSdkVersion 28
7 | buildToolsVersion '28.0.3'
8 |
9 | defaultConfig {
10 | applicationId "com.bilibili.burstlinker.sample"
11 | minSdkVersion 14
12 | targetSdkVersion 28
13 | versionCode 1
14 | versionName "1.0"
15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
16 |
17 | ndk {
18 | abiFilters 'armeabi-v7a', 'x86'
19 | }
20 | }
21 | buildTypes {
22 | release {
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
25 | }
26 | }
27 | }
28 |
29 | dependencies {
30 | implementation fileTree(dir: 'libs', include: ['*.jar'])
31 | testImplementation 'junit:junit:4.12'
32 |
33 | implementation 'com.android.support:appcompat-v7:28.0.0'
34 | implementation 'com.github.bumptech.glide:glide:4.4.0'
35 | annotationProcessor 'com.github.bumptech.glide:compiler:4.4.0'
36 | implementation project(path: ':lib')
37 | // implementation "com.bilibili:burst-linker:$libVersion"
38 | }
--------------------------------------------------------------------------------
/src/OctreeQuantizer.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-9-12.
3 | //
4 |
5 | #ifndef BURSTLINKER_OCTREEQUANTIZER_H
6 | #define BURSTLINKER_OCTREEQUANTIZER_H
7 |
8 | #include "ColorQuantizer.h"
9 |
10 | namespace blk {
11 |
12 | class OctreeQuantizer : public ColorQuantizer {
13 |
14 | protected:
15 |
16 | typedef struct Node {
17 | bool isLeaf;
18 | uint8_t colorIndex;
19 | uint32_t pixelCount;
20 | uint32_t rSum;
21 | uint32_t gSum;
22 | uint32_t bSum;
23 | Node *child[8];
24 | Node *next;
25 | } Node;
26 |
27 | public:
28 |
29 | ~OctreeQuantizer() override;
30 |
31 | int32_t quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) override;
32 |
33 | int32_t getColorIndex(uint8_t r, uint8_t g, uint8_t b) const;
34 |
35 | void getColorIndices(const std::vector &pixels, uint8_t *out);
36 |
37 | void freeTree(Node *&tree);
38 |
39 | protected:
40 |
41 | bool addColor(Node **node, uint32_t r, uint32_t g, uint32_t b, int level);
42 |
43 | Node *createNode(int inLevel);
44 |
45 | void reduceTree();
46 |
47 | void getColorPalette(Node *tree, int32_t &inIndex, std::vector &out);
48 |
49 | size_t leafCount = 0;
50 |
51 | Node *octree{};
52 |
53 | Node *nodeList[8]{};
54 | };
55 |
56 | }
57 |
58 | #endif //BURSTLINKER_OCTREEQUANTIZER_H
59 |
--------------------------------------------------------------------------------
/android/lib/src/main/cpp/DisableDithererWithRs.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/10/5.
3 | //
4 |
5 | #include "DisableDithererWithRs.h"
6 | #include "../../../../../src/Logger.h"
7 | #include "../../../../../src/BayerDitherer.h"
8 | #include "../../../build/generated/source/rs/debug/ScriptC_linear.h"
9 |
10 |
11 | void DisableDithererWithRs::dither(uint32_t *originalColors, int width, int height,
12 | unsigned char *quantizerColors, int quantizerSize, sp rs) {
13 | int32_t size = width * height;
14 | sp element = Element::U32(rs);
15 | sp type = Type::create(rs, element, size, 0, 0);
16 | sp inputAlloc = Allocation::createTyped(rs, type, RS_ALLOCATION_USAGE_SHARED |
17 | RS_ALLOCATION_USAGE_SCRIPT);
18 | sp outputAlloc = Allocation::createTyped(rs, type, RS_ALLOCATION_USAGE_SHARED |
19 | RS_ALLOCATION_USAGE_SCRIPT);
20 | inputAlloc->copy1DFrom(originalColors);
21 | ScriptC_linear *sc = new ScriptC_linear(rs);
22 | sc->set_quantizerSize(quantizerSize * 3);
23 | sc->set_quantizerColors(quantizerColors);
24 | sc->forEach_invert(inputAlloc, outputAlloc);
25 | colorIndices = new uint32_t[size];
26 | outputAlloc->copy1DTo(colorIndices);
27 | element.clear();
28 | type.clear();
29 | inputAlloc.clear();
30 | outputAlloc.clear();
31 | }
32 |
--------------------------------------------------------------------------------
/android/lib/src/main/cpp/NoDithererWithRs.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/10/5.
3 | //
4 |
5 | #include "NoDithererWithRs.h"
6 | #include "../../../../../src/Logger.h"
7 | #include "../../../../../src/BayerDitherer.h"
8 | #include "../../../build/generated/source/rs/debug/ScriptC_linear.h"
9 |
10 | void NoDithererWithRs::dither(blk::RGB *originPixels, uint16_t width, uint16_t height,
11 | blk::RGB quantizerPixels[], int32_t quantizerSize,
12 | uint8_t *colorIndices, sp rs) {
13 | int32_t size = width * height;
14 | sp element = Element::U32(rs);
15 | sp type = Type::create(rs, element, size, 0, 0);
16 | sp inputAlloc = Allocation::createTyped(rs, type, RS_ALLOCATION_USAGE_SHARED |
17 | RS_ALLOCATION_USAGE_SCRIPT);
18 | sp outputAlloc = Allocation::createTyped(rs, type, RS_ALLOCATION_USAGE_SHARED |
19 | RS_ALLOCATION_USAGE_SCRIPT);
20 | inputAlloc->copy1DFrom(originPixels);
21 | ScriptC_linear *sc = new ScriptC_linear(rs);
22 | sc->set_quantizerSize(quantizerSize);
23 | sc->set_quantizerColors((uint8_t *) quantizerPixels);
24 | sc->forEach_invert(inputAlloc, outputAlloc);
25 | outputAlloc->copy1DTo(colorIndices);
26 | element.clear();
27 | type.clear();
28 | inputAlloc.clear();
29 | outputAlloc.clear();
30 | }
31 |
--------------------------------------------------------------------------------
/src/UniformQuantizer.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-9-12.
3 | //
4 |
5 | #include
6 | #include
7 | #include
8 | #include "UniformQuantizer.h"
9 |
10 | using namespace blk;
11 |
12 | int32_t UniformQuantizer::quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) {
13 | uint32_t index = 0;
14 | auto baseSegments = static_cast(pow(maxColorCount, 1.0 / 3.0));
15 | int32_t redSegments = baseSegments;
16 | int32_t greenSegments = baseSegments;
17 | int32_t blueSegments = baseSegments;
18 | if (redSegments * (greenSegments + 1) * blueSegments <= maxColorCount) {
19 | ++greenSegments;
20 | }
21 | if ((redSegments + 1) * greenSegments * blueSegments <= maxColorCount) {
22 | ++redSegments;
23 | }
24 | for (size_t redSegment = 0; redSegment < redSegments; ++redSegment) {
25 | for (size_t greenSegment = 0; greenSegment < greenSegments; ++greenSegment) {
26 | for (size_t blueSegment = 0; blueSegment < blueSegments; ++blueSegment) {
27 | double dr = redSegment / (redSegments - 1.0);
28 | double dg = greenSegment / (greenSegments - 1.0);
29 | double db = blueSegment / (blueSegments - 1.0);
30 | auto r = static_cast(dr * 255);
31 | auto g = static_cast(dg * 255);
32 | auto b = static_cast(db * 255);
33 | out.emplace_back(r, g, b, index);
34 | index++;
35 | }
36 | }
37 | }
38 | resultSize = index;
39 | return resultSize;
40 | }
41 |
--------------------------------------------------------------------------------
/android/lib/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.novoda.bintray-release'
3 |
4 | ext {
5 | VERSION_NAME = rootProject.ext.burstLinkerVer
6 | }
7 |
8 | android {
9 | compileSdkVersion 28
10 | buildToolsVersion '28.0.3'
11 | defaultConfig {
12 | minSdkVersion 14
13 | targetSdkVersion 28
14 | versionCode 1
15 | versionName VERSION_NAME.toString()
16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
17 |
18 | // renderscriptTargetApi 26
19 | // renderscriptSupportModeEnabled true
20 | // renderscriptNdkModeEnabled true
21 |
22 | externalNativeBuild {
23 | cmake {
24 | // arguments "-DANDROID_ARM_NEON=TRUE"
25 | // arguments "-DANDROID_ABI=armeabi-v7a with NEON"
26 | arguments "-DANDROID_STL=c++_static"
27 | cppFlags "-std=c++14 -fno-rtti -fno-exceptions"
28 | }
29 | }
30 | }
31 | buildTypes {
32 | release {
33 | minifyEnabled false
34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
35 | }
36 | debug_native {
37 | jniDebuggable true
38 | renderscriptDebuggable true
39 | }
40 | }
41 | externalNativeBuild {
42 | cmake {
43 | path "CMakeLists.txt"
44 | }
45 | }
46 | }
47 |
48 | dependencies {
49 | implementation fileTree(dir: 'libs', include: ['*.jar'])
50 | testImplementation 'junit:junit:4.12'
51 | }
52 |
53 | apply from: rootProject.file('upload.gradle')
54 |
--------------------------------------------------------------------------------
/src/Logger.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-9-30.
3 | //
4 |
5 | #include
6 |
7 | #include "Logger.h"
8 |
9 | #if defined(__RenderScript__) || defined(__AndroidLog__)
10 |
11 | #include
12 |
13 | #define LOG_TAG "JNI_BURSTLINKER"
14 |
15 | #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG,__VA_ARGS__)
16 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , LOG_TAG,__VA_ARGS__)
17 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO , LOG_TAG,__VA_ARGS__)
18 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN , LOG_TAG,__VA_ARGS__)
19 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , LOG_TAG,__VA_ARGS__)
20 |
21 | #endif
22 |
23 | using namespace blk;
24 |
25 | static long long currentTime = 0;
26 |
27 | static long long currentTimeMillis() {
28 | //struct timeval tv{};
29 | //gettimeofday(&tv, nullptr);
30 | //return ((unsigned long long) tv.tv_sec * 1000 + (unsigned long long) tv.tv_usec / 1000);
31 | std::chrono::time_point tp = std::chrono::time_point_cast(
32 | std::chrono::system_clock::now());
33 | auto tmp = std::chrono::duration_cast(tp.time_since_epoch());
34 | auto timestamp = tmp.count();
35 | return timestamp;
36 | }
37 |
38 | void Logger::log(bool show, std::string str) {
39 | if (!show) {
40 | return;
41 | }
42 | long long diff = currentTimeMillis() - currentTime;
43 | if (currentTime == 0) {
44 | diff = 0;
45 | }
46 | #if defined(__RenderScript__) || defined(__AndroidLog__)
47 | LOGI("%s time : %dms", str.c_str(), (int) diff);
48 | #else
49 | std::cout << str << " - time " << diff << "ms" << std::endl;
50 | #endif
51 | currentTime = currentTimeMillis();
52 | }
53 |
--------------------------------------------------------------------------------
/src/M2Ditherer.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/10/26.
3 | //
4 |
5 | #include
6 | #include "M2Ditherer.h"
7 | #include "KDTree.h"
8 |
9 | using namespace blk;
10 |
11 | static const uint8_t matrix4x4[4][4] = {
12 | {0, 8, 2, 10},
13 | {12, 4, 14, 6},
14 | {3, 11, 1, 9},
15 | {15, 7, 13, 5}
16 | };
17 |
18 | void M2Ditherer::dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) {
19 | size_t totalSize = width * height;
20 | size_t size = origin.size();
21 | KDTree kdTree;
22 | KDTree::Node rootNode;
23 | size_t quantizeSize = quantize.size();
24 | auto end = static_cast(quantizeSize - 1);
25 | auto transparentColorIndex = static_cast(quantizeSize + 1);
26 | kdTree.createKDTree(&rootNode, quantize, 0, end, 0);
27 | int count = 0;
28 | for (int i = 0; i < size; ++count) {
29 | auto rgb = origin[i];
30 | if (rgb.unTranpsparentIndex == count) {
31 | ++i;
32 | int x = i % width;
33 | auto y = static_cast(std::ceil(i / width));
34 | int offset = (matrix4x4[(x & 3)][y & 3]);
35 | auto r = static_cast(std::min(255, std::max(0, rgb.r + offset)));
36 | auto g = static_cast(std::min(255, std::max(0, rgb.g + offset)));
37 | auto b = static_cast(std::min(255, std::max(0, rgb.b + offset)));
38 | kdTree.searchNoBacktracking(&rootNode, r, g, b, -1);
39 | colorIndices[count] = kdTree.nearest.index;
40 | } else {
41 | colorIndices[count] = transparentColorIndex;
42 | }
43 | }
44 | if (count < totalSize) {
45 | for (int i = count; i < totalSize; ++i) {
46 | colorIndices[i] = transparentColorIndex;
47 | }
48 | }
49 | kdTree.freeKDTree(&rootNode);
50 | }
51 |
--------------------------------------------------------------------------------
/src/BayerDitherer.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/10/26.
3 | //
4 |
5 | #include
6 | #include "BayerDitherer.h"
7 | #include "KDTree.h"
8 |
9 | using namespace blk;
10 |
11 | void BayerDitherer::dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) {
12 | int bayer[64];
13 | const int delta = 1 << (5 - bayerScale);
14 | for (int i = 0; i < 64; i++) {
15 | bayer[i] = (bayerDitherValue(i) >> 1) - delta;
16 | }
17 |
18 | size_t totalSize = width * height;
19 | size_t size = origin.size();
20 | KDTree kdTree;
21 | KDTree::Node rootNode;
22 | size_t quantizeSize = quantize.size();
23 | auto end = static_cast(quantizeSize - 1);
24 | auto transparentColorIndex = static_cast(quantizeSize + 1);
25 | kdTree.createKDTree(&rootNode, quantize, 0, end, 0);
26 | int count = 0;
27 | for (int i = 0; i < size; ++count) {
28 | auto rgb = origin[i];
29 | if (rgb.unTranpsparentIndex == count) {
30 | ++i;
31 | int x = i % width;
32 | auto y = static_cast(std::ceil(i / width));
33 | int offset = (bayer[((y & 7) << 3) | (x & 7)]);
34 | auto r = static_cast(std::min(255, std::max(0, rgb.r + offset)));
35 | auto g = static_cast(std::min(255, std::max(0, rgb.g + offset)));
36 | auto b = static_cast(std::min(255, std::max(0, rgb.b + offset)));
37 | kdTree.searchNoBacktracking(&rootNode, r, g, b, -1);
38 | colorIndices[count] = kdTree.nearest.index;
39 | } else {
40 | colorIndices[count] = transparentColorIndex;
41 | }
42 | }
43 | if (count < totalSize) {
44 | for (int i = count; i < totalSize; ++i) {
45 | colorIndices[i] = transparentColorIndex;
46 | }
47 | }
48 | kdTree.freeKDTree(&rootNode);
49 | }
50 |
--------------------------------------------------------------------------------
/android/lib/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.4.1)
2 |
3 | # add_library(cpufeatures STATIC ${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c)
4 |
5 | # SET(RENDER_SCRIPT_HEADERS_PATH
6 | # ${ANDROID_NDK}/toolchains/renderscript/prebuilt/${ANDROID_HOST_TAG}/platform/rs
7 | # )
8 |
9 | # INCLUDE_DIRECTORIES(
10 | # ${RENDER_SCRIPT_HEADERS_PATH}/cpp
11 | # ${RENDER_SCRIPT_HEADERS_PATH}/scriptc
12 | # ${RENDER_SCRIPT_HEADERS_PATH}
13 | # )
14 |
15 | # link_directories(${ANDROID_NDK}/toolchains/renderscript/prebuilt/${ANDROID_HOST_TAG}/platform/${ANDROID_SYSROOT_ABI})
16 |
17 | set(src_dir ../../src)
18 |
19 | set(SOURCE_FILES
20 | ${src_dir}/GifEncoder.cpp
21 | ${src_dir}/GifBlockWriter.cpp
22 | ${src_dir}/KDTree.cpp
23 | ${src_dir}/LzwEncoder.cpp
24 | ${src_dir}/Logger.cpp
25 |
26 | ${src_dir}/NoDitherer.cpp
27 | ${src_dir}/BayerDitherer.cpp
28 | ${src_dir}/M2Ditherer.cpp
29 | ${src_dir}/FloydSteinbergDitherer.cpp
30 |
31 | ${src_dir}/OctreeQuantizer.cpp
32 | ${src_dir}/UniformQuantizer.cpp
33 | ${src_dir}/KMeansQuantizer.cpp
34 | ${src_dir}/MedianCutQuantizer.cpp
35 | ${src_dir}/RandomQuantizer.cpp
36 | ${src_dir}/NeuQuant.cpp
37 | ${src_dir}/NeuQuantQuantizer.cpp
38 |
39 | # ${src_dir}/GifAnalyzer.h
40 | ${src_dir}/ThreadPool.h
41 |
42 | src/main/cpp/BurstLinker.cpp
43 | # src/main/cpp/DithererWithRs.cpp
44 | # src/main/cpp/DisableDithererWithRs.cpp
45 | # src/main/cpp/BayerDithererWithRs.cpp
46 | # build/generated/source/rs/debug/ScriptC_linear.cpp
47 | )
48 |
49 | add_library(BurstLinker SHARED ${SOURCE_FILES})
50 |
51 | target_link_libraries(BurstLinker
52 | log
53 | # RScpp_static
54 | jnigraphics
55 | )
56 |
57 | add_definitions (-Wno-format-security)
58 | add_definitions (-Wno-delete-non-virtual-dtor)
59 |
60 | # add_definitions (-D__Android__)
61 |
62 | add_definitions (-D__AndroidLog__)
63 | add_definitions (-D__SIMD__)
--------------------------------------------------------------------------------
/src/BurstLinker.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2018/1/30.
3 | //
4 |
5 | #include "BurstLinker.h"
6 | #include "GifAnalyzer.h"
7 |
8 | using namespace blk;
9 |
10 | bool BurstLinker::init(const char *path, uint16_t width, uint16_t height, uint32_t loopCount, uint32_t threadNum) {
11 | gifEncoder = std::make_unique();
12 | return gifEncoder->init(path, width, height, 0, threadNum);
13 | }
14 |
15 | bool BurstLinker::connect(std::vector &image, uint32_t delay,
16 | QuantizerType quantizerType, DitherType ditherType, int32_t transparencyOption,
17 | uint16_t left, uint16_t top) {
18 | if (gifEncoder == nullptr) {
19 | return false;
20 | }
21 | std::vector content;
22 | gifEncoder->addImage(image, delay, quantizerType, ditherType, transparencyOption, left, top, content);
23 | gifEncoder->flush(content);
24 | return true;
25 | }
26 |
27 | bool BurstLinker::connect(std::vector> &images, uint32_t delay,
28 | QuantizerType quantizerType, DitherType ditherType, int32_t transparencyOption,
29 | uint16_t left, uint16_t top) {
30 | if (gifEncoder == nullptr) {
31 | return false;
32 | }
33 | size_t size = images.size();
34 | std::vector>> tasks;
35 | for (int k = 0; k < size; ++k) {
36 | auto result = gifEncoder->threadPool->enqueue([=, &images]() {
37 | std::vector content;
38 | auto image = images[k];
39 | gifEncoder->addImage(image, delay, quantizerType, ditherType, transparencyOption, left, top, content);
40 | return content;
41 | });
42 | tasks.emplace_back(std::move(result));
43 | }
44 | for (auto &task : tasks) {
45 | std::vector result = task.get();
46 | gifEncoder->flush(result);
47 | }
48 | return true;
49 | }
50 |
51 | void BurstLinker::release() {
52 | if (gifEncoder != nullptr) {
53 | gifEncoder->finishEncoding();
54 | }
55 | }
56 |
57 | void BurstLinker::analyzerGifInfo(const char *path) {
58 | GifAnalyzer gifAnalyzer;
59 | gifAnalyzer.showGifInfo(path);
60 | }
61 |
--------------------------------------------------------------------------------
/android/lib/src/main/cpp/BayerDithererWithRs.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/11/15.
3 | //
4 |
5 | #include "BayerDithererWithRs.h"
6 | #include "../../../../../src/Logger.h"
7 | #include "../../../../../src/BayerDitherer.h"
8 | #include "../../../build/generated/source/rs/debug/ScriptC_linear.h"
9 |
10 |
11 | void BayerDithererWithRs::dither(blk::RGB *originPixels, uint16_t width, uint16_t height,
12 | blk::RGB quantizerPixels[], int32_t quantizerSize,
13 | uint8_t *colorIndices, sp rs) {
14 | int32_t size = width * height;
15 | int bayer[64];
16 | const int delta = 1 << (5 - bayerScale);
17 | for (int i = 0; i < 64; i++) {
18 | bayer[i] = (bayerDitherValue(i) >> 1) - delta;
19 | }
20 |
21 | for (int y = 0; y < height; ++y) {
22 | for (int x = 0; x < width; ++x) {
23 | int position = y * width + x;
24 | auto rgb = originPixels[position];
25 | int b = rgb.b;
26 | int g = rgb.g;
27 | int r = rgb.r;
28 | int offset = (bayer[((y & 7) << 3) | (x & 7)]);
29 | r = (min(255, max(0, r + offset)));
30 | g = (min(255, max(0, g + offset)));
31 | b = (min(255, max(0, b + offset)));
32 | originPixels[position].r = (uint8_t) r;
33 | originPixels[position].g = (uint8_t) g;
34 | originPixels[position].b = (uint8_t) b;
35 | }
36 | }
37 |
38 | sp element = Element::U32(rs);
39 | sp type = Type::create(rs, element, size, 0, 0);
40 | sp inputAlloc = Allocation::createTyped(rs, type, RS_ALLOCATION_USAGE_SHARED |
41 | RS_ALLOCATION_USAGE_SCRIPT);
42 | sp outputAlloc = Allocation::createTyped(rs, type, RS_ALLOCATION_USAGE_SHARED |
43 | RS_ALLOCATION_USAGE_SCRIPT);
44 | inputAlloc->copy1DFrom(originPixels);
45 | ScriptC_linear *sc = new ScriptC_linear(rs);
46 | sc->set_quantizerSize(quantizerSize * 3);
47 | sc->set_quantizerColors((uint8_t *) quantizerPixels);
48 | sc->forEach_invert(inputAlloc, outputAlloc);
49 | outputAlloc->copy1DTo(colorIndices);
50 | element.clear();
51 | type.clear();
52 | inputAlloc.clear();
53 | outputAlloc.clear();
54 | }
55 |
--------------------------------------------------------------------------------
/src/GifBlockWriter.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-9-6.
3 | //
4 |
5 | #ifndef BURSTLINKER_GIFBLOCKWRITER_H
6 | #define BURSTLINKER_GIFBLOCKWRITER_H
7 |
8 | #include
9 | #include
10 | #include
11 | #include "GifEncoder.h"
12 |
13 | namespace blk {
14 |
15 | class GifBlockWriter {
16 |
17 | public:
18 |
19 | static void writeHeaderBlock(std::ofstream &file);
20 |
21 | static void writeLogicalScreenDescriptorBlock(std::ofstream &file, int32_t logicalScreenWidth,
22 | int32_t logicalScreenHeight,
23 | bool globalColorTable, int32_t colorResolution, bool sort,
24 | int32_t globalColorTableSize,
25 | int32_t backgroundColorIndex, int32_t pixelAspectRatio);
26 |
27 | static void writeNetscapeLoopingExtensionBlock(std::ofstream &file, uint32_t loopCount);
28 |
29 | static void writeGraphicsControlExtensionBlock(std::vector &content, int32_t disposalMethod,
30 | bool userInput, bool transparentColor, int32_t delayCentiseconds,
31 | int32_t transparentColorIndex);
32 |
33 | static void writeImageDescriptorBlock(std::vector &content, uint16_t imageLeft, uint16_t imageTop,
34 | uint16_t imageWidth,
35 | uint16_t imageHeight,
36 | bool localColorTable, bool interlace, bool sort,
37 | int localColorTableSize);
38 |
39 | static int32_t paddedSize(int32_t size);
40 |
41 | static void
42 | writeColorTableEntity(std::vector &content, const std::vector &quantize, int paddedSize);
43 |
44 | static void writeColorTableTransparency(std::vector &content, uint8_t r, uint8_t g, uint8_t b);
45 |
46 | static void writeColorTableUnpadded(std::vector &content, int unpaddedSize, int paddedSize);
47 |
48 | static void writeImageDataBlock(std::ofstream &file, uint8_t colorDepth, std::list lzw, int lzwSize);
49 |
50 | static void writeTerminator(std::ofstream &file);
51 |
52 | };
53 |
54 | }
55 |
56 | #endif //BURSTLINKER_GIFBLOCKWRITER_H
57 |
--------------------------------------------------------------------------------
/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/src/NoDitherer.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/11/15.
3 | //
4 |
5 | #include
6 | #include
7 | #include "NoDitherer.h"
8 | #include "KDTree.h"
9 |
10 | using namespace blk;
11 |
12 | void NoDitherer::dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) {
13 | size_t totalSize = width * height;
14 | size_t size = origin.size();
15 | size_t quantizeSize = quantize.size();
16 |
17 | // int maxCentroidDistance = 255 * 255 * 255;
18 | // for (int i = 0; i < size; ++i) {
19 | // auto rgb = origin[i];
20 | // uint8_t r = rgb.r;
21 | // uint8_t g = rgb.g;
22 | // uint8_t b = rgb.b;
23 | // double nearestCentroidDistance = maxCentroidDistance;
24 | // int nearestIndices = 0;
25 | // for (int k = 0; k < quantizeSize; ++k) {
26 | // uint8_t qr = quantize[k].r;
27 | // uint8_t qg = quantize[k].g;
28 | // uint8_t qb = quantize[k].b;
29 | // int16_t nr = qr - r;
30 | // int16_t ng = qg - g;
31 | // int16_t nb = qb - b;
32 | // auto distance = nr * nr + ng * ng + nb * nb;
33 | // if (distance < nearestCentroidDistance) {
34 | // nearestCentroidDistance = distance;
35 | // nearestIndices = quantize[k].index;
36 | // };
37 | // }
38 | // colorIndices[i] = static_cast(nearestIndices);
39 | // }
40 |
41 | KDTree kdTree;
42 | KDTree::Node rootNode;
43 | auto end = static_cast(quantizeSize - 1);
44 | auto transparentColorIndex = static_cast(quantizeSize + 1);
45 | kdTree.createKDTree(&rootNode, quantize, 0, end, 0);
46 | uint16_t lastR = 256;
47 | uint16_t lastG = 256;
48 | uint16_t lastB = 256;
49 | uint8_t lastIndex = 0;
50 | int count = 0;
51 | for (int i = 0; i < size; ++count) {
52 | auto rgb = origin[i];
53 | if (rgb.unTranpsparentIndex == count) {
54 | ++i;
55 | if (!(lastR == rgb.r && lastG == rgb.g && lastB == rgb.b)) {
56 | lastR = rgb.r;
57 | lastG = rgb.g;
58 | lastB = rgb.b;
59 | kdTree.searchNoBacktracking(&rootNode, rgb.r, rgb.g, rgb.b, -1);
60 | lastIndex = kdTree.nearest.index;
61 | }
62 | colorIndices[count] = lastIndex;
63 | } else {
64 | colorIndices[count] = transparentColorIndex;
65 | }
66 | }
67 | if (count < totalSize) {
68 | for (int i = count; i < totalSize; ++i) {
69 | colorIndices[i] = transparentColorIndex;
70 | }
71 | }
72 | kdTree.freeKDTree(&rootNode);
73 | }
74 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.7)
2 | project(BurstLinker)
3 |
4 | set(CMAKE_C_STANDARD 99)
5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
6 |
7 | #file(GLOB SOURCE_FILES *.cpp)
8 |
9 | set(src_dir src)
10 | set(third_part_dir third_part)
11 | set(example_dir example)
12 |
13 | set(SOURCE_FILES
14 | ${src_dir}/ThreadPool.h
15 | ${src_dir}/GifEncoder.h ${src_dir}/GifEncoder.cpp
16 | ${src_dir}/GifAnalyzer.cpp ${src_dir}/GifAnalyzer.h
17 | ${src_dir}/GifBlockWriter.cpp ${src_dir}/GifBlockWriter.h
18 | ${src_dir}/KDTree.cpp ${src_dir}/KDTree.h
19 | ${src_dir}/LzwEncoder.cpp ${src_dir}/LzwEncoder.h
20 | ${src_dir}/Logger.cpp ${src_dir}/Logger.h
21 |
22 | ${src_dir}/Ditherer.h
23 | ${src_dir}/NoDitherer.cpp ${src_dir}/NoDitherer.h
24 | ${src_dir}/BayerDitherer.cpp ${src_dir}/BayerDitherer.h
25 | ${src_dir}/M2Ditherer.cpp ${src_dir}/M2Ditherer.h
26 | ${src_dir}/FloydSteinbergDitherer.cpp ${src_dir}/FloydSteinbergDitherer.h
27 |
28 | ${src_dir}/ColorQuantizer.h
29 | ${src_dir}/UniformQuantizer.cpp ${src_dir}/UniformQuantizer.h
30 | ${src_dir}/RandomQuantizer.cpp ${src_dir}/RandomQuantizer.h
31 | ${src_dir}/MedianCutQuantizer.cpp ${src_dir}/MedianCutQuantizer.h
32 | ${src_dir}/KMeansQuantizer.cpp ${src_dir}/KMeansQuantizer.h
33 | ${src_dir}/OctreeQuantizer.cpp ${src_dir}/OctreeQuantizer.h
34 | ${src_dir}/NeuQuant.cpp ${src_dir}/NeuQuant.h
35 | ${src_dir}/NeuQuantQuantizer.cpp ${src_dir}/NeuQuantQuantizer.h
36 | ${src_dir}/BurstLinker.cpp ${src_dir}/BurstLinker.h
37 |
38 | ${third_part_dir}/stb_image.h
39 | )
40 |
41 | set(BURSTLINKER_FILES
42 | ${src_dir}/Main.cpp
43 | )
44 |
45 | set(EXAMPLE_FILES
46 | ${example_dir}/Main.cpp
47 | )
48 |
49 | add_executable(BurstLinker ${SOURCE_FILES} ${BURSTLINKER_FILES})
50 | add_executable(Example ${SOURCE_FILES} ${EXAMPLE_FILES})
51 |
52 | message(STATUS "CMAKE_SYSTEM_INFO_FILE: ${CMAKE_SYSTEM_INFO_FILE}")
53 | message(STATUS "CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}")
54 | message(STATUS "CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}")
55 | message(STATUS "CMAKE_SYSTEM: ${CMAKE_SYSTEM}")
56 |
57 | message(STATUS "CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR}")
58 | message(STATUS "CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")
59 | message(STATUS "PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
60 | message(STATUS "PROJECT_BINARY_DIR: ${PROJECT_BINARY_DIR}")
61 |
62 | IF (CMAKE_SYSTEM MATCHES "Linux")
63 | find_package(Threads)
64 | target_link_libraries(BurstLinker ${CMAKE_THREAD_LIBS_INIT})
65 | target_link_libraries(Example ${CMAKE_THREAD_LIBS_INIT})
66 | ELSEIF (CMAKE_SYSTEM MATCHES "Darwin")
67 |
68 | ELSEIF (CMAKE_SYSTEM MATCHES "Windows")
69 |
70 | ENDIF ()
71 |
--------------------------------------------------------------------------------
/src/GifEncoder.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-9-5.
3 | //
4 |
5 | #include
6 | #include
7 | #include
8 | #include "ThreadPool.h"
9 |
10 | #ifndef BURSTLINKER_GIFENCODER_H
11 | #define BURSTLINKER_GIFENCODER_H
12 |
13 |
14 | namespace blk {
15 |
16 | struct ARGB {
17 | uint8_t a = 0;
18 | uint8_t r = 0;
19 | uint8_t g = 0;
20 | uint8_t b = 0;
21 | uint8_t index = 0;
22 |
23 | uint32_t unTranpsparentIndex = 0;
24 |
25 | bool operator==(const ARGB &rgb) const {
26 | return rgb.r == r && rgb.g == g && rgb.b == b;
27 | }
28 |
29 | bool operator<(const ARGB &rgb) const {
30 | return (r + g + b) < (rgb.r + rgb.g + rgb.b);
31 | }
32 |
33 | ARGB(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b) {}
34 |
35 | ARGB(uint8_t r, uint8_t g, uint8_t b, uint8_t index) : r(r), g(g), b(b), index(index) {}
36 |
37 | ARGB(uint8_t a, uint8_t r, uint8_t g, uint8_t b, uint32_t unTranpsparentIndex)
38 | : a(a), r(r), g(g), b(b), unTranpsparentIndex(unTranpsparentIndex) {}
39 |
40 | ARGB(uint8_t a, uint8_t r, uint8_t g, uint8_t b, uint8_t index,
41 | uint32_t unTranpsparentIndex)
42 | : a(a), r(r), g(g), b(b), index(index), unTranpsparentIndex(unTranpsparentIndex) {}
43 | };
44 |
45 | struct Compare {
46 | uint8_t split = 0;
47 |
48 | explicit Compare(uint8_t split) : split(split) {};
49 |
50 | bool operator()(const ARGB a, const ARGB b) {
51 | switch (split) {
52 | case 0:
53 | default:
54 | return a.r > b.r;
55 | case 1:
56 | return a.g > b.g;
57 | case 2:
58 | return a.b > b.b;
59 | }
60 | }
61 | };
62 |
63 | enum class QuantizerType {
64 | Uniform = 0,
65 | MedianCut = 1,
66 | KMeans = 2,
67 | Random = 3,
68 | Octree = 4,
69 | NeuQuant = 5
70 | };
71 |
72 | enum class DitherType {
73 | No = 0,
74 | M2 = 1,
75 | Bayer = 2,
76 | FloydSteinberg = 3
77 | };
78 |
79 | class GifEncoder {
80 |
81 | public:
82 |
83 | uint16_t screenWidth;
84 |
85 | uint16_t screenHeight;
86 |
87 | bool debugLog = false;
88 |
89 | const char *rsCacheDir = nullptr;
90 |
91 | std::unique_ptr threadPool = nullptr;
92 |
93 | ~GifEncoder();
94 |
95 | bool init(const char *path, uint16_t width, uint16_t height, uint32_t loopCount, uint32_t threadCount);
96 |
97 | std::vector addImage(const std::vector &original, uint32_t delay,
98 | QuantizerType qType, DitherType dType,
99 | int32_t transparencyOption, uint16_t left, uint16_t top,
100 | std::vector &content);
101 |
102 | void flush(const std::vector &content);
103 |
104 | void finishEncoding();
105 |
106 | private:
107 |
108 | std::ofstream outfile;
109 |
110 | };
111 |
112 | }
113 |
114 | #endif //BURSTLINKER_GIFENCODER_H
115 |
--------------------------------------------------------------------------------
/src/ThreadPool.h:
--------------------------------------------------------------------------------
1 | #ifndef PRPPELTHREADPOOL_H
2 | #define PRPPELTHREADPOOL_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 |
14 | namespace blk {
15 |
16 | class ThreadPool {
17 |
18 | public:
19 |
20 | ThreadPool(size_t);
21 |
22 | template
23 |
24 | auto enqueue(F &&f, Args &&... args)
25 |
26 | -> std::future::type>;
27 |
28 | ~ThreadPool();
29 |
30 | private:
31 | // need to keep track of threads so we can join them
32 | std::vector workers;
33 | // the task queue
34 | std::queue > tasks;
35 |
36 | // synchronization
37 | std::mutex queue_mutex;
38 | std::condition_variable condition;
39 | bool stop;
40 | };
41 |
42 | // the constructor just launches some amount of workers
43 | inline ThreadPool::ThreadPool(size_t threads) : stop(false) {
44 | for (size_t i = 0; i < threads; ++i)
45 | workers.emplace_back(
46 | [this] {
47 | for (;;) {
48 | std::function task;
49 |
50 | {
51 | std::unique_lock lock(this->queue_mutex);
52 | this->condition.wait(lock, [this] {
53 | return this->stop || !this->tasks.empty();
54 | });
55 | if (this->stop && this->tasks.empty()) {
56 | return;
57 | }
58 | task = std::move(this->tasks.front());
59 | this->tasks.pop();
60 | }
61 |
62 | task();
63 | }
64 | }
65 | );
66 | }
67 |
68 | // add new work item to the pool
69 | template
70 | auto ThreadPool::enqueue(F &&f, Args &&... args) -> std::future::type> {
72 | using return_type = typename std::result_of::type;
73 |
74 | auto task = std::make_shared >(
75 | std::bind(std::forward(f), std::forward(args)...)
76 | );
77 |
78 | std::future res = task->get_future();
79 | {
80 | std::unique_lock lock(queue_mutex);
81 |
82 | // don't allow enqueueing after stopping the pool
83 | if (stop) {
84 | // throw std::runtime_error("enqueue on stopped ThreadPool");
85 | }
86 |
87 | tasks.emplace([task]() { (*task)(); });
88 | }
89 | condition.notify_one();
90 | return res;
91 | }
92 |
93 | // the destructor joins all threads
94 | inline ThreadPool::~ThreadPool() {
95 | {
96 | std::unique_lock lock(queue_mutex);
97 | stop = true;
98 | }
99 | condition.notify_all();
100 | for (std::thread &worker: workers) {
101 | worker.join();
102 | }
103 | }
104 |
105 | }
106 |
107 | #endif //PRPPELTHREADPOOL_H
108 |
--------------------------------------------------------------------------------
/src/FloydSteinbergDitherer.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-9-19.
3 | //
4 |
5 | #include
6 | #include "FloydSteinbergDitherer.h"
7 | #include "KDTree.h"
8 |
9 | using namespace blk;
10 |
11 | static const int8_t ERROR_COMPONENT_SIZE = 4;
12 | static const float ERROR_COMPONENT_DELTA_X[] = {1.0f, -1.0f, 0.0f, 1.0f};
13 | static const float ERROR_COMPONENT_DELTA_Y[] = {0.0f, 1.0f, 1.0f, 1.0f};
14 | static const float ERROR_COMPONENT_FACTION[] = {7.0f / 16.0f, 3.0f / 16.0f, 5.0f / 16.0f, 1.0f / 16.0f};
15 |
16 | void FloydSteinbergDitherer::dither(std::vector &origin, std::vector &quantize, uint8_t *colorIndices) {
17 | int32_t totalSize = width * height;
18 | KDTree kdTree;
19 | KDTree::Node rootNode;
20 | size_t quantizeSize = quantize.size();
21 | auto end = static_cast(quantizeSize - 1);
22 | kdTree.createKDTree(&rootNode, quantize, 0, end, 0);
23 |
24 | uint8_t nearestCentroidR = 0;
25 | uint8_t nearestCentroidG = 0;
26 | uint8_t nearestCentroidB = 0;
27 | uint16_t lastR = 256;
28 | uint16_t lastG = 256;
29 | uint16_t lastB = 256;
30 | uint8_t r = 0;
31 | uint8_t g = 0;
32 | uint8_t b = 0;
33 | int position = 0;
34 | for (int y = 0; y < height; ++y) {
35 | for (int x = 0; x < width; ++x) {
36 | auto rgb = origin[position];
37 | r = rgb.r;
38 | g = rgb.g;
39 | b = rgb.b;
40 | if (!(lastR == r && lastG == g && lastB == b)) {
41 | lastR = r;
42 | lastG = g;
43 | lastB = b;
44 | kdTree.searchNoBacktracking(&rootNode, r, g, b, -1);
45 | }
46 |
47 | nearestCentroidR = kdTree.nearest.r;
48 | nearestCentroidG = kdTree.nearest.g;
49 | nearestCentroidB = kdTree.nearest.b;
50 |
51 | origin[position].r = nearestCentroidR;
52 | origin[position].g = nearestCentroidG;
53 | origin[position].b = nearestCentroidB;
54 |
55 | position++;
56 |
57 | int8_t errorR = (r - nearestCentroidR);
58 | int8_t errorG = (g - nearestCentroidG);
59 | int8_t errorB = (b - nearestCentroidB);
60 |
61 | for (int directionId = 0; directionId < ERROR_COMPONENT_SIZE; ++directionId) {
62 | auto siblingX = static_cast(x + ERROR_COMPONENT_DELTA_X[directionId]);
63 | auto siblingY = static_cast(y + ERROR_COMPONENT_DELTA_Y[directionId]);
64 |
65 | if (siblingX >= 0 && siblingY >= 0 && siblingX < width && siblingY < height) {
66 | float errorComponentR = errorR * ERROR_COMPONENT_FACTION[directionId];
67 | float errorComponentG = errorG * ERROR_COMPONENT_FACTION[directionId];
68 | float errorComponentB = errorB * ERROR_COMPONENT_FACTION[directionId];
69 |
70 | int ditherPosition = siblingY * width + siblingX;
71 | auto siblingRgb = origin[ditherPosition];
72 |
73 | auto siblingR = static_cast(siblingRgb.r + errorComponentR);
74 | auto siblingG = static_cast(siblingRgb.g + errorComponentG);
75 | auto siblingB = static_cast(siblingRgb.b + errorComponentB);
76 |
77 | origin[ditherPosition].r = static_cast(std::min(255, std::max(0, siblingR)));
78 | origin[ditherPosition].g = static_cast(std::min(255, std::max(0, siblingG)));
79 | origin[ditherPosition].b = static_cast(std::min(255, std::max(0, siblingB)));
80 | }
81 | }
82 | }
83 | }
84 | for (int i = 0; i < totalSize; ++i) {
85 | auto rgb = origin[i];
86 | kdTree.searchNoBacktracking(&rootNode, rgb.r, rgb.g, rgb.b, -1);
87 | colorIndices[i] = kdTree.nearest.index;
88 | }
89 | kdTree.freeKDTree(&rootNode);
90 | }
91 |
--------------------------------------------------------------------------------
/src/LzwEncoder.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-9-9.
3 | //
4 |
5 | #include
6 | #include "LzwEncoder.h"
7 |
8 | using namespace blk;
9 |
10 | static const int32_t MAX_STACK_SIZE = 4096;
11 | static const int32_t BYTE_NUM = 256;
12 | static const int32_t BLOCK_SIZE = 255;
13 |
14 | LzwEncoder::~LzwEncoder() {
15 | for (auto &data : datas) {
16 | delete[] data;
17 | }
18 | }
19 |
20 | LzwEncoder::LzwEncoder(int32_t paddedColorCount) {
21 | numColors = paddedColorCount;
22 | current = new uint8_t[BLOCK_SIZE];
23 | memset(current, 0, BLOCK_SIZE);
24 | datas.push_back(current);
25 | pos = 0;
26 | remain = 8;
27 | }
28 |
29 | int getMinimumCodeSize(int numColors) {
30 | // cannot be smaller than 2
31 | int size = 2;
32 | while (numColors > 1 << size) {
33 | ++size;
34 | }
35 | return size;
36 | }
37 |
38 | void LzwEncoder::writeBits(uint32_t src, int32_t bitNum) {
39 | while (0 < bitNum) {
40 | if (remain <= bitNum) {
41 | current[pos] = current[pos] | (src << (8 - remain));
42 | src >>= remain;
43 | bitNum -= remain;
44 | remain = 8;
45 | ++pos;
46 | if (pos == BLOCK_SIZE) {
47 | current = new uint8_t[BLOCK_SIZE];
48 | memset(current, 0, BLOCK_SIZE);
49 | datas.push_back(current);
50 | pos = 0;
51 | }
52 | } else {
53 | current[pos] = (current[pos] << bitNum) | (((1 << bitNum) - 1) & src);
54 | remain -= bitNum;
55 | bitNum = 0;
56 | }
57 | }
58 | }
59 |
60 | static const uint8_t BLOCK_TERMINATOR = 0x00;
61 | static const uint8_t BLOCK_MIN_CODE_SIZE = 0x8;
62 |
63 | int LzwEncoder::write(std::vector &content, uint8_t minimumCodeSize) {
64 | content.push_back(minimumCodeSize);
65 | int size;
66 | int total = 0;
67 | for (auto block : datas) {
68 | size = block == current ? (remain == 0 ? pos : pos + 1) : BLOCK_SIZE;
69 | total = total + size;
70 | content.push_back(size);
71 | for (int i = 0; i < size; ++i) {
72 | content.push_back(block[i]);
73 | }
74 | }
75 | content.push_back(BLOCK_TERMINATOR);
76 | return total;
77 | }
78 |
79 | void LzwEncoder::encode(uint8_t indices[], uint16_t width, uint16_t height, std::vector &content) {
80 | uint8_t *endPixels = indices + width * height;
81 | uint8_t dataSize = 8;
82 | uint32_t codeSize = dataSize + 1;
83 | uint32_t codeMask = (1 << codeSize) - 1;
84 |
85 | std::vector lzwInfoHolder;
86 | lzwInfoHolder.resize(MAX_STACK_SIZE * BYTE_NUM);
87 | uint16_t *lzwInfos = &lzwInfoHolder[0];
88 | uint32_t clearCode = 1 << dataSize;
89 | uint32_t eolCode = clearCode + 2;
90 | uint16_t current = *indices;
91 | indices++;
92 |
93 | writeBits(clearCode, codeSize);
94 |
95 | uint16_t *next;
96 | while (endPixels > indices) {
97 | next = &lzwInfos[current * BYTE_NUM + *indices];
98 | if (0 == *next || *next >= MAX_STACK_SIZE) {
99 | writeBits(current, codeSize);
100 | *next = eolCode;
101 | if (eolCode < MAX_STACK_SIZE) {
102 | ++eolCode;
103 | } else {
104 | writeBits(clearCode, codeSize);
105 | eolCode = clearCode + 2;
106 | codeSize = dataSize + 1;
107 | codeMask = (1 << codeSize) - 1;
108 | memset(lzwInfos, 0, MAX_STACK_SIZE * BYTE_NUM * sizeof(uint16_t));
109 | }
110 | if (codeMask < eolCode - 1 && eolCode < MAX_STACK_SIZE) {
111 | ++codeSize;
112 | codeMask = (1 << codeSize) - 1;
113 | }
114 | if (endPixels <= indices) {
115 | break;
116 | }
117 | current = *indices;
118 | } else {
119 | current = *next;
120 | }
121 | indices++;
122 | }
123 | writeBits(current, codeSize);
124 | write(content, getMinimumCodeSize(numColors));
125 | }
126 |
--------------------------------------------------------------------------------
/android/sample/src/main/java/com/bilibili/burstlinker/sample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.bilibili.burstlinker.sample;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapFactory;
6 | import android.graphics.Canvas;
7 | import android.graphics.Paint;
8 | import android.os.Bundle;
9 | import android.support.v7.app.AppCompatActivity;
10 | import android.util.Log;
11 | import android.widget.ImageView;
12 | import android.widget.TextView;
13 |
14 | import com.bilibili.burstlinker.BurstLinker;
15 | import com.bilibili.burstlinker.GifEncodeException;
16 | import com.bumptech.glide.Glide;
17 |
18 | import java.io.File;
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | import static com.bilibili.burstlinker.BurstLinker.CPU_COUNT;
23 |
24 | /**
25 | * Created by succlz123 on 2017/9/7.
26 | */
27 | public class MainActivity extends AppCompatActivity {
28 | private static final String TAG = "JAVA_BURSTLINKER";
29 |
30 | private ImageView mDisplayImg;
31 | private TextView mTimeTv;
32 | private TextView mEncodeTv;
33 | private String mFilePath;
34 | private String mText;
35 |
36 | @Override
37 | protected void onCreate(Bundle savedInstanceState) {
38 | super.onCreate(savedInstanceState);
39 | setContentView(R.layout.activity_main);
40 |
41 | mTimeTv = findViewById(R.id.time);
42 | mDisplayImg = findViewById(R.id.display);
43 | mEncodeTv = findViewById(R.id.text);
44 |
45 | String dstFile = "result.gif";
46 | mFilePath = getExternalCacheDir() + File.separator + dstFile;
47 |
48 | mEncodeTv.setOnClickListener(v -> {
49 | mEncodeTv.setEnabled(false);
50 | new Thread(this::encodeGIF).start();
51 | });
52 | }
53 |
54 | private void encodeGIF() {
55 | Log.i(TAG, "Start");
56 | long time = System.currentTimeMillis();
57 | final Context context = MainActivity.this;
58 |
59 | final Bitmap bitmap = loadBitmap(R.drawable.tcr);
60 | int count = 0;
61 | int width = bitmap.getWidth();
62 | int height = bitmap.getHeight();
63 | final int delayMs = 1000;
64 | final BurstLinker burstLinker = new BurstLinker();
65 |
66 | Exception exception = null;
67 | try {
68 | burstLinker.init(width, height, mFilePath, 0, CPU_COUNT);
69 | burstLinker.debugLog(true);
70 | // select one to test
71 | if (true) {
72 | List bitmaps = new ArrayList<>();
73 | bitmaps.add(bitmap);
74 | bitmaps.add(bitmap);
75 | bitmaps.add(bitmap);
76 | count = bitmaps.size();
77 | burstLinker.connect(bitmaps, BurstLinker.OCTREE_QUANTIZER,
78 | BurstLinker.NO_DITHER, 0, 0, delayMs);
79 | } else {
80 | Bitmap colorBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
81 | Canvas canvas = new Canvas(colorBitmap);
82 | Paint p = new Paint();
83 | int[] colors = new int[]{0xFFF00000, 0xFFFFFF00, 0xFFFFFFFF};
84 | for (int color : colors) {
85 | p.setColor(color);
86 | canvas.drawRect(0, 0, width, height, p);
87 | count++;
88 | burstLinker.connect(colorBitmap, BurstLinker.OCTREE_QUANTIZER,
89 | BurstLinker.NO_DITHER, 0, 0, delayMs);
90 | }
91 | }
92 | } catch (GifEncodeException e) {
93 | e.printStackTrace();
94 | exception = e;
95 | } finally {
96 | burstLinker.release();
97 | }
98 |
99 | final long diff = (System.currentTimeMillis() - time);
100 | Log.i(TAG, "End " + diff);
101 |
102 | if (exception != null) {
103 | mText = exception.toString();
104 | File file = new File(mFilePath);
105 | if (file.exists()) {
106 | file.delete();
107 | }
108 | } else {
109 | mText =
110 | "width: " + width + " height: " + height + " count: " + count + " time: " + diff + "ms";
111 | runOnUiThread(() -> Glide.with(context).load(mFilePath).into(mDisplayImg));
112 | }
113 | runOnUiThread(() -> {
114 | mTimeTv.setText(mText);
115 | mEncodeTv.setEnabled(true);
116 | });
117 | }
118 |
119 | private Bitmap loadBitmap(int resource) {
120 | final BitmapFactory.Options options = new BitmapFactory.Options();
121 | options.inPreferredConfig = Bitmap.Config.ARGB_8888;
122 | return BitmapFactory.decodeResource(getResources(), resource, options);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/KDTree.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/12/28.
3 | //
4 |
5 | #include
6 | #include "KDTree.h"
7 |
8 | using namespace blk;
9 |
10 | // euclideanDistance
11 | // int distance = nr * nr + ng * ng + nb * nb;
12 | // manhattanDistance
13 | // int distance = abs(nr) + abs(ng) + abs(nb);
14 | int calculateDist(KDTree::Node *node, uint8_t r, uint8_t g, uint8_t b) {
15 | int tmp = 0;
16 | int diff = node->r - r;
17 | tmp += (diff * diff) << 1;
18 | diff = node->g - g;
19 | tmp += (diff * diff) << 2;
20 | diff = node->b - b;
21 | tmp += (diff * diff) * 3;
22 | return tmp;
23 | }
24 |
25 | uint8_t getDimension(const std::vector &rgb, int start, int end) {
26 | int dataSize = end - start + 1;
27 | if (dataSize <= 0) {
28 | return 0;
29 | }
30 | int sumR = 0;
31 | int sumG = 0;
32 | int sumB = 0;
33 |
34 | for (int i = 0; i < dataSize; ++i) {
35 | sumR += rgb[i].r;
36 | sumG += rgb[i].g;
37 | sumB += rgb[i].b;
38 | }
39 |
40 | int rAve = sumR / dataSize;
41 | int gAve = sumG / dataSize;
42 | int bAve = sumB / dataSize;
43 |
44 | for (int i = 0; i < dataSize; ++i) {
45 | auto color = rgb[i];
46 | int qr = color.r;
47 | int qg = color.g;
48 | int qb = color.b;
49 | sumR += (qr - rAve) * (qr - rAve);
50 | sumG += (qg - gAve) * (qg - gAve);
51 | sumB += (qb - bAve) * (qb - bAve);
52 | }
53 |
54 | rAve = sumR / dataSize;
55 | gAve = sumG / dataSize;
56 | bAve = sumB / dataSize;
57 |
58 | int maxVariance = rAve;
59 | uint8_t dimension = 0;
60 | if (gAve > maxVariance) {
61 | maxVariance = gAve;
62 | dimension = 1;
63 | }
64 | if (bAve > maxVariance) {
65 | dimension = 2;
66 | }
67 | return dimension;
68 | }
69 |
70 | void *KDTree::createKDTree(Node *node, std::vector &rgb, int32_t start, int32_t end, uint8_t split) {
71 | int size = end - start + 1;
72 | if (size <= 0) {
73 | return nullptr;
74 | }
75 |
76 | if (size == 1) {
77 | node->r = rgb[start].r;
78 | node->g = rgb[start].g;
79 | node->b = rgb[start].b;
80 | node->index = rgb[start].index;
81 | node->split = split;
82 | node->left = nullptr;
83 | node->right = nullptr;
84 | return node;
85 | }
86 |
87 | std::sort(rgb.begin() + start, rgb.begin() + end, Compare(split));
88 |
89 | int splitSize = size / 2;
90 | int leftStart = start;
91 | int leftEnd = start + splitSize - 1;
92 | int rightStart = leftEnd + 2;
93 | int rightEnd = end;
94 | int current = start + splitSize;
95 |
96 | node->r = rgb[current].r;
97 | node->g = rgb[current].g;
98 | node->b = rgb[current].b;
99 | node->index = rgb[current].index;
100 | node->split = split;
101 |
102 | // (split + 1) % 3
103 | uint8_t leftSplit = getDimension(rgb, leftStart, leftEnd);
104 | uint8_t rightSplit = getDimension(rgb, rightStart, rightEnd);
105 |
106 | auto *leftNode = new Node();
107 | auto *rightNode = new Node();
108 | node->left = leftNode;
109 | node->right = rightNode;
110 | createKDTree(leftNode, rgb, leftStart, leftEnd, leftSplit);
111 | createKDTree(rightNode, rgb, rightStart, rightEnd, rightSplit);
112 | return node;
113 | }
114 |
115 | int KDTree::searchNoBacktracking(KDTree::Node *node, uint8_t r, uint8_t g, uint8_t b, int32_t dis) {
116 | if (node == nullptr) {
117 | return dis;
118 | }
119 | if (node->left == nullptr && node->right == nullptr) {
120 | if (dis < 0) {
121 | nearest = *node;
122 | dis = calculateDist(node, r, g, b);
123 | return dis;
124 | }
125 | }
126 | bool comp = false;
127 | switch (node->split) {
128 | case 0:
129 | comp = node->r <= r;
130 | break;
131 | case 1:
132 | comp = node->g <= g;
133 | break;
134 | case 2:
135 | comp = node->b <= b;
136 | break;
137 | default:
138 | break;
139 | }
140 | if (comp) {
141 | dis = searchNoBacktracking(node->left, r, g, b, dis);
142 | int tmp = calculateDist(node, r, g, b);
143 | if (tmp < dis || dis == -1) {
144 | nearest = *node;
145 | dis = tmp;
146 | }
147 | } else {
148 | dis = searchNoBacktracking(node->right, r, g, b, dis);
149 | int tmp = calculateDist(node, r, g, b);
150 | if (tmp < dis || dis == -1) {
151 | nearest = *node;
152 | dis = tmp;
153 | }
154 | }
155 | return dis;
156 | }
157 |
158 | void KDTree::freeKDTree(KDTree::Node *tree) {
159 | if (tree == nullptr) {
160 | return;
161 | }
162 | freeKDTree(tree->left);
163 | freeKDTree(tree->right);
164 | delete tree->left;
165 | delete tree->right;
166 | }
167 |
--------------------------------------------------------------------------------
/src/MedianCutQuantizer.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 2017/10/14.
3 | //
4 |
5 | #include
6 | #include
7 | #include
8 | #include "MedianCutQuantizer.h"
9 |
10 | using namespace blk;
11 |
12 | struct Cluster {
13 | int start = 0;
14 | int end = 0;
15 | int pixelSize = 0;
16 | int componentWithLargestSpread = 0;
17 |
18 | static bool cmpR(const ARGB &i, const ARGB &j) { return (i.r < j.r); }
19 |
20 | static bool cmpG(const ARGB &i, const ARGB &j) { return (i.g < j.g); }
21 |
22 | static bool cmpB(const ARGB &i, const ARGB &j) { return (i.b < j.b); }
23 |
24 | bool (*cmp[3])(const ARGB &, const ARGB &) = {cmpR, cmpG, cmpB};
25 |
26 | bool operator<(const Cluster &cluster) const {
27 | return (pixelSize < cluster.pixelSize);
28 | }
29 |
30 | void setStartAndEnd(int s, int e) {
31 | start = s;
32 | end = e;
33 | pixelSize = end - start;
34 | }
35 |
36 | int getComponentRSpread(std::vector &pixels) {
37 | uint8_t minCount = 0;
38 | uint8_t maxCount = 0;
39 | for (int i = start; i < end; i++) {
40 | uint8_t componentColor = pixels[i].r;
41 | minCount = std::min(minCount, componentColor);
42 | maxCount = std::max(maxCount, componentColor);
43 | }
44 | return maxCount - minCount;
45 | }
46 |
47 | int getComponentGSpread(std::vector &pixels) {
48 | uint8_t minCount = 0;
49 | uint8_t maxCount = 0;
50 | for (int i = start; i < end; i++) {
51 | uint8_t componentColor = pixels[i].g;
52 | minCount = std::min(minCount, componentColor);
53 | maxCount = std::max(maxCount, componentColor);
54 | }
55 | return maxCount - minCount;
56 | }
57 |
58 | int getComponentBSpread(std::vector &pixels) {
59 | uint8_t minCount = 0;
60 | uint8_t maxCount = 0;
61 | for (int i = start; i < end; i++) {
62 | uint8_t componentColor = pixels[i].b;
63 | minCount = std::min(minCount, componentColor);
64 | maxCount = std::max(maxCount, componentColor);
65 | }
66 | return maxCount - minCount;
67 | }
68 |
69 | void calculateSpread(std::vector &pixels) {
70 | int largestSpread = -1;
71 | int componentSpread = getComponentRSpread(pixels);
72 | if (componentSpread > largestSpread) {
73 | largestSpread = componentSpread;
74 | componentWithLargestSpread = 0;
75 | }
76 | componentSpread = getComponentGSpread(pixels);
77 | if (componentSpread > largestSpread) {
78 | largestSpread = componentSpread;
79 | componentWithLargestSpread = 1;
80 | }
81 | componentSpread = getComponentBSpread(pixels);
82 | if (componentSpread > largestSpread) {
83 | componentWithLargestSpread = 2;
84 | }
85 | }
86 |
87 | bool split(std::vector &pixels, Cluster *cluster1, Cluster *cluster2) {
88 | if (pixelSize < 2) {
89 | return false;
90 | }
91 | std::sort(pixels.begin() + start, pixels.begin() + end, cmp[componentWithLargestSpread]);
92 | int medianIndex = (pixelSize + 1) / 2;
93 | cluster1->setStartAndEnd(start, start + medianIndex);
94 | cluster2->setStartAndEnd(start + medianIndex, end);
95 | return true;
96 | }
97 | };
98 |
99 | int32_t MedianCutQuantizer::quantize(const std::vector &in, uint32_t maxColorCount, std::vector &out) {
100 | size_t pixelCount = in.size();
101 | std::priority_queue clusters;
102 | Cluster cluster;
103 | cluster.setStartAndEnd(0, pixelCount);
104 | clusters.push(cluster);
105 | std::vector sortPixels(in);
106 | for (uint32_t k = 0; k < maxColorCount - 1; ++k) {
107 | Cluster top, cluster1, cluster2;
108 | top = clusters.top();
109 | clusters.pop();
110 | top.calculateSpread(sortPixels);
111 | bool success = top.split(sortPixels, &cluster1, &cluster2);
112 | if (!success) {
113 | continue;
114 | }
115 | cluster1.calculateSpread(sortPixels);
116 | cluster1.calculateSpread(sortPixels);
117 | clusters.push(cluster1);
118 | clusters.push(cluster2);
119 | }
120 | resultSize = static_cast(clusters.size());
121 | int index = 0;
122 | for (int i = 0; i < resultSize; i++) {
123 | Cluster top = clusters.top();
124 | clusters.pop();
125 | uint32_t sumR = 0;
126 | uint32_t sumG = 0;
127 | uint32_t sumB = 0;
128 | for (int j = top.start; j < top.end; j++) {
129 | sumR += sortPixels[j].r;
130 | sumG += sortPixels[j].g;
131 | sumB += sortPixels[j].b;
132 | }
133 | auto r = static_cast(sumR / top.pixelSize);
134 | auto g = static_cast(sumG / top.pixelSize);
135 | auto b = static_cast(sumB / top.pixelSize);
136 | out.emplace_back(r, g, b, index);
137 | index++;
138 | }
139 | return resultSize;
140 | }
141 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BurstLinker
2 |
3 | [  ](https://bintray.com/succlz123/maven/burst-linker/_latestVersion)
4 |
5 | Idea from: [square/gifencoder](https://github.com/square/gifencoder)
6 |
7 | BurstLinker is a simple C++ GIF encode library.
8 |
9 | ## Download
10 |
11 | ### Android
12 |
13 | Gradle:
14 |
15 | ```
16 | implementation 'com.bilibili:burst-linker:latest-version'
17 | ```
18 |
19 | #### Build Environment
20 |
21 | Android Studio 3.4.1
22 |
23 | NDK r20
24 |
25 | #### Basic usage
26 |
27 | ``` java
28 | int delayMs = 1000;
29 | String filePath = getCacheDir() + File.separator + "out.gif";
30 | BurstLinker burstLinker = new BurstLinker();
31 |
32 | try {
33 | burstLinker.init(width, height, filePath);
34 | Bitmap colorBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
35 | Canvas canvas = new Canvas(colorBitmap);
36 | Paint p = new Paint();
37 | int[] colors = new int[]{0xFFF00000, 0xFFFFFF00, 0xFFFFFFFF};
38 | for (int color : colors) {
39 | p.setColor(color);
40 | canvas.drawRect(0, 0, width, height, p);
41 | burstLinker.connect(colorBitmap, BurstLinker.OCTREE_QUANTIZER,
42 | BurstLinker.NO_DITHER, 0, 0, delayMs);
43 | }
44 | } catch (GifEncodeException e) {
45 | e.printStackTrace();
46 | } finally {
47 | burstLinker.release();
48 | }
49 | ```
50 |
51 | #### Enable RenderScript Support
52 |
53 | > This is an untested feature.
54 |
55 | 1. Choose the Git branch "/feature/render-script".
56 | 2. Sync Project with Gradle Files, It will generate the required file named "ScriptC_*.cpp".
57 | 3. Uncomment the line 64 of the "/lib/CMakeLists.txt".
58 | 4. Try this function.
59 |
60 | ### Linux & Mac
61 |
62 | 1. Install [CMake](http://www.cmake.org/)
63 | - Mac `brew install cmake`
64 | - ArchLinux `sudo pacman -S cmake`
65 |
66 | 2. Build
67 | - `cd /BurstLinker`
68 | - `mkdir cmake-build-debug; cd cmake-build-debug`
69 | - `cmake ..`
70 | - `make BurstLinker`
71 |
72 | 3. Run
73 | - `./BurstLinker 1000 1.jpg 2.jpg 3.jpg`
74 | - See the "out.gif"
75 |
76 | ### Windows
77 |
78 | 1. Install [Microsoft Visual Studio](https://www.visualstudio.com/) & [CMake](http://www.cmake.org/)
79 |
80 | 2. Build
81 | - `cd /BurstLinker`
82 | - `mkdir cmake-build-debug; cd cmake-build-debug`
83 | - `cmake ..`
84 | - Open the "BurstLinker.sln"
85 | - Solution Explorer -> BurstLinker -> Build
86 |
87 | 3. Run
88 | - `cd Debug`
89 | - `BurstLinker.exe 1000 1.jpg 2.jpg 3.jpg`
90 | - See the "out.gif"
91 |
92 | ## Samples
93 |
94 | ### Different quantizers & ditherers
95 |
96 | - Original
97 |
98 | 
99 |
100 | - Uniform + No
101 |
102 | 
103 |
104 | - MedianCut + No
105 |
106 | 
107 |
108 | - KMeans + No
109 |
110 | 
111 |
112 | - Random + No
113 |
114 | 
115 |
116 | - Octree + No
117 |
118 | 
119 |
120 | - NeuQuant - 10 + No
121 |
122 | 
123 |
124 | - NeuQuant - 1 + No
125 |
126 | 
127 |
128 | - Octree + M2
129 |
130 | 
131 |
132 | - Octree + Bayer
133 |
134 | 
135 |
136 | - Octree + FloydSteinberg
137 |
138 | 
139 |
140 | ### Encodes images with transparent channels
141 |
142 | - Original
143 |
144 | 
145 |
146 | - Octree + No + Default
147 |
148 | Display all Alpha channels greater than 0. (ARGB.a != 0)
149 |
150 | 
151 |
152 | - Octree + No + Ignored translucency
153 |
154 | Display only the Alpha channels equal to 255. (ARGB.a == 255)
155 |
156 | 
157 |
158 | ## Thanks
159 |
160 | [square/gifencoder](https://github.com/square/gifencoder)
161 |
162 | [waynejo/android-ndk-gif](https://github.com/waynejo/android-ndk-gif)
163 |
164 | [lucent1090/MCCQ](https://github.com/lucent1090/MCCQ)
165 |
166 | [luxiaoxun/KMeans-GMM-HMM](https://github.com/luxiaoxun/KMeans-GMM-HMM)
167 |
168 | [SimonBoorer/Quantize](https://github.com/SimonBoorer/Quantize)
169 |
170 | [dali-neuquant](https://code.google.com/archive/p/dali-neuquant)
171 |
172 | [FFmpeg/FFmpeg](https://github.com/FFmpeg/FFmpeg)
173 |
174 | [progschj/ThreadPool](https://github.com/progschj/ThreadPool)
175 |
176 | ## License
177 |
178 | ```
179 | Copyright 2018 Bilibili
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
192 | ```
--------------------------------------------------------------------------------
/src/KMeansQuantizer.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by succlz123 on 17-10-9.
3 | //
4 |
5 | #include
6 | #include
7 | #include