| 1 | // tQuantize.cpp  |
| 2 | //  |
| 3 | // This module implements exact palettization of an image for cases when full quantization of an image is not necessary.  |
| 4 | // That is, when there will be no colour losses. Exact palettization is possible if the number of unique pixel colours  |
| 5 | // is less-than or equal to the number of colours available to the palette. Additionally a function to convert from  |
| 6 | // palette/index format back to straght pixels is provided.  |
| 7 | //  |
| 8 | // Copyright (c) 2022-2024 Tristan Grimmer.  |
| 9 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
| 10 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
| 11 | //  |
| 12 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
| 13 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
| 14 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
| 15 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
| 16 | // PERFORMANCE OF THIS SOFTWARE.  |
| 17 |   |
| 18 | #include <Math/tColour.h>  |
| 19 | #include <Foundation/tMap.h>  |
| 20 | #include "Image/tQuantize.h"  |
| 21 | namespace tImage {  |
| 22 |   |
| 23 |   |
| 24 | namespace tQuantize  |
| 25 | {  |
| 26 | int FindIndexOfExactColour(const tColour3b* searchSpace, int searchSize, const tColour3b& colour);  |
| 27 | }  |
| 28 |   |
| 29 |   |
| 30 | const char* tQuantize::GetMethodName(Method method)  |
| 31 | {  |
| 32 | switch (method)  |
| 33 | {  |
| 34 | case tQuantize::Method::Fixed: return "Fixed" ;  |
| 35 | case tQuantize::Method::Spatial: return "Scolorq" ;  |
| 36 | case tQuantize::Method::Neu: return "Neuquant" ;  |
| 37 | case tQuantize::Method::Wu: return "Wu" ;  |
| 38 | }  |
| 39 | return "Invalid" ;  |
| 40 | }  |
| 41 |   |
| 42 |   |
| 43 | int tQuantize::FindIndexOfExactColour(const tColour3b* searchSpace, int searchSize, const tColour3b& colour)  |
| 44 | {  |
| 45 | for (int i = 0; i < searchSize; i++)  |
| 46 | if (colour == searchSpace[i])  |
| 47 | return i;  |
| 48 |   |
| 49 | return -1;  |
| 50 | }  |
| 51 |   |
| 52 |   |
| 53 | //  |
| 54 | // The functions below make up the external interface.  |
| 55 | //  |
| 56 |   |
| 57 |   |
| 58 | bool tQuantize::QuantizeImageExact  |
| 59 | (  |
| 60 | int numColours, int width, int height, const tPixel3b* pixels,  |
| 61 | tColour3b* destPalette, uint8* destIndices  |
| 62 | )  |
| 63 | {  |
| 64 | if ((numColours < 2) || (numColours > 256) || (width <= 0) || (height <= 0) || !pixels || !destPalette || !destIndices)  |
| 65 | return false;  |
| 66 |   |
| 67 | // First we need to find how many unique colours are in the pixels.  |
| 68 | // We do this using a tMap which forces uniqueness on the key. We'll use the int value to count occurrences.  |
| 69 | tMap<tPixel3b, int> pixelCountMap;  |
| 70 | for (int xy = 0; xy < width*height; xy++)  |
| 71 | pixelCountMap[ pixels[xy] ]++;  |
| 72 |   |
| 73 | // Test print counts of each unique colour.  |
| 74 | #if 0  |
| 75 | for (auto pair : pixelCountMap)  |
| 76 | {  |
| 77 | tPixel3& c = pair.Key();  |
| 78 | tPrintf("Key RGB %03d %03d %03d. Value COUNT: %05d]\n" , c.R, c.G, c.B, pair.Value());  |
| 79 | }  |
| 80 | #endif  |
| 81 |   |
| 82 | int numUnique = pixelCountMap.GetNumItems();  |
| 83 | if (numUnique > numColours)  |
| 84 | return false;  |
| 85 |   |
| 86 | // Populate the palette.  |
| 87 | tStd::tMemset(dest: destPalette, val: 0, numBytes: numColours*sizeof(tColour3b));  |
| 88 | int entry = 0;  |
| 89 | for (auto pair : pixelCountMap)  |
| 90 | destPalette[entry++] = pair.Key();  |
| 91 |   |
| 92 | // Now populate the indices by finding each pixel's colour in the palette.  |
| 93 | for (int p = 0; p < width*height; p++)  |
| 94 | {  |
| 95 | int idx = FindIndexOfExactColour(searchSpace: destPalette, searchSize: numUnique, colour: pixels[p]);  |
| 96 | tAssert(idx != -1);  |
| 97 | destIndices[p] = idx;  |
| 98 | }  |
| 99 |   |
| 100 | return true;  |
| 101 | }  |
| 102 |   |
| 103 |   |
| 104 | bool tQuantize::ConvertToPixels  |
| 105 | (  |
| 106 | tPixel3b* destPixels, int width, int height,  |
| 107 | const tColour3b* srcPalette, const uint8* srcIndices  |
| 108 | )  |
| 109 | {  |
| 110 | if (!destPixels || (width <= 0) || (height <= 0) || !srcPalette || !srcIndices)  |
| 111 | return false;  |
| 112 |   |
| 113 | for (int y = 0; y < height; y++)  |
| 114 | {  |
| 115 | for (int x = 0; x < width; x++)  |
| 116 | {  |
| 117 | int index = x + y*width;  |
| 118 | int palIndex = srcIndices[index];  |
| 119 | tColour3b colour = srcPalette[palIndex];  |
| 120 | destPixels[index] = colour;  |
| 121 | }  |
| 122 | }  |
| 123 |   |
| 124 | return true;  |
| 125 | }  |
| 126 |   |
| 127 |   |
| 128 | bool tQuantize::ConvertToPixels  |
| 129 | (  |
| 130 | tPixel4b* destPixels, int width, int height,  |
| 131 | const tColour3b* srcPalette, const uint8* srcIndices, bool preserveDestAlpha  |
| 132 | )  |
| 133 | {  |
| 134 | if (!destPixels || (width <= 0) || (height <= 0) || !srcPalette || !srcIndices)  |
| 135 | return false;  |
| 136 |   |
| 137 | for (int y = 0; y < height; y++)  |
| 138 | {  |
| 139 | for (int x = 0; x < width; x++)  |
| 140 | {  |
| 141 | int index = x + y*width;  |
| 142 | int palIndex = srcIndices[index];  |
| 143 | tColour3b colour = srcPalette[palIndex];  |
| 144 | if (preserveDestAlpha)  |
| 145 | destPixels[index].SetRGB(r: colour.R, g: colour.G, b: colour.B);  |
| 146 | else  |
| 147 | destPixels[index].Set(r: colour.R, g: colour.G, b: colour.B);  |
| 148 | }  |
| 149 | }  |
| 150 |   |
| 151 | return true;  |
| 152 | }  |
| 153 |   |
| 154 |   |
| 155 | }  |
| 156 | |