/* * The Python Imaging Library * $Id$ * * image quantizer * * history: * 1998-09-10 tjs Contributed * 1998-12-29 fl Added to PIL 1.0b1 * 2004-02-21 fl Fixed bogus free() on quantization error * 2005-02-07 fl Limit number of colors to 256 * * Written by Toby J Sargeant . * * Copyright (c) 1998 by Toby J Sargeant * Copyright (c) 1998-2004 by Secret Labs AB. All rights reserved. * * See the README file for information on usage and redistribution. */ #include "Imaging.h" #include #include #include #include #include "QuantTypes.h" #include "QuantOctree.h" #include "QuantPngQuant.h" #include "QuantHash.h" #include "QuantHeap.h" /* MSVC9.0 */ #ifndef UINT32_MAX #define UINT32_MAX 0xffffffff #endif #define NO_OUTPUT typedef struct { uint32_t scale; } PixelHashData; typedef struct _PixelList { struct _PixelList *next[3], *prev[3]; Pixel p; unsigned int flag : 1; int count; } PixelList; typedef struct _BoxNode { struct _BoxNode *l, *r; PixelList *head[3], *tail[3]; int axis; int volume; uint32_t pixelCount; } BoxNode; #define _SQR(x) ((x) * (x)) #define _DISTSQR(p1, p2) \ _SQR((int)((p1)->c.r) - (int)((p2)->c.r)) + \ _SQR((int)((p1)->c.g) - (int)((p2)->c.g)) + \ _SQR((int)((p1)->c.b) - (int)((p2)->c.b)) #define MAX_HASH_ENTRIES 65536 #define PIXEL_HASH(r, g, b) \ (((unsigned int)(r)) * 463 ^ ((unsigned int)(g) << 8) * 10069 ^ \ ((unsigned int)(b) << 16) * 64997) #define PIXEL_UNSCALE(p, q, s) \ ((q)->c.r = (p)->c.r << (s)), ((q)->c.g = (p)->c.g << (s)), \ ((q)->c.b = (p)->c.b << (s)) #define PIXEL_SCALE(p, q, s) \ ((q)->c.r = (p)->c.r >> (s)), ((q)->c.g = (p)->c.g >> (s)), \ ((q)->c.b = (p)->c.b >> (s)) static uint32_t unshifted_pixel_hash(const HashTable *h, const Pixel pixel) { return PIXEL_HASH(pixel.c.r, pixel.c.g, pixel.c.b); } static int unshifted_pixel_cmp(const HashTable *h, const Pixel pixel1, const Pixel pixel2) { if (pixel1.c.r == pixel2.c.r) { if (pixel1.c.g == pixel2.c.g) { if (pixel1.c.b == pixel2.c.b) { return 0; } else { return (int)(pixel1.c.b) - (int)(pixel2.c.b); } } else { return (int)(pixel1.c.g) - (int)(pixel2.c.g); } } else { return (int)(pixel1.c.r) - (int)(pixel2.c.r); } } static uint32_t pixel_hash(const HashTable *h, const Pixel pixel) { PixelHashData *d = (PixelHashData *)hashtable_get_user_data(h); return PIXEL_HASH( pixel.c.r >> d->scale, pixel.c.g >> d->scale, pixel.c.b >> d->scale); } static int pixel_cmp(const HashTable *h, const Pixel pixel1, const Pixel pixel2) { PixelHashData *d = (PixelHashData *)hashtable_get_user_data(h); uint32_t A, B; A = PIXEL_HASH( pixel1.c.r >> d->scale, pixel1.c.g >> d->scale, pixel1.c.b >> d->scale); B = PIXEL_HASH( pixel2.c.r >> d->scale, pixel2.c.g >> d->scale, pixel2.c.b >> d->scale); return (A == B) ? 0 : ((A < B) ? -1 : 1); } static void exists_count_func(const HashTable *h, const Pixel key, uint32_t *val) { *val += 1; } static void new_count_func(const HashTable *h, const Pixel key, uint32_t *val) { *val = 1; } static void rehash_collide( const HashTable *h, Pixel *keyp, uint32_t *valp, Pixel newkey, uint32_t newval) { *valp += newval; } /* %% */ static HashTable * create_pixel_hash(Pixel *pixelData, uint32_t nPixels) { PixelHashData *d; HashTable *hash; uint32_t i; #ifndef NO_OUTPUT uint32_t timer, timer2, timer3; #endif /* malloc check ok, small constant allocation */ d = malloc(sizeof(PixelHashData)); if (!d) { return NULL; } hash = hashtable_new(pixel_hash, pixel_cmp); hashtable_set_user_data(hash, d); d->scale = 0; #ifndef NO_OUTPUT timer = timer3 = clock(); #endif for (i = 0; i < nPixels; i++) { if (!hashtable_insert_or_update_computed( hash, pixelData[i], new_count_func, exists_count_func)) { ; } while (hashtable_get_count(hash) > MAX_HASH_ENTRIES) { d->scale++; #ifndef NO_OUTPUT printf("rehashing - new scale: %d\n", (int)d->scale); timer2 = clock(); #endif hashtable_rehash_compute(hash, rehash_collide); #ifndef NO_OUTPUT timer2 = clock() - timer2; printf("rehash took %f sec\n", timer2 / (double)CLOCKS_PER_SEC); timer += timer2; #endif } } #ifndef NO_OUTPUT printf("inserts took %f sec\n", (clock() - timer) / (double)CLOCKS_PER_SEC); #endif #ifndef NO_OUTPUT printf("total %f sec\n", (clock() - timer3) / (double)CLOCKS_PER_SEC); #endif return hash; } static void destroy_pixel_hash(HashTable *hash) { PixelHashData *d = (PixelHashData *)hashtable_get_user_data(hash); if (d) { free(d); } hashtable_free(hash); } /* 1. hash quantized pixels. */ /* 2. create R,G,B lists of sorted quantized pixels. */ /* 3. median cut. */ /* 4. build hash table from median cut boxes. */ /* 5. for each pixel, compute entry in hash table, and hence median cut box. */ /* 6. compute median cut box pixel averages. */ /* 7. map each pixel to nearest average. */ static int compute_box_volume(BoxNode *b) { unsigned char rl, rh, gl, gh, bl, bh; if (b->volume >= 0) { return b->volume; } if (!b->head[0]) { b->volume = 0; } else { rh = b->head[0]->p.c.r; rl = b->tail[0]->p.c.r; gh = b->head[1]->p.c.g; gl = b->tail[1]->p.c.g; bh = b->head[2]->p.c.b; bl = b->tail[2]->p.c.b; b->volume = (rh - rl + 1) * (gh - gl + 1) * (bh - bl + 1); } return b->volume; } static void hash_to_list(const HashTable *h, const Pixel pixel, const uint32_t count, void *u) { PixelHashData *d = (PixelHashData *)hashtable_get_user_data(h); PixelList **pl = (PixelList **)u; PixelList *p; int i; Pixel q; PIXEL_SCALE(&pixel, &q, d->scale); /* malloc check ok, small constant allocation */ p = malloc(sizeof(PixelList)); if (!p) { return; } p->flag = 0; p->p = q; p->count = count; for (i = 0; i < 3; i++) { p->next[i] = pl[i]; p->prev[i] = NULL; if (pl[i]) { pl[i]->prev[i] = p; } pl[i] = p; } } static PixelList * mergesort_pixels(PixelList *head, int i) { PixelList *c, *t, *a, *b, *p; if (!head || !head->next[i]) { if (head) { head->next[i] = NULL; head->prev[i] = NULL; } return head; } for (c = t = head; c && t; c = c->next[i], t = (t->next[i]) ? t->next[i]->next[i] : NULL) ; if (c) { if (c->prev[i]) { c->prev[i]->next[i] = NULL; } c->prev[i] = NULL; } a = mergesort_pixels(head, i); b = mergesort_pixels(c, i); head = NULL; p = NULL; while (a && b) { if (a->p.a.v[i] > b->p.a.v[i]) { c = a; a = a->next[i]; } else { c = b; b = b->next[i]; } c->prev[i] = p; c->next[i] = NULL; if (p) { p->next[i] = c; } p = c; if (!head) { head = c; } } if (a) { c->next[i] = a; a->prev[i] = c; } else if (b) { c->next[i] = b; b->prev[i] = c; } return head; } #if defined(TEST_MERGESORT) || defined(TEST_SORTED) static int test_sorted(PixelList *pl[3]) { int i, n, l; PixelList *t; for (i = 0; i < 3; i++) { n = 0; l = 256; for (t = pl[i]; t; t = t->next[i]) { if (l < t->p.a.v[i]) return 0; l = t->p.a.v[i]; } } return 1; } #endif static int box_heap_cmp(const Heap *h, const void *A, const void *B) { BoxNode *a = (BoxNode *)A; BoxNode *b = (BoxNode *)B; return (int)a->pixelCount - (int)b->pixelCount; } #define LUMINANCE(p) (77 * (p)->c.r + 150 * (p)->c.g + 29 * (p)->c.b) static int splitlists( PixelList *h[3], PixelList *t[3], PixelList *nh[2][3], PixelList *nt[2][3], uint32_t nCount[2], int axis, uint32_t pixelCount) { uint32_t left; PixelList *l, *r, *c, *n; int i; int nRight, nLeft; int splitColourVal; #ifdef TEST_SPLIT { PixelList *_prevTest, *_nextTest; int _i, _nextCount[3], _prevCount[3]; for (_i = 0; _i < 3; _i++) { for (_nextCount[_i] = 0, _nextTest = h[_i]; _nextTest && _nextTest->next[_i]; _nextTest = _nextTest->next[_i], _nextCount[_i]++) ; for (_prevCount[_i] = 0, _prevTest = t[_i]; _prevTest && _prevTest->prev[_i]; _prevTest = _prevTest->prev[_i], _prevCount[_i]++) ; if (_nextTest != t[_i]) { printf("next-list of axis %d does not end at tail\n", _i); exit(1); } if (_prevTest != h[_i]) { printf("prev-list of axis %d does not end at head\n", _i); exit(1); } for (; _nextTest && _nextTest->prev[_i]; _nextTest = _nextTest->prev[_i]) ; for (; _prevTest && _prevTest->next[_i]; _prevTest = _prevTest->next[_i]) ; if (_nextTest != h[_i]) { printf("next-list of axis %d does not loop back to head\n", _i); exit(1); } if (_prevTest != t[_i]) { printf("prev-list of axis %d does not loop back to tail\n", _i); exit(1); } } for (_i = 1; _i < 3; _i++) { if (_prevCount[_i] != _prevCount[_i - 1] || _nextCount[_i] != _nextCount[_i - 1] || _prevCount[_i] != _nextCount[_i]) { printf( "{%d %d %d} {%d %d %d}\n", _prevCount[0], _prevCount[1], _prevCount[2], _nextCount[0], _nextCount[1], _nextCount[2]); exit(1); } } } #endif nCount[0] = nCount[1] = 0; nLeft = nRight = 0; for (left = 0, c = h[axis]; c;) { left = left + c->count; nCount[0] += c->count; c->flag = 0; nLeft++; c = c->next[axis]; if (left * 2 > pixelCount) { break; } } if (c) { splitColourVal = c->prev[axis]->p.a.v[axis]; for (; c; c = c->next[axis]) { if (splitColourVal != c->p.a.v[axis]) { break; } c->flag = 0; nLeft++; nCount[0] += c->count; } } for (; c; c = c->next[axis]) { c->flag = 1; nRight++; nCount[1] += c->count; } if (!nRight) { for (c = t[axis], splitColourVal = t[axis]->p.a.v[axis]; c; c = c->prev[axis]) { if (splitColourVal != c->p.a.v[axis]) { break; } c->flag = 1; nRight++; nLeft--; nCount[0] -= c->count; nCount[1] += c->count; } } #ifndef NO_OUTPUT if (!nLeft) { for (c = h[axis]; c; c = c->next[axis]) { printf("[%d %d %d]\n", c->p.c.r, c->p.c.g, c->p.c.b); } printf("warning... trivial split\n"); } #endif for (i = 0; i < 3; i++) { l = r = NULL; nh[0][i] = nt[0][i] = NULL; nh[1][i] = nt[1][i] = NULL; for (c = h[i]; c; c = n) { n = c->next[i]; if (c->flag) { /* move pixel to right list*/ if (r) { r->next[i] = c; } else { nh[1][i] = c; } c->prev[i] = r; r = c; } else { /* move pixel to left list */ if (l) { l->next[i] = c; } else { nh[0][i] = c; } c->prev[i] = l; l = c; } } if (l) { l->next[i] = NULL; } if (r) { r->next[i] = NULL; } nt[0][i] = l; nt[1][i] = r; } return 1; } static int split(BoxNode *node) { unsigned char rl, rh, gl, gh, bl, bh; int f[3]; int best, axis; int i; PixelList *heads[2][3]; PixelList *tails[2][3]; uint32_t newCounts[2]; BoxNode *left, *right; rh = node->head[0]->p.c.r; rl = node->tail[0]->p.c.r; gh = node->head[1]->p.c.g; gl = node->tail[1]->p.c.g; bh = node->head[2]->p.c.b; bl = node->tail[2]->p.c.b; #ifdef TEST_SPLIT printf("splitting node [%d %d %d] [%d %d %d] ", rl, gl, bl, rh, gh, bh); #endif f[0] = (rh - rl) * 77; f[1] = (gh - gl) * 150; f[2] = (bh - bl) * 29; best = f[0]; axis = 0; for (i = 1; i < 3; i++) { if (best < f[i]) { best = f[i]; axis = i; } } #ifdef TEST_SPLIT printf("along axis %d\n", axis + 1); #endif #ifdef TEST_SPLIT { PixelList *_prevTest, *_nextTest; int _i, _nextCount[3], _prevCount[3]; for (_i = 0; _i < 3; _i++) { if (node->tail[_i]->next[_i]) { printf("tail is not tail\n"); printf( "node->tail[%d]->next[%d]=%p\n", _i, _i, node->tail[_i]->next[_i]); } if (node->head[_i]->prev[_i]) { printf("head is not head\n"); printf( "node->head[%d]->prev[%d]=%p\n", _i, _i, node->head[_i]->prev[_i]); } } for (_i = 0; _i < 3; _i++) { for (_nextCount[_i] = 0, _nextTest = node->head[_i]; _nextTest && _nextTest->next[_i]; _nextTest = _nextTest->next[_i], _nextCount[_i]++) ; for (_prevCount[_i] = 0, _prevTest = node->tail[_i]; _prevTest && _prevTest->prev[_i]; _prevTest = _prevTest->prev[_i], _prevCount[_i]++) ; if (_nextTest != node->tail[_i]) { printf("next-list of axis %d does not end at tail\n", _i); } if (_prevTest != node->head[_i]) { printf("prev-list of axis %d does not end at head\n", _i); } for (; _nextTest && _nextTest->prev[_i]; _nextTest = _nextTest->prev[_i]) ; for (; _prevTest && _prevTest->next[_i]; _prevTest = _prevTest->next[_i]) ; if (_nextTest != node->head[_i]) { printf("next-list of axis %d does not loop back to head\n", _i); } if (_prevTest != node->tail[_i]) { printf("prev-list of axis %d does not loop back to tail\n", _i); } } for (_i = 1; _i < 3; _i++) { if (_prevCount[_i] != _prevCount[_i - 1] || _nextCount[_i] != _nextCount[_i - 1] || _prevCount[_i] != _nextCount[_i]) { printf( "{%d %d %d} {%d %d %d}\n", _prevCount[0], _prevCount[1], _prevCount[2], _nextCount[0], _nextCount[1], _nextCount[2]); } } } #endif node->axis = axis; if (!splitlists( node->head, node->tail, heads, tails, newCounts, axis, node->pixelCount)) { #ifndef NO_OUTPUT printf("list split failed.\n"); #endif return 0; } #ifdef TEST_SPLIT if (!test_sorted(heads[0])) { printf("bug in split"); exit(1); } if (!test_sorted(heads[1])) { printf("bug in split"); exit(1); } #endif /* malloc check ok, small constant allocation */ left = malloc(sizeof(BoxNode)); right = malloc(sizeof(BoxNode)); if (!left || !right) { free(left); free(right); return 0; } for (i = 0; i < 3; i++) { left->head[i] = heads[0][i]; left->tail[i] = tails[0][i]; right->head[i] = heads[1][i]; right->tail[i] = tails[1][i]; node->head[i] = NULL; node->tail[i] = NULL; } #ifdef TEST_SPLIT if (left->head[0]) { rh = left->head[0]->p.c.r; rl = left->tail[0]->p.c.r; gh = left->head[1]->p.c.g; gl = left->tail[1]->p.c.g; bh = left->head[2]->p.c.b; bl = left->tail[2]->p.c.b; printf(" left node [%3d %3d %3d] [%3d %3d %3d]\n", rl, gl, bl, rh, gh, bh); } if (right->head[0]) { rh = right->head[0]->p.c.r; rl = right->tail[0]->p.c.r; gh = right->head[1]->p.c.g; gl = right->tail[1]->p.c.g; bh = right->head[2]->p.c.b; bl = right->tail[2]->p.c.b; printf(" right node [%3d %3d %3d] [%3d %3d %3d]\n", rl, gl, bl, rh, gh, bh); } #endif left->l = left->r = NULL; right->l = right->r = NULL; left->axis = right->axis = -1; left->volume = right->volume = -1; left->pixelCount = newCounts[0]; right->pixelCount = newCounts[1]; node->l = left; node->r = right; return 1; } static BoxNode * median_cut(PixelList *hl[3], uint32_t imPixelCount, int nPixels) { PixelList *tl[3]; int i; BoxNode *root; Heap *h; BoxNode *thisNode; h = ImagingQuantHeapNew(box_heap_cmp); /* malloc check ok, small constant allocation */ root = malloc(sizeof(BoxNode)); if (!root) { ImagingQuantHeapFree(h); return NULL; } for (i = 0; i < 3; i++) { for (tl[i] = hl[i]; tl[i] && tl[i]->next[i]; tl[i] = tl[i]->next[i]) ; root->head[i] = hl[i]; root->tail[i] = tl[i]; } root->l = root->r = NULL; root->axis = -1; root->volume = -1; root->pixelCount = imPixelCount; ImagingQuantHeapAdd(h, (void *)root); while (--nPixels) { do { if (!ImagingQuantHeapRemove(h, (void **)&thisNode)) { goto done; } } while (compute_box_volume(thisNode) == 1); if (!split(thisNode)) { #ifndef NO_OUTPUT printf("Oops, split failed...\n"); #endif exit(1); } ImagingQuantHeapAdd(h, (void *)(thisNode->l)); ImagingQuantHeapAdd(h, (void *)(thisNode->r)); } done: ImagingQuantHeapFree(h); return root; } static void free_box_tree(BoxNode *n) { PixelList *p, *pp; if (n->l) { free_box_tree(n->l); } if (n->r) { free_box_tree(n->r); } for (p = n->head[0]; p; p = pp) { pp = p->next[0]; free(p); } free(n); } #ifdef TEST_SPLIT_INTEGRITY static int checkContained(BoxNode *n, Pixel *pp) { if (n->l && n->r) { return checkContained(n->l, pp) + checkContained(n->r, pp); } if (n->l || n->r) { #ifndef NO_OUTPUT printf("box tree is dead\n"); #endif return 0; } if (pp->c.r <= n->head[0]->p.c.r && pp->c.r >= n->tail[0]->p.c.r && pp->c.g <= n->head[1]->p.c.g && pp->c.g >= n->tail[1]->p.c.g && pp->c.b <= n->head[2]->p.c.b && pp->c.b >= n->tail[2]->p.c.b) { return 1; } return 0; } #endif static int annotate_hash_table(BoxNode *n, HashTable *h, uint32_t *box) { PixelList *p; PixelHashData *d = (PixelHashData *)hashtable_get_user_data(h); Pixel q; if (n->l && n->r) { return annotate_hash_table(n->l, h, box) && annotate_hash_table(n->r, h, box); } if (n->l || n->r) { #ifndef NO_OUTPUT printf("box tree is dead\n"); #endif return 0; } for (p = n->head[0]; p; p = p->next[0]) { PIXEL_UNSCALE(&(p->p), &q, d->scale); if (!hashtable_insert(h, q, *box)) { #ifndef NO_OUTPUT printf("hashtable insert failed\n"); #endif return 0; } } if (n->head[0]) { (*box)++; } return 1; } static int _sort_ulong_ptr_keys(const void *a, const void *b) { uint32_t A = **(uint32_t **)a; uint32_t B = **(uint32_t **)b; return (A == B) ? 0 : ((A < B) ? -1 : +1); } static int resort_distance_tables( uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries) { uint32_t i, j, k; uint32_t **skRow; uint32_t *skElt; for (i = 0; i < nEntries; i++) { avgDist[i * nEntries + i] = 0; for (j = 0; j < i; j++) { avgDist[j * nEntries + i] = avgDist[i * nEntries + j] = _DISTSQR(p + i, p + j); } } for (i = 0; i < nEntries; i++) { skRow = avgDistSortKey + i * nEntries; for (j = 1; j < nEntries; j++) { skElt = skRow[j]; for (k = j; k && (*(skRow[k - 1]) > *(skRow[k])); k--) { skRow[k] = skRow[k - 1]; } if (k != j) { skRow[k] = skElt; } } } return 1; } static int build_distance_tables( uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries) { uint32_t i, j; for (i = 0; i < nEntries; i++) { avgDist[i * nEntries + i] = 0; avgDistSortKey[i * nEntries + i] = &(avgDist[i * nEntries + i]); for (j = 0; j < i; j++) { avgDist[j * nEntries + i] = avgDist[i * nEntries + j] = _DISTSQR(p + i, p + j); avgDistSortKey[j * nEntries + i] = &(avgDist[j * nEntries + i]); avgDistSortKey[i * nEntries + j] = &(avgDist[i * nEntries + j]); } } for (i = 0; i < nEntries; i++) { qsort( avgDistSortKey + i * nEntries, nEntries, sizeof(uint32_t *), _sort_ulong_ptr_keys); } return 1; } static int map_image_pixels( Pixel *pixelData, uint32_t nPixels, Pixel *paletteData, uint32_t nPaletteEntries, uint32_t *avgDist, uint32_t **avgDistSortKey, uint32_t *pixelArray) { uint32_t *aD, **aDSK; uint32_t idx; uint32_t i, j; uint32_t bestdist, bestmatch, dist; uint32_t initialdist; HashTable *h2; h2 = hashtable_new(unshifted_pixel_hash, unshifted_pixel_cmp); for (i = 0; i < nPixels; i++) { if (!hashtable_lookup(h2, pixelData[i], &bestmatch)) { bestmatch = 0; initialdist = _DISTSQR(paletteData + bestmatch, pixelData + i); bestdist = initialdist; initialdist <<= 2; aDSK = avgDistSortKey + bestmatch * nPaletteEntries; aD = avgDist + bestmatch * nPaletteEntries; for (j = 0; j < nPaletteEntries; j++) { idx = aDSK[j] - aD; if (*(aDSK[j]) <= initialdist) { dist = _DISTSQR(paletteData + idx, pixelData + i); if (dist < bestdist) { bestdist = dist; bestmatch = idx; } } else { break; } } hashtable_insert(h2, pixelData[i], bestmatch); } pixelArray[i] = bestmatch; } hashtable_free(h2); return 1; } static int map_image_pixels_from_quantized_pixels( Pixel *pixelData, uint32_t nPixels, Pixel *paletteData, uint32_t nPaletteEntries, uint32_t *avgDist, uint32_t **avgDistSortKey, uint32_t *pixelArray, uint32_t *avg[3], uint32_t *count) { uint32_t *aD, **aDSK; uint32_t idx; uint32_t i, j; uint32_t bestdist, bestmatch, dist; uint32_t initialdist; HashTable *h2; int changes = 0; h2 = hashtable_new(unshifted_pixel_hash, unshifted_pixel_cmp); for (i = 0; i < nPixels; i++) { if (!hashtable_lookup(h2, pixelData[i], &bestmatch)) { bestmatch = pixelArray[i]; initialdist = _DISTSQR(paletteData + bestmatch, pixelData + i); bestdist = initialdist; initialdist <<= 2; aDSK = avgDistSortKey + bestmatch * nPaletteEntries; aD = avgDist + bestmatch * nPaletteEntries; for (j = 0; j < nPaletteEntries; j++) { idx = aDSK[j] - aD; if (*(aDSK[j]) <= initialdist) { dist = _DISTSQR(paletteData + idx, pixelData + i); if (dist < bestdist) { bestdist = dist; bestmatch = idx; } } else { break; } } hashtable_insert(h2, pixelData[i], bestmatch); } if (pixelArray[i] != bestmatch) { changes++; avg[0][bestmatch] += pixelData[i].c.r; avg[1][bestmatch] += pixelData[i].c.g; avg[2][bestmatch] += pixelData[i].c.b; avg[0][pixelArray[i]] -= pixelData[i].c.r; avg[1][pixelArray[i]] -= pixelData[i].c.g; avg[2][pixelArray[i]] -= pixelData[i].c.b; count[bestmatch]++; count[pixelArray[i]]--; pixelArray[i] = bestmatch; } } hashtable_free(h2); return changes; } static int map_image_pixels_from_median_box( Pixel *pixelData, uint32_t nPixels, Pixel *paletteData, uint32_t nPaletteEntries, HashTable *medianBoxHash, uint32_t *avgDist, uint32_t **avgDistSortKey, uint32_t *pixelArray) { uint32_t *aD, **aDSK; uint32_t idx; uint32_t i, j; uint32_t bestdist, bestmatch, dist; uint32_t initialdist; HashTable *h2; uint32_t pixelVal; h2 = hashtable_new(unshifted_pixel_hash, unshifted_pixel_cmp); for (i = 0; i < nPixels; i++) { if (hashtable_lookup(h2, pixelData[i], &pixelVal)) { pixelArray[i] = pixelVal; continue; } if (!hashtable_lookup(medianBoxHash, pixelData[i], &pixelVal)) { #ifndef NO_OUTPUT printf("pixel lookup failed\n"); #endif return 0; } initialdist = _DISTSQR(paletteData + pixelVal, pixelData + i); bestdist = initialdist; bestmatch = pixelVal; initialdist <<= 2; aDSK = avgDistSortKey + pixelVal * nPaletteEntries; aD = avgDist + pixelVal * nPaletteEntries; for (j = 0; j < nPaletteEntries; j++) { idx = aDSK[j] - aD; if (*(aDSK[j]) <= initialdist) { dist = _DISTSQR(paletteData + idx, pixelData + i); if (dist < bestdist) { bestdist = dist; bestmatch = idx; } } else { break; } } pixelArray[i] = bestmatch; hashtable_insert(h2, pixelData[i], bestmatch); } hashtable_free(h2); return 1; } static int compute_palette_from_median_cut( Pixel *pixelData, uint32_t nPixels, HashTable *medianBoxHash, Pixel **palette, uint32_t nPaletteEntries) { uint32_t i; uint32_t paletteEntry; Pixel *p; uint32_t *avg[3]; uint32_t *count; *palette = NULL; /* malloc check ok, using calloc */ if (!(count = calloc(nPaletteEntries, sizeof(uint32_t)))) { return 0; } for (i = 0; i < 3; i++) { avg[i] = NULL; } for (i = 0; i < 3; i++) { /* malloc check ok, using calloc */ if (!(avg[i] = calloc(nPaletteEntries, sizeof(uint32_t)))) { for (i = 0; i < 3; i++) { if (avg[i]) { free(avg[i]); } } free(count); return 0; } } for (i = 0; i < nPixels; i++) { #ifdef TEST_SPLIT_INTEGRITY if (!(i % 100)) { printf("%05d\r", i); fflush(stdout); } if (checkContained(root, pixelData + i) > 1) { printf("pixel in two boxes\n"); for (i = 0; i < 3; i++) { free(avg[i]); } free(count); return 0; } #endif if (!hashtable_lookup(medianBoxHash, pixelData[i], &paletteEntry)) { #ifndef NO_OUTPUT printf("pixel lookup failed\n"); #endif for (i = 0; i < 3; i++) { free(avg[i]); } free(count); return 0; } if (paletteEntry >= nPaletteEntries) { #ifndef NO_OUTPUT printf( "panic - paletteEntry>=nPaletteEntries (%d>=%d)\n", (int)paletteEntry, (int)nPaletteEntries); #endif for (i = 0; i < 3; i++) { free(avg[i]); } free(count); return 0; } avg[0][paletteEntry] += pixelData[i].c.r; avg[1][paletteEntry] += pixelData[i].c.g; avg[2][paletteEntry] += pixelData[i].c.b; count[paletteEntry]++; } /* malloc check ok, using calloc */ p = calloc(nPaletteEntries, sizeof(Pixel)); if (!p) { for (i = 0; i < 3; i++) { free(avg[i]); } free(count); return 0; } for (i = 0; i < nPaletteEntries; i++) { p[i].c.r = (int)(.5 + (double)avg[0][i] / (double)count[i]); p[i].c.g = (int)(.5 + (double)avg[1][i] / (double)count[i]); p[i].c.b = (int)(.5 + (double)avg[2][i] / (double)count[i]); } *palette = p; for (i = 0; i < 3; i++) { free(avg[i]); } free(count); return 1; } static int recompute_palette_from_averages( Pixel *palette, uint32_t nPaletteEntries, uint32_t *avg[3], uint32_t *count) { uint32_t i; for (i = 0; i < nPaletteEntries; i++) { palette[i].c.r = (int)(.5 + (double)avg[0][i] / (double)count[i]); palette[i].c.g = (int)(.5 + (double)avg[1][i] / (double)count[i]); palette[i].c.b = (int)(.5 + (double)avg[2][i] / (double)count[i]); } return 1; } static int compute_palette_from_quantized_pixels( Pixel *pixelData, uint32_t nPixels, Pixel *palette, uint32_t nPaletteEntries, uint32_t *avg[3], uint32_t *count, uint32_t *qp) { uint32_t i; memset(count, 0, sizeof(uint32_t) * nPaletteEntries); for (i = 0; i < 3; i++) { memset(avg[i], 0, sizeof(uint32_t) * nPaletteEntries); } for (i = 0; i < nPixels; i++) { if (qp[i] >= nPaletteEntries) { #ifndef NO_OUTPUT printf("scream\n"); #endif return 0; } avg[0][qp[i]] += pixelData[i].c.r; avg[1][qp[i]] += pixelData[i].c.g; avg[2][qp[i]] += pixelData[i].c.b; count[qp[i]]++; } for (i = 0; i < nPaletteEntries; i++) { palette[i].c.r = (int)(.5 + (double)avg[0][i] / (double)count[i]); palette[i].c.g = (int)(.5 + (double)avg[1][i] / (double)count[i]); palette[i].c.b = (int)(.5 + (double)avg[2][i] / (double)count[i]); } return 1; } static int k_means( Pixel *pixelData, uint32_t nPixels, Pixel *paletteData, uint32_t nPaletteEntries, uint32_t *qp, int threshold) { uint32_t *avg[3]; uint32_t *count; uint32_t i; uint32_t *avgDist; uint32_t **avgDistSortKey; int changes; int built = 0; if (nPaletteEntries > UINT32_MAX / (sizeof(uint32_t))) { return 0; } /* malloc check ok, using calloc */ if (!(count = calloc(nPaletteEntries, sizeof(uint32_t)))) { return 0; } for (i = 0; i < 3; i++) { avg[i] = NULL; } for (i = 0; i < 3; i++) { /* malloc check ok, using calloc */ if (!(avg[i] = calloc(nPaletteEntries, sizeof(uint32_t)))) { goto error_1; } } /* this is enough of a check, since the multiplication n*size is done above */ if (nPaletteEntries > UINT32_MAX / nPaletteEntries) { goto error_1; } /* malloc check ok, using calloc, checking n*n above */ avgDist = calloc(nPaletteEntries * nPaletteEntries, sizeof(uint32_t)); if (!avgDist) { goto error_1; } /* malloc check ok, using calloc, checking n*n above */ avgDistSortKey = calloc(nPaletteEntries * nPaletteEntries, sizeof(uint32_t *)); if (!avgDistSortKey) { goto error_2; } #ifndef NO_OUTPUT printf("["); fflush(stdout); #endif while (1) { if (!built) { compute_palette_from_quantized_pixels( pixelData, nPixels, paletteData, nPaletteEntries, avg, count, qp); build_distance_tables( avgDist, avgDistSortKey, paletteData, nPaletteEntries); built = 1; } else { recompute_palette_from_averages(paletteData, nPaletteEntries, avg, count); resort_distance_tables( avgDist, avgDistSortKey, paletteData, nPaletteEntries); } changes = map_image_pixels_from_quantized_pixels( pixelData, nPixels, paletteData, nPaletteEntries, avgDist, avgDistSortKey, qp, avg, count); if (changes < 0) { goto error_3; } #ifndef NO_OUTPUT printf(".(%d)", changes); fflush(stdout); #endif if (changes <= threshold) { break; } } #ifndef NO_OUTPUT printf("]\n"); #endif if (avgDistSortKey) { free(avgDistSortKey); } if (avgDist) { free(avgDist); } for (i = 0; i < 3; i++) { if (avg[i]) { free(avg[i]); } } if (count) { free(count); } return 1; error_3: if (avgDistSortKey) { free(avgDistSortKey); } error_2: if (avgDist) { free(avgDist); } error_1: for (i = 0; i < 3; i++) { if (avg[i]) { free(avg[i]); } } if (count) { free(count); } return 0; } int quantize( Pixel *pixelData, uint32_t nPixels, uint32_t nQuantPixels, Pixel **palette, uint32_t *paletteLength, uint32_t **quantizedPixels, int kmeans) { PixelList *hl[3]; HashTable *h; BoxNode *root; uint32_t i; uint32_t *qp; uint32_t nPaletteEntries; uint32_t *avgDist; uint32_t **avgDistSortKey; Pixel *p; #ifndef NO_OUTPUT uint32_t timer, timer2; #endif #ifndef NO_OUTPUT timer2 = clock(); printf("create hash table..."); fflush(stdout); timer = clock(); #endif h = create_pixel_hash(pixelData, nPixels); #ifndef NO_OUTPUT printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC); #endif if (!h) { goto error_0; } #ifndef NO_OUTPUT printf("create lists from hash table..."); fflush(stdout); timer = clock(); #endif hl[0] = hl[1] = hl[2] = NULL; hashtable_foreach(h, hash_to_list, hl); #ifndef NO_OUTPUT printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC); #endif if (!hl[0]) { goto error_1; } #ifndef NO_OUTPUT printf("mergesort lists..."); fflush(stdout); timer = clock(); #endif for (i = 0; i < 3; i++) { hl[i] = mergesort_pixels(hl[i], i); } #ifdef TEST_MERGESORT if (!test_sorted(hl)) { printf("bug in mergesort\n"); goto error_1; } #endif #ifndef NO_OUTPUT printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC); #endif #ifndef NO_OUTPUT printf("median cut..."); fflush(stdout); timer = clock(); #endif root = median_cut(hl, nPixels, nQuantPixels); #ifndef NO_OUTPUT printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC); #endif if (!root) { goto error_1; } nPaletteEntries = 0; #ifndef NO_OUTPUT printf("median cut tree to hash table..."); fflush(stdout); timer = clock(); #endif annotate_hash_table(root, h, &nPaletteEntries); #ifndef NO_OUTPUT printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC); #endif #ifndef NO_OUTPUT printf("compute palette...\n"); fflush(stdout); timer = clock(); #endif if (!compute_palette_from_median_cut(pixelData, nPixels, h, &p, nPaletteEntries)) { goto error_3; } #ifndef NO_OUTPUT printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC); #endif free_box_tree(root); root = NULL; /* malloc check ok, using calloc for overflow */ qp = calloc(nPixels, sizeof(uint32_t)); if (!qp) { goto error_4; } if (nPaletteEntries > UINT32_MAX / nPaletteEntries) { goto error_5; } /* malloc check ok, using calloc for overflow, check of n*n above */ avgDist = calloc(nPaletteEntries * nPaletteEntries, sizeof(uint32_t)); if (!avgDist) { goto error_5; } /* malloc check ok, using calloc for overflow, check of n*n above */ avgDistSortKey = calloc(nPaletteEntries * nPaletteEntries, sizeof(uint32_t *)); if (!avgDistSortKey) { goto error_6; } if (!build_distance_tables(avgDist, avgDistSortKey, p, nPaletteEntries)) { goto error_7; } if (!map_image_pixels_from_median_box( pixelData, nPixels, p, nPaletteEntries, h, avgDist, avgDistSortKey, qp)) { goto error_7; } #ifdef TEST_NEAREST_NEIGHBOUR #include { uint32_t bestmatch, bestdist, dist; HashTable *h2; printf("nearest neighbour search (full search)..."); fflush(stdout); timer = clock(); h2 = hashtable_new(unshifted_pixel_hash, unshifted_pixel_cmp); for (i = 0; i < nPixels; i++) { if (hashtable_lookup(h2, pixelData[i], &paletteEntry)) { bestmatch = paletteEntry; } else { bestmatch = 0; bestdist = _SQR(pixelData[i].c.r - p[0].c.r) + _SQR(pixelData[i].c.g - p[0].c.g) + _SQR(pixelData[i].c.b - p[0].c.b); for (j = 1; j < nPaletteEntries; j++) { dist = _SQR(pixelData[i].c.r - p[j].c.r) + _SQR(pixelData[i].c.g - p[j].c.g) + _SQR(pixelData[i].c.b - p[j].c.b); if (dist == bestdist && j == qp[i]) { bestmatch = j; } if (dist < bestdist) { bestdist = dist; bestmatch = j; } } hashtable_insert(h2, pixelData[i], bestmatch); } if (qp[i] != bestmatch) { printf ("discrepancy in matching algorithms pixel %d [%d %d] %f %f\n", i,qp[i],bestmatch, sqrt((double)(_SQR(pixelData[i].c.r-p[qp[i]].c.r)+ _SQR(pixelData[i].c.g-p[qp[i]].c.g)+ _SQR(pixelData[i].c.b-p[qp[i]].c.b))), sqrt((double)(_SQR(pixelData[i].c.r-p[bestmatch].c.r)+ _SQR(pixelData[i].c.g-p[bestmatch].c.g)+ _SQR(pixelData[i].c.b-p[bestmatch].c.b))) ); } } hashtable_free(h2); } #endif #ifndef NO_OUTPUT printf("k means...\n"); fflush(stdout); timer = clock(); #endif if (kmeans) { k_means(pixelData, nPixels, p, nPaletteEntries, qp, kmeans - 1); } #ifndef NO_OUTPUT printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC); #endif *quantizedPixels = qp; *palette = p; *paletteLength = nPaletteEntries; #ifndef NO_OUTPUT printf("cleanup..."); fflush(stdout); timer = clock(); #endif if (avgDist) { free(avgDist); } if (avgDistSortKey) { free(avgDistSortKey); } destroy_pixel_hash(h); #ifndef NO_OUTPUT printf("done (%f)\n", (clock() - timer) / (double)CLOCKS_PER_SEC); printf("-----\ntotal time %f\n", (clock() - timer2) / (double)CLOCKS_PER_SEC); #endif return 1; error_7: if (avgDistSortKey) { free(avgDistSortKey); } error_6: if (avgDist) { free(avgDist); } error_5: if (qp) { free(qp); } error_4: if (p) { free(p); } error_3: if (root) { free_box_tree(root); } error_1: destroy_pixel_hash(h); error_0: *quantizedPixels = NULL; *paletteLength = 0; *palette = NULL; return 0; } typedef struct { Pixel new; Pixel furthest; uint32_t furthestDistance; int secondPixel; } DistanceData; static void compute_distances(const HashTable *h, const Pixel pixel, uint32_t *dist, void *u) { DistanceData *data = (DistanceData *)u; uint32_t oldDist = *dist; uint32_t newDist; newDist = _DISTSQR(&(data->new), &pixel); if (data->secondPixel || newDist < oldDist) { *dist = newDist; oldDist = newDist; } if (oldDist > data->furthestDistance) { data->furthestDistance = oldDist; data->furthest.v = pixel.v; } } int quantize2( Pixel *pixelData, uint32_t nPixels, uint32_t nQuantPixels, Pixel **palette, uint32_t *paletteLength, uint32_t **quantizedPixels, int kmeans) { HashTable *h; uint32_t i; uint32_t mean[3]; Pixel *p; DistanceData data; uint32_t *qp; uint32_t *avgDist; uint32_t **avgDistSortKey; /* malloc check ok, using calloc */ p = calloc(nQuantPixels, sizeof(Pixel)); if (!p) { return 0; } mean[0] = mean[1] = mean[2] = 0; h = hashtable_new(unshifted_pixel_hash, unshifted_pixel_cmp); for (i = 0; i < nPixels; i++) { hashtable_insert(h, pixelData[i], 0xffffffff); mean[0] += pixelData[i].c.r; mean[1] += pixelData[i].c.g; mean[2] += pixelData[i].c.b; } data.new.c.r = (int)(.5 + (double)mean[0] / (double)nPixels); data.new.c.g = (int)(.5 + (double)mean[1] / (double)nPixels); data.new.c.b = (int)(.5 + (double)mean[2] / (double)nPixels); for (i = 0; i < nQuantPixels; i++) { data.furthestDistance = 0; data.secondPixel = (i == 1) ? 1 : 0; hashtable_foreach_update(h, compute_distances, &data); p[i].v = data.furthest.v; data.new.v = data.furthest.v; } hashtable_free(h); /* malloc check ok, using calloc */ qp = calloc(nPixels, sizeof(uint32_t)); if (!qp) { goto error_1; } if (nQuantPixels > UINT32_MAX / nQuantPixels) { goto error_2; } /* malloc check ok, using calloc for overflow, check of n*n above */ avgDist = calloc(nQuantPixels * nQuantPixels, sizeof(uint32_t)); if (!avgDist) { goto error_2; } /* malloc check ok, using calloc for overflow, check of n*n above */ avgDistSortKey = calloc(nQuantPixels * nQuantPixels, sizeof(uint32_t *)); if (!avgDistSortKey) { goto error_3; } if (!build_distance_tables(avgDist, avgDistSortKey, p, nQuantPixels)) { goto error_4; } if (!map_image_pixels( pixelData, nPixels, p, nQuantPixels, avgDist, avgDistSortKey, qp)) { goto error_4; } if (kmeans) { k_means(pixelData, nPixels, p, nQuantPixels, qp, kmeans - 1); } *paletteLength = nQuantPixels; *palette = p; *quantizedPixels = qp; free(avgDistSortKey); free(avgDist); return 1; error_4: free(avgDistSortKey); error_3: free(avgDist); error_2: free(qp); error_1: free(p); return 0; } Imaging ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { int i, j; int x, y, v; UINT8 *pp; Pixel *p; Pixel *palette; uint32_t paletteLength; int result; uint32_t *newData; Imaging imOut; int withAlpha = 0; ImagingSectionCookie cookie; if (!im) { return ImagingError_ModeError(); } if (colors < 1 || colors > 256) { /* FIXME: for colors > 256, consider returning an RGB image instead (see @PIL205) */ return (Imaging)ImagingError_ValueError("bad number of colors"); } if (strcmp(im->mode, "L") != 0 && strcmp(im->mode, "P") != 0 && strcmp(im->mode, "RGB") != 0 && strcmp(im->mode, "RGBA") != 0) { return ImagingError_ModeError(); } /* only octree and imagequant supports RGBA */ if (!strcmp(im->mode, "RGBA") && mode != 2 && mode != 3) { return ImagingError_ModeError(); } if (im->xsize > INT_MAX / im->ysize) { return ImagingError_MemoryError(); } /* malloc check ok, using calloc for final overflow, x*y above */ p = calloc(im->xsize * im->ysize, sizeof(Pixel)); if (!p) { return ImagingError_MemoryError(); } /* collect statistics */ /* FIXME: maybe we could load the hash tables directly from the image data? */ if (!strcmp(im->mode, "L")) { /* greyscale */ /* FIXME: converting a "L" image to "P" with 256 colors should be done by a simple copy... */ for (i = y = 0; y < im->ysize; y++) { for (x = 0; x < im->xsize; x++, i++) { p[i].c.r = p[i].c.g = p[i].c.b = im->image8[y][x]; p[i].c.a = 255; } } } else if (!strcmp(im->mode, "P")) { /* palette */ pp = im->palette->palette; for (i = y = 0; y < im->ysize; y++) { for (x = 0; x < im->xsize; x++, i++) { v = im->image8[y][x]; p[i].c.r = pp[v * 4 + 0]; p[i].c.g = pp[v * 4 + 1]; p[i].c.b = pp[v * 4 + 2]; p[i].c.a = pp[v * 4 + 3]; } } } else if (!strcmp(im->mode, "RGB") || !strcmp(im->mode, "RGBA")) { /* true colour */ withAlpha = !strcmp(im->mode, "RGBA"); int transparency = 0; unsigned char r, g, b; for (i = y = 0; y < im->ysize; y++) { for (x = 0; x < im->xsize; x++, i++) { p[i].v = im->image32[y][x]; if (withAlpha && p[i].c.a == 0) { if (transparency == 0) { transparency = 1; r = p[i].c.r; g = p[i].c.g; b = p[i].c.b; } else { /* Set all subsequent transparent pixels to the same colour as the first */ p[i].c.r = r; p[i].c.g = g; p[i].c.b = b; } } } } } else { free(p); return (Imaging)ImagingError_ValueError("internal error"); } ImagingSectionEnter(&cookie); switch (mode) { case 0: /* median cut */ result = quantize( p, im->xsize * im->ysize, colors, &palette, &paletteLength, &newData, kmeans); break; case 1: /* maximum coverage */ result = quantize2( p, im->xsize * im->ysize, colors, &palette, &paletteLength, &newData, kmeans); break; case 2: result = quantize_octree( p, im->xsize * im->ysize, colors, &palette, &paletteLength, &newData, withAlpha); break; case 3: #ifdef HAVE_LIBIMAGEQUANT result = quantize_pngquant( p, im->xsize, im->ysize, colors, &palette, &paletteLength, &newData, withAlpha); #else result = -1; #endif break; default: result = 0; break; } free(p); ImagingSectionLeave(&cookie); if (result > 0) { imOut = ImagingNewDirty("P", im->xsize, im->ysize); ImagingSectionEnter(&cookie); for (i = y = 0; y < im->ysize; y++) { for (x = 0; x < im->xsize; x++) { imOut->image8[y][x] = (unsigned char)newData[i++]; } } free(newData); pp = imOut->palette->palette; for (i = j = 0; i < (int)paletteLength; i++) { *pp++ = palette[i].c.r; *pp++ = palette[i].c.g; *pp++ = palette[i].c.b; if (withAlpha) { *pp++ = palette[i].c.a; } else { *pp++ = 255; } } for (; i < 256; i++) { *pp++ = 0; *pp++ = 0; *pp++ = 0; *pp++ = 255; } if (withAlpha) { strcpy(imOut->palette->mode, "RGBA"); } free(palette); ImagingSectionLeave(&cookie); return imOut; } else { if (result == -1) { return (Imaging)ImagingError_ValueError( "dependency required by this method was not " "enabled at compile time"); } return (Imaging)ImagingError_ValueError("quantization error"); } }