you can use google for other algorithms like mid slice, population, k-tool, etc.
I needed this too, but I was also pleased to look fast (I needed this to capture real-time video), so I managed to do something like this:
convert to 15 bit rgb
If you already have RGB, you can skip this step, but use shift / and to match the 5-bit channels. You can also use a 5: 6: 5 pattern
execute a histogram
15-bit rgb results in 32768 entries, so create 2 arrays
his[32768] - number of pixels (histogram)idx[32768] - index = color value
When counting colors, make sure the counters do not overflow when using low bit counts or high resolutions
reorder arrays so the zeros in his[] are at the end of the array
also count nonzero entries in his[] and call it hists
(index) sort hist[],idx[] , so hist[] sorted in descending order;
create an N-color palette
Take the color idx[i] ( i={ 0,1,2,3,...,hists-1 } ) and see if your palette has the same color. if you ignore this color (set it as the closest one found), otherwise add it to the palette. if you reach a stop of N colors
create color matching
So, take each color and find the closest color in the palette (this can be done partially in step 5). I call this table recolor[32][32][32]
repainting image
This is the source of C ++:
BYTE db,*p; AnsiString code; int e,b,bits,adr; int x0,x1,y0,y1,x,y,c; DWORD ix,cc,cm,i0,i,mask; union { DWORD dd; BYTE db[4]; } c0,c1; DWORD r,g,b; int a,aa,hists; DWORD his[32768]; DWORD idx[32768]; // 15bit histogram for (x=0;x<32768;x++) { his[x]=0; idx[x]=x; } for (y=0;y<ys;y++) for (x=0;x<xs;x++) { cc=pyx[y][x]; cc=((cc>>3)&0x1F)|((cc>>6)&0x3E0)|((cc>>9)&0x7C00); if (his[cc]<0xFFFFFFFF) his[cc]++; } // remove zeroes for (x=0,y=0;y<32768;y++) { his[x]=his[y]; idx[x]=idx[y]; if (his[x]) x++; } hists=x; // sort by hist for (i=1;i;) for (i=0,x=0,y=1;y<hists;x++,y++) if (his[x]<his[y]) { i=his[x]; his[x]=his[y]; his[y]=i; i=idx[x]; idx[x]=idx[y]; idx[y]=i; i=1; } // set lcolor color palete for (i0=0,x=0;x<hists;x++) // main colors { cc=idx[x]; b= cc &31; g=(cc>> 5)&31; r=(cc>>10)&31; c0.db[0]=b; c0.db[1]=g; c0.db[2]=r; c0.dd=(c0.dd<<3)&0x00F8F8F8; // skip if similar color already in lcolor[] for (a=0,i=0;i<i0;i++) { c1.dd=lcolor[i]; aa=int(BYTE(c1.db[0]))-int(BYTE(c0.db[0])); if (aa<=0) aa=-aa; a =aa; aa=int(BYTE(c1.db[1]))-int(BYTE(c0.db[1])); if (aa<=0) aa=-aa; a+=aa; aa=int(BYTE(c1.db[2]))-int(BYTE(c0.db[2])); if (aa<=0) aa=-aa; a+=aa; if (a<=16) { a=1; break; } a=0; // *** treshold *** } if (a) recolor[r][g][b]=i; else{ recolor[r][g][b]=i0; lcolor[i0]=c0.dd; i0++; if (i0>=DWORD(lcolors)) { x++; break; } } } // i0 = new color table size for (;x<hists;x++) // minor colors { cc=idx[x]; b= cc &31; g=(cc>> 5)&31; r=(cc>>10)&31; c0.db[0]=b; c0.db[1]=g; c0.db[2]=r; c0.dd=(c0.dd<<3)&0x00F8F8F8; // find closest color int dc=-1; DWORD ii=0; for (a=0,i=0;i<i0;i++) { c1.dd=lcolor[i]; aa=int(BYTE(c1.db[0]))-int(BYTE(c0.db[0])); if (aa<=0) aa=-aa; a =aa; aa=int(BYTE(c1.db[1]))-int(BYTE(c0.db[1])); if (aa<=0) aa=-aa; a+=aa; aa=int(BYTE(c1.db[2]))-int(BYTE(c0.db[2])); if (aa<=0) aa=-aa; a+=aa; if ((dc<0)||(dc>a)) { dc=a; ii=i; } } recolor[r][g][b]=ii; }
And the owner image class contains the following:
// image data Graphics::TBitmap *bmp,*bmp0,*bmp1; // actual and restore to 32bit frames,and 8bit input conversion frame int xs,ys; // resolution int *py; // interlace table DWORD **pyx,**pyx0; // ScanLine[] of bmp,bmp0 BYTE **pyx1; // colors (colors are computed from color_bits) DWORD gcolor[256]; //hdr DWORD lcolor[256]; //img BYTE recolor[32][32][32]; //encode reduce color table int scolors,scolor_bits; //hdr screen color depth int gcolors,gcolor_bits; //hdr global pallete int lcolors,lcolor_bits; //img/hdr local palette
pyx[],bmp contains the original 32-bit imagepyx1[],bmp1 is a temporary 8-bit image for encoding
So repainting is done:
// recolor to lcolors for (y=0;y<ys;y++) for (x=0;x<xs;x++) { int r,g,b; c0.dd=(pyx[y][x]>>3)&0x001F1F1F; b=c0.db[0]; g=c0.db[1]; r=c0.db[2]; i=recolor[r][g][b]; // pyx [y][x]=lcolor[i]; // 32 bit output (visual) pyx1[y][x]=i; // 8 bit output (encoding) }
Here are some sample output:
this is a comparison between VCL / GDI color reduction, my approach and the original image)

At the top is a color palette (the original image contains a palette from the middle image)
here is a real color photo:

and reduced to 256 colors:

This took ~ 185 ms to encode in GIF (including color reduction). I am very pleased with the result, but as you can see, the images do not match. Green grass clusters differ slightly after re-color (less area / intensity?)
[Note]
The code is not yet optimized, so there should be a way to make it faster. You can increase the encoding speed:
- reduction of the maximum word size for encoding
- using an index table for a dictionary or three structures to speed up the search
- can change the sorting of the histogram bubbles to speed up the sorting of algo (but this part of the code is far from critical)
- you can use one palette to encode the sequence (if the scene does not change too much)
- If you want even more speed, then create a static palette and use smoothing, and all this
Here's an example of an RT video recording (the source was 50 frames per second, so I reduce the resolution to match the speed):
