| 1 | // tQuantizeNeu.cpp  |
| 2 | //  |
| 3 | // This module implements the NeuQuant (Neural-Net) Quantization Algorithm written by Anthony Dekker. Only the parts  |
| 4 | // modified by me are under the ISC licence. The majority is under what looks like the MIT licence, The original  |
| 5 | // author's licence can be found below. Modifications include:  |
| 6 | // * Placing it in a namespace.  |
| 7 | // * Consolidating the state parameters so that it is threadsafe (no global state).  |
| 8 | // * Bridging to a standardized Tacent interface.  |
| 9 | // * Replacing the inxsearch and inxbuild with red-mean perceptual colour distance metric to choose best colours.  |
| 10 | // * Support for an arbitrary number of colours between 2 and 256.  |
| 11 | //  |
| 12 | // The algrithm works well for larger numbers of colours (generally 128 to 256 or 255) but it can handle values as  |
| 13 | // low as 2.  |
| 14 | //  |
| 15 | // Modifications Copyright (c) 2022-2024 Tristan Grimmer.  |
| 16 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
| 17 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
| 18 | //  |
| 19 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
| 20 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
| 21 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
| 22 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
| 23 | // PERFORMANCE OF THIS SOFTWARE.  |
| 24 | //  |
| 25 | // Here is the original copyright:  |
| 26 | //  |
| 27 | // Copyright (c) 1994 Anthony Dekker  |
| 28 | //  |
| 29 | // NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See "Kohonen neural networks for optimal colour  |
| 30 | // quantization" in "Network: Computation in Neural Systems" Vol. 5 (1994) pp351-367. for a discussion of the algorithm.  |
| 31 | // See also http://members.ozemail.com.au/~dekker/NEUQUANT.HTML.  |
| 32 | //  |
| 33 | // Any party obtaining a copy of these files from the author, directly or indirectly, is granted, free of charge, a full  |
| 34 | // and unrestricted irrevocable, world-wide, paid up, royalty-free, nonexclusive right and license to deal in this  |
| 35 | // software and documentation files (the "Software"), including without limitation the rights to use, copy, modify,  |
| 36 | // merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons who receive copies  |
| 37 | // from any such party to do so, with the only requirement being that this copyright notice remain intact.  |
| 38 |   |
| 39 | #include <cstdint>  |
| 40 | #include <Math/tColour.h>  |
| 41 | #include "Image/tQuantize.h"  |
| 42 | namespace tImage {  |
| 43 |   |
| 44 |   |
| 45 | namespace tQuantizeNeu  |
| 46 | {  |
| 47 | // For 256 colours, fixed arrays need 8kb, plus space for the image.  |
| 48 | const int maxnetsize = 256; // Maximum network size (number of colours).  |
| 49 |   |
| 50 | // Four primes near 500 - assume no image has a length so large that it is divisible by all four primes.  |
| 51 | const int prime1 = 499;  |
| 52 | const int prime2 = 491;  |
| 53 | const int prime3 = 487;  |
| 54 | const int prime4 = 503;  |
| 55 |   |
| 56 | const int minpicturebytes = (3 * prime4); // Minimum size for input image.  |
| 57 |   |
| 58 | // Network Definitions.  |
| 59 | const int maxnetpos = maxnetsize - 1;  |
| 60 | const int netbiasshift = 4; // Bias for colour values.  |
| 61 | const int ncycles = 100; // Number of learning cycles.  |
| 62 |   |
| 63 | // Defs for freq and bias.  |
| 64 | const int intbiasshift = 16; // Bias for fractions.  |
| 65 | const int intbias = 65536; // 1 << intbiasshift.  |
| 66 | const int gammashift = 10; // Gamma = 1024.  |
| 67 | const int gamma = 1024; // 1 << gammashift.  |
| 68 | const int betashift = 10;  |
| 69 | const int beta = 64; // intbias >> betashift beta = 1/1024.  |
| 70 | const int betagamma = 65536; // intbias << (gammashift - betashift).  |
| 71 |   |
| 72 | // Defs for decreasing radius factor.  |
| 73 | const int initrad = 32; // netsize >> 3 for 256 cols, radius starts  |
| 74 | const int radiusbiasshift = 6; // at 32.0 biased by 6 bits.  |
| 75 | const int radiusbias = 64; // 1 << radiusbiasshift  |
| 76 | const int initradius = 2048; // initrad * radiusbias and decreases by a  |
| 77 | const int radiusdec = 30; // factor of 1/30 each cycle.  |
| 78 |   |
| 79 | // Defs for decreasing alpha factor.  |
| 80 | const int alphabiasshift = 10; // Alpha starts at 1.0  |
| 81 | const int initalpha = 1024; // 1 << alphabiasshift  |
| 82 |   |
| 83 | // Radbias and alpharadbias used for radpower calculation.  |
| 84 | const int radbiasshift = 8;  |
| 85 | const int radbias = 256; // 1 << radbiasshift  |
| 86 | const int alpharadbshift = 18; // alphabiasshift + radbiasshift  |
| 87 | const int alpharadbias = 262144;  |
| 88 |   |
| 89 | struct State  |
| 90 | {  |
| 91 | int netsize = maxnetsize; // This defaults to maxnetsize but can be reduced to as low as 2 colours.  |
| 92 | int alphadec; // Biased by 10 bits.  |
| 93 | unsigned char *thepicture; // The input image itself.  |
| 94 | int lengthcount; // lengthcount = H*W*3.  |
| 95 | int samplefac; // Sampling factor 1..30.  |
| 96 | typedef int pixel_bgr[4]; // BGRc  |
| 97 | pixel_bgr network[maxnetsize]; // The network itself.  |
| 98 | int netindex[maxnetsize]; // For network lookup - really 256.  |
| 99 | int bias[maxnetsize]; // Bias and freq arrays for learning.  |
| 100 | int freq[maxnetsize]; // Frequency array for learning.  |
| 101 | int radpower[initrad]; // radpower for precomputation.  |
| 102 | };  |
| 103 |   |
| 104 | // Initialise network in range (0,0,0) to (255,255,255) and set parameters.  |
| 105 | int getNetwork(State&, int i, int j);  |
| 106 | void initnet(State&, unsigned char *thepic, int len, int sample);  |
| 107 |   |
| 108 | // Unbias network to give byte values 0..255 and record position i to prepare for sort.  |
| 109 | void unbiasnet(State&);  |
| 110 |   |
| 111 | // This gets the palette in the out variable.  |
| 112 | int getColourMap(State&, tColour3b* out);  |
| 113 |   |
| 114 | // Insertion sort of network and building of netindex[0..255] (to do after unbias).  |
| 115 | // We don't call this function since we do an exhaustive red-mean distance check.  |
| 116 | void inxbuild(State&);  |
| 117 |   |
| 118 | // Search for BGR values 0..255 (after net is unbiased) and return colour index.  |
| 119 | // We don't call this function since we do an exhaustive red-mean distance check.  |
| 120 | int inxsearch(State&, int b, int g, int r);  |
| 121 |   |
| 122 | // Main Learning Loop.  |
| 123 | void learn(State&);  |
| 124 |   |
| 125 | int contest(State&, int b, int g, int r);  |
| 126 | void altersingle(State&, int alpha, int i, int b, int g, int r);  |
| 127 | void alterneigh(State&, int rad, int i, int b, int g, int r);  |
| 128 |   |
| 129 | int FindIndexOfClosestColour_Redmean(const tColour3b* searchSpace, int searchSize, const tColour3b& searchColour);  |
| 130 | }  |
| 131 |   |
| 132 |   |
| 133 | int tQuantizeNeu::getNetwork(State& state, int i, int j)  |
| 134 | {  |
| 135 | return state.network[i][j];  |
| 136 | }  |
| 137 |   |
| 138 |   |
| 139 | void tQuantizeNeu::initnet(State& state, unsigned char *thepic, int len, int sample)  |
| 140 | {  |
| 141 | // Initialise network in range (0,0,0) to (255,255,255) and set parameters.  |
| 142 | int i;  |
| 143 | int *p;  |
| 144 |   |
| 145 | state.thepicture = thepic;  |
| 146 | state.lengthcount = len;  |
| 147 | state.samplefac = sample;  |
| 148 |   |
| 149 | for (i = 0; i < state.netsize; i++)  |
| 150 | {  |
| 151 | p = state.network[i];  |
| 152 | p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / state.netsize;  |
| 153 | state.freq[i] = intbias / state.netsize; /* 1/netsize */  |
| 154 | state.bias[i] = 0;  |
| 155 | }  |
| 156 | }  |
| 157 |   |
| 158 |   |
| 159 | void tQuantizeNeu::unbiasnet(State& state)  |
| 160 | {  |
| 161 | // Unbias network to give byte values 0..255 and record position i to prepare for sort.  |
| 162 | int i, j, temp;  |
| 163 |   |
| 164 | for (i = 0; i < state.netsize; i++)  |
| 165 | {  |
| 166 | for (j = 0; j < 3; j++)  |
| 167 | {  |
| 168 | // OLD CODE: network[i][j] >>= netbiasshift;  |
| 169 | // Fix based on bug report by Juergen Weigert jw@suse.de.  |
| 170 | temp = (state.network[i][j] + (1 << (netbiasshift - 1))) >> netbiasshift;  |
| 171 | if (temp > 255) temp = 255;  |
| 172 | state.network[i][j] = temp;  |
| 173 | }  |
| 174 |   |
| 175 | // Record colour no.  |
| 176 | state.network[i][3] = i;  |
| 177 | }  |
| 178 | }  |
| 179 |   |
| 180 |   |
| 181 | int tQuantizeNeu::getColourMap(State& state, tColour3b* out)  |
| 182 | {  |
| 183 | // Output colour map. The palette.  |
| 184 | int index[maxnetsize];  |
| 185 | for (int i = 0; i < state.netsize; i++)  |
| 186 | index[state.network[i][3]] = i;  |
| 187 |   |
| 188 | for (int j = 0; j < state.netsize; j++)  |
| 189 | {  |
| 190 | out[j].R = uint8(state.network[j][0]);  |
| 191 | out[j].G = uint8(state.network[j][1]);  |
| 192 | out[j].B = uint8(state.network[j][2]);  |
| 193 | }  |
| 194 | return state.netsize;  |
| 195 | }  |
| 196 |   |
| 197 |   |
| 198 | void tQuantizeNeu::inxbuild(State& state)  |
| 199 | {  |
| 200 | // Insertion sort of network and building of netindex[0..255] (to do after unbias).  |
| 201 | int i, j, smallpos, smallval;  |
| 202 | int *p, *q;  |
| 203 | int previouscol, startpos;  |
| 204 |   |
| 205 | previouscol = 0;  |
| 206 | startpos = 0;  |
| 207 | for (i = 0; i < state.netsize; i++)  |
| 208 | {  |
| 209 | p = state.network[i];  |
| 210 | smallpos = i;  |
| 211 | smallval = p[1]; // Index on g.  |
| 212 |   |
| 213 | // Find smallest in i..netsize-1.  |
| 214 | for (j = i + 1; j < state.netsize; j++)  |
| 215 | {  |
| 216 | q = state.network[j];  |
| 217 | if (q[1] < smallval) // Index on g.  |
| 218 | {  |
| 219 | smallpos = j;  |
| 220 | smallval = q[1]; // Index on g.  |
| 221 | }  |
| 222 | }  |
| 223 | q = state.network[smallpos];  |
| 224 |   |
| 225 | // Swap p (i) and q (smallpos) entries.  |
| 226 | if (i != smallpos)  |
| 227 | {  |
| 228 | j = q[0];  |
| 229 | q[0] = p[0];  |
| 230 | p[0] = j;  |
| 231 | j = q[1];  |
| 232 | q[1] = p[1];  |
| 233 | p[1] = j;  |
| 234 | j = q[2];  |
| 235 | q[2] = p[2];  |
| 236 | p[2] = j;  |
| 237 | j = q[3];  |
| 238 | q[3] = p[3];  |
| 239 | p[3] = j;  |
| 240 | }  |
| 241 |   |
| 242 | // Smallval entry is now in position i.  |
| 243 | if (smallval != previouscol)  |
| 244 | {  |
| 245 | state.netindex[previouscol] = (startpos + i) >> 1;  |
| 246 | for (j = previouscol + 1; j < smallval; j++) state.netindex[j] = i;  |
| 247 | previouscol = smallval;  |
| 248 | startpos = i;  |
| 249 | }  |
| 250 | }  |
| 251 | state.netindex[previouscol] = (startpos + maxnetpos) >> 1;  |
| 252 | for (j = previouscol + 1; j < 256; j++)  |
| 253 | state.netindex[j] = maxnetpos;  |
| 254 | }  |
| 255 |   |
| 256 |   |
| 257 | int tQuantizeNeu::inxsearch(State& state, int b, int g, int r)  |
| 258 | {  |
| 259 | // Search for BGR values 0..255 (after net is unbiased) and return colour index.  |
| 260 | int i, j, dist, a, bestd;  |
| 261 | int *p;  |
| 262 | int best;  |
| 263 |   |
| 264 | bestd = 1000; // Biggest possible dist is 256*3.  |
| 265 | best = -1;  |
| 266 | i = state.netindex[g]; // Index on g.  |
| 267 | j = i - 1; // Start at netindex[g] and work outwards.  |
| 268 |   |
| 269 | while ((i < state.netsize) || (j >= 0))  |
| 270 | {  |
| 271 | if (i < state.netsize)  |
| 272 | {  |
| 273 | p = state.network[i];  |
| 274 | dist = p[1] - g; // Inx key.  |
| 275 | if (dist >= bestd)  |
| 276 | i = state.netsize; // Stop iter.  |
| 277 | else  |
| 278 | {  |
| 279 | i++;  |
| 280 | if (dist < 0) dist = -dist;  |
| 281 | a = p[0] - b;  |
| 282 | if (a < 0) a = -a;  |
| 283 | dist += a;  |
| 284 | if (dist < bestd)  |
| 285 | {  |
| 286 | a = p[2] - r;  |
| 287 | if (a < 0) a = -a;  |
| 288 | dist += a;  |
| 289 | if (dist < bestd)  |
| 290 | {  |
| 291 | bestd = dist;  |
| 292 | best = p[3];  |
| 293 | }  |
| 294 | }  |
| 295 | }  |
| 296 | }  |
| 297 | if (j >= 0)  |
| 298 | {  |
| 299 | p = state.network[j];  |
| 300 | dist = g - p[1]; // Inx key - reverse dif.  |
| 301 | if (dist >= bestd)  |
| 302 | j = -1; // Stop iter.  |
| 303 | else  |
| 304 | {  |
| 305 | j--;  |
| 306 | if (dist < 0) dist = -dist;  |
| 307 | a = p[0] - b;  |
| 308 | if (a < 0) a = -a;  |
| 309 | dist += a;  |
| 310 | if (dist < bestd)  |
| 311 | {  |
| 312 | a = p[2] - r;  |
| 313 | if (a < 0) a = -a;  |
| 314 | dist += a;  |
| 315 | if (dist < bestd)  |
| 316 | {  |
| 317 | bestd = dist;  |
| 318 | best = p[3];  |
| 319 | }  |
| 320 | }  |
| 321 | }  |
| 322 | }  |
| 323 | }  |
| 324 | return best;  |
| 325 | }  |
| 326 |   |
| 327 |   |
| 328 | int tQuantizeNeu::contest(State& state, int b, int g, int r)  |
| 329 | {  |
| 330 | // Search for biased BGR values. Finds closest neuron (min dist) and updates freq finds best neuron (min dist-bias)  |
| 331 | // and returns position for frequently chosen neurons, freq[i] is high and bias[i] is negative.  |
| 332 | // bias[i] = gamma*((1/netsize)-freq[i])  |
| 333 | int i, dist, a, biasdist, betafreq;  |
| 334 | int bestpos, bestbiaspos, bestd, bestbiasd;  |
| 335 | int *p, *f, *n;  |
| 336 |   |
| 337 | bestd = ~(((int) 1) << 31);  |
| 338 | bestbiasd = bestd;  |
| 339 | bestpos = -1;  |
| 340 | bestbiaspos = bestpos;  |
| 341 | p = state.bias;  |
| 342 | f = state.freq;  |
| 343 |   |
| 344 | for (i = 0; i < state.netsize; i++)  |
| 345 | {  |
| 346 | n = state.network[i];  |
| 347 | dist = n[0] - b;  |
| 348 | if (dist < 0)  |
| 349 | dist = -dist;  |
| 350 |   |
| 351 | a = n[1] - g;  |
| 352 | if (a < 0)  |
| 353 | a = -a;  |
| 354 |   |
| 355 | dist += a;  |
| 356 | a = n[2] - r;  |
| 357 | if (a < 0)  |
| 358 | a = -a;  |
| 359 |   |
| 360 | dist += a;  |
| 361 | if (dist < bestd)  |
| 362 | {  |
| 363 | bestd = dist;  |
| 364 | bestpos = i;  |
| 365 | }  |
| 366 | biasdist = dist - ((*p) >> (intbiasshift - netbiasshift));  |
| 367 | if (biasdist < bestbiasd)  |
| 368 | {  |
| 369 | bestbiasd = biasdist;  |
| 370 | bestbiaspos = i;  |
| 371 | }  |
| 372 | betafreq = (*f >> betashift);  |
| 373 | *f++ -= betafreq;  |
| 374 | *p++ += (betafreq << gammashift);  |
| 375 | }  |
| 376 | state.freq[bestpos] += beta;  |
| 377 | state.bias[bestpos] -= betagamma;  |
| 378 | return (bestbiaspos);  |
| 379 | }  |
| 380 |   |
| 381 |   |
| 382 | void tQuantizeNeu::altersingle(State& state, int alpha, int i, int b, int g, int r)  |
| 383 | {  |
| 384 | // Move neuron i towards biased (b,g,r) by factor alpha.  |
| 385 | int *n;  |
| 386 |   |
| 387 | n = state.network[i]; // Alter hit neuron.  |
| 388 | *n -= (alpha * (*n - b)) / initalpha;  |
| 389 | n++;  |
| 390 | *n -= (alpha * (*n - g)) / initalpha;  |
| 391 | n++;  |
| 392 | *n -= (alpha * (*n - r)) / initalpha;  |
| 393 | }  |
| 394 |   |
| 395 |   |
| 396 | void tQuantizeNeu::alterneigh(State& state, int rad, int i, int b, int g, int r)  |
| 397 | {  |
| 398 | // Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|].  |
| 399 | int j, k, lo, hi, a;  |
| 400 | int *p, *q;  |
| 401 |   |
| 402 | lo = i - rad;  |
| 403 | if (lo < -1)  |
| 404 | lo = -1;  |
| 405 |   |
| 406 | hi = i + rad;  |
| 407 | if (hi > state.netsize)  |
| 408 | hi = state.netsize;  |
| 409 |   |
| 410 | j = i + 1;  |
| 411 | k = i - 1;  |
| 412 | q = state.radpower;  |
| 413 | while ((j < hi) || (k > lo))  |
| 414 | {  |
| 415 | a = (*(++q));  |
| 416 | if (j < hi)  |
| 417 | {  |
| 418 | p = state.network[j];  |
| 419 | *p -= (a * (*p - b)) / alpharadbias;  |
| 420 | p++;  |
| 421 | *p -= (a * (*p - g)) / alpharadbias;  |
| 422 | p++;  |
| 423 | *p -= (a * (*p - r)) / alpharadbias;  |
| 424 | j++;  |
| 425 | }  |
| 426 | if (k > lo)  |
| 427 | {  |
| 428 | p = state.network[k];  |
| 429 | *p -= (a * (*p - b)) / alpharadbias;  |
| 430 | p++;  |
| 431 | *p -= (a * (*p - g)) / alpharadbias;  |
| 432 | p++;  |
| 433 | *p -= (a * (*p - r)) / alpharadbias;  |
| 434 | k--;  |
| 435 | }  |
| 436 | }  |
| 437 | }  |
| 438 |   |
| 439 |   |
| 440 | void tQuantizeNeu::learn(State& state)  |
| 441 | {  |
| 442 | // Main Learning Loop.  |
| 443 | int i, j, b, g, r;  |
| 444 | int radius, rad, alpha, step, delta, samplepixels;  |
| 445 | unsigned char *p;  |
| 446 | unsigned char *lim;  |
| 447 |   |
| 448 | state.alphadec = 30 + ((state.samplefac - 1) / 3);  |
| 449 | p = state.thepicture;  |
| 450 | lim = state.thepicture + state.lengthcount;  |
| 451 | samplepixels = state.lengthcount / (3 * state.samplefac);  |
| 452 | delta = samplepixels / ncycles;  |
| 453 | alpha = initalpha;  |
| 454 | radius = initradius;  |
| 455 |   |
| 456 | rad = radius >> radiusbiasshift;  |
| 457 | if (rad <= 1)  |
| 458 | rad = 0;  |
| 459 |   |
| 460 | for (i = 0; i < rad; i++)  |
| 461 | state.radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad));  |
| 462 |   |
| 463 | if ((state.lengthcount % prime1) != 0)  |
| 464 | {  |
| 465 | step = 3 * prime1;  |
| 466 | }  |
| 467 | else  |
| 468 | {  |
| 469 | if ((state.lengthcount % prime2) != 0)  |
| 470 | {  |
| 471 | step = 3 * prime2;  |
| 472 | }  |
| 473 | else  |
| 474 | {  |
| 475 | if ((state.lengthcount % prime3) != 0)  |
| 476 | step = 3 * prime3;  |
| 477 | else  |
| 478 | step = 3 * prime4;  |
| 479 | }  |
| 480 | }  |
| 481 |   |
| 482 | i = 0;  |
| 483 | while (i < samplepixels)  |
| 484 | {  |
| 485 | b = p[0] << netbiasshift;  |
| 486 | g = p[1] << netbiasshift;  |
| 487 | r = p[2] << netbiasshift;  |
| 488 | j = contest(state, b, g, r);  |
| 489 |   |
| 490 | altersingle(state, alpha, i: j, b, g, r);  |
| 491 | if (rad)  |
| 492 | alterneigh(state, rad, i: j, b, g, r); // Alter neighbours.  |
| 493 |   |
| 494 | p += step;  |
| 495 | if (p >= lim)  |
| 496 | p -= state.lengthcount;  |
| 497 |   |
| 498 | i++;  |
| 499 | if (i % delta == 0)  |
| 500 | {  |
| 501 | alpha -= alpha / state.alphadec;  |
| 502 | radius -= radius / radiusdec;  |
| 503 | rad = radius >> radiusbiasshift;  |
| 504 | if (rad <= 1)  |
| 505 | rad = 0;  |
| 506 |   |
| 507 | for (j = 0; j < rad; j++)  |
| 508 | state.radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad));  |
| 509 | }  |
| 510 | }  |
| 511 | }  |
| 512 |   |
| 513 |   |
| 514 | int tQuantizeNeu::FindIndexOfClosestColour_Redmean(const tColour3b* searchSpace, int searchSize, const tColour3b& colour)  |
| 515 | {  |
| 516 | float closest = 1000.0f;  |
| 517 | int closestIndex = -1;  |
| 518 |   |
| 519 | for (int i = 0; i < searchSize; i++)  |
| 520 | {  |
| 521 | float diff = tMath::tColourDiffRedmean(a: colour, b: searchSpace[i]);  |
| 522 | if (diff < closest)  |
| 523 | {  |
| 524 | closest = diff;  |
| 525 | closestIndex = i;  |
| 526 | }  |
| 527 | }  |
| 528 | return closestIndex;  |
| 529 | }  |
| 530 |   |
| 531 |   |
| 532 | //  |
| 533 | // The functions below make up the external interface.  |
| 534 | //  |
| 535 |   |
| 536 |   |
| 537 | bool tQuantizeNeu::QuantizeImage  |
| 538 | (  |
| 539 | int numColours, int width, int height, const tPixel3b* pixels, tColour3b* destPalette, uint8* destIndices,  |
| 540 | bool checkExact, int sampleFactor  |
| 541 | )  |
| 542 | {  |
| 543 | if ((numColours < 2) || (numColours > 256) || (width <= 0) || (height <= 0) || !pixels || !destPalette || !destIndices)  |
| 544 | return false;  |
| 545 |   |
| 546 | if ((sampleFactor < 1) || (sampleFactor > 30))  |
| 547 | return false;  |
| 548 |   |
| 549 | if (checkExact)  |
| 550 | {  |
| 551 | bool success = tQuantize::QuantizeImageExact(numColours, width, height, pixels, destPalette, destIndices);  |
| 552 | if (success)  |
| 553 | return true;  |
| 554 | }  |
| 555 |   |
| 556 | State state;  |
| 557 | state.netsize = numColours;  |
| 558 |   |
| 559 | initnet(state, thepic: (uint8*)pixels, len: width*height*3, sample: sampleFactor);  |
| 560 | learn(state);  |
| 561 | unbiasnet(state);  |
| 562 | int resultNumColours = getColourMap(state, out: destPalette);  |
| 563 |   |
| 564 | // Exhaustive redmean is better.  |
| 565 | // inxbuild(state);  |
| 566 | for (int y = 0; y < height; y++)  |
| 567 | {  |
| 568 | for (int x = 0; x < width; x++)  |
| 569 | {  |
| 570 | const tPixel3b& pixel = pixels[x + y*width];  |
| 571 | destIndices[x + y*width] = FindIndexOfClosestColour_Redmean(searchSpace: destPalette, searchSize: numColours, colour: pixel);  |
| 572 |   |
| 573 | // Exhaustive redmean is better.  |
| 574 | // destIndices[x + y*width] = inxsearch(state, pixel.R, pixel.G, pixel.B);  |
| 575 | }  |
| 576 | }  |
| 577 |   |
| 578 | return (resultNumColours > 0);  |
| 579 | }  |
| 580 |   |
| 581 |   |
| 582 | bool tQuantizeNeu::QuantizeImage  |
| 583 | (  |
| 584 | int numColours, int width, int height, const tPixel4b* pixels, tColour3b* destPalette, uint8* destIndices,  |
| 585 | bool checkExact, int sampleFactor  |
| 586 | )  |
| 587 | {  |
| 588 | if ((numColours < 2) || (numColours > 256) || (width <= 0) || (height <= 0) || !pixels || !destPalette || !destIndices)  |
| 589 | return false;  |
| 590 |   |
| 591 | tPixel3b* pixels3 = new tPixel3b[width*height];  |
| 592 | for (int p = 0; p < width*height; p++)  |
| 593 | pixels3[p].Set( r: pixels[p].R, g: pixels[p].G, b: pixels[p].B );  |
| 594 |   |
| 595 | bool success = QuantizeImage(numColours, width, height, pixels: pixels3, destPalette, destIndices, checkExact, sampleFactor);  |
| 596 | delete[] pixels3;  |
| 597 | return success;  |
| 598 | }  |
| 599 |   |
| 600 |   |
| 601 | }  |
| 602 | |