├── .github
└── workflows
│ └── codeql.yml
├── CMakeLists.txt
├── CMakeSettings.json
├── Dockerfile
├── ImgV64.gif
├── LICENSE
├── README.md
├── nQuantCpp.sln
└── nQuantCpp
├── APNsgaIII.cpp
├── APNsgaIII.h
├── BlueNoise.cpp
├── BlueNoise.h
├── CIELABConvertor.cpp
├── CIELABConvertor.h
├── CMakeLists.txt
├── DivQuantizer.cpp
├── DivQuantizer.h
├── Dl3Quantizer.cpp
├── Dl3Quantizer.h
├── EdgeAwareSQuantizer.cpp
├── EdgeAwareSQuantizer.h
├── GifWriter.cpp
├── GifWriter.h
├── GilbertCurve.cpp
├── GilbertCurve.h
├── MedianCut.cpp
├── MedianCut.h
├── MoDEQuantizer.cpp
├── MoDEQuantizer.h
├── NeuQuantizer.cpp
├── NeuQuantizer.h
├── NsgaIII.cpp
├── NsgaIII.h
├── Otsu.cpp
├── Otsu.h
├── PnnLABGAQuantizer.cpp
├── PnnLABGAQuantizer.h
├── PnnLABQuantizer.cpp
├── PnnLABQuantizer.h
├── PnnQuantizer.cpp
├── PnnQuantizer.h
├── Resource.h
├── SpatialQuantizer.cpp
├── SpatialQuantizer.h
├── WuQuantizer.cpp
├── WuQuantizer.h
├── bitmapUtilities.cpp
├── bitmapUtilities.h
├── nQuantCpp.aps
├── nQuantCpp.cpp
├── nQuantCpp.h
├── nQuantCpp.rc
├── nQuantCpp.vcxproj
├── nQuantCpp.vcxproj.filters
├── nQuantCpp.vcxproj.user
├── stdafx.cpp
└── stdafx.h
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ "master" ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ "master" ]
20 | schedule:
21 | - cron: '15 3 * * 5'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | # Runner size impacts CodeQL analysis time. To learn more, please see:
27 | # - https://gh.io/recommended-hardware-resources-for-running-codeql
28 | # - https://gh.io/supported-runners-and-hardware-resources
29 | # - https://gh.io/using-larger-runners
30 | # Consider using larger runners for possible analysis time improvements.
31 | runs-on: windows-latest
32 | timeout-minutes: 360
33 | permissions:
34 | actions: read
35 | contents: read
36 | security-events: write
37 |
38 | strategy:
39 | fail-fast: false
40 | matrix:
41 | language: [ 'c-cpp' ]
42 | # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ]
43 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
44 | build_type:
45 | - Release
46 |
47 | steps:
48 | - name: Checkout repository
49 | uses: actions/checkout@v3
50 |
51 | # Initializes the CodeQL tools for scanning.
52 | - name: Initialize CodeQL
53 | uses: github/codeql-action/init@v3
54 | with:
55 | languages: ${{ matrix.language }}
56 | # If you wish to specify custom queries, you can do so here or in a config file.
57 | # By default, queries listed here will override any specified in a config file.
58 | # Prefix the list here with "+" to use these queries and those in the config file.
59 |
60 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
61 | # queries: security-extended,security-and-quality
62 |
63 |
64 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
65 | # If this step fails, then you should remove it and run the build manually (see below)
66 | - name: Autobuild
67 | uses: github/codeql-action/autobuild@v3
68 |
69 | - name: Perform CodeQL Analysis
70 | uses: github/codeql-action/analyze@v2
71 | with:
72 | category: "/language:${{matrix.language}}"
73 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required (VERSION 3.10)
2 |
3 | project ("nQuantCpp")
4 | add_definitions(-DUNICODE -D_UNICODE)
5 | set(CMAKE_CXX_STANDARD 17)
6 | find_file(gdiplus NAMES libgdiplus gdiplus)
7 | if(NOT gdiplus)
8 | find_library(gdiplus NAMES libgdiplus gdiplus)
9 | endif()
10 | message(gdiplus="${gdiplus}")
11 | set(CMAKE_CXX_STANDARD_REQUIRED ON)
12 | set(CMAKE_CXX_EXTENSIONS OFF)
13 | set(CMAKE_BUILD_TYPE
14 | "MinSizeRel"
15 | CACHE STRING "Build type: Debug, Release, RelWithDebInfo or MinSizeRel"
16 | FORCE)
17 |
18 | add_subdirectory ("nQuantCpp")
19 |
--------------------------------------------------------------------------------
/CMakeSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "configurations": [
3 | {
4 | "name": "x64-Release",
5 | "generator": "Ninja",
6 | "configurationType": "RelWithDebInfo",
7 | "buildRoot": "${projectDir}\\out\\build\\${name}",
8 | "installRoot": "${projectDir}\\out\\install\\${name}",
9 | "cmakeCommandArgs": "",
10 | "buildCommandArgs": "",
11 | "ctestCommandArgs": "",
12 | "inheritEnvironments": [ "msvc_x64_x64" ],
13 | "variables": []
14 | },
15 | {
16 | "name": "x86-Release",
17 | "generator": "Ninja",
18 | "configurationType": "RelWithDebInfo",
19 | "buildRoot": "${projectDir}\\out\\build\\${name}",
20 | "installRoot": "${projectDir}\\out\\install\\${name}",
21 | "cmakeCommandArgs": "",
22 | "buildCommandArgs": "",
23 | "ctestCommandArgs": "",
24 | "inheritEnvironments": [ "msvc_x86" ],
25 | "variables": []
26 | }
27 | ]
28 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:22.04
2 | WORKDIR /tmp
3 | RUN apt update -y
4 | RUN apt install -y build-essential cmake gcc-12 g++-12 libomp-dev
5 | RUN DEBIAN_FRONTEND="noninteractive" apt install -y libgdiplus mingw-w64 locales fontconfig
6 | ADD . /tmp/nQuantCpp
7 | WORKDIR /tmp/nQuantCpp
8 | RUN cmake -D CMAKE_CXX_COMPILER=g++-12 /usr/bin/g++-12 -S . -B ../build
9 | # RUN cmake --build ../build
10 | # RUN cp *.gif /tmp/build/nQuantCpp/
11 | # WORKDIR /tmp/build/nQuantCpp
12 | # docker system prune -a
13 | # docker build -t nquantcpp .
14 | # docker run -it nquantcpp bash
15 | # docker cp :/file/path/within/container /host/path/target
16 | # docker cp foo.txt :/foo.txt
17 |
--------------------------------------------------------------------------------
/ImgV64.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcychan/nQuantCpp/9415eb9f183faf14b5377eab83afe55176210128/ImgV64.gif
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # nQuantCpp
2 | nQuantCpp includes top 6 color quantization algorithms for visual c++ producing high quality optimized images. I enhance each of the algorithms to support semi transparent images.
3 | nQuantCpp also provides a command line wrapper in case you want to use it from the command line.
4 |
5 | Either download nQuantCpp from this site or add it to your Visual Studio project seamlessly.
6 | PNG is useful because it's the only widely supported format which can store partially transparent images. The format uses compression, but the files can still be large. Use Color quantization algorithms can be chosen by command line since version 1.10 using the /a algorithm.
7 | Only png can support semi transparent image and desired color depth. Gif can ensure the number of colors for the converted image is 256 or less. Bmp does support desired color depth. Jpg only supports 24-bit image format.
8 |
9 | Let's climb up the mountain: Ready, Go!!!
10 |
11 | Original photo of climbing

12 | Reduced to 256 colors by Divisive hierarchical clustering algorithm

13 | Reduced to 256 colors by NeuQuant Neural-Net Quantization Algorithm

14 | Reduced to 16 colors by Fast pairwise nearest neighbor based algorithm

15 | Reduced to 16 colors by Fast pairwise nearest neighbor based algorithm with CIELAB color space

16 | Reduced to 16 colors by Xialoin Wu's fast optimal color Quantization Algorithm

17 |
18 | Original photo of Aetna's Hartford headquarters

19 | Reduced to 256 colors by NeuQuant Neural-Net Quantization Algorithm

20 | Reduced to 256 colors by Fast pairwise nearest neighbor based algorithm

21 |
22 | Original image of Hong Kong Cuisines

23 | Fast pairwise nearest neighbor based algorithm with CIELAB color space in 16 colors
24 | High quality and fast
25 | 
26 | Fast pairwise nearest neighbor based algorithm with CIELAB color space Plus (parallel quantum inspired Genetic Algorithm) in 16 colors

