| 1 | // tQuantizeFixed.cpp  |
| 2 | //  |
| 3 | // This module implements quantization of an image based on a fixed palette of colours as well as a function to perform  |
| 4 | // an exact palettization if the number of unique pixel colours is less-than or equal to the number of colours available  |
| 5 | // to the palette.  |
| 6 | //  |
| 7 | // Copyright (c) 2022-2024 Tristan Grimmer.  |
| 8 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
| 9 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
| 10 | //  |
| 11 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
| 12 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
| 13 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
| 14 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
| 15 | // PERFORMANCE OF THIS SOFTWARE.  |
| 16 |   |
| 17 | #include <Math/tColour.h>  |
| 18 | #include <Foundation/tMap.h>  |
| 19 | #include <System/tPrint.h>  |
| 20 | #include <System/tFile.h>  |
| 21 | #include "Image/tQuantize.h"  |
| 22 | // #define QUANTIZE_GENERATE_FIXED_PALETTES  |
| 23 | namespace tImage {  |
| 24 |   |
| 25 |   |
| 26 | namespace tQuantizeFixed  |
| 27 | {  |
| 28 | int FindIndexOfClosestColour_Redmean(const tColour3b* searchSpace, int searchSize, const tColour3b& colour);  |
| 29 |   |
| 30 | #ifdef QUANTIZE_GENERATE_FIXED_PALETTES  |
| 31 | // This is the function used to generate the power of 2 palettes (2, 4, 8, 16, 32, 64, 128, and 256 colour).  |
| 32 | // It is ifdeffed out and ony for reference as it's output is just a bunch of tables that are generated once and  |
| 33 | // included as static tabled in this source file.  |
| 34 | void GenerateFixedPalettes(const tString& filename);  |
| 35 | void GenerateFixedPalette(tFileHandle, int bitsR, int bitsG, int bitsB);  |
| 36 | #endif  |
| 37 | }  |
| 38 |   |
| 39 |   |
| 40 | #ifdef QUANTIZE_GENERATE_FIXED_PALETTES  |
| 41 | void tQuantizeFixed::GenerateFixedPalettes(const tString& filename)  |
| 42 | {  |
| 43 | tFileHandle file = tSystem::tOpenFile(filename, "wt" );  |
| 44 |   |
| 45 | // Note favouring of green, then red, then blue.  |
| 46 | GenerateFixedPalette(file, 3, 3, 2); // 8 bits.  |
| 47 | GenerateFixedPalette(file, 2, 3, 2); // 7 bits.  |
| 48 | GenerateFixedPalette(file, 2, 2, 2); // 6 bits.  |
| 49 | GenerateFixedPalette(file, 2, 2, 1); // 5 bits.  |
| 50 | GenerateFixedPalette(file, 1, 2, 1); // 4 bits.  |
| 51 | GenerateFixedPalette(file, 1, 1, 1); // 3 bits.  |
| 52 | GenerateFixedPalette(file, 1, 1, 0); // 2 bits.  |
| 53 | GenerateFixedPalette(file, 0, 1, 0); // 1 bits.  |
| 54 |   |
| 55 | tSystem::tCloseFile(file);  |
| 56 | }  |
| 57 |   |
| 58 |   |
| 59 | void tQuantizeFixed::GenerateFixedPalette(tFileHandle file, int bitsR, int bitsG, int bitsB)  |
| 60 | {  |
| 61 | int numR = tMath::tPow2(bitsR);  |
| 62 | int numG = tMath::tPow2(bitsG);  |
| 63 | int numB = tMath::tPow2(bitsB);  |
| 64 | int numColours = numR * numG * numB;  |
| 65 |   |
| 66 | tfPrintf(file, "// Generated fixed %d colour palette.\n" , numColours);  |
| 67 | tfPrintf(file, "namespace tQuantizeFixed { tColour3b Palette%d[] =\n" , numColours);  |
| 68 | tfPrintf(file, "{\n" );  |
| 69 |   |
| 70 | int entriesPerRow = tMath::tMin(numColours, 8);  |
| 71 | int idx = 0;  |
| 72 |   |
| 73 | // Order is important. Favour green, then red, then blue.   |
| 74 | for (int bi = 0; bi < numB; bi++)  |
| 75 | {  |
| 76 | for (int ri = 0; ri < numR; ri++)  |
| 77 | {  |
| 78 | for (int gi = 0; gi < numG; gi++)  |
| 79 | {  |
| 80 | int r = (numR == 1) ? 0 : (255 * ri) / (numR-1);  |
| 81 | int g = (numG == 1) ? 0 : (255 * gi) / (numG-1);  |
| 82 | int b = (numB == 1) ? 0 : (255 * bi) / (numB-1);  |
| 83 |   |
| 84 | if (!((idx) % entriesPerRow))  |
| 85 | tfPrintf(file, "\t" );  |
| 86 | else  |
| 87 | tfPrintf(file, " " );  |
| 88 |   |
| 89 | if (((numColours == 4) && (idx == 3)) || ((numColours == 2) && (idx == 1)))  |
| 90 | r = g = b = 0xFF;  |
| 91 |   |
| 92 | tfPrintf(file, "{ 0x%02X, 0x%02X, 0x%02X }%s" , r, g, b, (idx == numColours-1) ? "" : "," );  |
| 93 | if (!((idx+1) % entriesPerRow))  |
| 94 | tfPrintf(file, "\n" );  |
| 95 | idx++;  |
| 96 | }  |
| 97 | }  |
| 98 | }  |
| 99 | tfPrintf(file, "}; tStaticAssert(tNumElements(tQuantizeFixed::Palette%d) == %d); }\n" , numColours, numColours);  |
| 100 | tfPrintf(file, "\n" );  |
| 101 | }  |
| 102 | #endif  |
| 103 |   |
| 104 |   |
| 105 | // Generated fixed 256 colour palette.  |
| 106 | namespace tQuantizeFixed { tColour3b Palette256[] =  |
| 107 | {  |
| 108 | { 0x00, 0x00, 0x00 }, { 0x00, 0x24, 0x00 }, { 0x00, 0x48, 0x00 }, { 0x00, 0x6D, 0x00 }, { 0x00, 0x91, 0x00 }, { 0x00, 0xB6, 0x00 }, { 0x00, 0xDA, 0x00 }, { 0x00, 0xFF, 0x00 },  |
| 109 | { 0x24, 0x00, 0x00 }, { 0x24, 0x24, 0x00 }, { 0x24, 0x48, 0x00 }, { 0x24, 0x6D, 0x00 }, { 0x24, 0x91, 0x00 }, { 0x24, 0xB6, 0x00 }, { 0x24, 0xDA, 0x00 }, { 0x24, 0xFF, 0x00 },  |
| 110 | { 0x48, 0x00, 0x00 }, { 0x48, 0x24, 0x00 }, { 0x48, 0x48, 0x00 }, { 0x48, 0x6D, 0x00 }, { 0x48, 0x91, 0x00 }, { 0x48, 0xB6, 0x00 }, { 0x48, 0xDA, 0x00 }, { 0x48, 0xFF, 0x00 },  |
| 111 | { 0x6D, 0x00, 0x00 }, { 0x6D, 0x24, 0x00 }, { 0x6D, 0x48, 0x00 }, { 0x6D, 0x6D, 0x00 }, { 0x6D, 0x91, 0x00 }, { 0x6D, 0xB6, 0x00 }, { 0x6D, 0xDA, 0x00 }, { 0x6D, 0xFF, 0x00 },  |
| 112 | { 0x91, 0x00, 0x00 }, { 0x91, 0x24, 0x00 }, { 0x91, 0x48, 0x00 }, { 0x91, 0x6D, 0x00 }, { 0x91, 0x91, 0x00 }, { 0x91, 0xB6, 0x00 }, { 0x91, 0xDA, 0x00 }, { 0x91, 0xFF, 0x00 },  |
| 113 | { 0xB6, 0x00, 0x00 }, { 0xB6, 0x24, 0x00 }, { 0xB6, 0x48, 0x00 }, { 0xB6, 0x6D, 0x00 }, { 0xB6, 0x91, 0x00 }, { 0xB6, 0xB6, 0x00 }, { 0xB6, 0xDA, 0x00 }, { 0xB6, 0xFF, 0x00 },  |
| 114 | { 0xDA, 0x00, 0x00 }, { 0xDA, 0x24, 0x00 }, { 0xDA, 0x48, 0x00 }, { 0xDA, 0x6D, 0x00 }, { 0xDA, 0x91, 0x00 }, { 0xDA, 0xB6, 0x00 }, { 0xDA, 0xDA, 0x00 }, { 0xDA, 0xFF, 0x00 },  |
| 115 | { 0xFF, 0x00, 0x00 }, { 0xFF, 0x24, 0x00 }, { 0xFF, 0x48, 0x00 }, { 0xFF, 0x6D, 0x00 }, { 0xFF, 0x91, 0x00 }, { 0xFF, 0xB6, 0x00 }, { 0xFF, 0xDA, 0x00 }, { 0xFF, 0xFF, 0x00 },  |
| 116 | { 0x00, 0x00, 0x55 }, { 0x00, 0x24, 0x55 }, { 0x00, 0x48, 0x55 }, { 0x00, 0x6D, 0x55 }, { 0x00, 0x91, 0x55 }, { 0x00, 0xB6, 0x55 }, { 0x00, 0xDA, 0x55 }, { 0x00, 0xFF, 0x55 },  |
| 117 | { 0x24, 0x00, 0x55 }, { 0x24, 0x24, 0x55 }, { 0x24, 0x48, 0x55 }, { 0x24, 0x6D, 0x55 }, { 0x24, 0x91, 0x55 }, { 0x24, 0xB6, 0x55 }, { 0x24, 0xDA, 0x55 }, { 0x24, 0xFF, 0x55 },  |
| 118 | { 0x48, 0x00, 0x55 }, { 0x48, 0x24, 0x55 }, { 0x48, 0x48, 0x55 }, { 0x48, 0x6D, 0x55 }, { 0x48, 0x91, 0x55 }, { 0x48, 0xB6, 0x55 }, { 0x48, 0xDA, 0x55 }, { 0x48, 0xFF, 0x55 },  |
| 119 | { 0x6D, 0x00, 0x55 }, { 0x6D, 0x24, 0x55 }, { 0x6D, 0x48, 0x55 }, { 0x6D, 0x6D, 0x55 }, { 0x6D, 0x91, 0x55 }, { 0x6D, 0xB6, 0x55 }, { 0x6D, 0xDA, 0x55 }, { 0x6D, 0xFF, 0x55 },  |
| 120 | { 0x91, 0x00, 0x55 }, { 0x91, 0x24, 0x55 }, { 0x91, 0x48, 0x55 }, { 0x91, 0x6D, 0x55 }, { 0x91, 0x91, 0x55 }, { 0x91, 0xB6, 0x55 }, { 0x91, 0xDA, 0x55 }, { 0x91, 0xFF, 0x55 },  |
| 121 | { 0xB6, 0x00, 0x55 }, { 0xB6, 0x24, 0x55 }, { 0xB6, 0x48, 0x55 }, { 0xB6, 0x6D, 0x55 }, { 0xB6, 0x91, 0x55 }, { 0xB6, 0xB6, 0x55 }, { 0xB6, 0xDA, 0x55 }, { 0xB6, 0xFF, 0x55 },  |
| 122 | { 0xDA, 0x00, 0x55 }, { 0xDA, 0x24, 0x55 }, { 0xDA, 0x48, 0x55 }, { 0xDA, 0x6D, 0x55 }, { 0xDA, 0x91, 0x55 }, { 0xDA, 0xB6, 0x55 }, { 0xDA, 0xDA, 0x55 }, { 0xDA, 0xFF, 0x55 },  |
| 123 | { 0xFF, 0x00, 0x55 }, { 0xFF, 0x24, 0x55 }, { 0xFF, 0x48, 0x55 }, { 0xFF, 0x6D, 0x55 }, { 0xFF, 0x91, 0x55 }, { 0xFF, 0xB6, 0x55 }, { 0xFF, 0xDA, 0x55 }, { 0xFF, 0xFF, 0x55 },  |
| 124 | { 0x00, 0x00, 0xAA }, { 0x00, 0x24, 0xAA }, { 0x00, 0x48, 0xAA }, { 0x00, 0x6D, 0xAA }, { 0x00, 0x91, 0xAA }, { 0x00, 0xB6, 0xAA }, { 0x00, 0xDA, 0xAA }, { 0x00, 0xFF, 0xAA },  |
| 125 | { 0x24, 0x00, 0xAA }, { 0x24, 0x24, 0xAA }, { 0x24, 0x48, 0xAA }, { 0x24, 0x6D, 0xAA }, { 0x24, 0x91, 0xAA }, { 0x24, 0xB6, 0xAA }, { 0x24, 0xDA, 0xAA }, { 0x24, 0xFF, 0xAA },  |
| 126 | { 0x48, 0x00, 0xAA }, { 0x48, 0x24, 0xAA }, { 0x48, 0x48, 0xAA }, { 0x48, 0x6D, 0xAA }, { 0x48, 0x91, 0xAA }, { 0x48, 0xB6, 0xAA }, { 0x48, 0xDA, 0xAA }, { 0x48, 0xFF, 0xAA },  |
| 127 | { 0x6D, 0x00, 0xAA }, { 0x6D, 0x24, 0xAA }, { 0x6D, 0x48, 0xAA }, { 0x6D, 0x6D, 0xAA }, { 0x6D, 0x91, 0xAA }, { 0x6D, 0xB6, 0xAA }, { 0x6D, 0xDA, 0xAA }, { 0x6D, 0xFF, 0xAA },  |
| 128 | { 0x91, 0x00, 0xAA }, { 0x91, 0x24, 0xAA }, { 0x91, 0x48, 0xAA }, { 0x91, 0x6D, 0xAA }, { 0x91, 0x91, 0xAA }, { 0x91, 0xB6, 0xAA }, { 0x91, 0xDA, 0xAA }, { 0x91, 0xFF, 0xAA },  |
| 129 | { 0xB6, 0x00, 0xAA }, { 0xB6, 0x24, 0xAA }, { 0xB6, 0x48, 0xAA }, { 0xB6, 0x6D, 0xAA }, { 0xB6, 0x91, 0xAA }, { 0xB6, 0xB6, 0xAA }, { 0xB6, 0xDA, 0xAA }, { 0xB6, 0xFF, 0xAA },  |
| 130 | { 0xDA, 0x00, 0xAA }, { 0xDA, 0x24, 0xAA }, { 0xDA, 0x48, 0xAA }, { 0xDA, 0x6D, 0xAA }, { 0xDA, 0x91, 0xAA }, { 0xDA, 0xB6, 0xAA }, { 0xDA, 0xDA, 0xAA }, { 0xDA, 0xFF, 0xAA },  |
| 131 | { 0xFF, 0x00, 0xAA }, { 0xFF, 0x24, 0xAA }, { 0xFF, 0x48, 0xAA }, { 0xFF, 0x6D, 0xAA }, { 0xFF, 0x91, 0xAA }, { 0xFF, 0xB6, 0xAA }, { 0xFF, 0xDA, 0xAA }, { 0xFF, 0xFF, 0xAA },  |
| 132 | { 0x00, 0x00, 0xFF }, { 0x00, 0x24, 0xFF }, { 0x00, 0x48, 0xFF }, { 0x00, 0x6D, 0xFF }, { 0x00, 0x91, 0xFF }, { 0x00, 0xB6, 0xFF }, { 0x00, 0xDA, 0xFF }, { 0x00, 0xFF, 0xFF },  |
| 133 | { 0x24, 0x00, 0xFF }, { 0x24, 0x24, 0xFF }, { 0x24, 0x48, 0xFF }, { 0x24, 0x6D, 0xFF }, { 0x24, 0x91, 0xFF }, { 0x24, 0xB6, 0xFF }, { 0x24, 0xDA, 0xFF }, { 0x24, 0xFF, 0xFF },  |
| 134 | { 0x48, 0x00, 0xFF }, { 0x48, 0x24, 0xFF }, { 0x48, 0x48, 0xFF }, { 0x48, 0x6D, 0xFF }, { 0x48, 0x91, 0xFF }, { 0x48, 0xB6, 0xFF }, { 0x48, 0xDA, 0xFF }, { 0x48, 0xFF, 0xFF },  |
| 135 | { 0x6D, 0x00, 0xFF }, { 0x6D, 0x24, 0xFF }, { 0x6D, 0x48, 0xFF }, { 0x6D, 0x6D, 0xFF }, { 0x6D, 0x91, 0xFF }, { 0x6D, 0xB6, 0xFF }, { 0x6D, 0xDA, 0xFF }, { 0x6D, 0xFF, 0xFF },  |
| 136 | { 0x91, 0x00, 0xFF }, { 0x91, 0x24, 0xFF }, { 0x91, 0x48, 0xFF }, { 0x91, 0x6D, 0xFF }, { 0x91, 0x91, 0xFF }, { 0x91, 0xB6, 0xFF }, { 0x91, 0xDA, 0xFF }, { 0x91, 0xFF, 0xFF },  |
| 137 | { 0xB6, 0x00, 0xFF }, { 0xB6, 0x24, 0xFF }, { 0xB6, 0x48, 0xFF }, { 0xB6, 0x6D, 0xFF }, { 0xB6, 0x91, 0xFF }, { 0xB6, 0xB6, 0xFF }, { 0xB6, 0xDA, 0xFF }, { 0xB6, 0xFF, 0xFF },  |
| 138 | { 0xDA, 0x00, 0xFF }, { 0xDA, 0x24, 0xFF }, { 0xDA, 0x48, 0xFF }, { 0xDA, 0x6D, 0xFF }, { 0xDA, 0x91, 0xFF }, { 0xDA, 0xB6, 0xFF }, { 0xDA, 0xDA, 0xFF }, { 0xDA, 0xFF, 0xFF },  |
| 139 | { 0xFF, 0x00, 0xFF }, { 0xFF, 0x24, 0xFF }, { 0xFF, 0x48, 0xFF }, { 0xFF, 0x6D, 0xFF }, { 0xFF, 0x91, 0xFF }, { 0xFF, 0xB6, 0xFF }, { 0xFF, 0xDA, 0xFF }, { 0xFF, 0xFF, 0xFF }  |
| 140 | }; tStaticAssert(tNumElements(tQuantizeFixed::Palette256) == 256); }  |
| 141 |   |
| 142 | // Generated fixed 128 colour palette.  |
| 143 | namespace tQuantizeFixed { tColour3b Palette128[] =  |
| 144 | {  |
| 145 | { 0x00, 0x00, 0x00 }, { 0x00, 0x24, 0x00 }, { 0x00, 0x48, 0x00 }, { 0x00, 0x6D, 0x00 }, { 0x00, 0x91, 0x00 }, { 0x00, 0xB6, 0x00 }, { 0x00, 0xDA, 0x00 }, { 0x00, 0xFF, 0x00 },  |
| 146 | { 0x55, 0x00, 0x00 }, { 0x55, 0x24, 0x00 }, { 0x55, 0x48, 0x00 }, { 0x55, 0x6D, 0x00 }, { 0x55, 0x91, 0x00 }, { 0x55, 0xB6, 0x00 }, { 0x55, 0xDA, 0x00 }, { 0x55, 0xFF, 0x00 },  |
| 147 | { 0xAA, 0x00, 0x00 }, { 0xAA, 0x24, 0x00 }, { 0xAA, 0x48, 0x00 }, { 0xAA, 0x6D, 0x00 }, { 0xAA, 0x91, 0x00 }, { 0xAA, 0xB6, 0x00 }, { 0xAA, 0xDA, 0x00 }, { 0xAA, 0xFF, 0x00 },  |
| 148 | { 0xFF, 0x00, 0x00 }, { 0xFF, 0x24, 0x00 }, { 0xFF, 0x48, 0x00 }, { 0xFF, 0x6D, 0x00 }, { 0xFF, 0x91, 0x00 }, { 0xFF, 0xB6, 0x00 }, { 0xFF, 0xDA, 0x00 }, { 0xFF, 0xFF, 0x00 },  |
| 149 | { 0x00, 0x00, 0x55 }, { 0x00, 0x24, 0x55 }, { 0x00, 0x48, 0x55 }, { 0x00, 0x6D, 0x55 }, { 0x00, 0x91, 0x55 }, { 0x00, 0xB6, 0x55 }, { 0x00, 0xDA, 0x55 }, { 0x00, 0xFF, 0x55 },  |
| 150 | { 0x55, 0x00, 0x55 }, { 0x55, 0x24, 0x55 }, { 0x55, 0x48, 0x55 }, { 0x55, 0x6D, 0x55 }, { 0x55, 0x91, 0x55 }, { 0x55, 0xB6, 0x55 }, { 0x55, 0xDA, 0x55 }, { 0x55, 0xFF, 0x55 },  |
| 151 | { 0xAA, 0x00, 0x55 }, { 0xAA, 0x24, 0x55 }, { 0xAA, 0x48, 0x55 }, { 0xAA, 0x6D, 0x55 }, { 0xAA, 0x91, 0x55 }, { 0xAA, 0xB6, 0x55 }, { 0xAA, 0xDA, 0x55 }, { 0xAA, 0xFF, 0x55 },  |
| 152 | { 0xFF, 0x00, 0x55 }, { 0xFF, 0x24, 0x55 }, { 0xFF, 0x48, 0x55 }, { 0xFF, 0x6D, 0x55 }, { 0xFF, 0x91, 0x55 }, { 0xFF, 0xB6, 0x55 }, { 0xFF, 0xDA, 0x55 }, { 0xFF, 0xFF, 0x55 },  |
| 153 | { 0x00, 0x00, 0xAA }, { 0x00, 0x24, 0xAA }, { 0x00, 0x48, 0xAA }, { 0x00, 0x6D, 0xAA }, { 0x00, 0x91, 0xAA }, { 0x00, 0xB6, 0xAA }, { 0x00, 0xDA, 0xAA }, { 0x00, 0xFF, 0xAA },  |
| 154 | { 0x55, 0x00, 0xAA }, { 0x55, 0x24, 0xAA }, { 0x55, 0x48, 0xAA }, { 0x55, 0x6D, 0xAA }, { 0x55, 0x91, 0xAA }, { 0x55, 0xB6, 0xAA }, { 0x55, 0xDA, 0xAA }, { 0x55, 0xFF, 0xAA },  |
| 155 | { 0xAA, 0x00, 0xAA }, { 0xAA, 0x24, 0xAA }, { 0xAA, 0x48, 0xAA }, { 0xAA, 0x6D, 0xAA }, { 0xAA, 0x91, 0xAA }, { 0xAA, 0xB6, 0xAA }, { 0xAA, 0xDA, 0xAA }, { 0xAA, 0xFF, 0xAA },  |
| 156 | { 0xFF, 0x00, 0xAA }, { 0xFF, 0x24, 0xAA }, { 0xFF, 0x48, 0xAA }, { 0xFF, 0x6D, 0xAA }, { 0xFF, 0x91, 0xAA }, { 0xFF, 0xB6, 0xAA }, { 0xFF, 0xDA, 0xAA }, { 0xFF, 0xFF, 0xAA },  |
| 157 | { 0x00, 0x00, 0xFF }, { 0x00, 0x24, 0xFF }, { 0x00, 0x48, 0xFF }, { 0x00, 0x6D, 0xFF }, { 0x00, 0x91, 0xFF }, { 0x00, 0xB6, 0xFF }, { 0x00, 0xDA, 0xFF }, { 0x00, 0xFF, 0xFF },  |
| 158 | { 0x55, 0x00, 0xFF }, { 0x55, 0x24, 0xFF }, { 0x55, 0x48, 0xFF }, { 0x55, 0x6D, 0xFF }, { 0x55, 0x91, 0xFF }, { 0x55, 0xB6, 0xFF }, { 0x55, 0xDA, 0xFF }, { 0x55, 0xFF, 0xFF },  |
| 159 | { 0xAA, 0x00, 0xFF }, { 0xAA, 0x24, 0xFF }, { 0xAA, 0x48, 0xFF }, { 0xAA, 0x6D, 0xFF }, { 0xAA, 0x91, 0xFF }, { 0xAA, 0xB6, 0xFF }, { 0xAA, 0xDA, 0xFF }, { 0xAA, 0xFF, 0xFF },  |
| 160 | { 0xFF, 0x00, 0xFF }, { 0xFF, 0x24, 0xFF }, { 0xFF, 0x48, 0xFF }, { 0xFF, 0x6D, 0xFF }, { 0xFF, 0x91, 0xFF }, { 0xFF, 0xB6, 0xFF }, { 0xFF, 0xDA, 0xFF }, { 0xFF, 0xFF, 0xFF }  |
| 161 | }; tStaticAssert(tNumElements(tQuantizeFixed::Palette128) == 128); }  |
| 162 |   |
| 163 | // Generated fixed 64 colour palette.  |
| 164 | namespace tQuantizeFixed { tColour3b Palette64[] =  |
| 165 | {  |
| 166 | { 0x00, 0x00, 0x00 }, { 0x00, 0x55, 0x00 }, { 0x00, 0xAA, 0x00 }, { 0x00, 0xFF, 0x00 }, { 0x55, 0x00, 0x00 }, { 0x55, 0x55, 0x00 }, { 0x55, 0xAA, 0x00 }, { 0x55, 0xFF, 0x00 },  |
| 167 | { 0xAA, 0x00, 0x00 }, { 0xAA, 0x55, 0x00 }, { 0xAA, 0xAA, 0x00 }, { 0xAA, 0xFF, 0x00 }, { 0xFF, 0x00, 0x00 }, { 0xFF, 0x55, 0x00 }, { 0xFF, 0xAA, 0x00 }, { 0xFF, 0xFF, 0x00 },  |
| 168 | { 0x00, 0x00, 0x55 }, { 0x00, 0x55, 0x55 }, { 0x00, 0xAA, 0x55 }, { 0x00, 0xFF, 0x55 }, { 0x55, 0x00, 0x55 }, { 0x55, 0x55, 0x55 }, { 0x55, 0xAA, 0x55 }, { 0x55, 0xFF, 0x55 },  |
| 169 | { 0xAA, 0x00, 0x55 }, { 0xAA, 0x55, 0x55 }, { 0xAA, 0xAA, 0x55 }, { 0xAA, 0xFF, 0x55 }, { 0xFF, 0x00, 0x55 }, { 0xFF, 0x55, 0x55 }, { 0xFF, 0xAA, 0x55 }, { 0xFF, 0xFF, 0x55 },  |
| 170 | { 0x00, 0x00, 0xAA }, { 0x00, 0x55, 0xAA }, { 0x00, 0xAA, 0xAA }, { 0x00, 0xFF, 0xAA }, { 0x55, 0x00, 0xAA }, { 0x55, 0x55, 0xAA }, { 0x55, 0xAA, 0xAA }, { 0x55, 0xFF, 0xAA },  |
| 171 | { 0xAA, 0x00, 0xAA }, { 0xAA, 0x55, 0xAA }, { 0xAA, 0xAA, 0xAA }, { 0xAA, 0xFF, 0xAA }, { 0xFF, 0x00, 0xAA }, { 0xFF, 0x55, 0xAA }, { 0xFF, 0xAA, 0xAA }, { 0xFF, 0xFF, 0xAA },  |
| 172 | { 0x00, 0x00, 0xFF }, { 0x00, 0x55, 0xFF }, { 0x00, 0xAA, 0xFF }, { 0x00, 0xFF, 0xFF }, { 0x55, 0x00, 0xFF }, { 0x55, 0x55, 0xFF }, { 0x55, 0xAA, 0xFF }, { 0x55, 0xFF, 0xFF },  |
| 173 | { 0xAA, 0x00, 0xFF }, { 0xAA, 0x55, 0xFF }, { 0xAA, 0xAA, 0xFF }, { 0xAA, 0xFF, 0xFF }, { 0xFF, 0x00, 0xFF }, { 0xFF, 0x55, 0xFF }, { 0xFF, 0xAA, 0xFF }, { 0xFF, 0xFF, 0xFF }  |
| 174 | }; tStaticAssert(tNumElements(tQuantizeFixed::Palette64) == 64); }  |
| 175 |   |
| 176 | // Generated fixed 32 colour palette.  |
| 177 | namespace tQuantizeFixed { tColour3b Palette32[] =  |
| 178 | {  |
| 179 | { 0x00, 0x00, 0x00 }, { 0x00, 0x55, 0x00 }, { 0x00, 0xAA, 0x00 }, { 0x00, 0xFF, 0x00 }, { 0x55, 0x00, 0x00 }, { 0x55, 0x55, 0x00 }, { 0x55, 0xAA, 0x00 }, { 0x55, 0xFF, 0x00 },  |
| 180 | { 0xAA, 0x00, 0x00 }, { 0xAA, 0x55, 0x00 }, { 0xAA, 0xAA, 0x00 }, { 0xAA, 0xFF, 0x00 }, { 0xFF, 0x00, 0x00 }, { 0xFF, 0x55, 0x00 }, { 0xFF, 0xAA, 0x00 }, { 0xFF, 0xFF, 0x00 },  |
| 181 | { 0x00, 0x00, 0xFF }, { 0x00, 0x55, 0xFF }, { 0x00, 0xAA, 0xFF }, { 0x00, 0xFF, 0xFF }, { 0x55, 0x00, 0xFF }, { 0x55, 0x55, 0xFF }, { 0x55, 0xAA, 0xFF }, { 0x55, 0xFF, 0xFF },  |
| 182 | { 0xAA, 0x00, 0xFF }, { 0xAA, 0x55, 0xFF }, { 0xAA, 0xAA, 0xFF }, { 0xAA, 0xFF, 0xFF }, { 0xFF, 0x00, 0xFF }, { 0xFF, 0x55, 0xFF }, { 0xFF, 0xAA, 0xFF }, { 0xFF, 0xFF, 0xFF }  |
| 183 | }; tStaticAssert(tNumElements(tQuantizeFixed::Palette32) == 32); }  |
| 184 |   |
| 185 | // Generated fixed 16 colour palette.  |
| 186 | namespace tQuantizeFixed { tColour3b Palette16[] =  |
| 187 | {  |
| 188 | { 0x00, 0x00, 0x00 }, { 0x00, 0x55, 0x00 }, { 0x00, 0xAA, 0x00 }, { 0x00, 0xFF, 0x00 }, { 0xFF, 0x00, 0x00 }, { 0xFF, 0x55, 0x00 }, { 0xFF, 0xAA, 0x00 }, { 0xFF, 0xFF, 0x00 },  |
| 189 | { 0x00, 0x00, 0xFF }, { 0x00, 0x55, 0xFF }, { 0x00, 0xAA, 0xFF }, { 0x00, 0xFF, 0xFF }, { 0xFF, 0x00, 0xFF }, { 0xFF, 0x55, 0xFF }, { 0xFF, 0xAA, 0xFF }, { 0xFF, 0xFF, 0xFF }  |
| 190 | }; tStaticAssert(tNumElements(tQuantizeFixed::Palette16) == 16); }  |
| 191 |   |
| 192 | // Generated fixed 8 colour palette.  |
| 193 | namespace tQuantizeFixed { tColour3b Palette8[] =  |
| 194 | {  |
| 195 | { 0x00, 0x00, 0x00 }, { 0x00, 0xFF, 0x00 }, { 0xFF, 0x00, 0x00 }, { 0xFF, 0xFF, 0x00 }, { 0x00, 0x00, 0xFF }, { 0x00, 0xFF, 0xFF }, { 0xFF, 0x00, 0xFF }, { 0xFF, 0xFF, 0xFF }  |
| 196 | }; tStaticAssert(tNumElements(tQuantizeFixed::Palette8) == 8); }  |
| 197 |   |
| 198 | // Generated fixed 4 colour palette.  |
| 199 | namespace tQuantizeFixed { tColour3b Palette4[] =  |
| 200 | {  |
| 201 | { 0x00, 0x00, 0x00 }, { 0x00, 0xFF, 0x00 }, { 0xFF, 0x00, 0x00 }, { 0xFF, 0xFF, 0xFF }  |
| 202 | }; tStaticAssert(tNumElements(tQuantizeFixed::Palette4) == 4); }  |
| 203 |   |
| 204 | // Generated fixed 2 colour palette.  |
| 205 | namespace tQuantizeFixed { tColour3b Palette2[] =  |
| 206 | {  |
| 207 | { 0x00, 0x00, 0x00 }, { 0xFF, 0xFF, 0xFF }  |
| 208 | }; tStaticAssert(tNumElements(tQuantizeFixed::Palette2) == 2); }  |
| 209 |   |
| 210 |   |
| 211 | int tQuantizeFixed::FindIndexOfClosestColour_Redmean(const tColour3b* searchSpace, int searchSize, const tColour3b& colour)  |
| 212 | {  |
| 213 | float closest = 1000.0f;  |
| 214 | int closestIndex = -1;  |
| 215 |   |
| 216 | for (int i = 0; i < searchSize; i++)  |
| 217 | {  |
| 218 | float diff = tMath::tColourDiffRedmean(a: colour, b: searchSpace[i]);  |
| 219 | if (diff < closest)  |
| 220 | {  |
| 221 | closest = diff;  |
| 222 | closestIndex = i;  |
| 223 | }  |
| 224 | }  |
| 225 | return closestIndex;  |
| 226 | }  |
| 227 |   |
| 228 |   |
| 229 | //  |
| 230 | // The functions below make up the external interface.  |
| 231 | //  |
| 232 |   |
| 233 |   |
| 234 | bool tQuantizeFixed::QuantizeImage  |
| 235 | (  |
| 236 | int numColours, int width, int height, const tPixel3b* pixels, tColour3b* destPalette, uint8* destIndices,  |
| 237 | bool checkExact  |
| 238 | )  |
| 239 | {  |
| 240 | #ifdef QUANTIZE_GENERATE_FIXED_PALETTES  |
| 241 | static bool hasRun = false;  |
| 242 | if (!hasRun)  |
| 243 | {  |
| 244 | GenerateFixedPalettes("FixedPalettes.cpp" );  |
| 245 | hasRun = true;  |
| 246 | }  |
| 247 | return false;  |
| 248 | #endif  |
| 249 |   |
| 250 | if ((numColours < 2) || (numColours > 256) || (width <= 0) || (height <= 0) || !pixels || !destPalette || !destIndices)  |
| 251 | return false;  |
| 252 |   |
| 253 | if (checkExact)  |
| 254 | {  |
| 255 | bool success = tQuantize::QuantizeImageExact(numColours, width, height, pixels, destPalette, destIndices);  |
| 256 | if (success)  |
| 257 | return true;  |
| 258 | }  |
| 259 |   |
| 260 | int tableSize = numColours;  |
| 261 | if (!tMath::tIsPower2(v: tableSize))  |
| 262 | tableSize = tMath::tNextHigherPower2(v: tableSize);  |
| 263 | tAssert(tMath::tIsPower2(tableSize) && (tableSize >= 2) && (tableSize <= 256));  |
| 264 |   |
| 265 | bool skipIndexArray[256];  |
| 266 | tStd::tMemset(dest: skipIndexArray, val: 0, numBytes: sizeof(skipIndexArray));  |
| 267 |   |
| 268 | // Skipping entries for non-power-of-two involves flipping from end to end (not including both ends). eg.  |
| 269 | //  |
| 270 | // 0 1 2 3 4 5 6 7 : Total 8. Worst case 5. Reduce by 3.  |
| 271 | // ^  |
| 272 | // ^  |
| 273 | // ^  |
| 274 | // 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 : Total 16. Worst case 9. Reduce by 7.  |
| 275 | // ^  |
| 276 | // ^  |
| 277 | // ^  |
| 278 | // ^  |
| 279 | // ^  |
| 280 | // ^  |
| 281 | // ^  |
| 282 | int numSkipped = 0;  |
| 283 | bool flipFlop = false;  |
| 284 | int skipIndexLow = 1;  |
| 285 | int skipIndexHigh = tableSize - 2;  |
| 286 | while (tableSize - numColours - numSkipped)  |
| 287 | {  |
| 288 | skipIndexArray[!flipFlop ? skipIndexLow : skipIndexHigh] = true;  |
| 289 | if (!flipFlop) skipIndexLow += 2;  |
| 290 | else skipIndexHigh -= 2;  |
| 291 | flipFlop = !flipFlop;  |
| 292 | numSkipped++;  |
| 293 | }  |
| 294 |   |
| 295 | tColour3b* srcPalette = nullptr;  |
| 296 | switch (tableSize)  |
| 297 | {  |
| 298 | case 2: srcPalette = Palette2; break;  |
| 299 | case 4: srcPalette = Palette4; break;  |
| 300 | case 8: srcPalette = Palette8; break;  |
| 301 | case 16: srcPalette = Palette16; break;  |
| 302 | case 32: srcPalette = Palette32; break;  |
| 303 | case 64: srcPalette = Palette64; break;  |
| 304 | case 128: srcPalette = Palette128; break;  |
| 305 | case 256: srcPalette = Palette256; break;  |
| 306 | }  |
| 307 |   |
| 308 | // Populate the dest palette, skipping as necessary.  |
| 309 | int destIndex = 0;  |
| 310 | for (int srcIndex = 0; srcIndex < tableSize; srcIndex++)  |
| 311 | {  |
| 312 | if (skipIndexArray[srcIndex])  |
| 313 | continue;  |
| 314 | destPalette[destIndex++] = srcPalette[srcIndex];  |
| 315 | }  |
| 316 | tAssert(destIndex == numColours);  |
| 317 |   |
| 318 | // Exhaustive redmean search for each pixel colour.  |
| 319 | for (int y = 0; y < height; y++)  |
| 320 | {  |
| 321 | for (int x = 0; x < width; x++)  |
| 322 | {  |
| 323 | const tPixel3b& pixel = pixels[x + y*width];  |
| 324 | destIndices[x + y*width] = FindIndexOfClosestColour_Redmean(searchSpace: destPalette, searchSize: numColours, colour: pixel);  |
| 325 | }  |
| 326 | }  |
| 327 |   |
| 328 | return true;  |
| 329 | }  |
| 330 |   |
| 331 |   |
| 332 | bool tQuantizeFixed::QuantizeImage  |
| 333 | (  |
| 334 | int numColours, int width, int height, const tPixel4b* pixels, tColour3b* destPalette, uint8* destIndices,  |
| 335 | bool checkExact  |
| 336 | )  |
| 337 | {  |
| 338 | if ((numColours < 2) || (numColours > 256) || (width <= 0) || (height <= 0) || !pixels || !destPalette || !destIndices)  |
| 339 | return false;  |
| 340 |   |
| 341 | tPixel3b* pixels3 = new tPixel3b[width*height];  |
| 342 | for (int p = 0; p < width*height; p++)  |
| 343 | pixels3[p].Set( r: pixels[p].R, g: pixels[p].G, b: pixels[p].B );  |
| 344 |   |
| 345 | bool success = QuantizeImage(numColours, width, height, pixels: pixels3, destPalette, destIndices, checkExact);  |
| 346 | delete[] pixels3;  |
| 347 | return success;  |
| 348 | }  |
| 349 |   |
| 350 |   |
| 351 | }  |
| 352 | |