mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-11-14 05:36:48 +03:00
1694 lines
44 KiB
C
1694 lines
44 KiB
C
/*
|
|
* 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 <tjs@longford.cs.monash.edu.au>.
|
|
*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <memory.h>
|
|
#include <time.h>
|
|
|
|
#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) {
|
|
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 <math.h>
|
|
{
|
|
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 */
|
|
|
|
for (i = y = 0; y < im->ysize; y++)
|
|
for (x = 0; x < im->xsize; x++, i++)
|
|
p[i].v = im->image32[y][x];
|
|
|
|
} 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:
|
|
if (!strcmp(im->mode, "RGBA")) {
|
|
withAlpha = 1;
|
|
}
|
|
result = quantize_octree(
|
|
p,
|
|
im->xsize*im->ysize,
|
|
colors,
|
|
&palette,
|
|
&paletteLength,
|
|
&newData,
|
|
withAlpha
|
|
);
|
|
break;
|
|
case 3:
|
|
#ifdef HAVE_LIBIMAGEQUANT
|
|
if (!strcmp(im->mode, "RGBA")) {
|
|
withAlpha = 1;
|
|
}
|
|
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");
|
|
|
|
}
|
|
}
|