├── .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 | Fast pairwise nearest neighbor based algorithm with CIELAB color space with CIELAB color space in 16 colors

26 |

Fast pairwise nearest neighbor based algorithm with CIELAB color space Plus (parallel quantum inspired Genetic Algorithm) in 16 colors
Fast pairwise nearest neighbor based algorithm with CIELAB color space 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 | Efficient, Edge-Aware, Combined Color Quantization and Dithering with CIELAB color space in 16 colors

30 |

Spatial color quantization with CIELAB color space in 16 colors
31 | Higher quality for 32 or less colors but the slowest
32 | Spatial color quantization with CIELAB color space in 16 colors

33 | All in all, the top 3 color quantization algorithms for 256 colors are: 34 |
    35 |
  1. Fast pairwise nearest neighbor based algorithm
  2. 36 |
  3. Xialoin Wu's fast optimal color quantizer
  4. 37 |
  5. NeuQuant Neural-Net Quantization Algorithm with CIELAB color space
  6. 38 |
39 | The top 3 color quantization algorithms for 32 colors or less are: 40 |
    41 |
  1. Fast pairwise nearest neighbor based algorithm with CIELAB color space Plus (parallel quantum inspired Genetic Algorithm)
  2. 42 |
  3. Efficient, Edge-Aware, Combined Color Quantization and Dithering algorithm with CIELAB color space
  4. 43 |
  5. Spatial color quantization algorithm with CIELAB color space
  6. 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 > NsgaIII::crossing(vector >& population) 486 | { 487 | auto populationSize = population.size(); 488 | vector > offspring(populationSize); 489 | #pragma omp parallel for 490 | for (int i = 0; i < populationSize; ++i) { 491 | if (i % 2 == 0) { 492 | int father = rand() % populationSize, mother = rand() % populationSize; 493 | offspring[i] = population[father]->crossover(*(population[mother]), _numberOfCrossoverPoints, _crossoverProbability); 494 | if(i < populationSize - 1) 495 | offspring[i + 1] = population[mother]->crossover(*(population[father]), _numberOfCrossoverPoints, _crossoverProbability); 496 | } 497 | } 498 | return offspring; 499 | } 500 | 501 | template 502 | vector > NsgaIII::initialize() 503 | { 504 | vector > result(_populationSize); 505 | result[0] = _prototype->makeNewFromPrototype(); 506 | // initialize new population with chromosomes randomly built using prototype 507 | #pragma omp parallel for 508 | for (int i = 1; i < _populationSize; ++i) { 509 | result[i] = _prototype->makeNewFromPrototype(); 510 | } 511 | return result; 512 | } 513 | 514 | template 515 | void NsgaIII::reform() 516 | { 517 | srand(time(NULL)); 518 | if(_crossoverProbability < 95) 519 | _crossoverProbability += 1.0f; 520 | else if(_mutationProbability < 30) 521 | _mutationProbability += 1.0f; 522 | } 523 | 524 | template 525 | vector > NsgaIII::replacement(vector >& population) 526 | { 527 | vector rps; 528 | ReferencePoint::generateReferencePoints(rps, _criteriaLength, _objDivision); 529 | return selection(population, rps, _populationSize); 530 | } 531 | 532 | // Starts and executes algorithm 533 | template 534 | void NsgaIII::run(int maxRepeat, double minFitness) 535 | { 536 | if (_prototype.get() == nullptr) 537 | return; 538 | 539 | vector > pop[2]; 540 | pop[0] = initialize(); 541 | 542 | // Current generation 543 | int currentGeneration = 0; 544 | int bestNotEnhance = 0; 545 | double lastBestFit = 0.0; 546 | 547 | int cur = 0, next = 1; 548 | for (; ;) 549 | { 550 | auto best = getResult(); 551 | if(currentGeneration > 0) { 552 | ostringstream status; 553 | status << "\rFitness: " << showpoint << best->getFitness() << "\t Generation: " << currentGeneration; 554 | wcout << status.str().c_str(); 555 | 556 | // algorithm has reached criteria? 557 | if (best->getFitness() > minFitness) 558 | break; 559 | 560 | double difference = abs(best->getFitness() - lastBestFit); 561 | if (difference <= 0.0000001) 562 | ++bestNotEnhance; 563 | else { 564 | lastBestFit = best->getFitness(); 565 | bestNotEnhance = 0; 566 | } 567 | 568 | if (bestNotEnhance > (maxRepeat / 50)) 569 | reform(); 570 | } 571 | 572 | /******************* crossover *****************/ 573 | auto offspring = crossing(pop[cur]); 574 | 575 | /******************* mutation *****************/ 576 | #pragma omp parallel for 577 | for (int i = 0; i < offspring.size(); ++i) { 578 | offspring[i]->mutation(_mutationSize, _mutationProbability); 579 | } 580 | 581 | pop[cur].insert(pop[cur].end(), offspring.begin(), offspring.end()); 582 | 583 | /******************* replacement *****************/ 584 | pop[next] = replacement(pop[cur]); 585 | _best = pop[next][0]->dominates(pop[cur][0].get()) ? pop[next][0] : pop[cur][0]; 586 | 587 | swap(cur, next); 588 | ++currentGeneration; 589 | } 590 | } 591 | 592 | // explicit instantiations 593 | template class NsgaIII; 594 | } 595 | -------------------------------------------------------------------------------- /nQuantCpp/NsgaIII.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | namespace nQuantGA 8 | { 9 | /* 10 | * Deb K , Jain H . An Evolutionary Many-Objective Optimization Algorithm Using Reference Point-Based Nondominated Sorting Approach, 11 | * Part I: Solving Problems With Box Constraints[J]. IEEE Transactions on Evolutionary Computation, 2014, 18(4):577-601. 12 | * Copyright (c) 2023 Miller Cy Chan 13 | */ 14 | template 15 | class NsgaIII 16 | { 17 | private: 18 | // Initializes NsgaIII 19 | NsgaIII(T& prototype, int numberOfChromosomes); 20 | 21 | protected: 22 | // Prototype of chromosomes in population 23 | shared_ptr _prototype; 24 | 25 | // Number of chromosomes 26 | int _populationSize; 27 | 28 | // Number of crossover points of parent's class tables 29 | int _numberOfCrossoverPoints; 30 | 31 | // Number of classes that is moved randomly by single mutation operation 32 | int _mutationSize; 33 | 34 | // Probability that crossover will occur 35 | float _crossoverProbability; 36 | 37 | // Probability that mutation will occur 38 | float _mutationProbability; 39 | 40 | vector _objDivision; 41 | 42 | int _criteriaLength; 43 | 44 | shared_ptr _best; 45 | 46 | virtual vector > crossing(vector >& population); 47 | virtual vector > initialize(); 48 | virtual void reform(); 49 | virtual vector > replacement(vector >& population); 50 | 51 | public: 52 | NsgaIII(T& pPrototype, int numberOfCrossoverPoints, int mutationSize, float crossoverProbability, float mutationProbability); 53 | 54 | // Returns pointer to best chromosomes in population 55 | T* getResult() const 56 | { 57 | return _best.get(); 58 | } 59 | 60 | // Starts and executes algorithm 61 | virtual void run(int maxRepeat, double minFitness); 62 | }; 63 | 64 | } 65 | -------------------------------------------------------------------------------- /nQuantCpp/Otsu.cpp: -------------------------------------------------------------------------------- 1 | /* Otsu's Image Segmentation Method 2 | Copyright (C) 2009 Tolga Birdal 3 | Copyright (c) 2018 - 2024 Miller Cy Chan 4 | */ 5 | 6 | #include "stdafx.h" 7 | #include "Otsu.h" 8 | #include "GilbertCurve.h" 9 | #define _USE_MATH_DEFINES 10 | #include 11 | #include 12 | 13 | namespace OtsuThreshold 14 | { 15 | BYTE alphaThreshold = 0xF; 16 | bool hasSemiTransparency = false; 17 | int m_transparentPixelIndex = -1; 18 | ARGB m_transparentColor = Color::Transparent; 19 | unordered_map nearestMap; 20 | 21 | // function is used to compute the q values in the equation 22 | static float px(int init, int end, int* hist) 23 | { 24 | int sum = 0; 25 | for (int i = init; i <= end; ++i) 26 | sum += hist[i]; 27 | 28 | return (float) sum; 29 | } 30 | 31 | // function is used to compute the mean values in the equation (mu) 32 | static float mx(int init, int end, int* hist) 33 | { 34 | int sum = 0; 35 | for (int i = init; i <= end; ++i) 36 | sum += i * hist[i]; 37 | 38 | return (float) sum; 39 | } 40 | 41 | // finds the maximum element in a vector 42 | static short findMax(float* vec, int n) 43 | { 44 | float maxVec = 0; 45 | short idx = 0; 46 | 47 | for (int i = 1; i < n - 1; ++i) { 48 | if (vec[i] > maxVec) { 49 | maxVec = vec[i]; 50 | idx = i; 51 | } 52 | } 53 | return idx; 54 | } 55 | 56 | // simply computes the image histogram 57 | void getHistogram(const vector& pixels, int* hist) 58 | { 59 | for (auto pixel : pixels) { 60 | Color c(pixel); 61 | if (c.GetA() <= alphaThreshold) 62 | continue; 63 | 64 | hist[c.GetR()]++; 65 | hist[c.GetG()]++; 66 | hist[c.GetB()]++; 67 | } 68 | } 69 | 70 | short getOtsuThreshold(const vector& pixels) 71 | { 72 | float vet[256] = { 0 }; 73 | int hist[256] = { 0 }; 74 | 75 | getHistogram(pixels, hist); 76 | 77 | // loop through all possible t values and maximize between class variance 78 | for (int k = 1; k != BYTE_MAX; ++k) { 79 | float p1 = px(0, k, hist); 80 | float p2 = px(k + 1, BYTE_MAX, hist); 81 | float p12 = p1 * p2; 82 | if (p12 == 0) 83 | p12 = 1; 84 | float diff = (mx(0, k, hist) * p2) - (mx(k + 1, BYTE_MAX, hist) * p1); 85 | vet[k] = diff * diff / p12; 86 | } 87 | 88 | return findMax(vet, 256); 89 | } 90 | 91 | void threshold(const vector& pixels, vector& dest, short thresh, float weight = 1.0f) 92 | { 93 | auto maxThresh = (BYTE)thresh; 94 | if (thresh >= 200) 95 | { 96 | weight = .78f; 97 | maxThresh = (BYTE)(thresh * weight); 98 | thresh = 200; 99 | } 100 | 101 | auto minThresh = (BYTE)(thresh * (m_transparentPixelIndex >= 0 ? .9f : weight)); 102 | const auto shadow = m_transparentPixelIndex >= 0 ? 3.5 : 3; 103 | for (int i = 0; i < pixels.size(); ++i) { 104 | Color c(pixels[i]); 105 | if (c.GetA() < alphaThreshold && c.GetR() + c.GetG() + c.GetB() > maxThresh * 3) 106 | dest[i] = Color::MakeARGB(c.GetA(), BYTE_MAX, BYTE_MAX, BYTE_MAX); 107 | else if (c.GetR() + c.GetG() + c.GetB() < minThresh * shadow) 108 | dest[i] = Color::MakeARGB(c.GetA(), 0, 0, 0); 109 | } 110 | } 111 | 112 | vector cannyFilter(const UINT width, const vector& pixelsGray, double lowerThreshold, double higherThreshold, bool dither) { 113 | const auto height = pixelsGray.size() / width; 114 | const auto area = (size_t)(width * height); 115 | 116 | vector pixelsCanny(area, Color::White); 117 | 118 | int gx[3][3] = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}}; 119 | int gy[3][3] = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}}; 120 | auto G = make_unique(area); 121 | vector theta(area); 122 | auto largestG = 0.0; 123 | 124 | // perform canny edge detection on everything but the edges 125 | for (int i = 1; i < height - 1; ++i) { 126 | for (int j = 1; j < width - 1; ++j) { 127 | // find gx and gy for each pixel 128 | auto gxValue = 0.0; 129 | auto gyValue = 0.0; 130 | for (int x = -1; x <= 1; ++x) { 131 | for (int y = -1; y <= 1; ++y) { 132 | const Color c = pixelsGray[(i + x) * width + j + y]; 133 | gxValue += gx[1 - x][1 - y] * c.GetG(); 134 | gyValue += gy[1 - x][1 - y] * c.GetG(); 135 | } 136 | } 137 | 138 | const int center = i * width + j; 139 | // calculate G and theta 140 | G[center] = sqrt(pow(gxValue, 2) + pow(gyValue, 2)); 141 | auto atanResult = atan2(gyValue, gxValue) * 180.0 / M_PI; 142 | theta[center] = (int)(180.0 + atanResult); 143 | 144 | if (G[center] > largestG) 145 | largestG = G[center]; 146 | 147 | // setting the edges 148 | if (i == 1) { 149 | G[center - 1] = G[center]; 150 | theta[center - 1] = theta[center]; 151 | } 152 | else if (j == 1) { 153 | G[center - width] = G[center]; 154 | theta[center - width] = theta[center]; 155 | } 156 | else if (i == height - 1) { 157 | G[center + 1] = G[center]; 158 | theta[center + 1] = theta[center]; 159 | } 160 | else if (j == width - 1) { 161 | G[center + width] = G[center]; 162 | theta[center + width] = theta[center]; 163 | } 164 | 165 | // setting the corners 166 | if (i == 1 && j == 1) { 167 | G[center - width - 1] = G[center]; 168 | theta[center - width - 1] = theta[center]; 169 | } 170 | else if (i == 1 && j == width - 1) { 171 | G[center - width + 1] = G[center]; 172 | theta[center - width + 1] = theta[center]; 173 | } 174 | else if (i == height - 1 && j == 1) { 175 | G[center + width - 1] = G[center]; 176 | theta[center + width - 1] = theta[center]; 177 | } 178 | else if (i == height - 1 && j == width - 1) { 179 | G[center + width + 1] = G[center]; 180 | theta[center + width + 1] = theta[center]; 181 | } 182 | 183 | // to the nearest 45 degrees 184 | theta[center] = rint(theta[center] / 45) * 45; 185 | } 186 | } 187 | 188 | largestG *= .5; 189 | 190 | // non-maximum suppression 191 | for (int i = 1; i < height - 1; ++i) { 192 | for (int j = 1; j < width - 1; ++j) { 193 | const int center = i * width + j; 194 | if (theta[center] == 0 || theta[center] == 180) { 195 | if (G[center] < G[center - 1] || G[center] < G[center + 1]) 196 | G[center] = 0; 197 | } 198 | else if (theta[center] == 45 || theta[center] == 225) { 199 | if (G[center] < G[center + width + 1] || G[center] < G[center - width - 1]) 200 | G[center] = 0; 201 | } 202 | else if (theta[center] == 90 || theta[center] == 270) { 203 | if (G[center] < G[center + width] || G[center] < G[center - width]) 204 | G[center] = 0; 205 | } 206 | else { 207 | if (G[center] < G[center + width - 1] || G[center] < G[center - width + 1]) 208 | G[center] = 0; 209 | } 210 | 211 | auto grey = ~(BYTE)(G[center] * (255.0 / largestG)); 212 | Color c(pixelsGray[center]); 213 | pixelsCanny[center] = Color::MakeARGB(c.GetA(), grey, grey, grey); 214 | } 215 | } 216 | 217 | int k = 0; 218 | auto minThreshold = lowerThreshold * largestG, maxThreshold = higherThreshold * largestG; 219 | do { 220 | for (int i = 1; i < height - 1; ++i) { 221 | for (int j = 1; j < width - 1; ++j) { 222 | const int center = i * width + j; 223 | if (G[center] < minThreshold) 224 | G[center] = 0; 225 | else if (G[center] < maxThreshold) { 226 | G[center] = 0; 227 | for (int x = -1; x <= 1; ++x) { 228 | for (int y = -1; y <= 1; ++y) { 229 | if (x == 0 && y == 0) 230 | continue; 231 | if (G[center + x * width + y] >= maxThreshold) { 232 | G[center] = higherThreshold * largestG; 233 | k = 0; 234 | x = 2; 235 | break; 236 | } 237 | } 238 | } 239 | } 240 | 241 | auto grey = ~(BYTE)(G[center] * 255.0 / largestG); 242 | Color c(pixelsGray[center]); 243 | pixelsCanny[center] = Color::MakeARGB(c.GetA(), grey, grey, grey); 244 | } 245 | } 246 | } while (k++ < 100 && dither); 247 | return pixelsCanny; 248 | } 249 | 250 | unsigned short nearestColorIndex(const ARGB* pPalette, const UINT nMaxColors, const ARGB argb, const UINT pos) 251 | { 252 | auto got = nearestMap.find(argb); 253 | if (got != nearestMap.end()) 254 | return got->second; 255 | 256 | unsigned short k = 0; 257 | Color c(argb); 258 | if (c.GetA() <= alphaThreshold) 259 | return k; 260 | 261 | double mindist = INT_MAX; 262 | for (UINT i = 0; i < nMaxColors; ++i) { 263 | Color c2(pPalette[i]); 264 | double curdist = sqr(c2.GetA() - c.GetA()); 265 | if (curdist > mindist) 266 | continue; 267 | 268 | curdist += sqr(c2.GetR() - c.GetR()); 269 | if (curdist > mindist) 270 | continue; 271 | 272 | curdist += sqr(c2.GetG() - c.GetG()); 273 | if (curdist > mindist) 274 | continue; 275 | 276 | curdist += sqr(c2.GetB() - c.GetB()); 277 | if (curdist > mindist) 278 | continue; 279 | 280 | mindist = curdist; 281 | k = i; 282 | } 283 | nearestMap[argb] = k; 284 | return k; 285 | } 286 | 287 | inline int GetColorIndex(const Color& c) 288 | { 289 | return GetARGBIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0); 290 | } 291 | 292 | void convertToGrayScale(const vector& pixels, vector& dest) 293 | { 294 | float min1 = BYTE_MAX; 295 | float max1 = .0f; 296 | 297 | for (const auto& pixel : pixels) 298 | { 299 | int alfa = (pixel >> 24) & 0xff; 300 | if (alfa <= alphaThreshold) 301 | continue; 302 | 303 | int green = (pixel >> 8) & 0xff; 304 | if (min1 > green) 305 | min1 = green; 306 | 307 | if (max1 < green) 308 | max1 = green; 309 | } 310 | 311 | for (int i = 0; i < pixels.size(); ++i) 312 | { 313 | int alfa = (pixels[i] >> 24) & 0xff; 314 | if (alfa <= alphaThreshold) 315 | continue; 316 | 317 | int green = (pixels[i] >> 8) & 0xff; 318 | auto grey = (int)((green - min1) * (BYTE_MAX / (max1 - min1))); 319 | dest[i] = Color::MakeARGB(alfa, grey, grey, grey); 320 | } 321 | } 322 | 323 | Bitmap* Otsu::ConvertToGrayScale(Bitmap* pSrcImg) 324 | { 325 | auto iWidth = pSrcImg->GetWidth(); 326 | auto iHeight = pSrcImg->GetHeight(); 327 | 328 | auto pixelFormat = pSrcImg->GetPixelFormat(); 329 | auto bitDepth = GetPixelFormatSize(pixelFormat); 330 | if (bitDepth != 32 && bitDepth != 24) 331 | pixelFormat = PixelFormat32bppARGB; 332 | 333 | Rect rect(0, 0, iWidth, iHeight); 334 | auto pSourceImg = pSrcImg->Clone(rect, pixelFormat); 335 | BitmapData data; 336 | Status status = pSourceImg->LockBits(&rect, ImageLockModeWrite, pSourceImg->GetPixelFormat(), &data); 337 | if (status != Ok) 338 | return pSrcImg; 339 | 340 | bitDepth = GetPixelFormatSize(pSourceImg->GetPixelFormat()); 341 | auto DJ = (BYTE)(bitDepth >> 3); 342 | 343 | auto ptr = (LPBYTE)data.Scan0; 344 | 345 | float min1 = BYTE_MAX; 346 | float max1 = .0f; 347 | int remain = data.Stride - iWidth * DJ; 348 | 349 | for (int i = 0; i < iHeight; ++i) 350 | { 351 | for (int j = 0; j < iWidth; ++j) 352 | { 353 | if (DJ > 3 && ptr[3] <= alphaThreshold) { 354 | ptr += DJ; 355 | continue; 356 | } 357 | 358 | if (min1 > ptr[1]) 359 | min1 = ptr[1]; 360 | 361 | if (max1 < ptr[1]) 362 | max1 = ptr[1]; 363 | ptr += DJ; 364 | } 365 | ptr += remain; 366 | } 367 | 368 | ptr = (LPBYTE)data.Scan0; 369 | 370 | for (int i = 0; i < iHeight; ++i) 371 | { 372 | for (int j = 0; j < iWidth; ++j) 373 | { 374 | auto grey = (BYTE)((ptr[1] - min1) * (BYTE_MAX / (max1 - min1))); 375 | ptr[0] = ptr[1] = ptr[2] = grey; 376 | ptr += DJ; 377 | } 378 | ptr += remain; 379 | } 380 | 381 | pSourceImg->UnlockBits(&data); 382 | return pSourceImg; 383 | } 384 | 385 | 386 | bool Otsu::ConvertGrayScaleToBinary(Bitmap* pSrcImg, Bitmap* pDest, bool isGrayscale, bool dither) 387 | { 388 | const auto bitmapWidth = pSrcImg->GetWidth(); 389 | const auto bitmapHeight = pSrcImg->GetHeight(); 390 | const auto area = (size_t) (bitmapWidth * bitmapHeight); 391 | 392 | vector pixels(area); 393 | GrabPixels(pSrcImg, pixels, hasSemiTransparency, m_transparentPixelIndex, m_transparentColor, alphaThreshold); 394 | 395 | auto pixelsGray = pixels; 396 | if (!isGrayscale) 397 | convertToGrayScale(pixels, pixelsGray); 398 | 399 | auto otsuThreshold = getOtsuThreshold(pixelsGray); 400 | auto lowerThreshold = .03, higherThreshold = .1; 401 | if(!dither) { 402 | lowerThreshold = otsuThreshold / 3.0; 403 | higherThreshold = otsuThreshold; 404 | } 405 | pixels = cannyFilter(bitmapWidth, pixelsGray, lowerThreshold, higherThreshold, dither); 406 | threshold(pixelsGray, pixels, otsuThreshold); 407 | 408 | auto pPaletteBytes = make_unique(sizeof(ColorPalette) + 2 * sizeof(ARGB)); 409 | auto pPalette = (ColorPalette*)pPaletteBytes.get(); 410 | pPalette->Count = 2; 411 | if (m_transparentPixelIndex >= 0) { 412 | pPalette->Entries[0] = m_transparentColor; 413 | pPalette->Entries[1] = Color::Black; 414 | } 415 | else { 416 | pPalette->Entries[0] = Color::Black; 417 | pPalette->Entries[1] = Color::White; 418 | } 419 | 420 | auto qPixels = make_unique(pixels.size()); 421 | Peano::GilbertCurve::dither(bitmapWidth, bitmapHeight, pixels.data(), pPalette->Entries, pPalette->Count, nearestColorIndex, GetColorIndex, qPixels.get(), nullptr, 3.0f); 422 | if (m_transparentPixelIndex >= 0) 423 | { 424 | auto k = qPixels[m_transparentPixelIndex]; 425 | if (pPalette->Entries[k] != m_transparentColor) 426 | swap(pPalette->Entries[0], pPalette->Entries[1]); 427 | } 428 | 429 | nearestMap.clear(); 430 | pDest->SetPalette(pPalette); 431 | return ProcessImagePixels(pDest, qPixels.get(), m_transparentPixelIndex >= 0); 432 | } 433 | } 434 | -------------------------------------------------------------------------------- /nQuantCpp/Otsu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "bitmapUtilities.h" 3 | 4 | namespace OtsuThreshold 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 Otsu 23 | { 24 | public: 25 | static Bitmap* ConvertToGrayScale(Bitmap* pSrcImg); 26 | bool ConvertGrayScaleToBinary(Bitmap* pSrcImg, Bitmap* pDest, bool isGrayscale = false, bool dither = true); 27 | }; 28 | } -------------------------------------------------------------------------------- /nQuantCpp/PnnLABGAQuantizer.cpp: -------------------------------------------------------------------------------- 1 | /* Fast pairwise nearest neighbor based genetic algorithm with CIELAB color space 2 | * Copyright (c) 2023 - 2025 Miller Cy Chan */ 3 | 4 | #include "stdafx.h" 5 | #include "PnnLABGAQuantizer.h" 6 | #include "CIELABConvertor.h" 7 | #include "BlueNoise.h" 8 | 9 | #define _USE_MATH_DEFINES 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace PnnLABQuant 20 | { 21 | vector _bitmapWidths; 22 | UINT _dp = 1, _nMaxColors = 256; 23 | double minRatio = 0, maxRatio = 1.0; 24 | 25 | static unordered_map > _fitnessMap; 26 | static mutex _mutex; 27 | 28 | PnnLABGAQuantizer::PnnLABGAQuantizer(PnnLABQuantizer& pq, const vector >& pSources, UINT nMaxColors) { 29 | // increment value when criteria violation occurs 30 | _objectives.resize(4); 31 | _bitmapWidths.clear(); 32 | srand(pSources[0]->GetWidth() * pSources[0]->GetHeight()); 33 | 34 | m_pq = make_unique(pq); 35 | if(pq.IsGA()) 36 | return; 37 | 38 | clear(); 39 | _nMaxColors = nMaxColors; 40 | m_pixelsList.clear(); 41 | 42 | bool hasSemiTransparency = false; 43 | for(auto& pSource : pSources) { 44 | _bitmapWidths.emplace_back(pSource->GetWidth()); 45 | const auto area = (size_t) (pSource->GetWidth() * pSource->GetHeight()); 46 | vector pixels(area); 47 | m_pq->grabPixels(pSource.get(), pixels, _nMaxColors, hasSemiTransparency); 48 | m_pixelsList.emplace_back(pixels); 49 | } 50 | minRatio = (hasSemiTransparency || nMaxColors < 64) ? .0111 : .85; 51 | maxRatio = min(1.0, nMaxColors / ((nMaxColors < 64) ? 400.0 : 50.0)); 52 | if (nMaxColors < 16) 53 | maxRatio = .2; 54 | _dp = maxRatio < .1 ? 10000 : 100; 55 | } 56 | 57 | PnnLABGAQuantizer::PnnLABGAQuantizer(PnnLABQuantizer& pq, const vector >& pixelsList, const vector& bitmapWidths, UINT nMaxColors) 58 | { 59 | m_pq = make_unique(pq); 60 | // increment value when criteria violation occurs 61 | _objectives.resize(4); 62 | m_pixelsList = pixelsList; 63 | _bitmapWidths = bitmapWidths; 64 | srand(m_pixelsList[0].size()); 65 | _nMaxColors = nMaxColors; 66 | } 67 | 68 | string PnnLABGAQuantizer::getRatioKey() const 69 | { 70 | ostringstream ss; 71 | ss << (int)(_ratioX * _dp); 72 | auto difference = abs(_ratioX - _ratioY); 73 | if (difference <= 0.0000001) 74 | return ss.str(); 75 | 76 | ss << ";" << (int)(_ratioY * _dp * 100); 77 | return ss.str(); 78 | } 79 | 80 | auto PnnLABGAQuantizer::findByRatioKey(const string& ratioKey) const 81 | { 82 | lock_guard lock(_mutex); 83 | auto got = _fitnessMap.find(ratioKey); 84 | if (got != _fitnessMap.end()) 85 | return got->second; 86 | return vector(); 87 | } 88 | 89 | void PnnLABGAQuantizer::calculateError(vector& errors) { 90 | auto maxError = maxRatio < .1 ? .5 : .0625; 91 | if (m_pq->hasAlpha()) 92 | maxError = 1; 93 | 94 | auto fitness = 0.0; 95 | int length = accumulate(begin(m_pixelsList), end(m_pixelsList), 0, [](int i, const vector& pixels){ 96 | return pixels.size() + i; 97 | }); 98 | for (int i = 0; i < errors.size(); ++i) 99 | errors[i] /= maxError * length; 100 | 101 | for (int i = 0; i < errors.size(); ++i) { 102 | if (i >= 1) 103 | errors[i] /= 2.55; 104 | fitness -= errors[i]; 105 | } 106 | 107 | _objectives = errors; 108 | _fitness = fitness; 109 | } 110 | 111 | void PnnLABGAQuantizer::calculateFitness() { 112 | auto ratioKey = getRatioKey(); 113 | auto objectives = findByRatioKey(ratioKey); 114 | if (!objectives.empty()) { 115 | _fitness = -1.0 * accumulate(objectives.begin(), objectives.end(), 0.0); 116 | _objectives = objectives; 117 | return; 118 | } 119 | 120 | _objectives.resize(4); 121 | m_pq->setRatio(_ratioX, _ratioY); 122 | 123 | auto pPaletteBytes = make_unique(sizeof(ColorPalette) + _nMaxColors * sizeof(ARGB)); 124 | auto pPalette = (ColorPalette*)pPaletteBytes.get(); 125 | pPalette->Count = _nMaxColors; 126 | m_pq->pnnquan(m_pixelsList[0], pPalette->Entries, _nMaxColors); 127 | 128 | auto errors = _objectives; 129 | fill(errors.begin(), errors.end(), 0); 130 | 131 | int threshold = maxRatio < .1 ? -64 : -112; 132 | for (auto& pixels : m_pixelsList) { 133 | for (int i = 0; i < pixels.size(); ++i) 134 | { 135 | if (BlueNoise::TELL_BLUE_NOISE[i & 4095] > threshold) 136 | continue; 137 | 138 | auto argb = pixels[i]; 139 | Color c(argb); 140 | CIELABConvertor::Lab lab1, lab2; 141 | m_pq->getLab(c, lab1); 142 | auto qPixelIndex = m_pq->nearestColorIndex(pPalette->Entries, _nMaxColors, argb, i); 143 | Color c2(pPalette->Entries[qPixelIndex]); 144 | m_pq->getLab(c2, lab2); 145 | 146 | if (m_pq->hasAlpha()) { 147 | errors[0] += sqr(lab2.L - lab1.L); 148 | errors[1] += sqr(lab2.A - lab1.A); 149 | errors[2] += sqr(lab2.B - lab1.B); 150 | errors[3] += sqr(lab2.alpha - lab1.alpha) / exp(1.5); 151 | } 152 | else { 153 | errors[0] += abs(lab2.L - lab1.L); 154 | errors[1] += sqrt(sqr(lab2.A - lab1.A) + sqr(lab2.B - lab1.B)); 155 | } 156 | } 157 | } 158 | 159 | calculateError(errors); 160 | lock_guard lock(_mutex); 161 | _fitnessMap.insert({ ratioKey, _objectives }); 162 | } 163 | 164 | bool PnnLABGAQuantizer::QuantizeImage(vector >& pBitmaps, bool dither) { 165 | m_pq->setRatio(_ratioX, _ratioY); 166 | if (_nMaxColors > 256) { 167 | auto pPalettes = make_unique(_nMaxColors); 168 | auto pPalette = pPalettes.get(); 169 | m_pq->pnnquan(m_pixelsList[0], pPalette, _nMaxColors); 170 | int i = 0; 171 | for (auto& pixels : m_pixelsList) { 172 | m_pq->QuantizeImageByPal(pixels, _bitmapWidths[i], pPalette, pBitmaps[i].get(), _nMaxColors, dither); 173 | ++i; 174 | } 175 | m_pq->clear(); 176 | return !pBitmaps.empty(); 177 | } 178 | 179 | auto pPaletteBytes = make_unique(sizeof(ColorPalette) + _nMaxColors * sizeof(ARGB)); 180 | auto pPalette = (ColorPalette*)pPaletteBytes.get(); 181 | pPalette->Count = _nMaxColors; 182 | m_pq->pnnquan(m_pixelsList[0], pPalette->Entries, _nMaxColors); 183 | 184 | int i = 0; 185 | for(auto& pixels : m_pixelsList) { 186 | m_pq->QuantizeImage(pixels, _bitmapWidths[i], pPalette->Entries, pBitmaps[i].get(), _nMaxColors, dither); 187 | pBitmaps[i++]->SetPalette((ColorPalette*) pPalette); 188 | } 189 | m_pq->clear(); 190 | return !pBitmaps.empty(); 191 | } 192 | 193 | void PnnLABGAQuantizer::clear() { 194 | lock_guard lock(_mutex); 195 | m_pixelsList.clear(); 196 | _fitnessMap.clear(); 197 | } 198 | 199 | double randrange(double min, double max) 200 | { 201 | auto f = (double) rand() / RAND_MAX; 202 | return min + f * (max - min); 203 | } 204 | 205 | void PnnLABGAQuantizer::setRatio(double ratioX, double ratioY) 206 | { 207 | auto difference = abs(ratioX - ratioY); 208 | if (difference <= minRatio) 209 | ratioY = ratioX; 210 | _ratioX = min(max(ratioX, minRatio), maxRatio); 211 | _ratioY = min(max(ratioY, minRatio), maxRatio); 212 | } 213 | 214 | float PnnLABGAQuantizer::getFitness() { 215 | return (float) _fitness; 216 | } 217 | 218 | static double rotateLeft(double u, double v, double delta) { 219 | auto theta = M_PI * randrange(minRatio, maxRatio) / exp(delta); 220 | auto result = u * sin(theta) + v * cos(theta); 221 | if (delta < 50 && (result <= minRatio || result >= maxRatio)) 222 | result = rotateLeft(u, v, delta + .5); 223 | return result; 224 | } 225 | 226 | static double rotateRight(double u, double v, double delta) { 227 | auto theta = M_PI * randrange(minRatio, maxRatio) / exp(delta); 228 | auto result = u * cos(theta) - v * sin(theta); 229 | if (delta < 50 && (result <= minRatio || result >= maxRatio)) 230 | result = rotateRight(u, v, delta + .5); 231 | return result; 232 | } 233 | 234 | shared_ptr PnnLABGAQuantizer::crossover(const PnnLABGAQuantizer& mother, int numberOfCrossoverPoints, float crossoverProbability) 235 | { 236 | auto child = makeNewFromPrototype(); 237 | if ((rand() % 100) <= crossoverProbability) 238 | return child; 239 | 240 | auto ratioX = rotateRight(_ratioX, mother._ratioY, 0.0); 241 | auto ratioY = rotateLeft(_ratioY, mother._ratioX, 0.0); 242 | child->setRatio(ratioX, ratioY); 243 | child->calculateFitness(); 244 | return child; 245 | } 246 | 247 | static double boxMuller(double value) { 248 | auto r1 = randrange(minRatio, maxRatio); 249 | return sqrt(-2 * log(value)) * cos(2 * M_PI * r1); 250 | } 251 | 252 | bool PnnLABGAQuantizer::dominates(const PnnLABGAQuantizer* right) { 253 | bool better = false; 254 | for (int f = 0; f < getObjectives().size(); ++f) { 255 | if (getObjectives()[f] > right->getObjectives()[f]) 256 | return false; 257 | 258 | if (getObjectives()[f] < right->getObjectives()[f]) 259 | better = true; 260 | } 261 | return better; 262 | } 263 | 264 | void PnnLABGAQuantizer::mutation(int mutationSize, float mutationProbability) { 265 | // check probability of mutation operation 266 | if ((rand() % 100) > mutationProbability) 267 | return; 268 | 269 | auto ratioX = _ratioX; 270 | auto ratioY = _ratioY; 271 | if(randrange(.0, 1.0) > .5) 272 | ratioX = boxMuller(ratioX); 273 | else 274 | ratioY = boxMuller(ratioY); 275 | 276 | setRatio(ratioX, ratioY); 277 | calculateFitness(); 278 | } 279 | 280 | vector PnnLABGAQuantizer::getObjectives() const 281 | { 282 | return _objectives; 283 | } 284 | 285 | vector& PnnLABGAQuantizer::getConvertedObjectives() 286 | { 287 | return _convertedObjectives; 288 | } 289 | 290 | void PnnLABGAQuantizer::resizeConvertedObjectives(int numObj) { 291 | _convertedObjectives.resize(numObj); 292 | } 293 | 294 | shared_ptr PnnLABGAQuantizer::makeNewFromPrototype() { 295 | auto child = make_shared(*m_pq, m_pixelsList, _bitmapWidths, _nMaxColors); 296 | auto minRatio2 = 2.0 * minRatio; 297 | if(minRatio2 > 1) 298 | minRatio2 = 0; 299 | auto ratioX = randrange(minRatio, maxRatio); 300 | auto ratioY = ratioX < minRatio2 ? randrange(minRatio, maxRatio) : ratioX; 301 | child->setRatio(ratioX, ratioY); 302 | child->calculateFitness(); 303 | return child; 304 | } 305 | 306 | UINT PnnLABGAQuantizer::getMaxColors() const { 307 | return _nMaxColors; 308 | } 309 | 310 | 311 | string PnnLABGAQuantizer::getResult() const 312 | { 313 | ostringstream ss; 314 | auto difference = abs(_ratioX - _ratioY); 315 | if (difference <= 0.0000001) 316 | ss << setprecision(6) << _ratioX; 317 | else 318 | ss << setprecision(6) << _ratioX << ", " << _ratioY; 319 | return ss.str(); 320 | } 321 | 322 | } 323 | -------------------------------------------------------------------------------- /nQuantCpp/PnnLABGAQuantizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "PnnLABQuantizer.h" 3 | #include "APNsgaIII.h" 4 | 5 | #include 6 | 7 | namespace PnnLABQuant 8 | { 9 | // ============================================================= 10 | // Quantizer objects and functions 11 | // 12 | // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY 13 | // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES 14 | // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE 15 | // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED 16 | // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT 17 | // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY 18 | // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL 19 | // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER 20 | // THIS DISCLAIMER. 21 | // 22 | // Use at your own risk! 23 | // ============================================================= 24 | 25 | class PnnLABGAQuantizer 26 | { 27 | private: 28 | //Asserts floating point compatibility at compile time 29 | static_assert(std::numeric_limits::is_iec559, "IEEE 754 required"); 30 | 31 | double _fitness = -numeric_limits::infinity(); 32 | double _ratioX = 0, _ratioY = 0; 33 | vector _convertedObjectives; 34 | vector _objectives; 35 | vector > m_pixelsList; 36 | unique_ptr m_pq; 37 | 38 | void calculateError(vector& errors); 39 | void calculateFitness(); 40 | string getRatioKey() const; 41 | auto findByRatioKey(const string& ratioKey) const; 42 | void clear(); 43 | 44 | public: 45 | PnnLABGAQuantizer(PnnLABQuantizer& pq, const vector >& pSources, UINT nMaxColors); 46 | PnnLABGAQuantizer(PnnLABQuantizer& pq, const vector >& pixelsList, const vector& bitmapWidths, UINT nMaxColors); 47 | 48 | float getFitness(); 49 | shared_ptr crossover(const PnnLABGAQuantizer& mother, int numberOfCrossoverPoints, float crossoverProbability); 50 | bool dominates(const PnnLABGAQuantizer* right); 51 | void mutation(int mutationSize, float mutationProbability); 52 | vector getObjectives() const; 53 | vector& getConvertedObjectives(); 54 | void resizeConvertedObjectives(int numObj); 55 | shared_ptr makeNewFromPrototype(); 56 | 57 | UINT getMaxColors() const; 58 | string getResult() const; 59 | bool hasAlpha() const { 60 | return m_pq->hasAlpha(); 61 | } 62 | void setRatio(double ratioX, double ratioY); 63 | bool QuantizeImage(vector >& pBitmaps, bool dither = true); 64 | }; 65 | 66 | } 67 | -------------------------------------------------------------------------------- /nQuantCpp/PnnLABQuantizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "CIELABConvertor.h" 3 | #include 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | namespace PnnLABQuant 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 PnnLABQuantizer 27 | { 28 | private: 29 | bool hasSemiTransparency = false; 30 | int m_transparentPixelIndex = -1; 31 | bool isGA = false; 32 | double proportional = 1.0, ratio = .5, ratioY = .5; 33 | unordered_map pixelMap; 34 | unordered_map > closestMap; 35 | unordered_map nearestMap; 36 | vector saliencies; 37 | 38 | struct pnnbin { 39 | float ac = 0, Lc = 0, Ac = 0, Bc = 0, err = 0; 40 | float cnt = 0; 41 | int nn = 0, fw = 0, bk = 0, tm = 0, mtm = 0; 42 | }; 43 | 44 | void find_nn(pnnbin* bins, int idx, bool texicab); 45 | unsigned short closestColorIndex(const ARGB* pPalette, const UINT nMaxColors, ARGB argb, const UINT pos); 46 | bool quantize_image(const ARGB* pixels, const ARGB* pPalette, const UINT nMaxColors, unsigned short* qPixels, const UINT width, const UINT height, const bool dither); 47 | 48 | public: 49 | PnnLABQuantizer(); 50 | PnnLABQuantizer(const PnnLABQuantizer& quantizer); 51 | void clear(); 52 | void pnnquan(const vector& pixels, ARGB* pPalette, UINT& nMaxColors); 53 | bool IsGA() const; 54 | void getLab(const Color& c, CIELABConvertor::Lab& lab1); 55 | bool hasAlpha() const; 56 | unsigned short nearestColorIndex(const ARGB* pPalette, const UINT nMaxColors, ARGB argb, const UINT pos); 57 | void setRatio(double ratioX, double ratioY); 58 | void grabPixels(Bitmap* srcImg, vector& pixels, UINT& nMaxColors, bool& hasSemiTransparency); 59 | bool QuantizeImageByPal(const vector& pixels, const UINT bitmapWidth, const ARGB* pPalette, Bitmap* pDest, UINT& nMaxColors, bool dither = true); 60 | bool QuantizeImage(const vector& pixels, const UINT bitmapWidth, ARGB* pPalette, Bitmap* pDest, UINT& nMaxColors, bool dither = true); 61 | bool QuantizeImage(Bitmap* pSource, Bitmap* pDest, UINT& nMaxColors, bool dither = true); 62 | }; 63 | } -------------------------------------------------------------------------------- /nQuantCpp/PnnQuantizer.cpp: -------------------------------------------------------------------------------- 1 | /* Fast pairwise nearest neighbor based algorithm for multilevel thresholding 2 | Copyright (C) 2004-2016 Mark Tyler and Dmitry Groshev 3 | Copyright (c) 2018-2024 Miller Cy Chan 4 | * error measure; time used is proportional to number of bins squared - WJ */ 5 | 6 | #include "stdafx.h" 7 | #include "PnnQuantizer.h" 8 | #include "GilbertCurve.h" 9 | #include "BlueNoise.h" 10 | #include 11 | 12 | namespace PnnQuant 13 | { 14 | BYTE alphaThreshold = 0xF; 15 | bool hasSemiTransparency = false; 16 | int m_transparentPixelIndex = -1; 17 | double ratio = .5, weight = 1.0; 18 | ARGB m_transparentColor = Color::Transparent; 19 | double PR = .299, PG = .587, PB = .114, PA = .3333; 20 | unordered_map > closestMap; 21 | unordered_map nearestMap; 22 | 23 | static const float coeffs[3][3] = { 24 | {0.299f, 0.587f, 0.114f}, 25 | {-0.14713f, -0.28886f, 0.436f}, 26 | {0.615f, -0.51499f, -0.10001f} 27 | }; 28 | 29 | struct pnnbin { 30 | float ac = 0, rc = 0, gc = 0, bc = 0, err = 0; 31 | float cnt = 0; 32 | int nn = 0, fw = 0, bk = 0, tm = 0, mtm = 0; 33 | }; 34 | 35 | void find_nn(pnnbin* bins, int idx) 36 | { 37 | int nn = 0; 38 | float err = 1e100; 39 | 40 | auto& bin1 = bins[idx]; 41 | auto n1 = bin1.cnt; 42 | auto wa = bin1.ac; 43 | auto wr = bin1.rc; 44 | auto wg = bin1.gc; 45 | auto wb = bin1.bc; 46 | 47 | int start = 0; 48 | if (BlueNoise::TELL_BLUE_NOISE[idx & 4095] > 0) 49 | start = (PG < coeffs[0][1]) ? 3 : 1; 50 | 51 | for (int i = bin1.fw; i; i = bins[i].fw) { 52 | auto n2 = bins[i].cnt, nerr2 = (n1 * n2) / (n1 + n2); 53 | if (nerr2 >= err) 54 | continue; 55 | 56 | auto nerr = 0.0; 57 | if (hasSemiTransparency) { 58 | nerr += nerr2 * PA * sqr(bins[i].ac - wa); 59 | if (nerr >= err) 60 | continue; 61 | } 62 | 63 | nerr += nerr2 * (1 - ratio) * PR * sqr(bins[i].rc - wr); 64 | if (nerr >= err) 65 | continue; 66 | 67 | nerr += nerr2 * (1 - ratio) * PG * sqr(bins[i].gc - wg); 68 | if (nerr >= err) 69 | continue; 70 | 71 | nerr += nerr2 * (1 - ratio) * PB * sqr(bins[i].bc - wb); 72 | if (nerr >= err) 73 | continue; 74 | 75 | for (int j = start; j < 3; ++j) { 76 | nerr += nerr2 * ratio * sqr(coeffs[j][0] * (bins[i].rc - wr)); 77 | if (nerr >= err) 78 | break; 79 | 80 | nerr += nerr2 * ratio * sqr(coeffs[j][1] * (bins[i].gc - wg)); 81 | if (nerr >= err) 82 | break; 83 | 84 | nerr += nerr2 * ratio * sqr(coeffs[j][2] * (bins[i].bc - wb)); 85 | if (nerr >= err) 86 | break; 87 | } 88 | 89 | if (nerr >= err) 90 | continue; 91 | err = nerr; 92 | nn = i; 93 | } 94 | bin1.err = err; 95 | bin1.nn = nn; 96 | } 97 | 98 | typedef float (*QuanFn)(const float& cnt); 99 | QuanFn getQuanFn(const UINT& nMaxColors, const short quan_rt) { 100 | if (quan_rt > 0) { 101 | if (nMaxColors < 64) 102 | return[](const float& cnt) { 103 | return (float)(int)_sqrt(cnt); 104 | }; 105 | return[](const float& cnt) { 106 | return (float)_sqrt(cnt); 107 | }; 108 | } 109 | if (quan_rt < 0) 110 | return[](const float& cnt) { return (float)(int)cbrt(cnt); }; 111 | return[](const float& cnt) { return cnt; }; 112 | } 113 | 114 | void pnnquan(const vector& pixels, ARGB* pPalette, UINT& nMaxColors) 115 | { 116 | short quan_rt = 1; 117 | vector bins(USHRT_MAX + 1); 118 | 119 | /* Build histogram */ 120 | for (const auto& pixel : pixels) { 121 | Color c(pixel); 122 | if (c.GetA() <= alphaThreshold) 123 | c = m_transparentColor; 124 | 125 | int index = GetARGBIndex(c, hasSemiTransparency, nMaxColors < 64 || m_transparentPixelIndex >= 0); 126 | auto& tb = bins[index]; 127 | tb.ac += c.GetA(); 128 | tb.rc += c.GetR(); 129 | tb.gc += c.GetG(); 130 | tb.bc += c.GetB(); 131 | tb.cnt += 1.0; 132 | } 133 | 134 | /* Cluster nonempty bins at one end of array */ 135 | int maxbins = 0; 136 | 137 | for (int i = 0; i < bins.size(); ++i) { 138 | if (bins[i].cnt <= 0) 139 | continue; 140 | 141 | auto& tb = bins[i]; 142 | double d = 1.0 / tb.cnt; 143 | tb.ac *= d; 144 | tb.rc *= d; 145 | tb.gc *= d; 146 | tb.bc *= d; 147 | 148 | bins[maxbins++] = tb; 149 | } 150 | 151 | if (nMaxColors < 16) 152 | quan_rt = -1; 153 | 154 | weight = min(0.9, nMaxColors * 1.0 / maxbins); 155 | if (weight < .03 && PG < 1 && PG >= coeffs[0][1]) { 156 | PR = PG = PB = PA = 1; 157 | if (nMaxColors >= 64) 158 | quan_rt = 0; 159 | } 160 | 161 | auto quanFn = getQuanFn(nMaxColors, quan_rt); 162 | 163 | int j = 0; 164 | for (; j < maxbins - 1; ++j) { 165 | bins[j].fw = j + 1; 166 | bins[j + 1].bk = j; 167 | 168 | bins[j].cnt = quanFn(bins[j].cnt); 169 | } 170 | bins[j].cnt = quanFn(bins[j].cnt); 171 | 172 | auto heap = make_unique(bins.size() + 1); 173 | int h, l, l2; 174 | /* Initialize nearest neighbors and build heap of them */ 175 | for (int i = 0; i < maxbins; ++i) { 176 | find_nn(bins.data(), i); 177 | /* Push slot on heap */ 178 | auto err = bins[i].err; 179 | for (l = ++heap[0]; l > 1; l = l2) { 180 | l2 = l >> 1; 181 | if (bins[h = heap[l2]].err <= err) 182 | break; 183 | heap[l] = h; 184 | } 185 | heap[l] = i; 186 | } 187 | 188 | /* Merge bins which increase error the least */ 189 | int extbins = maxbins - nMaxColors; 190 | for (int i = 0; i < extbins; ) { 191 | int b1; 192 | 193 | /* Use heap to find which bins to merge */ 194 | for (;;) { 195 | auto& tb = bins[b1 = heap[1]]; /* One with least error */ 196 | /* Is stored error up to date? */ 197 | if ((tb.tm >= tb.mtm) && (bins[tb.nn].mtm <= tb.tm)) 198 | break; 199 | if (tb.mtm == USHRT_MAX) /* Deleted node */ 200 | b1 = heap[1] = heap[heap[0]--]; 201 | else /* Too old error value */ 202 | { 203 | find_nn(bins.data(), b1); 204 | tb.tm = i; 205 | } 206 | /* Push slot down */ 207 | auto err = bins[b1].err; 208 | for (l = 1; (l2 = l + l) <= heap[0]; l = l2) { 209 | if ((l2 < heap[0]) && (bins[heap[l2]].err > bins[heap[l2 + 1]].err)) 210 | ++l2; 211 | if (err <= bins[h = heap[l2]].err) 212 | break; 213 | heap[l] = h; 214 | } 215 | heap[l] = b1; 216 | } 217 | 218 | /* Do a merge */ 219 | auto& tb = bins[b1]; 220 | auto& nb = bins[tb.nn]; 221 | auto n1 = tb.cnt; 222 | auto n2 = nb.cnt; 223 | auto d = 1.0f / (n1 + n2); 224 | tb.ac = d * rint(n1 * tb.ac + n2 * nb.ac); 225 | tb.rc = d * rint(n1 * tb.rc + n2 * nb.rc); 226 | tb.gc = d * rint(n1 * tb.gc + n2 * nb.gc); 227 | tb.bc = d * rint(n1 * tb.bc + n2 * nb.bc); 228 | tb.cnt += n2; 229 | tb.mtm = ++i; 230 | 231 | /* Unchain deleted bin */ 232 | bins[nb.bk].fw = nb.fw; 233 | bins[nb.fw].bk = nb.bk; 234 | nb.mtm = USHRT_MAX; 235 | } 236 | 237 | /* Fill palette */ 238 | UINT k = 0; 239 | for (int i = 0; k < nMaxColors; ++k) { 240 | auto alpha = (hasSemiTransparency || m_transparentPixelIndex > -1) ? rint(bins[i].ac) : BYTE_MAX; 241 | pPalette[k] = Color::MakeARGB(alpha, (int) bins[i].rc, (int) bins[i].gc, (int) bins[i].bc); 242 | 243 | i = bins[i].fw; 244 | } 245 | } 246 | 247 | unsigned short nearestColorIndex(const ARGB* pPalette, const UINT nMaxColors, ARGB argb, const UINT pos) 248 | { 249 | auto got = nearestMap.find(argb); 250 | if (got != nearestMap.end()) 251 | return got->second; 252 | 253 | unsigned short k = 0; 254 | Color c(argb); 255 | if (c.GetA() <= alphaThreshold) 256 | c = m_transparentColor; 257 | 258 | if (nMaxColors > 2 && m_transparentPixelIndex >= 0 && c.GetA() > alphaThreshold) 259 | k = 1; 260 | 261 | auto pr = PR, pg = PG, pb = PB, pa = PA; 262 | if(nMaxColors < 3) 263 | pr = pg = pb = pa = 1; 264 | 265 | double mindist = INT_MAX; 266 | for (UINT i = k; i < nMaxColors; ++i) { 267 | Color c2(pPalette[i]); 268 | double curdist = pa * sqr(c2.GetA() - c.GetA()); 269 | if (curdist > mindist) 270 | continue; 271 | 272 | curdist += pr * sqr(c2.GetR() - c.GetR()); 273 | if (curdist > mindist) 274 | continue; 275 | 276 | curdist += pg * sqr(c2.GetG() - c.GetG()); 277 | if (curdist > mindist) 278 | continue; 279 | 280 | curdist += pb * sqr(c2.GetB() - c.GetB()); 281 | if (curdist > mindist) 282 | continue; 283 | 284 | mindist = curdist; 285 | k = i; 286 | } 287 | nearestMap[argb] = k; 288 | return k; 289 | } 290 | 291 | unsigned short closestColorIndex(const ARGB* pPalette, const UINT nMaxColors, ARGB argb, const UINT pos) 292 | { 293 | UINT k = 0; 294 | Color c(argb); 295 | if (c.GetA() <= alphaThreshold) 296 | return nearestColorIndex(pPalette, nMaxColors, argb, pos); 297 | 298 | vector closest(4); 299 | auto got = closestMap.find(argb); 300 | if (got == closestMap.end()) { 301 | closest[2] = closest[3] = USHRT_MAX; 302 | 303 | auto pr = PR, pg = PG, pb = PB, pa = PA; 304 | if(nMaxColors < 3) 305 | pr = pg = pb = pa = 1; 306 | 307 | for (; k < nMaxColors; ++k) { 308 | Color c2(pPalette[k]); 309 | auto err = pr * sqr(c2.GetR() - c.GetR()); 310 | if (err >= closest[3]) 311 | break; 312 | 313 | err += pg* sqr(c2.GetG() - c.GetG()); 314 | if (err >= closest[3]) 315 | break; 316 | 317 | err += pb* sqr(c2.GetB() - c.GetB()); 318 | if (err >= closest[3]) 319 | break; 320 | 321 | if (hasSemiTransparency) 322 | err += pa * sqr(c2.GetA() - c.GetA()); 323 | 324 | if (err < closest[2]) { 325 | closest[1] = closest[0]; 326 | closest[3] = closest[2]; 327 | closest[0] = k; 328 | closest[2] = err; 329 | } 330 | else if (err < closest[3]) { 331 | closest[1] = k; 332 | closest[3] = err; 333 | } 334 | } 335 | 336 | if (closest[3] == USHRT_MAX) 337 | closest[1] = closest[0]; 338 | 339 | closestMap[argb] = closest; 340 | } 341 | else 342 | closest = got->second; 343 | 344 | auto MAX_ERR = nMaxColors << 2; 345 | int idx = (pos + 1) % 2; 346 | if (closest[3] * .67 < (closest[3] - closest[2])) 347 | idx = 0; 348 | else if (closest[0] > closest[1]) 349 | idx = pos % 2; 350 | 351 | if (closest[idx + 2] >= MAX_ERR || (m_transparentPixelIndex >= 0 && closest[idx] == 0)) 352 | return nearestColorIndex(pPalette, nMaxColors, argb, pos); 353 | return closest[idx]; 354 | } 355 | 356 | inline int GetColorIndex(const Color& c) 357 | { 358 | return GetARGBIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0); 359 | } 360 | 361 | bool quantize_image(const ARGB* pixels, const ColorPalette* pPalette, const UINT nMaxColors, unsigned short* qPixels, const UINT width, const UINT height, const bool dither) 362 | { 363 | if (dither) 364 | return dither_image(pixels, pPalette->Entries, nMaxColors, nearestColorIndex, hasSemiTransparency, m_transparentPixelIndex, qPixels, width, height); 365 | 366 | DitherFn ditherFn = (m_transparentPixelIndex >= 0 || nMaxColors < 256) ? nearestColorIndex : closestColorIndex; 367 | UINT pixelIndex = 0; 368 | for (int j = 0; j < height; ++j) { 369 | for (int i = 0; i < width; ++i, ++pixelIndex) 370 | qPixels[pixelIndex] = ditherFn(pPalette->Entries, nMaxColors, pixels[pixelIndex], i + j); 371 | } 372 | 373 | BlueNoise::dither(width, height, pixels, pPalette->Entries, nMaxColors, ditherFn, GetColorIndex, qPixels); 374 | return true; 375 | } 376 | 377 | bool PnnQuantizer::QuantizeImage(const vector& pixels, const UINT bitmapWidth, ARGB* pPalette, Bitmap* pDest, UINT& nMaxColors, bool dither) 378 | { 379 | if (nMaxColors <= 32) 380 | PR = PG = PB = PA = 1; 381 | else { 382 | PR = coeffs[0][0]; PG = coeffs[0][1]; PB = coeffs[0][2]; 383 | } 384 | 385 | const auto bitmapHeight = pixels.size() / bitmapWidth; 386 | 387 | if (nMaxColors > 2) 388 | pnnquan(pixels, pPalette, nMaxColors); 389 | else { 390 | if (m_transparentPixelIndex >= 0) { 391 | pPalette[0] = m_transparentColor; 392 | pPalette[1] = Color::Black; 393 | } 394 | else { 395 | pPalette[0] = Color::Black; 396 | pPalette[1] = Color::White; 397 | } 398 | } 399 | 400 | DitherFn ditherFn = dither ? nearestColorIndex : closestColorIndex; 401 | if (hasSemiTransparency) 402 | weight *= -1; 403 | 404 | vector saliencies; 405 | 406 | if (nMaxColors > 256) { 407 | auto qPixels = make_unique(pixels.size()); 408 | Peano::GilbertCurve::dither(bitmapWidth, bitmapHeight, pixels.data(), pPalette, nMaxColors, ditherFn, GetColorIndex, qPixels.get(), saliencies.data(), weight, dither); 409 | 410 | closestMap.clear(); 411 | nearestMap.clear(); 412 | return ProcessImagePixels(pDest, qPixels.get(), hasSemiTransparency, m_transparentPixelIndex); 413 | } 414 | 415 | auto qPixels = make_unique(pixels.size()); 416 | Peano::GilbertCurve::dither(bitmapWidth, bitmapHeight, pixels.data(), pPalette, nMaxColors, ditherFn, GetColorIndex, qPixels.get(), saliencies.data(), weight, dither); 417 | 418 | if (!dither && nMaxColors > 32) 419 | BlueNoise::dither(bitmapWidth, bitmapHeight, pixels.data(), pPalette, nMaxColors, ditherFn, GetColorIndex, qPixels.get()); 420 | 421 | if (m_transparentPixelIndex >= 0) { 422 | UINT k = qPixels[m_transparentPixelIndex]; 423 | if (nMaxColors > 2) 424 | pPalette[k] = m_transparentColor; 425 | else if (pPalette[k] != m_transparentColor) 426 | swap(pPalette[0], pPalette[1]); 427 | } 428 | closestMap.clear(); 429 | nearestMap.clear(); 430 | 431 | return ProcessImagePixels(pDest, qPixels.get(), m_transparentPixelIndex >= 0); 432 | } 433 | 434 | bool PnnQuantizer::QuantizeImage(Bitmap* pSource, Bitmap* pDest, UINT& nMaxColors, bool dither) 435 | { 436 | const auto bitmapWidth = pSource->GetWidth(); 437 | const auto bitmapHeight = pSource->GetHeight(); 438 | const auto area = (size_t) (bitmapWidth * bitmapHeight); 439 | 440 | vector pixels(area); 441 | int semiTransCount = 0; 442 | GrabPixels(pSource, pixels, semiTransCount, m_transparentPixelIndex, m_transparentColor, alphaThreshold, nMaxColors); 443 | hasSemiTransparency = semiTransCount > 0; 444 | 445 | if (nMaxColors > 256) { 446 | auto pPalettes = make_unique(nMaxColors); 447 | auto pPalette = pPalettes.get(); 448 | return QuantizeImage(pixels, bitmapWidth, pPalette, pDest, nMaxColors, dither); 449 | } 450 | 451 | auto pPaletteBytes = make_unique(sizeof(ColorPalette) + nMaxColors * sizeof(ARGB)); 452 | auto pPalette = (ColorPalette*)pPaletteBytes.get(); 453 | pPalette->Count = nMaxColors; 454 | auto result = QuantizeImage(pixels, bitmapWidth, pPalette->Entries, pDest, nMaxColors, dither); 455 | pDest->SetPalette(pPalette); 456 | return result; 457 | } 458 | 459 | } 460 | -------------------------------------------------------------------------------- /nQuantCpp/PnnQuantizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "bitmapUtilities.h" 3 | 4 | namespace PnnQuant 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 PnnQuantizer 23 | { 24 | public: 25 | bool QuantizeImage(const vector& pixels, const UINT bitmapWidth, ARGB* pPalette, Bitmap* pDest, UINT& nMaxColors, bool dither = true); 26 | bool QuantizeImage(Bitmap* pSource, Bitmap* pDest, UINT& nMaxColors, bool dither = true); 27 | }; 28 | } -------------------------------------------------------------------------------- /nQuantCpp/Resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by nQuantCpp.rc 4 | // 5 | 6 | #define IDS_APP_TITLE 103 7 | 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 101 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1000 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /nQuantCpp/SpatialQuantizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "bitmapUtilities.h" 3 | 4 | namespace SpatialQuant 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 SpatialQuantizer 23 | { 24 | public: 25 | bool QuantizeImage(Bitmap* pSource, Bitmap* pDest, UINT& nMaxColors, bool dither = true); 26 | }; 27 | } -------------------------------------------------------------------------------- /nQuantCpp/WuQuantizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "bitmapUtilities.h" 3 | 4 | // ============================================================= 5 | // Quantizer objects and functions 6 | // 7 | // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY 8 | // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES 9 | // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE 10 | // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED 11 | // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT 12 | // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY 13 | // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL 14 | // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER 15 | // THIS DISCLAIMER. 16 | // 17 | // Use at your own risk! 18 | // ============================================================= 19 | 20 | namespace nQuant 21 | { 22 | /** 23 | Xiaolin Wu color quantization algorithm 24 | */ 25 | enum Pixel : BYTE { Blue, Green, Red, Alpha }; 26 | 27 | class WuQuantizer 28 | { 29 | public: 30 | bool QuantizeImage(Bitmap* pSource, Bitmap* pDest, UINT& nMaxColors, bool dither = true, BYTE alphaThreshold = 0, BYTE alphaFader = 1); 31 | }; 32 | } -------------------------------------------------------------------------------- /nQuantCpp/bitmapUtilities.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | #ifndef _WIN32 9 | #include 10 | #include 11 | using namespace Gdiplus; 12 | #endif 13 | 14 | 15 | ////////////////////////////////////////////////////////////////////////// 16 | // 17 | // GetBitmapHeaderSize 18 | // 19 | 20 | ULONG GetBitmapHeaderSize(LPCVOID pDib); 21 | 22 | 23 | ////////////////////////////////////////////////////////////////////////// 24 | // 25 | // GetBitmapLineWidthInBytes 26 | // 27 | 28 | ULONG GetBitmapLineWidthInBytes(ULONG nWidthInPixels, ULONG nBitCount); 29 | 30 | ////////////////////////////////////////////////////////////////////////// 31 | // 32 | // GetBitmapDimensions 33 | // 34 | 35 | BOOL GetBitmapDimensions(LPCVOID pDib, UINT *pWidth, UINT *pHeight); 36 | 37 | 38 | ////////////////////////////////////////////////////////////////////////// 39 | // 40 | // GetBitmapSize 41 | // 42 | 43 | ULONG GetBitmapSize(LPCVOID pDib); 44 | 45 | 46 | 47 | ////////////////////////////////////////////////////////////////////////// 48 | // 49 | // GetBitmapOffsetBits 50 | // 51 | 52 | ULONG GetBitmapOffsetBits(LPCVOID pDib); 53 | 54 | ////////////////////////////////////////////////////////////////////////// 55 | // 56 | // FixBitmapHeight 57 | // 58 | 59 | BOOL FixBitmapHeight(PVOID pDib, ULONG nSize, BOOL bTopDown); 60 | 61 | 62 | ////////////////////////////////////////////////////////////////////////// 63 | // 64 | // FillBitmapFileHeader 65 | // 66 | 67 | BOOL FillBitmapFileHeader(LPCVOID pDib, PBITMAPFILEHEADER pbmfh); 68 | 69 | using DitherFn = function; 70 | 71 | using GetColorIndexFn = function; 72 | 73 | void CalcDitherPixel(int* pDitherPixel, const Color& c, const BYTE* clamp, const short* rowerr, int cursor, const bool noBias); 74 | 75 | bool dither_image(const ARGB* pixels, const ARGB* pPalette, const UINT nMaxColors, DitherFn ditherFn, const bool& hasSemiTransparency, const int& transparentPixelIndex, unsigned short* qPixels, const UINT width, const UINT height); 76 | 77 | bool dithering_image(const ARGB* pixels, const ColorPalette* pPalette, DitherFn ditherFn, const bool& hasSemiTransparency, const int& transparentPixelIndex, const UINT nMaxColors, ARGB* qPixels, const UINT width, const UINT height); 78 | 79 | bool ProcessImagePixels(Bitmap* pDest, const ARGB* qPixels, const bool& hasSemiTransparency, const int& transparentPixelIndex); 80 | 81 | bool ProcessImagePixels(Bitmap* pDest, const unsigned short* qPixels, const bool hasTransparent); 82 | 83 | bool GrabPixels(Bitmap* pSource, vector& pixels, int& semiTransCount, int& transparentPixelIndex, ARGB& transparentColor, const BYTE alphaThreshold, const UINT nMaxColors = 2); 84 | 85 | int GrabPixels(Bitmap* pSource, vector& pixels, bool& hasSemiTransparency, int& transparentPixelIndex, ARGB& transparentColor, const BYTE alphaThreshold, const UINT nMaxColors = 2); 86 | 87 | bool HasTransparency(Bitmap* pSource); 88 | 89 | inline int GetARGB1555(const Color& c) 90 | { 91 | return (c.GetA() & 0x80) << 8 | (c.GetR() & 0xF8) << 7 | (c.GetG() & 0xF8) << 2 | (c.GetB() >> 3); 92 | } 93 | 94 | inline int GetARGBIndex(const Color& c, const bool hasSemiTransparency, const bool hasTransparency) 95 | { 96 | if (hasSemiTransparency) 97 | return (c.GetA() & 0xF0) << 8 | (c.GetR() & 0xF0) << 4 | (c.GetG() & 0xF0) | (c.GetB() >> 4); 98 | if (hasTransparency) 99 | return GetARGB1555(c); 100 | return (c.GetR() & 0xF8) << 8 | (c.GetG() & 0xFC) << 3 | (c.GetB() >> 3); 101 | } 102 | -------------------------------------------------------------------------------- /nQuantCpp/nQuantCpp.aps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcychan/nQuantCpp/9415eb9f183faf14b5377eab83afe55176210128/nQuantCpp/nQuantCpp.aps -------------------------------------------------------------------------------- /nQuantCpp/nQuantCpp.cpp: -------------------------------------------------------------------------------- 1 | // nQuantCpp.cpp 2 | // 3 | 4 | #include "stdafx.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | namespace fs = std::filesystem; 14 | 15 | #include "nQuantCpp.h" 16 | 17 | #include "PnnQuantizer.h" 18 | #include "NeuQuantizer.h" 19 | #include "WuQuantizer.h" 20 | #include "PnnLABQuantizer.h" 21 | #include "PnnLABGAQuantizer.h" 22 | #include "EdgeAwareSQuantizer.h" 23 | #include "SpatialQuantizer.h" 24 | #include "DivQuantizer.h" 25 | #include "Dl3Quantizer.h" 26 | #include "MedianCut.h" 27 | #include "Otsu.h" 28 | #include "GifWriter.h" 29 | #include 30 | 31 | #ifdef _DEBUG 32 | #define new DEBUG_NEW 33 | #endif 34 | 35 | GdiplusStartupInput m_gdiplusStartupInput; 36 | ULONG_PTR m_gdiplusToken; 37 | 38 | wstring algs[] = { L"PNN", L"PNNLAB", L"PNNLAB+", L"NEU", L"WU", L"EAS", L"SPA", L"DIV", L"DL3", L"MMC", L"OTSU" }; 39 | unordered_map extensionMap; 40 | 41 | void PrintUsage() 42 | { 43 | wcout << endl; 44 | wcout << "usage: nQuantCpp [options]" << endl; 45 | wcout << endl; 46 | wcout << "Valid options:" << endl; 47 | wcout << " /a : Algorithm used - Choose one of them, otherwise give you the defaults from ["; 48 | int i = 0; 49 | for(; i" << endl; 56 | } 57 | 58 | bool isdigit(const wchar_t* chars, const bool positiveOnly = true) { 59 | const int string_len = wcslen(chars); 60 | for (int i = 0; i < string_len; ++i) { 61 | if (!isdigit(chars[i])) 62 | return positiveOnly ? false : (chars[0] == L'-'); 63 | } 64 | return true; 65 | } 66 | 67 | bool isAlgo(const wstring& alg) { 68 | for (const auto& algo : algs) { 69 | if (alg == algo) 70 | return true; 71 | } 72 | return false; 73 | } 74 | 75 | bool ProcessArgs(int argc, wstring& algo, UINT& nMaxColors, bool& dither, wstring& targetPath, wstring* argv, long& delay) 76 | { 77 | for (int index = 1; index < argc; ++index) { 78 | auto currentArg = argv[index]; 79 | transform(currentArg.begin(), currentArg.end(), currentArg.begin(), ::toupper); 80 | 81 | auto currentCmd = currentArg[0]; 82 | if (currentArg.length() > 1 && 83 | (currentCmd == L'–' || currentCmd == L'/')) { 84 | if (index >= argc - 1) { 85 | PrintUsage(); 86 | return false; 87 | } 88 | 89 | if (currentArg[1] == L'A') { 90 | auto strAlgo = argv[index + 1]; 91 | transform(strAlgo.begin(), strAlgo.end(), strAlgo.begin(), ::toupper); 92 | if (!isAlgo(strAlgo)) { 93 | PrintUsage(); 94 | return false; 95 | } 96 | algo = strAlgo; 97 | } 98 | else if (currentArg[1] == L'M') { 99 | if (!isdigit(argv[index + 1].c_str())) { 100 | PrintUsage(); 101 | return false; 102 | } 103 | nMaxColors = stoi(argv[index + 1].c_str()); 104 | if (nMaxColors < 2) 105 | nMaxColors = 2; 106 | else if (nMaxColors > 65536) 107 | nMaxColors = 65536; 108 | } 109 | else if (currentArg[1] == L'D') { 110 | auto strDither = argv[index + 1]; 111 | transform(strDither.begin(), strDither.end(), strDither.begin(), ::toupper); 112 | if (!(strDither == L"Y" || strDither == L"N")) { 113 | PrintUsage(); 114 | return false; 115 | } 116 | dither = strDither == L"Y"; 117 | } 118 | else if (currentArg[1] == L'F') { 119 | if (!isdigit(argv[index + 1].c_str(), false)) { 120 | PrintUsage(); 121 | return false; 122 | } 123 | delay = stol(argv[index + 1].c_str()); 124 | } 125 | else if (currentArg[1] == L'O') { 126 | auto szPath = argv[index + 1].c_str(); 127 | wstring tmpPath(szPath, szPath + wcslen(szPath)); 128 | targetPath = tmpPath; 129 | } 130 | else { 131 | PrintUsage(); 132 | return false; 133 | } 134 | } 135 | } 136 | return true; 137 | } 138 | 139 | inline bool fileExists(const wstring& path) 140 | { 141 | return fs::exists(fs::path(path)); 142 | } 143 | 144 | bool OutputImage(const fs::path& sourcePath, const wstring& algorithm, const UINT& nMaxColors, wstring& targetDir, Bitmap* pDest, LPCWSTR defaultExtension = L".png") 145 | { 146 | auto fileName = sourcePath.filename().wstring(); 147 | fileName = fileName.substr(0, fileName.find_last_of(L'.')); 148 | 149 | targetDir = fileExists(targetDir) ? fs::canonical(fs::path(targetDir)) : fs::current_path(); 150 | auto destPath = targetDir + L"/" + fileName + L"-"; 151 | wstring algo(algorithm.begin(), algorithm.end()); 152 | destPath += algo + L"quant"; 153 | 154 | // image/bmp : {557cf400-1a04-11d3-9a73-0000f81ef32e} 155 | const CLSID bmpEncoderClsId = { 0x557cf400, 0x1a04, 0x11d3,{ 0x9a,0x73,0x00,0x00,0xf8,0x1e,0xf3,0x2e } }; 156 | extensionMap.emplace(L".bmp", bmpEncoderClsId); 157 | 158 | // image/gif : {557cf402-1a04-11d3-9a73-0000f81ef32e} 159 | const CLSID gifEncoderClsId = { 0x557cf402, 0x1a04, 0x11d3,{ 0x9a,0x73,0x00,0x00,0xf8,0x1e,0xf3,0x2e } }; 160 | extensionMap.emplace(L".gif", gifEncoderClsId); 161 | 162 | // image/png : {557cf406-1a04-11d3-9a73-0000f81ef32e} 163 | const CLSID pngEncoderClsId = { 0x557cf406, 0x1a04, 0x11d3,{ 0x9a,0x73,0x00,0x00,0xf8,0x1e,0xf3,0x2e } }; 164 | extensionMap.emplace(L".png", pngEncoderClsId); 165 | 166 | auto targetExtension = (pDest->GetPixelFormat() < PixelFormat16bppARGB1555 && nMaxColors > 256) ? L".bmp" : defaultExtension; 167 | destPath += std::to_wstring(nMaxColors) + targetExtension; 168 | auto status = pDest->Save(destPath.c_str(), &extensionMap[targetExtension]); 169 | if (status == Status::Ok) 170 | wcout << L"Converted image: " << destPath << endl; 171 | else 172 | wcout << L"Failed to save image in '" << destPath << L"' file" << endl; 173 | 174 | return status == Status::Ok; 175 | } 176 | 177 | bool QuantizeImage(const wstring& algorithm, const wstring& sourceFile, wstring& targetDir, shared_ptr pSource, UINT nMaxColors, bool dither) 178 | { 179 | // Create 8 bpp indexed bitmap of the same size 180 | auto pDest = make_shared(pSource->GetWidth(), pSource->GetHeight(), (nMaxColors > 256) ? PixelFormat16bppARGB1555 : (nMaxColors > 16) ? PixelFormat8bppIndexed : (nMaxColors > 2) ? PixelFormat4bppIndexed : PixelFormat1bppIndexed); 181 | 182 | bool bSucceeded = false; 183 | if (algorithm == L"PNN") { 184 | PnnQuant::PnnQuantizer pnnQuantizer; 185 | bSucceeded = pnnQuantizer.QuantizeImage(pSource.get(), pDest.get(), nMaxColors, dither); 186 | } 187 | else if (algorithm == L"PNNLAB") { 188 | PnnLABQuant::PnnLABQuantizer pnnLABQuantizer; 189 | bSucceeded = pnnLABQuantizer.QuantizeImage(pSource.get(), pDest.get(), nMaxColors, dither); 190 | } 191 | else if (algorithm == L"PNNLAB+") { 192 | PnnLABQuant::PnnLABQuantizer pnnLABQuantizer; 193 | vector > sources(1, pSource); 194 | PnnLABQuant::PnnLABGAQuantizer pnnLABGAQuantizer(pnnLABQuantizer, sources, nMaxColors); 195 | nQuantGA::APNsgaIII alg(pnnLABGAQuantizer); 196 | alg.run(9999, -numeric_limits::epsilon()); 197 | auto pGAq = alg.getResult(); 198 | wcout << L"\n" << pGAq->getResult().c_str() << endl; 199 | vector > dests; 200 | dests.emplace_back(pDest); 201 | bSucceeded = pGAq->QuantizeImage(dests, dither); 202 | } 203 | else if (algorithm == L"NEU") { 204 | NeuralNet::NeuQuantizer neuQuantizer; 205 | bSucceeded = neuQuantizer.QuantizeImage(pSource.get(), pDest.get(), nMaxColors, dither); 206 | } 207 | else if (algorithm == L"WU") { 208 | nQuant::WuQuantizer wuQuantizer; 209 | bSucceeded = wuQuantizer.QuantizeImage(pSource.get(), pDest.get(), nMaxColors, dither); 210 | } 211 | else if (algorithm == L"EAS") { 212 | EdgeAwareSQuant::EdgeAwareSQuantizer easQuantizer; 213 | bSucceeded = easQuantizer.QuantizeImage(pSource.get(), pDest.get(), nMaxColors, dither); 214 | } 215 | else if (algorithm == L"SPA") { 216 | SpatialQuant::SpatialQuantizer spaQuantizer; 217 | bSucceeded = spaQuantizer.QuantizeImage(pSource.get(), pDest.get(), nMaxColors, dither); 218 | } 219 | else if (algorithm == L"DIV") { 220 | DivQuant::DivQuantizer divQuantizer; 221 | bSucceeded = divQuantizer.QuantizeImage(pSource.get(), pDest.get(), nMaxColors, dither); 222 | } 223 | else if (algorithm == L"DL3") { 224 | Dl3Quant::Dl3Quantizer dl3Quantizer; 225 | bSucceeded = dl3Quantizer.QuantizeImage(pSource.get(), pDest.get(), nMaxColors, dither); 226 | } 227 | else if (algorithm == L"MMC") { 228 | MedianCutQuant::MedianCut mmcQuantizer; 229 | bSucceeded = mmcQuantizer.QuantizeImage(pSource.get(), pDest.get(), nMaxColors, dither); 230 | } 231 | else if (algorithm == L"OTSU") { 232 | nMaxColors = 2; 233 | OtsuThreshold::Otsu otsu; 234 | bSucceeded = otsu.ConvertGrayScaleToBinary(pSource.get(), pDest.get(), false, dither); 235 | } 236 | 237 | if (!bSucceeded) 238 | return bSucceeded; 239 | 240 | auto sourcePath = fs::canonical(fs::path(sourceFile)); 241 | return OutputImage(sourcePath, algorithm, nMaxColors, targetDir, pDest.get()); 242 | } 243 | 244 | static void OutputImages(const fs::path& sourceDir, wstring& targetDir, const UINT& nMaxColors, const bool dither, const wstring& algo, const long& delay) 245 | { 246 | auto start = chrono::steady_clock::now(); 247 | 248 | vector sourcePaths; 249 | vector > pSources, pDests; 250 | for (const auto& entry : fs::recursive_directory_iterator(sourceDir)) { 251 | if (entry.is_regular_file() && !entry.is_symlink()) { 252 | auto pSource = shared_ptr(Bitmap::FromFile(entry.path().wstring().c_str())); 253 | auto status = pSource->GetLastStatus(); 254 | if (status != Ok) 255 | continue; 256 | sourcePaths.emplace_back(entry.path()); 257 | pSources.emplace_back(pSource); 258 | pDests.emplace_back(make_shared(pSource->GetWidth(), pSource->GetHeight(), (nMaxColors > 256) ? PixelFormat16bppARGB1555 259 | : (nMaxColors > 16) ? PixelFormat8bppIndexed : (nMaxColors > 2) ? PixelFormat4bppIndexed : PixelFormat1bppIndexed)); 260 | } 261 | } 262 | 263 | if (algo == L"PNN") { 264 | if (nMaxColors > 256 || delay < 0) { 265 | int i = 0; 266 | for (auto& sourcePath : sourcePaths) 267 | QuantizeImage(algo, sourcePath, targetDir, pSources[i], nMaxColors, dither); 268 | } 269 | else { 270 | auto fileName = sourcePaths[0].filename().wstring(); 271 | fileName = fileName.substr(0, fileName.find_last_of(L'.')); 272 | 273 | targetDir = fileExists(targetDir) ? fs::canonical(fs::path(targetDir)) : fs::current_path(); 274 | auto destPath = targetDir + L"/" + fileName + L"-"; 275 | if (algo == L"PNNLAB") { 276 | destPath += L"PNNLABquant.gif"; 277 | 278 | UINT maxColors = nMaxColors; 279 | for (int i = 0; i < pSources.size(); ++i) { 280 | ostringstream ss; 281 | ss << "\r" << i << " of " << pSources.size() << " completed." << showpoint; 282 | wcout << ss.str().c_str(); 283 | 284 | PnnLABQuant::PnnLABQuantizer pnnLABQuantizer; 285 | pnnLABQuantizer.QuantizeImage(pSources[i].get(), pDests[i].get(), maxColors, dither); 286 | } 287 | } 288 | else { 289 | destPath += L"PNNquant.gif"; 290 | 291 | UINT maxColors = nMaxColors; 292 | for (int i = 0; i < pSources.size(); ++i) { 293 | ostringstream ss; 294 | ss << "\r" << i << " of " << pSources.size() << " completed." << showpoint; 295 | wcout << ss.str().c_str(); 296 | 297 | PnnQuant::PnnQuantizer pnnQuantizer; 298 | pnnQuantizer.QuantizeImage(pSources[i].get(), pDests[i].get(), maxColors, dither); 299 | } 300 | } 301 | wcout << L"\rWell done!!! " << endl; 302 | 303 | GifEncode::GifWriter gifWriter(destPath, false, abs(delay)); 304 | auto status = gifWriter.AddImages(pDests); 305 | if (status == Status::Ok) 306 | wcout << L"Converted image: " << destPath << endl; 307 | else 308 | wcout << L"Failed to save image in '" << destPath << L"' file" << endl; 309 | } 310 | } 311 | else { 312 | PnnLABQuant::PnnLABQuantizer pnnLABQuantizer; 313 | PnnLABQuant::PnnLABGAQuantizer pnnLABGAQuantizer(pnnLABQuantizer, pSources, nMaxColors); 314 | nQuantGA::APNsgaIII alg(pnnLABGAQuantizer); 315 | alg.run(9999, -numeric_limits::epsilon()); 316 | auto pGAq = alg.getResult(); 317 | wcout << L"\n" << pGAq->getResult().c_str() << endl; 318 | if (pGAq->QuantizeImage(pDests, dither)) { 319 | if (nMaxColors > 256 || delay < 0) { 320 | int i = 0; 321 | for (auto& sourcePath : sourcePaths) 322 | OutputImage(sourcePath, algo, nMaxColors, targetDir, pDests[i++].get(), nMaxColors > 256 || delay > -2 ? L".png" : L".gif"); 323 | } 324 | else { 325 | auto fileName = sourcePaths[0].filename().wstring(); 326 | fileName = fileName.substr(0, fileName.find_last_of(L'.')); 327 | 328 | targetDir = fileExists(targetDir) ? fs::canonical(fs::path(targetDir)) : fs::current_path(); 329 | auto destPath = targetDir + L"/" + fileName + L"-"; 330 | destPath += L"PNNLAB+quant.gif"; 331 | GifEncode::GifWriter gifWriter(destPath, pGAq->hasAlpha(), abs(delay)); 332 | auto status = gifWriter.AddImages(pDests); 333 | if (status == Status::Ok) 334 | wcout << L"Converted image: " << destPath << endl; 335 | else 336 | wcout << L"Failed to save image in '" << destPath << L"' file" << endl; 337 | } 338 | } 339 | } 340 | 341 | auto dur = chrono::duration_cast(chrono::steady_clock::now() - start).count() / 1000000.0; 342 | wcout << "Completed in " << dur << " secs." << endl; 343 | } 344 | 345 | int wmain(int argc, wchar_t** argv) 346 | { 347 | _setmode(_fileno(stdout), _O_U16TEXT); 348 | 349 | #ifndef _DEBUG 350 | if (argc <= 1) { 351 | PrintUsage(); 352 | return 0; 353 | } 354 | #endif 355 | 356 | auto szDir = fs::current_path().wstring(); 357 | 358 | bool dither = true; 359 | UINT nMaxColors = 256; 360 | long delay = -1; 361 | wstring algo = L""; 362 | wstring targetDir = L""; 363 | 364 | vector argList(argc); 365 | for (int i = 1; i < argc; ++i) 366 | argList[i] = wstring(argv[i], argv[i] + wcslen(argv[i])); 367 | 368 | #ifdef _DEBUG 369 | wstring sourceFile = szDir + L"/../ImgV64.gif"; 370 | nMaxColors = 1024; 371 | #else 372 | if (!ProcessArgs(argc, algo, nMaxColors, dither, targetDir, argList.data(), delay)) 373 | return 0; 374 | 375 | wstring sourceFile(argv[1], argv[1] + wcslen(argv[1])); 376 | if (!fileExists(sourceFile) && sourceFile.find_first_of(L"\\/") != wstring::npos) 377 | sourceFile = szDir + L"/" + sourceFile; 378 | #endif 379 | 380 | if(!fileExists(sourceFile)) { 381 | wcout << "The source file you specified does not exist." << endl; 382 | return 0; 383 | } 384 | 385 | if(GdiplusStartup(&m_gdiplusToken, &m_gdiplusStartupInput, NULL) == Ok) { 386 | if(fs::is_directory(fs::status(sourceFile.c_str())) ) { 387 | if (!targetDir.empty() && !fileExists(targetDir)) 388 | fs::create_directories(targetDir); 389 | OutputImages(sourceFile, targetDir, nMaxColors, dither, algo, delay); 390 | GdiplusShutdown(m_gdiplusToken); 391 | return 0; 392 | } 393 | 394 | auto pSource = shared_ptr(Bitmap::FromFile(sourceFile.c_str())); 395 | auto status = pSource->GetLastStatus(); 396 | if (status == Ok) { 397 | auto start = chrono::steady_clock::now(); 398 | if (!fileExists(targetDir)) 399 | targetDir = fs::path(sourceFile).parent_path().wstring(); 400 | 401 | #ifdef _DEBUG 402 | auto sourcePath = fs::canonical(fs::path(sourceFile)); 403 | sourceFile = sourcePath; 404 | #endif 405 | 406 | sourceFile = (sourceFile[sourceFile.length() - 1] != L'/' && sourceFile[sourceFile.length() - 1] != L'\\') ? sourceFile : sourceFile.substr(0, sourceFile.find_last_of(L"\\/")); 407 | if (algo == L"") { 408 | //QuantizeImage(L"MMC", sourceFile, targetDir, pSource, nMaxColors, dither); 409 | QuantizeImage(L"DIV", sourceFile, targetDir, pSource, nMaxColors, dither); 410 | if (nMaxColors > 32) { 411 | QuantizeImage(L"PNN", sourceFile, targetDir, pSource, nMaxColors, dither); 412 | QuantizeImage(L"WU", sourceFile, targetDir, pSource, nMaxColors, dither); 413 | QuantizeImage(L"NEU", sourceFile, targetDir, pSource, nMaxColors, dither); 414 | } 415 | else { 416 | QuantizeImage(L"PNNLAB", sourceFile, targetDir, pSource, nMaxColors, dither); 417 | QuantizeImage(L"EAS", sourceFile, targetDir, pSource, nMaxColors, dither); 418 | QuantizeImage(L"SPA", sourceFile, targetDir, pSource, nMaxColors, dither); 419 | } 420 | } 421 | else 422 | QuantizeImage(algo, sourceFile, targetDir, pSource, nMaxColors, dither); 423 | 424 | auto dur = chrono::duration_cast(chrono::steady_clock::now() - start).count() / 1000000.0; 425 | wcout << "Completed in " << dur << " secs." << endl; 426 | } 427 | else 428 | wcout << "Failed to read image in '" << sourceFile.c_str() << "' file"; 429 | } 430 | GdiplusShutdown(m_gdiplusToken); 431 | return 0; 432 | } 433 | -------------------------------------------------------------------------------- /nQuantCpp/nQuantCpp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Resource.h" 4 | -------------------------------------------------------------------------------- /nQuantCpp/nQuantCpp.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcychan/nQuantCpp/9415eb9f183faf14b5377eab83afe55176210128/nQuantCpp/nQuantCpp.rc -------------------------------------------------------------------------------- /nQuantCpp/nQuantCpp.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {D9959AF4-B098-4444-AD33-B377E98A187C} 23 | Win32Proj 24 | nQuantCpp 25 | 10.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v143 32 | Unicode 33 | false 34 | 35 | 36 | Application 37 | false 38 | v143 39 | true 40 | Unicode 41 | false 42 | 43 | 44 | Application 45 | true 46 | v143 47 | Unicode 48 | Dynamic 49 | 50 | 51 | Application 52 | false 53 | v143 54 | true 55 | Unicode 56 | false 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | true 78 | 79 | 80 | true 81 | 82 | 83 | false 84 | 85 | 86 | false 87 | 88 | 89 | 90 | Use 91 | Level3 92 | Disabled 93 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 94 | true 95 | true 96 | 97 | 98 | Console 99 | true 100 | 101 | 102 | 103 | 104 | Use 105 | Level3 106 | Disabled 107 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 108 | true 109 | stdcpp17 110 | 111 | 112 | Console 113 | true 114 | 115 | 116 | 117 | 118 | Level3 119 | Use 120 | MaxSpeed 121 | true 122 | true 123 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 124 | true 125 | false 126 | 127 | 128 | Console 129 | true 130 | true 131 | true 132 | 133 | 134 | 135 | 136 | Level3 137 | Use 138 | MaxSpeed 139 | true 140 | true 141 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 142 | true 143 | true 144 | stdcpp17 145 | stdc17 146 | 147 | 148 | Console 149 | true 150 | true 151 | true 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | Create 203 | Create 204 | Create 205 | Create 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /nQuantCpp/nQuantCpp.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 標頭檔 23 | 24 | 25 | 標頭檔 26 | 27 | 28 | 標頭檔 29 | 30 | 31 | 標頭檔 32 | 33 | 34 | 標頭檔 35 | 36 | 37 | 標頭檔 38 | 39 | 40 | 標頭檔 41 | 42 | 43 | 標頭檔 44 | 45 | 46 | 標頭檔 47 | 48 | 49 | 標頭檔 50 | 51 | 52 | 標頭檔 53 | 54 | 55 | 標頭檔 56 | 57 | 58 | 標頭檔 59 | 60 | 61 | 標頭檔 62 | 63 | 64 | 標頭檔 65 | 66 | 67 | 標頭檔 68 | 69 | 70 | 標頭檔 71 | 72 | 73 | 標頭檔 74 | 75 | 76 | 標頭檔 77 | 78 | 79 | 標頭檔 80 | 81 | 82 | 標頭檔 83 | 84 | 85 | 標頭檔 86 | 87 | 88 | 89 | 90 | 原始程式檔 91 | 92 | 93 | 原始程式檔 94 | 95 | 96 | 原始程式檔 97 | 98 | 99 | 原始程式檔 100 | 101 | 102 | 原始程式檔 103 | 104 | 105 | 原始程式檔 106 | 107 | 108 | 原始程式檔 109 | 110 | 111 | 原始程式檔 112 | 113 | 114 | 原始程式檔 115 | 116 | 117 | 原始程式檔 118 | 119 | 120 | 原始程式檔 121 | 122 | 123 | 原始程式檔 124 | 125 | 126 | 原始程式檔 127 | 128 | 129 | 原始程式檔 130 | 131 | 132 | 原始程式檔 133 | 134 | 135 | 原始程式檔 136 | 137 | 138 | 原始程式檔 139 | 140 | 141 | 原始程式檔 142 | 143 | 144 | 原始程式檔 145 | 146 | 147 | 原始程式檔 148 | 149 | 150 | 原始程式檔 151 | 152 | 153 | 154 | 155 | 資源檔 156 | 157 | 158 | -------------------------------------------------------------------------------- /nQuantCpp/nQuantCpp.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /nQuantCpp/stdafx.cpp: -------------------------------------------------------------------------------- 1 | #include "stdafx.h" 2 | -------------------------------------------------------------------------------- /nQuantCpp/stdafx.h: -------------------------------------------------------------------------------- 1 | // stdafx.h 2 | 3 | #pragma once 4 | 5 | #define COM_NO_WINDOWS_H 6 | #define GDIPVER 0x0110 //Use more advanced GDI+ features 7 | #include // Needed for non-MFC/ATL use 8 | 9 | #ifdef _WIN32 10 | #pragma comment(lib, "gdiplus") 11 | 12 | #include 13 | using namespace Gdiplus; 14 | #ifdef _WIN64 15 | #pragma comment(linker, "\"/manifestdependency:type='Win32' name='Microsoft.Windows.GdiPlus' version='1.1.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") 16 | #else 17 | #pragma comment(linker, "\"/manifestdependency:type='Win32' name='Microsoft.Windows.GdiPlus' version='1.1.0.0' processorArchitecture='X86' publicKeyToken='6595b64144ccf1df' language='*'\"") 18 | #endif 19 | #else 20 | #include 21 | #include 22 | using namespace Gdiplus; 23 | 24 | #ifndef LPCWSTR 25 | typedef _Null_terminated_ CONST WCHAR* LPCWSTR, * PCWSTR; 26 | #endif 27 | #endif 28 | 29 | #ifndef BYTE_MAX 30 | #include 31 | #define BYTE_MAX UCHAR_MAX 32 | #endif 33 | 34 | inline double sqr(double value) 35 | { 36 | return value * value; 37 | } 38 | 39 | #ifdef _WIN64 40 | #define _sqrt sqrt 41 | #elif defined(_WIN32) 42 | inline double __declspec (naked) __fastcall _sqrt(double n) 43 | { 44 | _asm fld qword ptr[esp + 4] 45 | _asm fsqrt 46 | _asm ret 8 47 | } 48 | #endif --------------------------------------------------------------------------------