27 | Efficient, Edge-Aware, Combined Color Quantization and Dithering with CIELAB color space in 16 colors
28 | Higher quality for 32 or less colors but slower
29 | 
30 | Spatial color quantization with CIELAB color space in 16 colors
31 | Higher quality for 32 or less colors but the slowest
32 | 
33 | All in all, the top 3 color quantization algorithms for 256 colors are:
34 |
35 | - Fast pairwise nearest neighbor based algorithm
36 | - Xialoin Wu's fast optimal color quantizer
37 | - NeuQuant Neural-Net Quantization Algorithm with CIELAB color space
38 |
39 | The top 3 color quantization algorithms for 32 colors or less are:
40 |
41 | - Fast pairwise nearest neighbor based algorithm with CIELAB color space Plus (parallel quantum inspired Genetic Algorithm)
42 | - Efficient, Edge-Aware, Combined Color Quantization and Dithering algorithm with CIELAB color space
43 | - Spatial color quantization algorithm with CIELAB color space
44 |
45 |
46 | If you are using the command line. Assuming you are in the same directory as nQuantCpp.exe, you would enter: `nQuantCpp yourImage.jpg /m 16 /a pnnlab`.
47 | To avoid dot gain, `/d n` can set the dithering to false. However, false contours will be resulted for gradient color zones.
48 | nQuantCpp will quantize yourImage.jpg with maximum colors 16, algorithm pnnlab and create yourImage-PNNLABquant16.png in the same directory.
49 |
50 | The readers can see coding of the error diffusion and dithering are quite similar among the above quantization algorithms.
51 | Each algorithm has its own advantages. I share the source of color quantization to invite further discussion and improvements.
52 | Such source code are written in C++ to gain best performance. It is readable and convertible to c#, java, or javascript.
53 | Welcome for C++ experts for further improvement or provide color quantization algorithms better than the above algorithms.
54 | Please use issues to track ideas, enhancements, tasks, or bugs for work on GitHub.
55 |
--------------------------------------------------------------------------------
/nQuantCpp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.28307.438
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nQuantCpp", "nQuantCpp\nQuantCpp.vcxproj", "{D9959AF4-B098-4444-AD33-B377E98A187C}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|x64 = Debug|x64
11 | Debug|x86 = Debug|x86
12 | Release|x64 = Release|x64
13 | Release|x86 = Release|x86
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {D9959AF4-B098-4444-AD33-B377E98A187C}.Debug|x64.ActiveCfg = Debug|x64
17 | {D9959AF4-B098-4444-AD33-B377E98A187C}.Debug|x64.Build.0 = Debug|x64
18 | {D9959AF4-B098-4444-AD33-B377E98A187C}.Debug|x86.ActiveCfg = Debug|Win32
19 | {D9959AF4-B098-4444-AD33-B377E98A187C}.Debug|x86.Build.0 = Debug|Win32
20 | {D9959AF4-B098-4444-AD33-B377E98A187C}.Release|x64.ActiveCfg = Release|x64
21 | {D9959AF4-B098-4444-AD33-B377E98A187C}.Release|x64.Build.0 = Release|x64
22 | {D9959AF4-B098-4444-AD33-B377E98A187C}.Release|x86.ActiveCfg = Release|Win32
23 | {D9959AF4-B098-4444-AD33-B377E98A187C}.Release|x86.Build.0 = Release|Win32
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {8CD6CB65-BE9A-4992-AA9F-F6046A35955E}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/nQuantCpp/APNsgaIII.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Wu, M.; Yang, D.; Zhou, B.; Yang, Z.; Liu, T.; Li, L.; Wang, Z.; Hu,
3 | * K. Adaptive Population NSGA-III with Dual Control Strategy for Flexible Job
4 | * Shop Scheduling Problem with the Consideration of Energy Consumption and Weight. Machines 2021, 9, 344.
5 | * https://doi.org/10.3390/machines9120344
6 | * Copyright (c) 2023 Miller Cy Chan
7 | */
8 |
9 | #include "stdafx.h"
10 | #include "APNsgaIII.h"
11 | #include "PnnLABGAQuantizer.h"
12 |
13 | #include
14 | #include
15 | #include
16 |
17 | namespace nQuantGA
18 | {
19 | int _currentGeneration = 0, _max_iterations = 5;
20 | int _maxRepeat = min(15, _max_iterations / 2);
21 |
22 | // Initializes Adaptive Population NSGA-III with Dual Control Strategy
23 | template
24 | APNsgaIII::APNsgaIII(T& prototype, int numberOfCrossoverPoints, int mutationSize, float crossoverProbability, float mutationProbability)
25 | : NsgaIII(prototype, numberOfCrossoverPoints, mutationSize, crossoverProbability, mutationProbability)
26 | {
27 | }
28 |
29 | template
30 | APNsgaIII::APNsgaIII(T& prototype) : APNsgaIII(prototype, 2, 2, 80.0f, 3.0f)
31 | {
32 | }
33 |
34 | template
35 | double APNsgaIII::ex(T& chromosome)
36 | {
37 | double numerator = 0.0, denominator = 0.0;
38 | for (int f = 0; f < chromosome.getObjectives().size(); ++f) {
39 | numerator += chromosome.getObjectives()[f] - this->_best->getObjectives()[f];
40 | denominator += _worst->getObjectives()[f] - this->_best->getObjectives()[f];
41 | }
42 | return (numerator + 1) / (denominator + 1);
43 | }
44 |
45 | template
46 | void APNsgaIII::popDec(vector >& population)
47 | {
48 | int N = population.size();
49 | if(N <= this->_populationSize)
50 | return;
51 |
52 | int rank = (int) (.3 * this->_populationSize);
53 |
54 | for(int i = 0; i < N; ++i) {
55 | auto exValue = ex(*population[i]);
56 |
57 | if(exValue > .5 && i > rank) {
58 | population.erase(population.begin() + i);
59 | if(--N <= this->_populationSize)
60 | break;
61 | }
62 | }
63 | }
64 |
65 | template
66 | void APNsgaIII::dualCtrlStrategy(vector >& population, int bestNotEnhance, int nMax)
67 | {
68 | int N = population.size();
69 | int nTmp = N;
70 | for(int i = 0; i < nTmp; ++i) {
71 | auto& chromosome = population[i];
72 | auto pTumor = chromosome->makeNewFromPrototype();
73 | pTumor->mutation(this->_mutationSize, this->_mutationProbability);
74 |
75 | _worst = population[population.size() - 1];
76 | if(pTumor->dominates(chromosome.get() )) {
77 | chromosome = pTumor;
78 | if(pTumor->dominates(this->_best.get()))
79 | this->_best = pTumor;
80 | }
81 | else {
82 | if(bestNotEnhance >= _maxRepeat && N < nMax) {
83 | ++N;
84 | if(_worst->dominates(pTumor.get())) {
85 | population.emplace_back(pTumor);
86 | _worst = pTumor;
87 | }
88 | else
89 | population.insert(population.end() - 1, pTumor);
90 | }
91 | }
92 | }
93 | popDec(population);
94 | }
95 |
96 | template
97 | vector > APNsgaIII::replacement(vector >& population)
98 | {
99 | auto result = NsgaIII::replacement(population);
100 | sort(result.begin(), result.end(), [](const auto& lhs, const auto& rhs)
101 | {
102 | return lhs->getFitness() > rhs->getFitness();
103 | });
104 | return result;
105 | }
106 |
107 | // Starts and executes algorithm
108 | template
109 | void APNsgaIII::run(int maxRepeat, double minFitness)
110 | {
111 | if (this->_prototype.get() == nullptr)
112 | return;
113 |
114 | vector > pop[2];
115 | pop[0] = this->initialize();
116 | int nMax = (int) (1.5 * this->_populationSize);
117 |
118 | int bestNotEnhance = 0;
119 | _currentGeneration = 0;
120 | double lastBestFit = 0.0;
121 |
122 | int cur = 0, next = 1;
123 | while(_currentGeneration < _max_iterations)
124 | {
125 | if(_currentGeneration > 0) {
126 | auto best = this->getResult();
127 | auto difference = abs(best->getFitness() - lastBestFit);
128 | if (difference <= 1e-6)
129 | ++bestNotEnhance;
130 | else {
131 | lastBestFit = best->getFitness();
132 | bestNotEnhance = 0;
133 | }
134 |
135 | ostringstream status;
136 | if(bestNotEnhance >= _maxRepeat)
137 | status << "\rFitness: " << showpoint << best->getFitness() << "\t Generation: " << _currentGeneration << " ...";
138 | else
139 | status << "\rFitness: " << showpoint << best->getFitness() << "\t Generation: " << _currentGeneration;
140 | wcout << status.str().c_str();
141 |
142 | if (best->getFitness() > minFitness)
143 | break;
144 |
145 | if (bestNotEnhance > (maxRepeat / 50))
146 | this->reform();
147 | }
148 |
149 | /******************* crossover *****************/
150 | auto offspring = this->crossing(pop[cur]);
151 |
152 | /******************* mutation *****************/
153 | #pragma omp parallel for
154 | for (int i = 0; i < offspring.size(); ++i) {
155 | offspring[i]->mutation(this->_mutationSize, this->_mutationProbability);
156 | }
157 |
158 | pop[cur].insert(pop[cur].end(), offspring.begin(), offspring.end());
159 |
160 | /******************* replacement *****************/
161 | pop[next] = replacement(pop[cur]);
162 | this->_best = pop[next][0]->dominates(pop[cur][0].get()) ? pop[next][0] : pop[cur][0];
163 |
164 | dualCtrlStrategy(pop[next], bestNotEnhance, nMax);
165 |
166 | swap(cur, next);
167 | ++_currentGeneration;
168 | }
169 |
170 | }
171 |
172 | // explicit instantiations
173 | template class APNsgaIII;
174 | }
--------------------------------------------------------------------------------
/nQuantCpp/APNsgaIII.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "NsgaIII.h"
3 |
4 | namespace nQuantGA
5 | {
6 | /*
7 | * Wu, M.; Yang, D.; Zhou, B.; Yang, Z.; Liu, T.; Li, L.; Wang, Z.; Hu,
8 | * K. Adaptive Population NSGA-III with Dual Control Strategy for Flexible Job
9 | * Shop Scheduling Problem with the Consideration of Energy Consumption and Weight. Machines 2021, 9, 344.
10 | * https://doi.org/10.3390/machines9120344
11 | * Copyright (c) 2023 Miller Cy Chan
12 | */
13 |
14 | template
15 | class APNsgaIII : public NsgaIII
16 | {
17 | protected:
18 | // Worst of chromosomes
19 | shared_ptr _worst;
20 |
21 | void dualCtrlStrategy(vector >& population, int bestNotEnhance, int nMax);
22 | double ex(T& chromosome);
23 | void popDec(vector >& population);
24 | vector > replacement(vector >& population) override;
25 |
26 | public:
27 | APNsgaIII(T& pPrototype, int numberOfCrossoverPoints, int mutationSize, float crossoverProbability, float mutationProbability);
28 | APNsgaIII(T& pPrototype);
29 |
30 | // Starts and executes algorithm
31 | void run(int maxRepeat, double minFitness) override;
32 | };
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/nQuantCpp/BlueNoise.cpp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mcychan/nQuantCpp/9415eb9f183faf14b5377eab83afe55176210128/nQuantCpp/BlueNoise.cpp
--------------------------------------------------------------------------------
/nQuantCpp/BlueNoise.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "bitmapUtilities.h"
3 |
4 | namespace BlueNoise
5 | {
6 | extern const char TELL_BLUE_NOISE[];
7 |
8 | ARGB diffuse(const Color& pixel, const Color& qPixel, const float weight, const float strength, const int x, const int y);
9 |
10 | void dither(const UINT width, const UINT height, const ARGB* pixels, const ARGB* pPalette, const unsigned short nMaxColors, DitherFn ditherFn, GetColorIndexFn getColorIndexFn, unsigned short* qPixels, const float weight = 1.0f);
11 | }
12 |
--------------------------------------------------------------------------------
/nQuantCpp/CIELABConvertor.cpp:
--------------------------------------------------------------------------------
1 | #include "stdafx.h"
2 | #include "CIELABConvertor.h"
3 |
4 | #include
5 | #include
6 |
7 | using namespace std;
8 |
9 | #ifdef _DEBUG
10 | #define new DEBUG_NEW
11 | #endif
12 |
13 | static const double XYZ_WHITE_REFERENCE_X = 95.047;
14 | static const double XYZ_WHITE_REFERENCE_Y = 100;
15 | static const double XYZ_WHITE_REFERENCE_Z = 108.883;
16 | static const double XYZ_EPSILON = 0.008856;
17 | static const double XYZ_KAPPA = 903.3;
18 |
19 | static float pivotXyzComponent(double component) {
20 | return component > XYZ_EPSILON
21 | ? (float) cbrt(component)
22 | : (float)((XYZ_KAPPA * component + 16) / 116.0);
23 | }
24 |
25 | static double gammaToLinear(int channel)
26 | {
27 | auto c = channel / 255.0;
28 | return c < 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4);
29 | }
30 |
31 | void CIELABConvertor::RGB2LAB(const Color& c1, Lab& lab)
32 | {
33 | auto sr = gammaToLinear(c1.GetR());
34 | auto sg = gammaToLinear(c1.GetG());
35 | auto sb = gammaToLinear(c1.GetB());
36 | const auto x = pivotXyzComponent(100 * (sr * 0.4124 + sg * 0.3576 + sb * 0.1805) / XYZ_WHITE_REFERENCE_X);
37 | const auto y = pivotXyzComponent(100 * (sr * 0.2126 + sg * 0.7152 + sb * 0.0722) / XYZ_WHITE_REFERENCE_Y);
38 | const auto z = pivotXyzComponent(100 * (sr * 0.0193 + sg * 0.1192 + sb * 0.9505) / XYZ_WHITE_REFERENCE_Z);
39 |
40 | lab.alpha = c1.GetA();
41 | lab.L = max(0, 116 * y - 16);
42 | lab.A = 500 * (x - y);
43 | lab.B = 200 * (y - z);
44 | }
45 |
46 | ARGB CIELABConvertor::LAB2RGB(const Lab& lab){
47 | const auto fy = (lab.L + 16.0) / 116.0;
48 | const auto fx = lab.A / 500 + fy;
49 | const auto fz = fy - lab.B / 200.0;
50 | double tmp = fx * fx * fx;
51 | const auto xr = tmp > XYZ_EPSILON ? tmp : (116.0 * fx - 16) / XYZ_KAPPA;
52 | const auto yr = lab.L > XYZ_KAPPA * XYZ_EPSILON ? fy * fy * fy : lab.L / XYZ_KAPPA;
53 | tmp = fz * fz * fz;
54 | const auto zr = tmp > XYZ_EPSILON ? tmp : (116.0 * fz - 16) / XYZ_KAPPA;
55 | const auto x = xr * XYZ_WHITE_REFERENCE_X;
56 | const auto y = yr * XYZ_WHITE_REFERENCE_Y;
57 | const auto z = zr * XYZ_WHITE_REFERENCE_Z;
58 |
59 | auto r = (x * 3.2406 + y * -1.5372 + z * -0.4986) / 100.0;
60 | auto g = (x * -0.9689 + y * 1.8758 + z * 0.0415) / 100.0;
61 | auto b = (x * 0.0557 + y * -0.2040 + z * 1.0570) / 100.0;
62 | r = r > 0.0031308 ? 1.055 * pow(r, 1 / 2.4) - 0.055 : 12.92 * r;
63 | g = g > 0.0031308 ? 1.055 * pow(g, 1 / 2.4) - 0.055 : 12.92 * g;
64 | b = b > 0.0031308 ? 1.055 * pow(b, 1 / 2.4) - 0.055 : 12.92 * b;
65 |
66 | return Color::MakeARGB(clamp((int)lab.alpha, 0, BYTE_MAX), clamp((int)rint(r * BYTE_MAX), 0, BYTE_MAX),
67 | clamp((int)rint(g * BYTE_MAX), 0, BYTE_MAX), clamp((int)rint(b * BYTE_MAX), 0, BYTE_MAX));
68 | }
69 |
70 | /*******************************************************************************
71 | * Conversions.
72 | ******************************************************************************/
73 |
74 | inline const float deg2Rad(const float deg)
75 | {
76 | return (float) (deg * M_PI / 180.0);
77 | }
78 |
79 | float CIELABConvertor::L_prime_div_k_L_S_L(const Lab& lab1, const Lab& lab2)
80 | {
81 | const auto k_L = 1.0f; // kL
82 | auto deltaLPrime = lab2.L - lab1.L;
83 | auto barLPrime = (lab1.L + lab2.L) / 2.0f;
84 | auto S_L = (float)(1 + ((0.015 * sqr(barLPrime - 50.0)) / _sqrt(20 + sqr(barLPrime - 50.0))));
85 | return deltaLPrime / (k_L * S_L);
86 | }
87 |
88 | float CIELABConvertor::C_prime_div_k_L_S_L(const Lab& lab1, const Lab& lab2, double& a1Prime, double& a2Prime, double& CPrime1, double& CPrime2)
89 | {
90 | const auto k_C = 1.0f, K1 = 0.045f; // K1
91 | const auto pow25To7 = 6103515625.0; /* pow(25, 7) */
92 | auto C1 = (float)(_sqrt((lab1.A * lab1.A) + (lab1.B * lab1.B)));
93 | auto C2 = (float)(_sqrt((lab2.A * lab2.A) + (lab2.B * lab2.B)));
94 | auto barC = (C1 + C2) / 2.0f;
95 | auto G = (float)(0.5f * (1 - _sqrt(pow(barC, 7) / (pow(barC, 7) + pow25To7))));
96 | a1Prime = (1.0 + G) * lab1.A;
97 | a2Prime = (1.0 + G) * lab2.A;
98 |
99 | CPrime1 = _sqrt((a1Prime * a1Prime) + (lab1.B * lab1.B));
100 | CPrime2 = _sqrt((a2Prime * a2Prime) + (lab2.B * lab2.B));
101 | auto deltaCPrime = (float)(CPrime2 - CPrime1);
102 | auto barCPrime = (float)((CPrime1 + CPrime2) / 2.0);
103 |
104 | auto S_C = 1 + (K1 * barCPrime);
105 | return deltaCPrime / (k_C * S_C);
106 | }
107 |
108 | float CIELABConvertor::H_prime_div_k_L_S_L(const Lab& lab1, const Lab& lab2, const double a1Prime, const double a2Prime, const double CPrime1, const double CPrime2, double& barCPrime, double& barhPrime)
109 | {
110 | const auto k_H = 1.0f, K2 = 0.015f; // K2
111 | const auto deg360InRad = deg2Rad(360.0f);
112 | const auto deg180InRad = deg2Rad(180.0f);
113 | auto CPrimeProduct = CPrime1 * CPrime2;
114 | double hPrime1;
115 | if (rint(lab1.B) == 0 && rint(a1Prime) == 0)
116 | hPrime1 = 0.0;
117 | else {
118 | hPrime1 = atan2(lab1.B, a1Prime);
119 | /*
120 | * This must be converted to a hue angle in degrees between 0
121 | * and 360 by addition of 2π to negative hue angles.
122 | */
123 | if (hPrime1 < 0)
124 | hPrime1 += deg360InRad;
125 | }
126 | double hPrime2;
127 | if (rint(lab2.B) == 0 && rint(a2Prime) == 0)
128 | hPrime2 = 0.0;
129 | else {
130 | hPrime2 = atan2(lab2.B, a2Prime);
131 | /*
132 | * This must be converted to a hue angle in degrees between 0
133 | * and 360 by addition of 2π to negative hue angles.
134 | */
135 | if (hPrime2 < 0)
136 | hPrime2 += deg360InRad;
137 | }
138 | double deltahPrime;
139 | if (rint(CPrimeProduct) == 0)
140 | deltahPrime = 0;
141 | else {
142 | /* Avoid the fabs() call */
143 | deltahPrime = hPrime2 - hPrime1;
144 | if (deltahPrime < -deg180InRad)
145 | deltahPrime += deg360InRad;
146 | else if (deltahPrime > deg180InRad)
147 | deltahPrime -= deg360InRad;
148 | }
149 |
150 | auto deltaHPrime = 2.0 * _sqrt(CPrimeProduct) * sin(deltahPrime / 2.0);
151 | auto hPrimeSum = hPrime1 + hPrime2;
152 | if (rint(CPrime1 * CPrime2) == 0) {
153 | barhPrime = hPrimeSum;
154 | }
155 | else {
156 | if (fabs(hPrime1 - hPrime2) <= deg180InRad)
157 | barhPrime = hPrimeSum / 2.0;
158 | else {
159 | if (hPrimeSum < deg360InRad)
160 | barhPrime = (hPrimeSum + deg360InRad) / 2.0;
161 | else
162 | barhPrime = (hPrimeSum - deg360InRad) / 2.0;
163 | }
164 | }
165 |
166 | barCPrime = (CPrime1 + CPrime2) / 2.0;
167 | auto T = 1.0 - (0.17 * cos(barhPrime - deg2Rad(30.0))) +
168 | (0.24 * cos(2.0 * barhPrime)) +
169 | (0.32 * cos((3.0 * barhPrime) + deg2Rad(6.0))) -
170 | (0.20 * cos((4.0 * barhPrime) - deg2Rad(63.0)));
171 | auto S_H = 1 + (K2 * barCPrime * T);
172 | return (float) (deltaHPrime / (k_H * S_H));
173 | }
174 |
175 | float CIELABConvertor::R_T(const double barCPrime, const double barhPrime, const double C_prime_div_k_L_S_L, const double H_prime_div_k_L_S_L)
176 | {
177 | const auto pow25To7 = 6103515625.0; /* pow(25, 7) */
178 | auto deltaTheta = deg2Rad(30.0f) * exp(-pow((barhPrime - deg2Rad(275.0f)) / deg2Rad(25.0f), 2.0));
179 | auto R_C = 2.0 * _sqrt(pow(barCPrime, 7.0) / (pow(barCPrime, 7.0) + pow25To7));
180 | auto R_T = -sin(2.0 * deltaTheta) * R_C;
181 | return (float) (R_T * C_prime_div_k_L_S_L * H_prime_div_k_L_S_L);
182 | }
183 |
184 | float CIELABConvertor::CIEDE2000(const Lab& lab1, const Lab& lab2)
185 | {
186 | auto deltaL_prime_div_k_L_S_L = L_prime_div_k_L_S_L(lab1, lab2);
187 | double a1Prime, a2Prime, CPrime1, CPrime2;
188 | auto deltaC_prime_div_k_L_S_L = C_prime_div_k_L_S_L(lab1, lab2, a1Prime, a2Prime, CPrime1, CPrime2);
189 | double barCPrime, barhPrime;
190 | auto deltaH_prime_div_k_L_S_L = H_prime_div_k_L_S_L(lab1, lab2, a1Prime, a2Prime, CPrime1, CPrime2, barCPrime, barhPrime);
191 | auto deltaR_T = R_T(barCPrime, barhPrime, deltaC_prime_div_k_L_S_L, deltaH_prime_div_k_L_S_L);
192 | return (float) (pow(deltaL_prime_div_k_L_S_L, 2.0) +
193 | pow(deltaC_prime_div_k_L_S_L, 2.0) +
194 | pow(deltaH_prime_div_k_L_S_L, 2.0) +
195 | deltaR_T);
196 | }
197 |
198 | double CIELABConvertor::Y_Diff(const Color& c1, const Color& c2)
199 | {
200 | auto color2Y = [](const Color& c) -> double {
201 | auto sr = gammaToLinear(c.GetR());
202 | auto sg = gammaToLinear(c.GetG());
203 | auto sb = gammaToLinear(c.GetB());
204 | return sr * 0.2126 + sg * 0.7152 + sb * 0.0722;
205 | };
206 |
207 | auto y = color2Y(c1);
208 | auto y2 = color2Y(c2);
209 | return abs(y2 - y) * XYZ_WHITE_REFERENCE_Y;
210 | }
211 |
212 | double CIELABConvertor::U_Diff(const Color& c1, const Color& c2)
213 | {
214 | auto color2U = [](const Color& c) -> double {
215 | return -0.09991 * c.GetR() - 0.33609 * c.GetG() + 0.436 * c.GetB();
216 | };
217 |
218 | auto u = color2U(c1);
219 | auto u2 = color2U(c2);
220 | return abs(u2 - u);
221 | }
222 |
--------------------------------------------------------------------------------
/nQuantCpp/CIELABConvertor.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #ifndef _WIN32
3 | #include
4 | #include
5 | using namespace Gdiplus;
6 |
7 | #ifndef BYTE
8 | typedef unsigned char BYTE;
9 | #endif
10 | #ifndef BYTE_MAX
11 | #include
12 | #define BYTE_MAX UCHAR_MAX
13 | #endif
14 | #endif
15 | #define _USE_MATH_DEFINES
16 | #include
17 |
18 | class CIELABConvertor
19 | {
20 |
21 | public:
22 | struct Lab {
23 | BYTE alpha = BYTE_MAX;
24 | float A = 0.0;
25 | float B = 0.0;
26 | float L = 0.0;
27 | };
28 |
29 | static ARGB LAB2RGB(const Lab& lab);
30 | static void RGB2LAB(const Color& c1, Lab& lab);
31 | static float L_prime_div_k_L_S_L(const Lab& lab1, const Lab& lab2);
32 | static float C_prime_div_k_L_S_L(const Lab& lab1, const Lab& lab2, double& a1Prime, double& a2Prime, double& CPrime1, double& CPrime2);
33 | static float H_prime_div_k_L_S_L(const Lab& lab1, const Lab& lab2, const double a1Prime, const double a2Prime, const double CPrime1, const double CPrime2, double& barCPrime, double& barhPrime);
34 | static float R_T(const double barCPrime, const double barhPrime, const double C_prime_div_k_L_S_L, const double H_prime_div_k_L_S_L);
35 |
36 | /* From the paper "The CIEDE2000 Color-Difference Formula: Implementation Notes, */
37 | /* Supplementary Test Data, and Mathematical Observations", by */
38 | /* Gaurav Sharma, Wencheng Wu and Edul N. Dalal, */
39 | /* Color Res. Appl., vol. 30, no. 1, pp. 21-30, Feb. 2005. */
40 | /* Return the CIEDE2000 Delta E color difference measure squared, for two Lab values */
41 | static float CIEDE2000(const Lab& lab1, const Lab& lab2);
42 |
43 | static double Y_Diff(const Color& c1, const Color& c2);
44 | static double U_Diff(const Color& c1, const Color& c2);
45 | };
--------------------------------------------------------------------------------
/nQuantCpp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required (VERSION 3.10)
2 |
3 | if(NOT WIN32)
4 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I /usr/share/mingw-w64/include")
5 | endif()
6 | add_executable(nQuantCpp "nQuantCpp.cpp" "nQuantCpp.h" "nQuantCpp.rc" "bitmapUtilities.cpp" "bitmapUtilities.h" "BlueNoise.cpp" "BlueNoise.h" "CIELABConvertor.cpp" "CIELABConvertor.h" "DivQuantizer.cpp" "DivQuantizer.h"
7 | "Dl3Quantizer.cpp" "Dl3Quantizer.h" "EdgeAwareSQuantizer.cpp" "EdgeAwareSQuantizer.h" "GifWriter.cpp" "GifWriter.h" "GilbertCurve.cpp" "GilbertCurve.h" "MedianCut.cpp" "MedianCut.h" "Otsu.cpp" "Otsu.h"
8 | "NeuQuantizer.cpp" "NeuQuantizer.h" "PnnLABQuantizer.cpp" "PnnLABQuantizer.h" "PnnLABGAQuantizer.cpp" "PnnLABGAQuantizer.h" "PnnQuantizer.cpp" "PnnQuantizer.h" "Resource.h"
9 | "SpatialQuantizer.cpp" "SpatialQuantizer.h" "stdafx.cpp" "stdafx.h" "WuQuantizer.cpp" "WuQuantizer.h"
10 | "NsgaIII.cpp" "NsgaIII.h" "APNsgaIII.cpp" "APNsgaIII.h")
11 |
12 | find_package(OpenMP)
13 | if(OpenMP_CXX_FOUND)
14 | target_link_libraries(nQuantCpp PUBLIC gdiplus OpenMP::OpenMP_CXX)
15 | else()
16 | target_link_libraries(nQuantCpp gdiplus)
17 | endif()
--------------------------------------------------------------------------------
/nQuantCpp/DivQuantizer.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "bitmapUtilities.h"
3 |
4 | #include
5 | #include
6 | using namespace std;
7 |
8 | namespace DivQuant
9 | {
10 | // =============================================================
11 | // Quantizer objects and functions
12 | //
13 | // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
14 | // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
15 | // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
16 | // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
17 | // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
18 | // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
19 | // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
20 | // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
21 | // THIS DISCLAIMER.
22 | //
23 | // Use at your own risk!
24 | // =============================================================
25 |
26 | class DivQuantizer
27 | {
28 | public:
29 | void quant_varpart_fast(const ARGB* inPixels, const UINT numPixels, ColorPalette* pPalette,
30 | const UINT numRows = 1, const bool allPixelsUnique = true,
31 | const int num_bits = 8, const int dec_factor = 1, const int max_iters = 10);
32 | bool QuantizeImage(Bitmap* pSource, Bitmap* pDest, UINT& nMaxColors, bool dither = true);
33 | };
34 | }
--------------------------------------------------------------------------------
/nQuantCpp/Dl3Quantizer.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * DL3 Quantization
3 | * ================
4 | *
5 | * Author: Dennis Lee E-mail: denlee@ecf.utoronto.ca
6 | *
7 | * Copyright (C) 1993-1997 Dennis Lee
8 | * Copyright (c) 2019-2021 Miller Cy Chan
9 | *
10 | * C implementation of DL3 Quantization.
11 | * DL3 Quantization is a 2-pass color quantizer that uses an
12 | * exhaustive search technique to minimize error introduced at
13 | * each step during palette reduction.
14 | *
15 | * I believe DL3 Quant offers the highest quality of all existing
16 | * color quantizers. It is truly 'optimal' except for a few provisos.
17 | * These provisos and other information about DL3 Quant can be found
18 | * in DLQUANT.TXT, which is included in this distribution.
19 | *
20 | *
21 | * NOTES
22 | * =====
23 | *
24 | * The dithering code is based on code from the IJG's jpeg library.
25 | *
26 | * DL3 Quantization can take a long time to reduce a palette.
27 | * Times can range from seconds to minutes or even hours depending on
28 | * the image and the computer used. This eliminates DL3 Quant for
29 | * typical usage unless the user has a very fast computer and/or has a
30 | * lot of patience. However, the reward is a quantized image that is
31 | * the best currently possible. The number of colors in the source image,
32 | * not the image size, determines the time required to quantize it.
33 | *
34 | * This source code may be freely copied, modified, and redistributed,
35 | * provided this copyright notice is attached.
36 | * Compiled versions of this code, modified or not, are free for
37 | * personal use. Compiled versions used in distributed software
38 | * is also free, but a notification must be sent to the author.
39 | * An e-mail to denlee@ecf.utoronto.ca will do.
40 | *
41 | */
42 |
43 | #include "stdafx.h"
44 | #include "Dl3Quantizer.h"
45 | #include "BlueNoise.h"
46 | #include
47 |
48 | namespace Dl3Quant
49 | {
50 | bool hasSemiTransparency = false;
51 | int m_transparentPixelIndex = -1;
52 | ARGB m_transparentColor = Color::Transparent;
53 | unordered_map > closestMap;
54 |
55 | using namespace std;
56 |
57 | struct CUBE3 {
58 | int a, r, g, b;
59 | int aa, rr, gg, bb;
60 | UINT cc;
61 | UINT pixel_count = 0;
62 | double err;
63 | };
64 |
65 | void setARGB(CUBE3& rec)
66 | {
67 | UINT v = rec.pixel_count, v2 = v >> 1;
68 | rec.aa = (rec.a + v2) / v;
69 | rec.rr = (rec.r + v2) / v;
70 | rec.gg = (rec.g + v2) / v;
71 | rec.bb = (rec.b + v2) / v;
72 | }
73 |
74 | double calc_err(CUBE3* rgb_table3, const int* squares3, const UINT& c1, const UINT& c2)
75 | {
76 | auto P1 = rgb_table3[c1].pixel_count;
77 | auto P2 = rgb_table3[c2].pixel_count;
78 | auto P3 = P1 + P2;
79 |
80 | if (P3 == 0)
81 | return UINT_MAX;
82 |
83 | int A3 = (rgb_table3[c1].a + rgb_table3[c2].a + (P3 >> 1)) / P3;
84 | int R3 = (rgb_table3[c1].r + rgb_table3[c2].r + (P3 >> 1)) / P3;
85 | int G3 = (rgb_table3[c1].g + rgb_table3[c2].g + (P3 >> 1)) / P3;
86 | int B3 = (rgb_table3[c1].b + rgb_table3[c2].b + (P3 >> 1)) / P3;
87 |
88 | int A1 = rgb_table3[c1].aa;
89 | int R1 = rgb_table3[c1].rr;
90 | int G1 = rgb_table3[c1].gg;
91 | int B1 = rgb_table3[c1].bb;
92 |
93 | int A2 = rgb_table3[c2].aa;
94 | int R2 = rgb_table3[c2].rr;
95 | int G2 = rgb_table3[c2].gg;
96 | int B2 = rgb_table3[c2].bb;
97 |
98 | double dist1 = squares3[A3 - A1] + squares3[R3 - R1] + squares3[G3 - G1] + squares3[B3 - B1];
99 | dist1 *= P1;
100 |
101 | double dist2 = squares3[A2 - A3] + squares3[R2 - R3] + squares3[G2 - G3] + squares3[B2 - B3];
102 | dist2 *= P2;
103 |
104 | return (dist1 + dist2);
105 | }
106 |
107 | void build_table3(CUBE3* rgb_table3, ARGB argb)
108 | {
109 | Color c(argb);
110 | int index = GetARGBIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0);
111 |
112 | rgb_table3[index].a += c.GetA();
113 | rgb_table3[index].r += c.GetR();
114 | rgb_table3[index].g += c.GetG();
115 | rgb_table3[index].b += c.GetB();
116 | rgb_table3[index].pixel_count++;
117 | }
118 |
119 | UINT build_table3(CUBE3* rgb_table3, vector& pixels)
120 | {
121 | for (const auto & pixel : pixels)
122 | build_table3(rgb_table3, pixel);
123 |
124 | UINT tot_colors = 0;
125 | for (int i = 0; i < USHRT_MAX + 1; ++i) {
126 | if (rgb_table3[i].pixel_count > 0) {
127 | setARGB(rgb_table3[i]);
128 | rgb_table3[tot_colors] = rgb_table3[i];
129 | ++tot_colors;
130 | }
131 | }
132 | return tot_colors;
133 | }
134 |
135 | void recount_next(CUBE3* rgb_table3, const int* squares3, const UINT& tot_colors, const UINT& i)
136 | {
137 | UINT c2 = 0;
138 | double err = UINT_MAX;
139 | for (UINT j = i + 1; j < tot_colors; ++j) {
140 | auto cur_err = calc_err(rgb_table3, squares3, i, j);
141 | if (cur_err < err) {
142 | err = cur_err;
143 | c2 = j;
144 | }
145 | }
146 | rgb_table3[i].err = err;
147 | rgb_table3[i].cc = c2;
148 | }
149 |
150 | void recount_dist(CUBE3* rgb_table3, const int* squares3, const UINT& tot_colors, const UINT& c1)
151 | {
152 | recount_next(rgb_table3, squares3, tot_colors, c1);
153 | for (int i = 0; i < c1; ++i) {
154 | if (rgb_table3[i].cc == c1)
155 | recount_next(rgb_table3, squares3, tot_colors, i);
156 | else {
157 | auto cur_err = calc_err(rgb_table3, squares3, i, c1);
158 | if (cur_err < rgb_table3[i].err) {
159 | rgb_table3[i].err = cur_err;
160 | rgb_table3[i].cc = c1;
161 | }
162 | }
163 | }
164 | }
165 |
166 | void reduce_table3(CUBE3* rgb_table3, const int* squares3, UINT tot_colors, const UINT& num_colors)
167 | {
168 | UINT i = 0;
169 | for (; i < (tot_colors - 1); ++i)
170 | recount_next(rgb_table3, squares3, tot_colors, i);
171 |
172 | rgb_table3[i].err = UINT_MAX;
173 | rgb_table3[i].cc = tot_colors;
174 |
175 | UINT c1 = 0, grand_total = tot_colors - num_colors;
176 | while (tot_colors > num_colors) {
177 | UINT err = UINT_MAX;
178 | for (i = 0; i < tot_colors; ++i) {
179 | if (rgb_table3[i].err < err) {
180 | err = rgb_table3[i].err;
181 | c1 = i;
182 | }
183 | }
184 | auto c2 = rgb_table3[c1].cc;
185 | rgb_table3[c2].a += rgb_table3[c1].a;
186 | rgb_table3[c2].r += rgb_table3[c1].r;
187 | rgb_table3[c2].g += rgb_table3[c1].g;
188 | rgb_table3[c2].b += rgb_table3[c1].b;
189 | rgb_table3[c2].pixel_count += rgb_table3[c1].pixel_count;
190 | setARGB(rgb_table3[c2]);
191 |
192 | rgb_table3[c1] = rgb_table3[--tot_colors];
193 | rgb_table3[tot_colors - 1].err = UINT_MAX;
194 | rgb_table3[tot_colors - 1].cc = tot_colors;
195 |
196 | for (i = 0; i < c1; ++i) {
197 | if (rgb_table3[i].cc == tot_colors)
198 | rgb_table3[i].cc = c1;
199 | }
200 |
201 | for (i = c1 + 1; i < tot_colors; ++i) {
202 | if (rgb_table3[i].cc == tot_colors)
203 | recount_next(rgb_table3, squares3, tot_colors, i);
204 | }
205 |
206 | recount_dist(rgb_table3, squares3, tot_colors, c1);
207 | if (c2 != tot_colors)
208 | recount_dist(rgb_table3, squares3, tot_colors, c2);
209 | }
210 | }
211 |
212 | unsigned short nearestColorIndex(const ARGB* pPalette, const UINT nMaxColors, ARGB argb, const UINT pos)
213 | {
214 | unsigned short k = 0;
215 | Color c(argb);
216 |
217 | double mindist = INT_MAX;
218 | for (UINT i = 0; i < nMaxColors; ++i) {
219 | Color c2(pPalette[i]);
220 | double curdist = sqr(c2.GetA() - c.GetA());
221 | if (curdist > mindist)
222 | continue;
223 |
224 | curdist += sqr(c2.GetR() - c.GetR());
225 | if (curdist > mindist)
226 | continue;
227 |
228 | curdist += sqr(c2.GetG() - c.GetG());
229 | if (curdist > mindist)
230 | continue;
231 |
232 | curdist += sqr(c2.GetB() - c.GetB());
233 | if (curdist > mindist)
234 | continue;
235 |
236 | mindist = curdist;
237 | k = i;
238 | }
239 | return k;
240 | }
241 |
242 | unsigned short closestColorIndex(const ARGB* pPalette, const UINT nMaxColors, ARGB argb, const UINT pos)
243 | {
244 | UINT k = 0;
245 | Color c(argb);
246 | if (c.GetA() <= 0xF)
247 | c = m_transparentColor;
248 |
249 | vector closest(5);
250 | auto got = closestMap.find(argb);
251 | if (got == closestMap.end()) {
252 | closest[2] = closest[3] = SHRT_MAX;
253 |
254 | for (; k < nMaxColors; k++) {
255 | Color c2(pPalette[k]);
256 | closest[4] = abs(c.GetA() - c2.GetA()) + abs(c.GetR() - c2.GetR()) + abs(c.GetG() - c2.GetG()) + abs(c.GetB() - c2.GetB());
257 | if (closest[4] < closest[2]) {
258 | closest[1] = closest[0];
259 | closest[3] = closest[2];
260 | closest[0] = k;
261 | closest[2] = closest[4];
262 | }
263 | else if (closest[4] < closest[3]) {
264 | closest[1] = k;
265 | closest[3] = closest[4];
266 | }
267 | }
268 |
269 | if (closest[3] == SHRT_MAX)
270 | closest[2] = 0;
271 | }
272 | else
273 | closest = got->second;
274 |
275 | if (closest[2] == 0 || (rand() % (closest[3] + closest[2])) <= closest[3])
276 | k = closest[0];
277 | else
278 | k = closest[1];
279 |
280 | closestMap[argb] = closest;
281 | return (unsigned short) k;
282 | }
283 |
284 | inline int GetColorIndex(const Color& c)
285 | {
286 | return GetARGBIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0);
287 | }
288 |
289 | bool quantize_image(const ARGB* pixels, const ColorPalette* pPalette, const UINT nMaxColors, unsigned short* qPixels, const UINT width, const UINT height, const bool dither)
290 | {
291 | if (dither)
292 | return dither_image(pixels, pPalette->Entries, nMaxColors, nearestColorIndex, hasSemiTransparency, m_transparentPixelIndex, qPixels, width, height);
293 |
294 | DitherFn ditherFn = (m_transparentPixelIndex >= 0 || nMaxColors < 256) ? nearestColorIndex : closestColorIndex;
295 | UINT pixelIndex = 0;
296 | for (int j = 0; j < height; ++j) {
297 | for (int i = 0; i < width; ++i, ++pixelIndex)
298 | qPixels[pixelIndex] = ditherFn(pPalette->Entries, nMaxColors, pixels[pixelIndex], i + j);
299 | }
300 |
301 | BlueNoise::dither(width, height, pixels, pPalette->Entries, nMaxColors, ditherFn, GetColorIndex, qPixels);
302 | return true;
303 | }
304 |
305 | void GetQuantizedPalette(ColorPalette* pPalette, const CUBE3* rgb_table3)
306 | {
307 | for (UINT k = 0; k < pPalette->Count; ++k) {
308 | UINT sum = rgb_table3[k].pixel_count;
309 | if (sum > 0) {
310 | pPalette->Entries[k] = Color::MakeARGB(rgb_table3[k].aa, rgb_table3[k].rr, rgb_table3[k].gg, rgb_table3[k].bb);
311 |
312 | if (m_transparentPixelIndex >= 0 && pPalette->Entries[k] == m_transparentColor)
313 | swap(pPalette->Entries[0], pPalette->Entries[k]);
314 | }
315 | }
316 | }
317 |
318 | bool Dl3Quantizer::QuantizeImage(Bitmap* pSource, Bitmap* pDest, UINT& nMaxColors, bool dither)
319 | {
320 | const UINT bitmapWidth = pSource->GetWidth();
321 | const UINT bitmapHeight = pSource->GetHeight();
322 | const auto area = (size_t) (bitmapWidth * bitmapHeight);
323 |
324 | vector pixels(area);
325 | GrabPixels(pSource, pixels, hasSemiTransparency, m_transparentPixelIndex, m_transparentColor, nMaxColors);
326 |
327 | auto pPaletteBytes = make_unique(sizeof(ColorPalette) + nMaxColors * sizeof(ARGB));
328 | auto pPalette = (ColorPalette*)pPaletteBytes.get();
329 | pPalette->Count = nMaxColors;
330 |
331 | if (nMaxColors > 2) {
332 | auto rgb_table3 = make_unique(USHRT_MAX + 1);
333 | UINT tot_colors = build_table3(rgb_table3.get(), pixels);
334 | int sqr_tbl[BYTE_MAX + BYTE_MAX + 1];
335 |
336 | for (int i = (-BYTE_MAX); i <= BYTE_MAX; ++i)
337 | sqr_tbl[i + BYTE_MAX] = i * i;
338 |
339 | auto squares3 = &sqr_tbl[BYTE_MAX];
340 |
341 | reduce_table3(rgb_table3.get(), squares3, tot_colors, nMaxColors);
342 |
343 | GetQuantizedPalette(pPalette, rgb_table3.get());
344 | }
345 | else {
346 | if (m_transparentPixelIndex >= 0) {
347 | pPalette->Entries[0] = m_transparentColor;
348 | pPalette->Entries[1] = Color::Black;
349 | }
350 | else {
351 | pPalette->Entries[0] = Color::Black;
352 | pPalette->Entries[1] = Color::White;
353 | }
354 | }
355 |
356 | if (nMaxColors > 256) {
357 | auto qPixels = make_unique(pixels.size());
358 | dithering_image(pixels.data(), pPalette, nearestColorIndex, hasSemiTransparency, m_transparentPixelIndex, nMaxColors, qPixels.get(), bitmapWidth, bitmapHeight);
359 | closestMap.clear();
360 | return ProcessImagePixels(pDest, qPixels.get(), hasSemiTransparency, m_transparentPixelIndex);
361 | }
362 |
363 | auto qPixels = make_unique(pixels.size());
364 | quantize_image(pixels.data(), pPalette, nMaxColors, qPixels.get(), bitmapWidth, bitmapHeight, dither);
365 | closestMap.clear();
366 |
367 | if (m_transparentPixelIndex >= 0) {
368 | UINT k = qPixels[m_transparentPixelIndex];
369 | if (nMaxColors > 2)
370 | pPalette->Entries[k] = m_transparentColor;
371 | else if (pPalette->Entries[k] != m_transparentColor)
372 | swap(pPalette->Entries[0], pPalette->Entries[1]);
373 | }
374 |
375 | pDest->SetPalette(pPalette);
376 | return ProcessImagePixels(pDest, qPixels.get(), m_transparentPixelIndex >= 0);
377 | }
378 |
379 | }
380 |
--------------------------------------------------------------------------------
/nQuantCpp/Dl3Quantizer.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "bitmapUtilities.h"
3 |
4 | namespace Dl3Quant
5 | {
6 | // =============================================================
7 | // Quantizer objects and functions
8 | //
9 | // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
10 | // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
11 | // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
12 | // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
13 | // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
14 | // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
15 | // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
16 | // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
17 | // THIS DISCLAIMER.
18 | //
19 | // Use at your own risk!
20 | // =============================================================
21 |
22 | class Dl3Quantizer
23 | {
24 | public:
25 | bool QuantizeImage(Bitmap* pSource, Bitmap* pDest, UINT& nMaxColors, bool dither = true);
26 | };
27 | }
--------------------------------------------------------------------------------
/nQuantCpp/EdgeAwareSQuantizer.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "bitmapUtilities.h"
3 |
4 | namespace EdgeAwareSQuant
5 | {
6 | template
7 | class vector_fixed
8 | {
9 | public:
10 | vector_fixed()
11 | {
12 | }
13 |
14 | vector_fixed(const vector_fixed& rhs)
15 | {
16 | copy(rhs.data, rhs.data + length, data);
17 | }
18 |
19 | vector_fixed(const vector& rhs)
20 | {
21 | copy(rhs.data, rhs.data + length, data);
22 | }
23 |
24 | inline T& operator[](int index)
25 | {
26 | return data[index];
27 | }
28 |
29 | inline const T& operator[](int index) const
30 | {
31 | return data[index];
32 | }
33 |
34 | inline int get_length() const { return length; }
35 |
36 | T norm_squared() {
37 | T result = 0;
38 | for (int i = 0; i& operator=(const vector_fixed& rhs)
45 | {
46 | copy(rhs.data, rhs.data + length, data);
47 | return *this;
48 | }
49 |
50 | vector_fixed direct_product(const vector_fixed& rhs) {
51 | vector_fixed result;
52 | for (int i = 0; i& rhs) {
59 | T result = 0;
60 | for (int i = 0; i& operator+=(const vector_fixed& rhs) {
67 | for (int i = 0; i operator+(const vector_fixed& rhs) {
74 | vector_fixed result(*this);
75 | result += rhs;
76 | return result;
77 | }
78 |
79 | vector_fixed& operator-=(const vector_fixed& rhs) {
80 | for (int i = 0; i operator-(const vector_fixed& rhs) {
87 | vector_fixed result(*this);
88 | result -= rhs;
89 | return result;
90 | }
91 |
92 | vector_fixed& operator*=(const T scalar) {
93 | for (int i = 0; i operator*(const T scalar) {
100 | vector_fixed result(*this);
101 | result *= scalar;
102 | return result;
103 | }
104 |
105 | private:
106 | T data[length] = { 0 };
107 | };
108 |
109 | template
110 | vector_fixed operator*(const T scalar, vector_fixed vec) {
111 | return vec * scalar;
112 | }
113 |
114 |
115 | template
116 | class array2d
117 | {
118 | public:
119 | array2d()
120 | {
121 | this->width = 0;
122 | this->height = 0;
123 | }
124 |
125 | array2d(int width, int height)
126 | {
127 | reset(width, height);
128 | }
129 |
130 | array2d(const array2d& rhs)
131 | {
132 | width = rhs.get_width();
133 | height = rhs.height;
134 | const auto area = (size_t) (width * height);
135 | data = make_unique(area);
136 | copy(rhs.data.get(), rhs.data.get() + area, data.get());
137 | }
138 |
139 | inline T& operator()(int col, int row)
140 | {
141 | return data[row * width + col];
142 | }
143 |
144 | inline const T& operator()(int col, int row) const
145 | {
146 | return data[row * width + col];
147 | }
148 |
149 | inline const T& operator[](int index) const
150 | {
151 | return data[index];
152 | }
153 |
154 | inline int get_width() const { return width; }
155 | inline int get_height() const { return height; }
156 |
157 | array2d& operator*=(const T scalar) {
158 | for (int i = 0; i<(width * height); i++)
159 | data[i] *= scalar;
160 | return *this;
161 | }
162 |
163 | array2d operator*(const T scalar) {
164 | array2d result(*this);
165 | result *= scalar;
166 | return result;
167 | }
168 |
169 | void reset(int width, int height)
170 | {
171 | this->width = width;
172 | this->height = height;
173 | const auto area = (size_t) (width * height);
174 | data = make_unique(area);
175 | }
176 |
177 | vector operator*(const vector& vec) {
178 | vector result(get_height());
179 | for (int row = 0; row < get_height(); ++row) {
180 | T sum = 0;
181 | for (int col = 0; col < get_width(); ++col)
182 | sum += (*this)(col, row) * vec[col];
183 |
184 | result[row] = sum;
185 | }
186 | return result;
187 | }
188 |
189 | array2d& multiply_row_scalar(int row, T mult) {
190 | for (int i = 0; i < get_width(); ++i)
191 | (*this)(i, row) *= mult;
192 |
193 | return *this;
194 | }
195 |
196 | array2d& add_row_multiple(int from_row, int to_row, T mult) {
197 | if (mult != 0) {
198 | for (int i = 0; i < get_width(); ++i)
199 | (*this)(i, to_row) += mult * (*this)(i, from_row);
200 | }
201 |
202 | return *this;
203 | }
204 |
205 | // We use simple Gaussian elimination - perf doesn't matter since
206 | // the matrices will be K x K, where K = number of palette entries.
207 | array2d matrix_inverse() {
208 | array2d result(get_width(), get_height());
209 | auto& a = *this;
210 |
211 | // Set result to identity matrix
212 | for (int i = 0; i < get_width(); ++i)
213 | result(i, i) = 1;
214 |
215 | // Reduce to echelon form, mirroring in result
216 | for (int i = 0; i < get_width(); ++i) {
217 | auto detA = a(i, i);
218 | float val = (detA != 0.0f) ? 1.0f / detA : 0.0f;
219 | result.multiply_row_scalar(i, val);
220 | multiply_row_scalar(i, val);
221 | for (int j = i + 1; j < get_height(); ++j) {
222 | result.add_row_multiple(i, j, -a(i, j));
223 | add_row_multiple(i, j, -a(i, j));
224 | }
225 | }
226 | // Back substitute, mirroring in result
227 | for (int i = get_width() - 1; i >= 0; --i) {
228 | for (int j = i - 1; j >= 0; --j) {
229 | result.add_row_multiple(i, j, -a(i, j));
230 | add_row_multiple(i, j, -a(i, j));
231 | }
232 | }
233 | // result is now the inverse
234 | return result;
235 | }
236 |
237 | private:
238 | unique_ptr data;
239 | int width, height;
240 | };
241 |
242 | template
243 | array2d operator*(const T scalar, array2d a) {
244 | return a * scalar;
245 | }
246 |
247 | template
248 | class Mat
249 | {
250 | public:
251 | Mat()
252 | {
253 | this->width = 0;
254 | this->height = 0;
255 | }
256 |
257 | Mat(int height, int width)
258 | {
259 | reset(height, width);
260 | }
261 |
262 | Mat(const Mat& rhs)
263 | {
264 | width = rhs.get_width();
265 | height = rhs.height;
266 | const auto area = (size_t) (width * height);
267 | data = make_unique(area);
268 | copy(rhs.data.get(), rhs.data.get() + (width * height), data.get());
269 | }
270 |
271 | inline T& at(int row, int col)
272 | {
273 | return data[row * width + col];
274 | }
275 |
276 | inline const T& at(int row, int col) const
277 | {
278 | return data[row * width + col];
279 | }
280 |
281 | inline T& operator()(int row, int col)
282 | {
283 | return data[row * width + col];
284 | }
285 |
286 | inline const T& operator()(int row, int col) const
287 | {
288 | return data[row * width + col];
289 | }
290 |
291 | inline T* get() const
292 | {
293 | return data.get();
294 | }
295 |
296 | inline int get_width() const { return width; }
297 | inline int get_height() const { return height; }
298 |
299 | Mat& operator*=(const T scalar) {
300 | for (int i = 0; i<(width * height); i++)
301 | data[i] *= scalar;
302 | return *this;
303 | }
304 |
305 | Mat& operator/=(const T scalar) {
306 | for (int i = 0; i<(width * height); i++)
307 | data[i] /= scalar;
308 | return *this;
309 | }
310 |
311 | Mat operator*(const T scalar) {
312 | Mat result(*this);
313 | result *= scalar;
314 | return result;
315 | }
316 |
317 | vector operator*(const vector& vec) {
318 | vector result(get_height());
319 | for (int row = 0; rowwidth = width;
332 | this->height = height;
333 | const auto area = (size_t) (width * height);
334 | data = make_unique(area);
335 | }
336 |
337 | private:
338 | unique_ptr data;
339 | int width, height;
340 | };
341 |
342 | template
343 | Mat operator*(T scalar, Mat a) {
344 | return a * scalar;
345 | }
346 |
347 | // =============================================================
348 | // Quantizer objects and functions
349 | //
350 | // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
351 | // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
352 | // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
353 | // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
354 | // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
355 | // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
356 | // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
357 | // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
358 | // THIS DISCLAIMER.
359 | //
360 | // Use at your own risk!
361 | // =============================================================
362 |
363 | class EdgeAwareSQuantizer
364 | {
365 | public:
366 | bool QuantizeImage(Bitmap* pSource, Bitmap* pDest, UINT& nMaxColors, bool dither = false);
367 | };
368 | }
--------------------------------------------------------------------------------
/nQuantCpp/GifWriter.cpp:
--------------------------------------------------------------------------------
1 | #include "stdafx.h"
2 | #include "GifWriter.h"
3 |
4 | namespace GifEncode
5 | {
6 | const int UintBytes = sizeof(long);
7 |
8 | GifWriter::GifWriter(const wstring& destPath, const bool hasAlpha, const long delay, const bool loop)
9 | {
10 | _destPath = destPath;
11 | _hasAlpha = hasAlpha;
12 | _delay = delay;
13 | _loop = loop;
14 | }
15 |
16 | static GUID GetEncoder()
17 | {
18 | const CLSID gifEncoderClsId = { 0x557cf402, 0x1a04, 0x11d3,{ 0x9a,0x73,0x00,0x00,0xf8,0x1e,0xf3,0x2e } };
19 | return gifEncoderClsId;
20 | }
21 |
22 | static void SetFrameDelay(Bitmap* pFirstBitmap, PropertyItem& frameDelay, vector& frameDelays)
23 | {
24 | // PropertyItem for the frame delay (apparently, no other way to create a fresh instance).
25 | frameDelay.id = PropertyTagFrameDelay;
26 | frameDelay.type = PropertyTagTypeLong;
27 | // Length of the value in bytes.
28 | frameDelay.length = frameDelays.size() * UintBytes;
29 | // The value is an array of 4-byte entries: one per frame.
30 | // Every entry is the frame delay in 1/100-s of a second, in little endian.
31 | // E.g., here, we're setting the delay of every frame to 1 second.
32 | frameDelay.value = frameDelays.data();
33 | pFirstBitmap->SetPropertyItem(&frameDelay);
34 | }
35 |
36 | static void SetLoop(Bitmap* pFirstBitmap, PropertyItem& loopPropertyItem, const bool loop)
37 | {
38 | if (!loop)
39 | return;
40 |
41 | loopPropertyItem.id = PropertyTagLoopCount;
42 | loopPropertyItem.type = PropertyTagTypeShort;
43 | loopPropertyItem.length = 2;
44 | // 0 means to animate forever.
45 | short sValue = 0;
46 | loopPropertyItem.value = &sValue;
47 | pFirstBitmap->SetPropertyItem(&loopPropertyItem);
48 | }
49 |
50 | Status GifWriter::AddImages(vector >& bitmaps)
51 | {
52 | auto& pFirstBitmap = bitmaps[0];
53 | auto gifGUID = GetEncoder();
54 |
55 | // Params of the first frame.
56 | EncoderParameters encoderParams;
57 | encoderParams.Count = 1;
58 | encoderParams.Parameter[0].Guid = EncoderSaveFlag;
59 | encoderParams.Parameter[0].NumberOfValues = 1;
60 | encoderParams.Parameter[0].Type = EncoderParameterValueTypeLong;
61 | auto valueMultiFrame = EncoderValueMultiFrame;
62 | encoderParams.Parameter[0].Value = &valueMultiFrame;
63 |
64 | PropertyItem frameDelay;
65 | vector frameDelays(bitmaps.size(), _delay / 10);
66 | SetFrameDelay(pFirstBitmap.get(), frameDelay, frameDelays);
67 | PropertyItem loopPropertyItem;
68 | SetLoop(pFirstBitmap.get(), loopPropertyItem, _loop);
69 | pFirstBitmap->Save(_destPath.c_str(), &gifGUID, &encoderParams);
70 |
71 | // Params of other frames.
72 | auto valueFrameDimensionTime = EncoderValueFrameDimensionTime;
73 | encoderParams.Parameter[0].Value = &valueFrameDimensionTime;
74 | Status status;
75 | for (int i = 1; i < bitmaps.size(); ++i)
76 | status = pFirstBitmap->SaveAdd(bitmaps[i].get(), &encoderParams);
77 |
78 | // Params for the finalizing call.
79 | encoderParams.Parameter[0].Type = EncoderParameterValueTypeLong;
80 | auto valueFlush = EncoderValueFlush;
81 | encoderParams.Parameter[0].Value = &valueFlush;
82 | return pFirstBitmap->SaveAdd(&encoderParams);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/nQuantCpp/GifWriter.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "bitmapUtilities.h"
3 |
4 | namespace GifEncode
5 | {
6 | class GifWriter
7 | {
8 | private:
9 | bool _hasAlpha, _loop;
10 | wstring _destPath;
11 | long _delay;
12 |
13 | public:
14 | GifWriter(const wstring& destPath, const bool hasAlpha, const long delay = 850, const bool loop = true);
15 | Status AddImages(vector >& bitmaps);
16 | };
17 | }
18 |
--------------------------------------------------------------------------------
/nQuantCpp/GilbertCurve.cpp:
--------------------------------------------------------------------------------
1 | /* Generalized Hilbert ("gilbert") space-filling curve for rectangular domains of arbitrary (non-power of two) sizes.
2 | Copyright (c) 2021 - 2025 Miller Cy Chan
3 | * A general rectangle with a known orientation is split into three regions ("up", "right", "down"), for which the function calls itself recursively, until a trivial path can be produced. */
4 |
5 | #include "stdafx.h"
6 | #include "GilbertCurve.h"
7 | #include "BlueNoise.h"
8 | #include "CIELABConvertor.h"
9 |
10 | #include
11 | #include
12 | #include
13 |
14 | namespace Peano
15 | {
16 | struct ErrorBox
17 | {
18 | double yDiff = 0;
19 | float p[4] = { 0 };
20 |
21 | ErrorBox() {
22 | }
23 | ErrorBox(const Color& c) {
24 | p[0] = c.GetR();
25 | p[1] = c.GetG();
26 | p[2] = c.GetB();
27 | p[3] = c.GetA();
28 | }
29 | inline float& operator[](int index)
30 | {
31 | return p[index];
32 | }
33 | inline BYTE length() const
34 | {
35 | return 4;
36 | }
37 | };
38 |
39 | bool m_hasAlpha, m_dither, sortedByYDiff;
40 | unsigned short m_nMaxColor;
41 | UINT m_width, m_height;
42 | float beta;
43 | double m_weight;
44 | const ARGB *m_image, *m_pPalette;
45 | unsigned short* m_qPixels;
46 | ARGB* m_qColorPixels;
47 | DitherFn m_ditherFn;
48 | float* m_saliencies;
49 | GetColorIndexFn m_getColorIndexFn;
50 | list errorq;
51 | vector m_weights;
52 | unsigned short* m_lookup;
53 | static BYTE DITHER_MAX = 9, ditherMax;
54 | static int margin, thresold;
55 | static const float BLOCK_SIZE = 343.0f;
56 |
57 | template int sign(T val) {
58 | return (T(0) < val) - (val < T(0));
59 | }
60 |
61 | template
62 | void insert_in_order(list& items, T& element, int (*compareFn)(const T& a, const T& b)) {
63 | auto begin = items.begin();
64 | auto end = items.end();
65 | while (begin != end && compareFn(*begin, element) < 0)
66 | ++begin;
67 |
68 | items.insert(begin, element);
69 | }
70 |
71 | void initWeights(int size) {
72 | /* Dithers all pixels of the image in sequence using
73 | * the Gilbert path, and distributes the error in
74 | * a sequence of pixels size.
75 | */
76 | errorq.resize(size);
77 | const auto weightRatio = (float)pow(BLOCK_SIZE + 1.0f, 1.0f / (size - 1.0f));
78 | auto weight = 1.0f;
79 | auto sumweight = 0.0f;
80 | m_weights.resize(size);
81 | for (int c = 0; c < size; ++c) {
82 | sumweight += (m_weights[size - c - 1] = 1.0f / weight);
83 | weight *= weightRatio;
84 | }
85 |
86 | weight = 0.0f; /* Normalize */
87 | for (int c = 0; c < size; ++c)
88 | weight += (m_weights[c] /= sumweight);
89 | m_weights[0] += 1.0f - weight;
90 | }
91 |
92 | int compare(const ErrorBox& o1, const ErrorBox& o2)
93 | {
94 | if (o2.yDiff < o1.yDiff)
95 | return -1;
96 | if (o2.yDiff > o1.yDiff)
97 | return 1;
98 | return 0;
99 | }
100 |
101 | unsigned short ditherPixel(int x, int y, Color c2, float beta)
102 | {
103 | int bidx = x + y * m_width;
104 | Color pixel(m_image[bidx]);
105 | auto r_pix = c2.GetR();
106 | auto g_pix = c2.GetG();
107 | auto b_pix = c2.GetB();
108 | auto a_pix = c2.GetA();
109 |
110 | auto qPixelIndex = m_qPixels[bidx];
111 | auto strength = 1 / 3.0f;
112 | int acceptedDiff = max(2, m_nMaxColor - margin);
113 | if (m_nMaxColor <= 4 && m_saliencies[bidx] > .2f && m_saliencies[bidx] < .25f)
114 | c2 = BlueNoise::diffuse(pixel, m_pPalette[qPixelIndex], beta * 2 / m_saliencies[bidx], strength, x, y);
115 | else if (m_nMaxColor <= 4 || CIELABConvertor::Y_Diff(pixel, c2) < (2 * acceptedDiff)) {
116 | c2 = BlueNoise::diffuse(pixel, m_pPalette[qPixelIndex], beta * .5f / m_saliencies[bidx], strength, x, y);
117 | if (m_nMaxColor <= 4 && CIELABConvertor::U_Diff(pixel, c2) > (8 * acceptedDiff)) {
118 | Color c1 = m_saliencies[bidx] > .65f ? pixel : Color::MakeARGB(a_pix, r_pix, g_pix, b_pix);
119 | c2 = BlueNoise::diffuse(c1, m_pPalette[qPixelIndex], beta * m_saliencies[bidx], strength, x, y);
120 | }
121 | if (CIELABConvertor::U_Diff(pixel, c2) > (margin * acceptedDiff))
122 | c2 = BlueNoise::diffuse(pixel, m_pPalette[qPixelIndex], beta / m_saliencies[bidx], strength, x, y);
123 | }
124 |
125 | if (m_nMaxColor < 3 || margin > 6) {
126 | auto delta = (m_weight > .0015 && m_weight < .0025) ? beta : M_PI;
127 | if (m_nMaxColor > 4 && (CIELABConvertor::Y_Diff(pixel, c2) > (delta * acceptedDiff) || CIELABConvertor::U_Diff(pixel, c2) > (margin * acceptedDiff))) {
128 | auto kappa = m_saliencies[bidx] < .4f ? beta * .4f * m_saliencies[bidx] : beta * .4f / m_saliencies[bidx];
129 | Color c1 = m_saliencies[bidx] < .4f ? pixel : Color::MakeARGB(a_pix, r_pix, g_pix, b_pix);
130 | c2 = BlueNoise::diffuse(c1, m_pPalette[qPixelIndex], kappa, strength, x, y);
131 | }
132 | }
133 | else if (m_nMaxColor > 4 && (CIELABConvertor::Y_Diff(pixel, c2) > (beta * acceptedDiff) || CIELABConvertor::U_Diff(pixel, c2) > acceptedDiff)) {
134 | if(beta < .3f && (m_nMaxColor <= 32 || m_saliencies[bidx] < beta))
135 | c2 = BlueNoise::diffuse(c2, m_pPalette[qPixelIndex], beta * .4f * m_saliencies[bidx], strength, x, y);
136 | else
137 | c2 = Color::MakeARGB(a_pix, r_pix, g_pix, b_pix);
138 | }
139 |
140 | if (DITHER_MAX < 16 && m_nMaxColor > 4 && m_saliencies[bidx] < .6f && CIELABConvertor::Y_Diff(pixel, c2) > margin - 1)
141 | c2 = Color::MakeARGB(a_pix, r_pix, g_pix, b_pix);
142 | if (beta > 1 && CIELABConvertor::Y_Diff(pixel, c2) > DITHER_MAX)
143 | c2 = Color::MakeARGB(a_pix, r_pix, g_pix, b_pix);
144 |
145 | int offset = m_getColorIndexFn(c2);
146 | if (!m_lookup[offset])
147 | m_lookup[offset] = m_ditherFn(m_pPalette, m_nMaxColor, c2.GetValue(), bidx) + 1;
148 | return m_lookup[offset] - 1;
149 | }
150 |
151 | void diffusePixel(int x, int y)
152 | {
153 | int bidx = x + y * m_width;
154 | Color pixel(m_image[bidx]);
155 | ErrorBox error(pixel);
156 | int i = sortedByYDiff ? m_weights.size() - 1 : 0;
157 | auto maxErr = DITHER_MAX - 1;
158 | for (auto& eb : errorq) {
159 | if (i < 0 || i >= m_weights.size())
160 | break;
161 |
162 | for (int j = 0; j < eb.length(); ++j) {
163 | error[j] += eb[j] * m_weights[i];
164 | if (error[j] > maxErr)
165 | maxErr = error[j];
166 | }
167 | i += sortedByYDiff ? -1 : 1;
168 | }
169 |
170 | auto r_pix = static_cast(min(BYTE_MAX, max(error[0], 0)));
171 | auto g_pix = static_cast(min(BYTE_MAX, max(error[1], 0)));
172 | auto b_pix = static_cast(min(BYTE_MAX, max(error[2], 0)));
173 | auto a_pix = static_cast(min(BYTE_MAX, max(error[3], 0)));
174 |
175 | Color c2 = Color::MakeARGB(a_pix, r_pix, g_pix, b_pix);
176 | auto qPixelIndex = m_qPixels[bidx];
177 | if (m_saliencies != nullptr && m_dither && !sortedByYDiff)
178 | qPixelIndex = ditherPixel(x, y, c2, beta);
179 | else if (m_nMaxColor <= 32 && a_pix > 0xF0)
180 | {
181 | int offset = m_getColorIndexFn(c2);
182 | if (!m_lookup[offset])
183 | m_lookup[offset] = m_ditherFn(m_pPalette, m_nMaxColor, c2.GetValue(), bidx) + 1;
184 | qPixelIndex = m_lookup[offset] - 1;
185 |
186 | int acceptedDiff = max(2, m_nMaxColor - margin);
187 | if (m_saliencies != nullptr && (CIELABConvertor::Y_Diff(pixel, c2) > acceptedDiff || CIELABConvertor::U_Diff(pixel, c2) > (2 * acceptedDiff))) {
188 | auto strength = 1 / 3.0f;
189 | c2 = BlueNoise::diffuse(pixel, m_pPalette[qPixelIndex], 1 / m_saliencies[bidx], strength, x, y);
190 | qPixelIndex = m_ditherFn(m_pPalette, m_nMaxColor, c2.GetValue(), bidx);
191 | }
192 | }
193 | else
194 | qPixelIndex = m_ditherFn(m_pPalette, m_nMaxColor, c2.GetValue(), bidx);
195 |
196 | if (errorq.size() >= DITHER_MAX)
197 | errorq.pop_front();
198 | else if (!errorq.empty())
199 | initWeights(errorq.size());
200 |
201 | c2 = m_pPalette[qPixelIndex];
202 | if (m_qPixels)
203 | m_qPixels[bidx] = qPixelIndex;
204 | else if (m_hasAlpha)
205 | m_qColorPixels[bidx] = c2.GetValue();
206 | else {
207 | Color c0 = m_pPalette[0];
208 | m_qColorPixels[bidx] = GetARGBIndex(c2, false, c0.GetA() == 0);
209 | }
210 |
211 | error[0] = r_pix - c2.GetR();
212 | error[1] = g_pix - c2.GetG();
213 | error[2] = b_pix - c2.GetB();
214 | error[3] = a_pix - c2.GetA();
215 |
216 | auto denoise = m_nMaxColor > 2;
217 | auto diffuse = BlueNoise::TELL_BLUE_NOISE[bidx & 4095] > thresold;
218 | error.yDiff = sortedByYDiff ? CIELABConvertor::Y_Diff(pixel, c2) : 1;
219 | auto illusion = !diffuse && BlueNoise::TELL_BLUE_NOISE[(int)(error.yDiff * 4096) & 4095] > thresold;
220 |
221 | auto unaccepted = false;
222 | int errLength = denoise ? error.length() - 1 : 0;
223 | for (int j = 0; j < errLength; ++j) {
224 | if (abs(error.p[j]) >= ditherMax) {
225 | if (sortedByYDiff && m_saliencies != nullptr)
226 | unaccepted = true;
227 |
228 | if (diffuse)
229 | error[j] = (float)tanh(error.p[j] / maxErr * 20) * (ditherMax - 1);
230 | else if (illusion)
231 | error[j] = (float)(error.p[j] / maxErr * error.yDiff) * (ditherMax - 1);
232 | else
233 | error[j] /= (float)(1 + _sqrt(ditherMax));
234 | }
235 |
236 | if (sortedByYDiff && m_saliencies == nullptr && abs(error.p[j]) >= DITHER_MAX)
237 | unaccepted = true;
238 | }
239 |
240 | if (unaccepted) {
241 | if (m_saliencies != nullptr)
242 | qPixelIndex = ditherPixel(x, y, c2, 1.25f);
243 | else if (CIELABConvertor::Y_Diff(pixel, c2) > 3 && CIELABConvertor::U_Diff(pixel, c2) > 3) {
244 | auto strength = 1 / 3.0f;
245 | c2 = BlueNoise::diffuse(pixel, m_pPalette[qPixelIndex], strength, strength, x, y);
246 | qPixelIndex = m_ditherFn(m_pPalette, m_nMaxColor, c2.GetValue(), bidx);
247 | }
248 |
249 | c2 = m_pPalette[qPixelIndex];
250 | if (m_qPixels)
251 | m_qPixels[bidx] = qPixelIndex;
252 | else if (m_hasAlpha)
253 | m_qColorPixels[bidx] = c2.GetValue();
254 | else {
255 | Color c0 = m_pPalette[0];
256 | m_qColorPixels[bidx] = GetARGBIndex(c2, false, c0.GetA() == 0);
257 | }
258 | }
259 |
260 | if (sortedByYDiff)
261 | insert_in_order(errorq, error, &compare);
262 | else
263 | errorq.emplace_back(error);
264 | }
265 |
266 | void generate2d(int x, int y, int ax, int ay, int bx, int by) {
267 | int w = abs(ax + ay);
268 | int h = abs(bx + by);
269 | int dax = sign(ax);
270 | int day = sign(ay);
271 | int dbx = sign(bx);
272 | int dby = sign(by);
273 |
274 | if (h == 1) {
275 | for (int i = 0; i < w; ++i) {
276 | diffusePixel(x, y);
277 | x += dax;
278 | y += day;
279 | }
280 | return;
281 | }
282 |
283 | if (w == 1) {
284 | for (int i = 0; i < h; ++i) {
285 | diffusePixel(x, y);
286 | x += dbx;
287 | y += dby;
288 | }
289 | return;
290 | }
291 |
292 | int ax2 = ax / 2;
293 | int ay2 = ay / 2;
294 | int bx2 = bx / 2;
295 | int by2 = by / 2;
296 |
297 | int w2 = abs(ax2 + ay2);
298 | int h2 = abs(bx2 + by2);
299 |
300 | if (2 * w > 3 * h) {
301 | if ((w2 % 2) != 0 && w > 2) {
302 | ax2 += dax;
303 | ay2 += day;
304 | }
305 | generate2d(x, y, ax2, ay2, bx, by);
306 | generate2d(x + ax2, y + ay2, ax - ax2, ay - ay2, bx, by);
307 | return;
308 | }
309 |
310 | if ((h2 % 2) != 0 && h > 2) {
311 | bx2 += dbx;
312 | by2 += dby;
313 | }
314 |
315 | generate2d(x, y, bx2, by2, ax2, ay2);
316 | generate2d(x + bx2, y + by2, ax, ay, bx - bx2, by - by2);
317 | generate2d(x + (ax - dax) + (bx2 - dbx), y + (ay - day) + (by2 - dby), -bx2, -by2, -(ax - ax2), -(ay - ay2));
318 | }
319 |
320 | void doDither(const UINT width, const UINT height, const ARGB* pixels, const ARGB* pPalette, const UINT nMaxColor, DitherFn ditherFn, GetColorIndexFn getColorIndexFn, float* saliencies, double weight)
321 | {
322 | m_width = width;
323 | m_height = height;
324 | m_image = pixels;
325 | m_pPalette = pPalette;
326 | m_nMaxColor = nMaxColor;
327 |
328 | m_ditherFn = ditherFn;
329 | m_getColorIndexFn = getColorIndexFn;
330 | m_hasAlpha = weight < 0;
331 | m_saliencies = m_hasAlpha ? nullptr : saliencies;
332 |
333 | errorq.clear();
334 | weight = m_weight = abs(weight);
335 | margin = weight < .0025 ? 12 : weight < .004 ? 8 : 6;
336 | sortedByYDiff = m_saliencies && m_nMaxColor >= 128 && (!m_hasAlpha || weight < .18);
337 | beta = m_nMaxColor > 4 ? (float) (.6f - .00625f * m_nMaxColor) : 1;
338 | if (m_nMaxColor > 4) {
339 | auto boundary = .005 - .0000625 * m_nMaxColor;
340 | beta = (float) (weight > boundary ? max(.25, beta - m_nMaxColor * weight) : min(1.5, beta + m_nMaxColor * weight));
341 | if(m_nMaxColor > 32 && m_nMaxColor < 256)
342 | beta += .1f;
343 | }
344 | else
345 | beta *= .95f;
346 | if (m_nMaxColor > 64 || (m_nMaxColor > 4 && weight > .02))
347 | beta *= .4f;
348 | DITHER_MAX = weight < .015 ? (weight > .0025) ? (BYTE)25 : 16 : 9;
349 | auto edge = m_hasAlpha ? 1 : exp(weight) + .25;
350 | auto deviation = !m_hasAlpha && weight > .002 ? .25 : 1;
351 | ditherMax = (m_hasAlpha || DITHER_MAX > 9) ? (BYTE)sqr(_sqrt(DITHER_MAX) + edge * deviation) : (BYTE) (DITHER_MAX * 1.5);
352 | int density = m_nMaxColor > 16 ? 3200 : 1500;
353 | if (m_nMaxColor / weight > 5000 && (weight > .045 || (weight > .01 && m_nMaxColor < 64)))
354 | ditherMax = (BYTE)sqr(5 + edge);
355 | else if (weight < .03 && m_nMaxColor / weight < density && m_nMaxColor >= 16 && m_nMaxColor < 256)
356 | ditherMax = (BYTE)sqr(5 + edge);
357 | thresold = DITHER_MAX > 9 ? -112 : -64;
358 | auto pLookup = make_unique(USHRT_MAX + 1);
359 | m_lookup = pLookup.get();
360 |
361 | if (!sortedByYDiff)
362 | initWeights(DITHER_MAX);
363 |
364 | if (width >= height)
365 | generate2d(0, 0, width, 0, 0, height);
366 | else
367 | generate2d(0, 0, 0, height, width, 0);
368 | }
369 |
370 | void GilbertCurve::dither(const UINT width, const UINT height, const ARGB* pixels, const ARGB* pPalette, const UINT nMaxColor, DitherFn ditherFn, GetColorIndexFn getColorIndexFn, unsigned short* qPixels, float* saliencies, double weight, bool dither)
371 | {
372 | m_qPixels = qPixels;
373 | m_dither = dither;
374 | doDither(width, height, pixels, pPalette, nMaxColor, ditherFn, getColorIndexFn, saliencies, weight);
375 | }
376 |
377 | void GilbertCurve::dither(const UINT width, const UINT height, const ARGB* pixels, const ARGB* pPalette, const UINT nMaxColor, DitherFn ditherFn, GetColorIndexFn getColorIndexFn, ARGB* qPixels, float* saliencies, double weight, bool dither)
378 | {
379 | m_qColorPixels = qPixels;
380 | m_dither = dither;
381 | doDither(width, height, pixels, pPalette, nMaxColor, ditherFn, getColorIndexFn, saliencies, weight);
382 | }
383 | }
384 |
--------------------------------------------------------------------------------
/nQuantCpp/GilbertCurve.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "bitmapUtilities.h"
3 |
4 | namespace Peano
5 | {
6 | class GilbertCurve
7 | {
8 | public:
9 | static void dither(const UINT width, const UINT height, const ARGB* pixels, const ARGB* pPalette, const UINT nMaxColor, DitherFn ditherFn, GetColorIndexFn getColorIndexFn, unsigned short* qPixels, float* saliencies, double weight = 1.0, bool dither = true);
10 |
11 | static void dither(const UINT width, const UINT height, const ARGB* pixels, const ARGB* pPalette, const UINT nMaxColor, DitherFn ditherFn, GetColorIndexFn getColorIndexFn, ARGB* qPixels, float* saliencies, double weight = 1.0, bool dither = true);
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/nQuantCpp/MedianCut.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright(c) 1989-1991 Jef Poskanzer.
3 | Copyright(c) 1997-2002 Greg Roelofs; based on an idea by Stefan Schneider.
4 | Copyright(c) 2009-2015 by Kornel Lesiński.
5 | Copyright(c) 2015 Hao-Zhi Huang
6 | Copyright (c) 2018 Miller Cy Chan
7 |
8 | All rights reserved.
9 |
10 | Redistribution and use in source and binary forms, with or without modification,
11 | are permitted provided that the following conditions are met:
12 |
13 | 1. Redistributions of source code must retain the above copyright notice,
14 | this list of conditions and the following disclaimer.
15 |
16 | 2. Redistributions in binary form must reproduce the above copyright notice,
17 | this list of conditions and the following disclaimer in the documentation
18 | and/or other materials provided with the distribution.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | */
31 |
32 | #pragma once
33 | #include
34 | #include
35 | #include "EdgeAwareSQuantizer.h"
36 |
37 | using namespace EdgeAwareSQuant;
38 |
39 | //#define VITER_CACHE_LINE_GAP ((64+sizeof(viter_state)-1)/sizeof(viter_state))
40 |
41 | namespace MedianCutQuant
42 | {
43 | class MedianCut
44 | {
45 | public:
46 | virtual int quantizeImg(const vector& pixels, const UINT& width, Mat& saliencyMap_float, ColorPalette* pPalette, UINT& newcolors);
47 | bool QuantizeImage(Bitmap* pSource, Bitmap* pDest, UINT& nMaxColors, bool dither = true);
48 | };
49 | }
--------------------------------------------------------------------------------
/nQuantCpp/MoDEQuantizer.cpp:
--------------------------------------------------------------------------------
1 | /* Multiobjective Image Color Quantization Algorithm Based on Self-Adaptive Hybrid Differential Evolution
2 | Copyright (C) 2014-2016 Zhongbo Hu, Qinghua Su, Xuewen Xia
3 | Copyright (c) 2018 - 2021 Miller Cy Chan
4 | * Adaptive algorithm k-means idea-accelerated differential evolution algorithm: each individual is a set of cluster centers, according to the probability K_probability
5 | * The mean center of the K_number sub-cluster of this class replaces the original individual (following the acceptance criteria of parent-child competition)
6 | * Iterate the differential evolution algorithm
7 | * Initialize the individual is the pixel in the picture
8 | * The program with the smallest error within the class + Maximum distance between classes + Minimize MSE */
9 |
10 | #include "stdafx.h"
11 | #include "MoDEQuantizer.h"
12 | #include
13 | #include // std::setprecision
14 | #include
15 |
16 | namespace MoDEQuant
17 | {
18 | const double a1 = 0.1, a2 = 0.05, a3 = 0.01; // Linear combination parameters
19 | const unsigned short K_number = 10; // Number of cluster iterations
20 | const double K_probability = 0.05; // Probability of cluster iteration for each individual
21 | const unsigned short N = 100; // population size
22 | const double Max_diff = 100.0; // Maximum variation
23 | const BYTE high = BYTE_MAX;
24 | const BYTE low = 0; // the initial bounding region
25 | const int my_gens = 200; //the generation number
26 | const int LOOP = 5; //loop number
27 | const int seed[50] = { 20436,18352,10994,26845,24435,29789,28299,11375,10222,9885,25855,4282,22102,29385,16014,32018,3200,11252,6227,5939,8712,12504,25965,6101,30359,1295,29533,19841,14690,2695,3503,16802,18931,28464,1245,13279,5676,8951,7280,24488,6537,27128,9320,16399,24997,24303,16862,17882,15360,31216 };
28 |
29 | BYTE SIDE = 3;
30 | bool hasSemiTransparency = false;
31 | int m_transparentPixelIndex = -1;
32 | ARGB m_transparentColor = Color::Transparent;
33 | unordered_map > closestMap;
34 |
35 | inline double rand1()
36 | {
37 | return (double)rand() / (RAND_MAX + 1.0);
38 | }
39 |
40 | unsigned short find_nn(const vector& data, const Color& c, double& idis)
41 | {
42 | auto argb = c.GetValue();
43 | const unsigned short nMaxColors = data.size() / SIDE;
44 | unsigned short temp_k = nMaxColors; //Record the ith pixel is divided into classes in the center of the temp_k
45 | for (unsigned short k = 0; k < nMaxColors; ++k) {
46 | double iidis = sqr(data[SIDE * k] - c.GetB());
47 | if (iidis >= idis)
48 | continue;
49 |
50 | iidis += sqr(data[SIDE * k + 1] - c.GetG());
51 | if (iidis >= idis)
52 | continue;
53 |
54 | iidis += sqr(data[SIDE * k + 2] - c.GetR());
55 | if (iidis >= idis)
56 | continue;
57 |
58 | if (hasSemiTransparency) {
59 | iidis += sqr(data[SIDE * k + 3] - c.GetA());
60 | if (iidis >= idis)
61 | continue;
62 | }
63 |
64 | idis = iidis;
65 | temp_k = k; //Record the ith pixel is divided into classes in the center of the temp_k
66 | }
67 | return temp_k;
68 | }
69 |
70 | void updateCentroids(vector& data, double* temp_x, const int* temp_x_number)
71 | {
72 | const unsigned short nMaxColors = data.size() / SIDE;
73 |
74 | for (unsigned short i = 0; i < nMaxColors; ++i) { //update classes and centroids
75 | if (temp_x_number[i] > 0) {
76 | data[SIDE * i] = temp_x[SIDE * i] / temp_x_number[i];
77 | data[SIDE * i + 1] = temp_x[SIDE * i + 1] / temp_x_number[i];
78 | data[SIDE * i + 2] = temp_x[SIDE * i + 2] / temp_x_number[i];
79 | if (hasSemiTransparency)
80 | data[SIDE * i + 3] = temp_x[SIDE * i + 3] / temp_x_number[i];
81 | }
82 | }
83 | }
84 |
85 | double evaluate1(const vector& pixels, const vector& data)
86 | {
87 | const unsigned short nMaxColors = data.size() / SIDE;
88 | UINT nSize = pixels.size();
89 | auto k_class_Number = make_unique(nMaxColors); //store distance of each class and related parameters
90 | auto k_class_dis = make_unique(nMaxColors);
91 | for (UINT i = 0; i < nSize; ++i) {
92 | double idis = INT_MAX;
93 | auto k_class_Temp = find_nn(data, pixels[i], idis);
94 | k_class_dis[k_class_Temp] += _sqrt(idis);
95 | k_class_Number[k_class_Temp]++;
96 | }
97 |
98 | double dis_sum = 0.0;
99 | for (unsigned short j = 0; j < nMaxColors; ++j) {
100 | double TT = k_class_dis[j] / k_class_Number[j];
101 | if (dis_sum < TT)
102 | dis_sum = TT;
103 | }
104 | return dis_sum;
105 | }
106 |
107 | // Adaptation function designed for multiple targets (1): the minimum value of each inner class distance is the smallest
108 | double evaluate1_K(const vector& pixels, vector& data) //Adaptive value function with K-means variation
109 | {
110 | const unsigned short nMaxColors = data.size() / SIDE;
111 | UINT nSize = pixels.size();
112 | auto temp_i_k = make_unique(nSize);
113 |
114 | for (int ii = 0; ii < K_number; ++ii) {
115 | auto temp_x_number = make_unique(nMaxColors); //store the pixel count of each class
116 | auto temp_x = make_unique(data.size()); //store average value centroid
117 | for (UINT i = 0; i < nSize; ++i) {
118 | double idis = INT_MAX;
119 | Color c(pixels[i]);
120 | auto temp_k = find_nn(data, c, idis);
121 | temp_x_number[temp_k]++;
122 | temp_x[SIDE * temp_k] += c.GetB(); //Put each pixel of the original image into categories and put them in an array
123 | temp_x[SIDE * temp_k + 1] += c.GetG();
124 | temp_x[SIDE * temp_k + 2] += c.GetR();
125 | if (hasSemiTransparency)
126 | temp_x[SIDE * temp_k + 3] += c.GetA();
127 | temp_i_k[i] = temp_k;
128 | }
129 | updateCentroids(data, temp_x.get(), temp_x_number.get());
130 | }
131 |
132 | auto k_class_dis = make_unique(nMaxColors);
133 | auto k_class_Number = make_unique(nMaxColors);
134 | for (UINT i = 0; i < nSize; ++i) {
135 | Color c(pixels[i]);
136 | int j = SIDE * temp_i_k[i];
137 | int jj = temp_i_k[i];
138 | k_class_Number[jj]++;
139 | double iid0 = sqr(data[j] - c.GetB());
140 | double iid1 = sqr(data[j + 1] - c.GetG());
141 | double iid2 = sqr(data[j + 2] - c.GetR());
142 | double iid3 = hasSemiTransparency ? sqr(data[j + 3] - c.GetA()) : 0;
143 | k_class_dis[jj] += _sqrt(iid0 + iid1 + iid2 + iid3);
144 | }
145 |
146 | double dis_sum = 0.0;
147 | for (unsigned short j = 0; j < nMaxColors; ++j) {
148 | double TT = k_class_dis[j] / k_class_Number[j];
149 | if (dis_sum < TT)
150 | dis_sum = TT;
151 | }
152 | return dis_sum;
153 | }
154 |
155 | double evaluate2(const vector& pixels, vector& data, const int K_num = 1) //Adaptive value function with K-means variation
156 | {
157 | const unsigned short nMaxColors = data.size() / SIDE;
158 | UINT nSize = pixels.size();
159 |
160 | for (int ii = 0; ii < K_num; ++ii) {
161 | auto temp_x_number = make_unique(nMaxColors); //store the pixel count of each class
162 | auto temp_x = make_unique(data.size()); //store average value centroid
163 | for (UINT i = 0; i < nSize; ++i) {
164 | double idis = INT_MAX;
165 | Color c(pixels[i]);
166 | auto temp_k = find_nn(data, c, idis);
167 | temp_x_number[temp_k]++;
168 | temp_x[SIDE * temp_k] += c.GetB(); //Put each pixel of the original image into categories and put them in an array
169 | temp_x[SIDE * temp_k + 1] += c.GetG();
170 | temp_x[SIDE * temp_k + 2] += c.GetR();
171 | if (hasSemiTransparency)
172 | temp_x[SIDE * temp_k + 3] += c.GetA();
173 | }
174 | updateCentroids(data, temp_x.get(), temp_x_number.get());
175 | }
176 |
177 | double Temp_dis = INT_MAX;
178 | for (unsigned short i = 0; i < nMaxColors - 1; ++i) { // Calculate the fitness value after several clusters(class with minimum distance)
179 | for (unsigned short j = i + 1; j < nMaxColors; ++j) {
180 | double T_Temp_dis = sqr(data[SIDE * i] - data[SIDE * j]);
181 | if (T_Temp_dis > Temp_dis)
182 | continue;
183 |
184 | T_Temp_dis += sqr(data[SIDE * i + 1] - data[SIDE * j + 1]);
185 | if (T_Temp_dis > Temp_dis)
186 | continue;
187 |
188 | T_Temp_dis += sqr(data[SIDE * i + 2] - data[SIDE * j + 2]);
189 | if (T_Temp_dis > Temp_dis)
190 | continue;
191 |
192 | if (hasSemiTransparency) {
193 | T_Temp_dis += sqr(data[SIDE * i + 3] - data[SIDE * j + 3]);
194 | if (T_Temp_dis > Temp_dis)
195 | continue;
196 | }
197 |
198 | Temp_dis = T_Temp_dis;
199 | }
200 | }
201 | return _sqrt(Temp_dis);
202 | }
203 |
204 | // designed for multi objective application function(2):to maximize the minimum distance of class
205 | double evaluate2_K(const vector& pixels, vector& data) //Adaptive value function with K-means variation
206 | {
207 | return evaluate2(pixels, data, K_number);
208 | }
209 |
210 | //designed for multi objective application function(3) MSE
211 | double evaluate3(const vector& pixels, const vector& data)
212 | {
213 | const unsigned short nMaxColors = data.size() / SIDE;
214 | double dis_sum = 0.0;
215 | UINT nSize = pixels.size();
216 | for (UINT i = 0; i < nSize; ++i) {
217 | double idis = INT_MAX;
218 | auto k_class_Temp = find_nn(data, pixels[i], idis);
219 | if (k_class_Temp < nMaxColors)
220 | dis_sum += _sqrt(idis);
221 | }
222 |
223 | return dis_sum / nSize;
224 | }
225 |
226 | double evaluate3_K(const vector& pixels, vector& data) //Adaptive value function with K-means variation
227 | {
228 | const unsigned short nMaxColors = data.size() / SIDE;
229 | UINT nSize = pixels.size();
230 | auto temp_i_k = make_unique(nSize);
231 |
232 | for (int ii = 0; ii < K_number; ++ii) {
233 | auto temp_x_number = make_unique(nMaxColors); //store the pixel count of each class
234 | auto temp_x = make_unique(data.size()); //store average value centroid
235 | for (UINT i = 0; i < nSize; ++i) {
236 | double idis = INT_MAX;
237 | Color c(pixels[i]);
238 | auto temp_k = find_nn(data, c, idis);
239 | temp_x_number[temp_k]++;
240 | temp_x[SIDE * temp_k] += c.GetB(); //Put each pixel of the original image into categories and put them in an array
241 | temp_x[SIDE * temp_k + 1] += c.GetG();
242 | temp_x[SIDE * temp_k + 2] += c.GetR();
243 | if (hasSemiTransparency)
244 | temp_x[SIDE * temp_k + 3] += c.GetA();
245 | temp_i_k[i] = temp_k;
246 | }
247 | updateCentroids(data, temp_x.get(), temp_x_number.get());
248 | }
249 |
250 | double dis_sum = 0.0;
251 | for (UINT i = 0; i < nSize; ++i) {
252 | Color c(pixels[i]);
253 | int j = SIDE * temp_i_k[i];
254 | double iid0 = sqr(data[j] - c.GetB());
255 | double iid1 = sqr(data[j + 1] - c.GetG());
256 | double iid2 = sqr(data[j + 2] - c.GetR());
257 | double iid3 = hasSemiTransparency ? sqr(data[j + 3] - c.GetA()) : 0;
258 | dis_sum += _sqrt(iid0 + iid1 + iid2 + iid3);
259 | }
260 |
261 | return dis_sum / nSize;
262 | }
263 |
264 | int moDEquan(const vector& pixels, ColorPalette* pPalette, const unsigned short nMaxColors)
265 | {
266 | const BYTE INCR_STEP = 1;
267 | const float INCR_PERC = INCR_STEP * 100.0f / my_gens;
268 | const clock_t begin = clock();
269 | cout << std::setprecision(1) << std::fixed;
270 |
271 | const UINT nSizeInit = pixels.size();
272 | const UINT D = nMaxColors * SIDE;
273 | auto x1 = make_unique[]>(N);
274 | auto x2 = make_unique[]>(N);
275 | for (int i = 0; i < N; ++i) {
276 | x1[i].resize(D);
277 | x2[i].resize(D);
278 | }
279 |
280 | double cost[N];
281 | auto bestx = make_unique(D);
282 |
283 | float percCompleted = 0;
284 | const int ii = LOOP - 1;
285 |
286 | double F = 0.5, CR = 0.6, BVATG = INT_MAX;
287 | srand(seed[ii]);
288 | for (int i = 0; i < N; ++i) { //the initial population
289 | for (UINT j = 0; j < D; j += SIDE) {
290 | int TempInit = static_cast(rand1() * nSizeInit);
291 | Color c(pixels[TempInit]);
292 | x1[i][j] = c.GetR();
293 | }
294 |
295 | cost[i] = a1 * evaluate1(pixels, x1[i]);
296 | cost[i] -= a2 * evaluate2(pixels, x1[i]);
297 | cost[i] += a3 * evaluate3(pixels, x1[i]) + 1000;
298 |
299 | if (cost[i] < BVATG) {
300 | BVATG = cost[i];
301 | for (UINT j = 0; j < D; ++j)
302 | bestx[j] = x1[i][j];
303 | }
304 | }
305 |
306 | for (int g = 0; g < my_gens; ++g) { //generation loop
307 | if (g % INCR_STEP == 0) {
308 | int elapsed_secs = int(clock() - begin) / CLOCKS_PER_SEC;
309 | cout << "\rMultiobjective CQ ALGO Based on Self-Adaptive Hybrid DE: " << percCompleted << "% COMPL (" << elapsed_secs << " sec)" << std::flush;
310 | percCompleted += INCR_PERC;
311 | }
312 |
313 | for (int i = 0; i < N; ++i) {
314 | if (rand1() < K_probability) { // individual according to probability to perform clustering
315 | double temp_costx1 = cost[i];
316 | cost[i] = a1 * evaluate1_K(pixels, x1[i]);
317 | cost[i] -= a2 * evaluate2_K(pixels, x1[i]);
318 | cost[i] += a3 * evaluate3_K(pixels, x1[i]) + 1000; // clustered and changed the original data of x1[i]
319 |
320 | if (cost[i] >= temp_costx1)
321 | cost[i] = temp_costx1;
322 | else if (BVATG >= cost[i]) {
323 | BVATG = cost[i];
324 | // update with the best individual
325 | for (UINT j = 0; j < D; ++j)
326 | bestx[j] = x1[i][j];
327 | }
328 | }
329 | else { // Differential Evolution
330 | int d, b;
331 | do {
332 | d = (int)(rand1() * N);
333 | } while (d == i);
334 | do {
335 | b = (int)(rand1() * N);
336 | } while (b == d || b == i);
337 |
338 | int jr = (int)(rand1() * D); // every individual update control parameters
339 | if (rand1() < 0.1) {
340 | F = 0.1 + rand1() * 0.9;
341 | CR = rand1();
342 | }
343 |
344 | for (UINT j = 0; j < D; ++j) {
345 | if (rand1() <= CR || j == jr) {
346 | double diff = (x1[d][j] - x1[b][j]);
347 | if (diff > Max_diff)
348 | diff -= Max_diff;
349 | if (diff > Max_diff)
350 | diff -= Max_diff;
351 | if (diff < -Max_diff)
352 | diff += Max_diff;
353 | if (diff < -Max_diff)
354 | diff += Max_diff;
355 | x2[i][j] = bestx[j] + F * diff;
356 |
357 | // periodic mode
358 | if (x2[i][j] < low)
359 | x2[i][j] = high - (low - x2[i][j]);
360 | else if (x2[i][j] > high)
361 | x2[i][j] = low + (x2[i][j] - high);
362 | }
363 | else
364 | x2[i][j] = x1[i][j];
365 | }
366 |
367 | double score = a1 * evaluate1(pixels, x1[i]);
368 | score -= a2 * evaluate2(pixels, x1[i]);
369 | if (score > cost[i])
370 | continue;
371 | score += a3 * evaluate3(pixels, x1[i]) + 1000;
372 | if (score > cost[i])
373 | continue;
374 |
375 | cost[i] = score;
376 | if (BVATG >= score) {
377 | BVATG = score;
378 | for (UINT j = 0; j < D; ++j)
379 | bestx[j] = x1[i][j] = x2[i][j];
380 | }
381 | else {
382 | for (UINT j = 0; j < D; ++j)
383 | x1[i][j] = x2[i][j];
384 | }
385 | }
386 | }
387 |
388 | }
389 |
390 | int elapsed_secs = int(clock() - begin) / CLOCKS_PER_SEC;
391 | cout << "\rMultiobjective CQ ALGO Based on Self-Adaptive Hybrid DE: Well done!! (" << elapsed_secs << " sec)" << endl;
392 |
393 | /* Fill palette */
394 | UINT j = 0;
395 | for (unsigned short k = 0; k < nMaxColors; ++k, j += SIDE)
396 | pPalette->Entries[k] = Color::MakeARGB(hasSemiTransparency ? static_cast(bestx[j + 3]) : BYTE_MAX, static_cast(bestx[j + 2]), static_cast(bestx[j + 1]), static_cast(bestx[j]));
397 |
398 | return 0;
399 | }
400 |
401 | unsigned short nearestColorIndex(const ARGB* pPalette, const unsigned short nMaxColors, ARGB argb, const UINT pos)
402 | {
403 | unsigned short k = 0;
404 | Color c(argb);
405 | if (c.GetA() <= 0)
406 | c = m_transparentColor;
407 |
408 | UINT mindist = INT_MAX;
409 | for (UINT i = 0; i < nMaxColors; ++i) {
410 | Color c2(pPalette[i]);
411 | UINT curdist = sqr(c2.GetA() - c.GetA());
412 | if (curdist > mindist)
413 | continue;
414 |
415 | curdist += sqr(c2.GetR() - c.GetR());
416 | if (curdist > mindist)
417 | continue;
418 |
419 | curdist += sqr(c2.GetG() - c.GetG());
420 | if (curdist > mindist)
421 | continue;
422 |
423 | curdist += sqr(c2.GetB() - c.GetB());
424 | if (curdist > mindist)
425 | continue;
426 |
427 | mindist = curdist;
428 | k = i;
429 | }
430 | return k;
431 | }
432 |
433 | unsigned short closestColorIndex(const ARGB* pPalette, const unsigned short nMaxColors, ARGB argb, const UINT pos)
434 | {
435 | UINT k = 0;
436 | Color c(argb);
437 |
438 | vector closest(5);
439 | auto got = closestMap.find(argb);
440 | if (got == closestMap.end()) {
441 | closest[2] = closest[3] = INT_MAX;
442 |
443 | for (; k < nMaxColors; ++k) {
444 | Color c2(pPalette[k]);
445 | closest[4] = sqr(c.GetA() - c2.GetA()) + sqr(c.GetR() - c2.GetR()) + sqr(c.GetG() - c2.GetG()) + sqr(c.GetB() - c2.GetB());
446 | if (closest[4] < closest[2]) {
447 | closest[1] = closest[0];
448 | closest[3] = closest[2];
449 | closest[0] = k;
450 | closest[2] = closest[4];
451 | }
452 | else if (closest[4] < closest[3]) {
453 | closest[1] = k;
454 | closest[3] = closest[4];
455 | }
456 | }
457 |
458 | if (closest[3] == INT_MAX)
459 | closest[2] = 0;
460 | }
461 | else
462 | closest = got->second;
463 |
464 | if (closest[2] == 0 || (rand() % (closest[3] + closest[2])) <= closest[3])
465 | k = closest[0];
466 | else
467 | k = closest[1];
468 |
469 | closestMap[argb] = closest;
470 | return k;
471 | }
472 |
473 | bool quantize_image(const ARGB* pixels, const ColorPalette* pPalette, const UINT nMaxColors, unsigned short* qPixels, const UINT width, const UINT height, const bool dither)
474 | {
475 | if (dither)
476 | return dither_image(pixels, pPalette->Entries, nMaxColors, nearestColorIndex, hasSemiTransparency, m_transparentPixelIndex, qPixels, width, height);
477 |
478 | DitherFn ditherFn = (m_transparentPixelIndex >= 0 || nMaxColors < 256) ? nearestColorIndex : closestColorIndex;
479 | UINT pixelIndex = 0;
480 | for (int j = 0; j < height; ++j) {
481 | for (int i = 0; i < width; ++i)
482 | qPixels[pixelIndex++] = ditherFn(pPalette->Entries, nMaxColors, pixels[pixelIndex], i + j);
483 | }
484 |
485 | return true;
486 | }
487 |
488 | bool MoDEQuantizer::QuantizeImage(Bitmap* pSource, Bitmap* pDest, UINT& nMaxColors, bool dither)
489 | {
490 | UINT bitDepth = GetPixelFormatSize(pSource->GetPixelFormat());
491 | UINT bitmapWidth = pSource->GetWidth();
492 | UINT bitmapHeight = pSource->GetHeight();
493 |
494 | hasSemiTransparency = false;
495 | m_transparentPixelIndex = -1;
496 | int pixelIndex = 0;
497 | const auto area = (size_t) (bitmapWidth * bitmapHeight);
498 | vector pixels(area);
499 | GrabPixels(pSource, pixels, hasSemiTransparency, m_transparentPixelIndex, m_transparentColor, 0xF, nMaxColors);
500 |
501 | SIDE = hasSemiTransparency ? 4 : 3;
502 | auto pPaletteBytes = make_unique(sizeof(ColorPalette) + nMaxColors * sizeof(ARGB));
503 | auto pPalette = (ColorPalette*)pPaletteBytes.get();
504 | pPalette->Count = nMaxColors;
505 |
506 | if (nMaxColors > 2)
507 | moDEquan(pixels, pPalette, nMaxColors);
508 | else {
509 | if (m_transparentPixelIndex >= 0) {
510 | pPalette->Entries[0] = Color::Transparent;
511 | pPalette->Entries[1] = Color::Black;
512 | }
513 | else {
514 | pPalette->Entries[0] = Color::Black;
515 | pPalette->Entries[1] = Color::White;
516 | }
517 | }
518 |
519 | if (nMaxColors > 256) {
520 | auto qPixels = make_unique(pixels.size());
521 | dithering_image(pixels.data(), pPalette, nearestColorIndex, hasSemiTransparency, m_transparentPixelIndex, nMaxColors, qPixels.get(), bitmapWidth, bitmapHeight);
522 | closestMap.clear();
523 | return ProcessImagePixels(pDest, qPixels.get(), hasSemiTransparency, m_transparentPixelIndex);
524 | }
525 |
526 | auto qPixels = make_unique(pixels.size());
527 | quantize_image(pixels.data(), pPalette, nMaxColors, qPixels.get(), bitmapWidth, bitmapHeight, dither);
528 |
529 | if (m_transparentPixelIndex >= 0) {
530 | UINT k = qPixels[m_transparentPixelIndex];
531 | if (nMaxColors > 2)
532 | pPalette->Entries[k] = m_transparentColor;
533 | else if (pPalette->Entries[k] != m_transparentColor)
534 | swap(pPalette->Entries[0], pPalette->Entries[1]);
535 | }
536 | closestMap.clear();
537 |
538 | pDest->SetPalette(pPalette);
539 | return ProcessImagePixels(pDest, qPixels.get(), m_transparentPixelIndex >= 0);
540 | }
541 |
542 | }
--------------------------------------------------------------------------------
/nQuantCpp/MoDEQuantizer.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "bitmapUtilities.h"
3 |
4 | namespace MoDEQuant
5 | {
6 | // =============================================================
7 | // Quantizer objects and functions
8 | //
9 | // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
10 | // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
11 | // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
12 | // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
13 | // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
14 | // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
15 | // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
16 | // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
17 | // THIS DISCLAIMER.
18 | //
19 | // Use at your own risk!
20 | // =============================================================
21 |
22 | class MoDEQuantizer
23 | {
24 | public:
25 | bool QuantizeImage(Bitmap* pSource, Bitmap* pDest, UINT& nMaxColors, bool dither = true);
26 | };
27 | }
--------------------------------------------------------------------------------
/nQuantCpp/NeuQuantizer.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "bitmapUtilities.h"
3 |
4 | namespace NeuralNet
5 | {
6 | // =============================================================
7 | // Quantizer objects and functions
8 | //
9 | // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
10 | // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
11 | // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
12 | // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
13 | // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
14 | // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
15 | // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
16 | // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
17 | // THIS DISCLAIMER.
18 | //
19 | // Use at your own risk!
20 | // =============================================================
21 |
22 | class NeuQuantizer
23 | {
24 | public:
25 | bool QuantizeImage(Bitmap* pSource, Bitmap *pDest, UINT& nMaxColors, bool dither = true);
26 | };
27 | }
--------------------------------------------------------------------------------
/nQuantCpp/NsgaIII.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | * Deb K , Jain H . An Evolutionary Many-Objective Optimization Algorithm Using Reference Point-Based Nondominated Sorting Approach,
3 | * Part I: Solving Problems With Box Constraints[J]. IEEE Transactions on Evolutionary Computation, 2014, 18(4):577-601.
4 | * Copyright (c) 2023 Miller Cy Chan
5 | */
6 |
7 | #include "stdafx.h"
8 | #include "NsgaIII.h"
9 | #include "PnnLABGAQuantizer.h"
10 |
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 |
19 | using namespace std;
20 |
21 | namespace nQuantGA
22 | {
23 | struct ReferencePoint {
24 | private:
25 | int memberSize;
26 | vector position;
27 | unordered_map potentialMembers;
28 |
29 | public:
30 | ReferencePoint(int M) {
31 | memberSize = 0;
32 | position.resize(M);
33 | potentialMembers.clear();
34 | }
35 |
36 | inline double& operator[](int index)
37 | {
38 | return position[index];
39 | }
40 |
41 | inline const double& operator[](int index) const
42 | {
43 | return position[index];
44 | }
45 |
46 | void addMember()
47 | {
48 | ++memberSize;
49 | }
50 |
51 | void addPotentialMember(int memberInd, double distance)
52 | {
53 | auto got = potentialMembers.find(memberInd);
54 | if (got == potentialMembers.end() || distance < got->second)
55 | potentialMembers[memberInd] = distance;
56 | }
57 |
58 | int findClosestMember() const
59 | {
60 | auto it = min_element(potentialMembers.begin(), potentialMembers.end(),
61 | [](const auto& l, const auto& r) { return l.second < r.second; });
62 | return it->first;
63 | }
64 |
65 | bool hasPotentialMember() const
66 | {
67 | return !potentialMembers.empty();
68 | }
69 |
70 | int randomMember() const
71 | {
72 | if (potentialMembers.empty())
73 | return -1;
74 |
75 | vector members;
76 | members.reserve(potentialMembers.size());
77 | for (const auto& [key, _] : potentialMembers) {
78 | members.emplace_back(key);
79 | }
80 | return members[rand() % potentialMembers.size()];
81 | }
82 |
83 | void removePotentialMember(int memberInd)
84 | {
85 | potentialMembers.erase(memberInd);
86 | }
87 |
88 | int getMemberSize() const
89 | {
90 | return memberSize;
91 | }
92 |
93 | int size() const
94 | {
95 | return position.size();
96 | }
97 |
98 | static void generateRecursive(vector& rps, ReferencePoint& pt, int numObjs, int left, int total, int element) {
99 | if (element == numObjs - 1) {
100 | pt[element] = left * 1.0 / total;
101 | rps.emplace_back(pt);
102 | }
103 | else {
104 | for (int i = 0; i <= left; ++i) {
105 | pt[element] = i * 1.0 / total;
106 | generateRecursive(rps, pt, numObjs, left - i, total, element + 1);
107 | }
108 | }
109 | }
110 |
111 | static void generateReferencePoints(vector& rps, int M, const vector& p) {
112 | ReferencePoint pt(M);
113 | generateRecursive(rps, pt, M, p[0], p[0], 0);
114 |
115 | if (p.size() > 1) { // two layers of reference points (Check Fig. 4 in NSGA-III paper)
116 | vector insideRps;
117 | generateRecursive(insideRps, pt, M, p[1], p[1], 0);
118 |
119 | double center = 1.0 / M;
120 |
121 | for (auto& insideRp : insideRps) {
122 | for (int j = 0; j < insideRp.getMemberSize(); ++j)
123 | insideRp[j] = center + insideRp[j] / 2; // (k=num_divisions/M, k, k, ..., k) is the center point
124 |
125 | rps.emplace_back(insideRp);
126 | }
127 | }
128 | }
129 |
130 | static double perpendicularDistance(const ReferencePoint& rp, const vector& point)
131 | {
132 | double numerator = 0, denominator = 0;
133 | for (int i = 0; i < rp.size(); ++i) {
134 | numerator += rp[i] * point[i];
135 | denominator += pow(rp[i], 2);
136 | }
137 |
138 | if (denominator <= 0)
139 | return (numeric_limits::max)();
140 |
141 | double k = numerator / denominator;
142 | double d = 0;
143 | for (int i = 0; i < rp.size(); ++i)
144 | d += pow(k * rp[i] - point[i], 2);
145 |
146 | return sqrt(d);
147 | }
148 |
149 | };
150 |
151 |
152 | template
153 | void associate(vector& rps, const vector >& pop, const vector >& fronts) {
154 | for (int t = 0; t < fronts.size(); ++t) {
155 | for (auto memberInd : fronts[t]) {
156 | int minRp = rps.size() - 1;
157 | auto minDist = (numeric_limits::max)();
158 | for (int r = 0; r < rps.size(); ++r) {
159 | auto d = ReferencePoint::perpendicularDistance(rps[r], pop[memberInd]->getConvertedObjectives());
160 | if (d < minDist) {
161 | minDist = d;
162 | minRp = r;
163 | }
164 | }
165 |
166 | if (t + 1 != fronts.size()) // associating members in St/Fl (only counting)
167 | rps[minRp].addMember();
168 | else
169 | rps[minRp].addPotentialMember(memberInd, minDist);
170 |
171 | }// for - members in front
172 | }// for - fronts
173 | }
174 |
175 |
176 | static unique_ptr guassianElimination(vector >& A, const double* b)
177 | {
178 | const int N = A.size();
179 | for (int i = 0; i < N; ++i)
180 | A[i].emplace_back(b[i]);
181 |
182 | for (int base = 0; base < N - 1; ++base) {
183 | for (int target = base + 1; target < N; ++target) {
184 | double ratio = A[target][base] / A[base][base];
185 | for (int term = 0; term < A[base].size(); ++term)
186 | A[target][term] -= A[base][term] * ratio;
187 | }
188 | }
189 |
190 | auto x = make_unique(N);
191 | for (int i = N - 1; i >= 0; --i) {
192 | for (int known = i + 1; known < N; ++known)
193 | A[i][N] -= A[i][known] * x[known];
194 |
195 | x[i] = A[i][N] / A[i][i];
196 | }
197 | return x;
198 | }
199 |
200 | // ----------------------------------------------------------------------
201 | // ASF: Achivement Scalarization Function
202 | // ----------------------------------------------------------------------
203 | static double ASF(const vector& objs, const double* weight)
204 | {
205 | auto max_ratio = -(numeric_limits::max)();
206 | for (int f = 0; f < objs.size(); ++f) {
207 | auto w = max(weight[f], 1e-6);
208 | max_ratio = max(max_ratio, objs[f] / w);
209 | }
210 | return max_ratio;
211 | }
212 |
213 | template
214 | vector findExtremePoints(const vector >& pop, const vector >& fronts) {
215 | const int numObj = pop[0]->getObjectives().size();
216 |
217 | vector exp;
218 | for (int f = 0; f < numObj; ++f) {
219 | vector w(numObj, 1e-6);
220 | w[f] = 1.0;
221 |
222 | auto minASF = (numeric_limits::max)();
223 | int minIndv = fronts[0].size();
224 |
225 | for (int frontIndv : fronts[0]) { // only consider the individuals in the first front
226 | auto asf = ASF(pop[frontIndv]->getConvertedObjectives(), w.data());
227 |
228 | if (asf < minASF) {
229 | minASF = asf;
230 | minIndv = frontIndv;
231 | }
232 | }
233 |
234 | exp.emplace_back(minIndv);
235 | }
236 |
237 | return exp;
238 | }
239 |
240 | template
241 | vector findMaxObjectives(const vector >& pop)
242 | {
243 | const int numObj = pop[0]->getObjectives().size();
244 | vector maxPoint(numObj, -(numeric_limits::max)());
245 | for (int i = 0; i < pop.size(); ++i) {
246 | for (int f = 0; f < maxPoint.size(); ++f)
247 | maxPoint[f] = max(maxPoint[f], pop[i]->getObjectives()[f]);
248 | }
249 |
250 | return maxPoint;
251 | }
252 |
253 | int findNicheReferencePoint(const vector& rps)
254 | {
255 | // find the minimal cluster size
256 | int minSize = (numeric_limits::max)();
257 | for (auto& rp : rps)
258 | minSize = min(minSize, rp.size());
259 |
260 | // find the reference points with the minimal cluster size Jmin
261 | vector minRps;
262 | for (int r = 0; r < rps.size(); ++r) {
263 | if (rps[r].size() == minSize)
264 | minRps.emplace_back(r);
265 | }
266 |
267 | // return a random reference point (j-bar)
268 | return minRps[rand() % minRps.size()];
269 | }
270 |
271 | template
272 | vector constructHyperplane(const vector >& pop, const vector& extremePoints)
273 | {
274 | const int numObj = pop[0]->getObjectives().size();
275 | // Check whether there are duplicate extreme points.
276 | // This might happen but the original paper does not mention how to deal with it.
277 | bool duplicate = false;
278 | for (int i = 0; !duplicate && i < extremePoints.size(); ++i) {
279 | for (int j = i + 1; !duplicate && j < extremePoints.size(); ++j)
280 | duplicate = (extremePoints[i] == extremePoints[j]);
281 | }
282 |
283 | vector intercepts;
284 |
285 | bool negativeIntercept = false;
286 | if (!duplicate) {
287 | // Find the equation of the hyperplane
288 | vector b(numObj, 1.0);
289 | vector > A(extremePoints.size());
290 | for (int p = 0; p < extremePoints.size(); ++p)
291 | A[p] = pop[ extremePoints[p] ]->getConvertedObjectives();
292 |
293 | auto x = guassianElimination(A, b.data());
294 | // Find intercepts
295 | for (int f = 0; f < numObj; ++f) {
296 | intercepts.emplace_back(1.0 / x[f]);
297 |
298 | if(x[f] < 0) {
299 | negativeIntercept = true;
300 | break;
301 | }
302 | }
303 | }
304 |
305 | if (duplicate || negativeIntercept) // follow the method in Yuan et al. (GECCO 2015)
306 | intercepts = findMaxObjectives(pop);
307 | return intercepts;
308 | }
309 |
310 | template
311 | void normalizeObjectives(vector >& pop, const vector >& fronts, const vector& intercepts, const vector& idealPoint)
312 | {
313 | for (auto& front : fronts) {
314 | for (int ind : front) {
315 | auto& convObjs = pop[ind]->getConvertedObjectives();
316 | for (int f = 0; f < convObjs.size(); ++f) {
317 | if (abs(intercepts[f] - idealPoint[f]) > 10e-10) // avoid the divide-by-zero error
318 | convObjs[f] /= intercepts[f] - idealPoint[f];
319 | else
320 | convObjs[f] /= 10e-10;
321 | }
322 | }
323 | }
324 | }
325 |
326 | template
327 | vector > nondominatedSort(vector >& pop) {
328 | vector > fronts;
329 | int numAssignedIndividuals = 0;
330 | int rank = 1;
331 | auto indvRanks = make_unique(pop.size());
332 |
333 | while (numAssignedIndividuals < pop.size()) {
334 | vector curFront;
335 |
336 | for (int i = 0; i < pop.size(); ++i) {
337 | if (indvRanks[i] > 0)
338 | continue; // already assigned a rank
339 |
340 | bool beDominated = false;
341 | for (int j = 0; j < curFront.size(); ++j) {
342 | if (pop[curFront[j]]->dominates(pop[i].get()) ) { // i is dominated
343 | beDominated = true;
344 | break;
345 | }
346 | else if (pop[i]->dominates(pop[ curFront[j] ].get()) ) // i dominates a member in the current front
347 | curFront.erase(curFront.begin() + j--);
348 | }
349 |
350 | if (!beDominated)
351 | curFront.emplace_back(i);
352 | }
353 |
354 | for (int front : curFront)
355 | indvRanks[front] = rank;
356 |
357 | fronts.emplace_back(curFront);
358 | numAssignedIndividuals += curFront.size();
359 |
360 | ++rank;
361 | }
362 |
363 | return fronts;
364 | }
365 |
366 | int selectClusterMember(const ReferencePoint& rp) {
367 | if (rp.hasPotentialMember()) {
368 | if (rp.size() == 0) // currently has no member
369 | return rp.findClosestMember();
370 |
371 | return rp.randomMember();
372 | }
373 | return -1;
374 | }
375 |
376 | template
377 | vector translateObjectives(vector >& pop, const vector >& fronts)
378 | {
379 | vector idealPoint;
380 | const int numObj = pop[0]->getObjectives().size();
381 | for (int f = 0; f < numObj; ++f) {
382 | auto minf = (numeric_limits::max)();
383 | for (int frontIndv : fronts[0]) // min values must appear in the first front
384 | minf = min(minf, pop[frontIndv]->getObjectives()[f]);
385 |
386 | idealPoint.emplace_back(minf);
387 |
388 | for (auto& front : fronts) {
389 | for (int ind : front) {
390 | auto chromosome = pop[ind];
391 | chromosome->resizeConvertedObjectives(numObj);
392 | chromosome->getConvertedObjectives()[f] = chromosome->getObjectives()[f] - minf;
393 | }
394 | }
395 | }
396 |
397 | return idealPoint;
398 | }
399 |
400 | template
401 | vector > selection(vector >& cur, vector& rps, const int populationSize) {
402 | vector > next;
403 |
404 | // ---------- Step 4 in Algorithm 1: non-dominated sorting ----------
405 | auto fronts = nondominatedSort(cur);
406 |
407 | // ---------- Steps 5-7 in Algorithm 1 ----------
408 | int last = 0, next_size = 0;
409 | while (next_size < populationSize) {
410 | next_size += fronts[last++].size();
411 | }
412 |
413 | fronts.resize(last); // remove useless individuals
414 |
415 | for (int t = 0; t < fronts.size() - 1; ++t) {
416 | for (int frontIndv : fronts[t])
417 | next.emplace_back(cur[frontIndv]);
418 | }
419 |
420 | // ---------- Steps 9-10 in Algorithm 1 ----------
421 | if (next.size() == populationSize)
422 | return next;
423 |
424 | // ---------- Step 14 / Algorithm 2 ----------
425 | auto idealPoint = translateObjectives(cur, fronts);
426 |
427 | auto extremePoints = findExtremePoints(cur, fronts);
428 |
429 | auto intercepts = constructHyperplane(cur, extremePoints);
430 |
431 | normalizeObjectives(cur, fronts, intercepts, idealPoint);
432 |
433 | // ---------- Step 15 / Algorithm 3, Step 16 ----------
434 | associate(rps, cur, fronts);
435 |
436 | // ---------- Step 17 / Algorithm 4 ----------
437 | while (next.size() < populationSize) {
438 | int minRp = findNicheReferencePoint(rps);
439 |
440 | int chosen = selectClusterMember(rps[minRp]);
441 | if (chosen < 0) // no potential member in Fl, disregard this reference point
442 | rps.erase(rps.begin() + minRp);
443 | else {
444 | rps[minRp].addMember();
445 | rps[minRp].removePotentialMember(chosen);
446 | next.emplace_back(cur[chosen]);
447 | }
448 | }
449 |
450 | return next;
451 | }
452 |
453 | // Initializes NsgaIII
454 | template
455 | NsgaIII::NsgaIII(T& prototype, int numberOfChromosomes)
456 | {
457 | _prototype = prototype.makeNewFromPrototype();
458 |
459 | // there should be at least 2 chromosomes in population
460 | if (numberOfChromosomes < 2)
461 | numberOfChromosomes = 2;
462 | _populationSize = numberOfChromosomes;
463 | }
464 |
465 | template
466 | NsgaIII::NsgaIII(T& prototype, int numberOfCrossoverPoints, int mutationSize, float crossoverProbability, float mutationProbability) : NsgaIII(prototype, 9)
467 | {
468 | _criteriaLength = prototype.getObjectives().size();
469 | _mutationSize = mutationSize;
470 | _numberOfCrossoverPoints = numberOfCrossoverPoints;
471 | _crossoverProbability = crossoverProbability;
472 | _mutationProbability = mutationProbability;
473 |
474 | _objDivision.clear();
475 | if(_criteriaLength < 8)
476 | _objDivision.emplace_back(6);
477 | else {
478 | _objDivision.emplace_back(3);
479 | _objDivision.emplace_back(2);
480 | }
481 | }
482 |
483 |
484 | template
485 | vector