[comp.graphics] The 24-bit question.

wjc@XN.LL.MIT.EDU (William J. Chiarchiaro) (03/11/88)

Does anyone know of an algorithm for doing the following:

	Take a digitized, color image composed of 24-bit pixels
     (8 bits red, 8 bits green, 8 bits blue), and convert it to
     an 8-bit-per-pixel color image along with a suitable
     256-entry colormap (each entry has 8 bits of red, 8 of green,
     and 8 of blue).

Thanks,
Bill
(reachable on Internet at wjc@xn.ll.mit.edu)

jec@iuvax.cs.indiana.edu (James E. Conley) (03/11/88)

	I'd also be interested in seeing this algorithm.  What we've done is 
fairly crude (striped off the high bits of each color)-- it works, but isn't
what we are after.

    III			Usenet:     iuvax!jec
UUU  I  UUU		ARPANet:    jec@iuvax.cs.indiana.edu
 U   I   U		Phone:      (812) 335-7729
 U   I   U		U.S. Mail:  Indiana University
 U   I   U			    Dept. of Computer Science
  UUUIUUU			    021-E Lindley Hall
     I				    Bloomington, IN. 47405
    III (Home of the Indiana Hoosiers-- 1987 NCAA Basketball Champions)

fnf@fishpond.UUCP (Fred Fish) (03/11/88)

In article <927@xn.LL.MIT.EDU> wjc@XN.LL.MIT.EDU (William J. Chiarchiaro) writes:
>Does anyone know of an algorithm for doing the following:
>
>	Take a digitized, color image composed of 24-bit pixels
>     (8 bits red, 8 bits green, 8 bits blue), and convert it to
>     an 8-bit-per-pixel color image along with a suitable
>     256-entry colormap (each entry has 8 bits of red, 8 of green,
>     and 8 of blue).

Many of the 256-color images for the Mac-II currently making the rounds
were generated precisely this way.  The were digitized on a Commodore
Amiga using a Digiview digitizer, and dumped into a file in 24-bit
color (1 byte each of RGB), transfered to a Mac-II, and then converted
to 256 color images using a program written by Steve Blackstock called
"quantize".  The resulting images were written in a format called GIF,
defined and promoted by Compuserve, and displayable by another program
of Steve's called "giffer".  I believe that both the quantizer and giffer
programs are available on Compuserve, and Steve can also be contacted
there.

-Fred



-- 
# Fred Fish    hao!noao!mcdsun!fishpond!fnf     (602) 921-1113
# Ye Olde Fishpond, 1346 West 10th Place, Tempe, AZ 85281  USA

kyriazis@pawl17.pawl.rpi.edu (George Kyriazis) (03/11/88)

In article <927@xn.LL.MIT.EDU> wjc@XN.LL.MIT.EDU (William J. Chiarchiaro) writes:
>Does anyone know of an algorithm for doing the following:
>
>	Take a digitized, color image composed of 24-bit pixels
>     (8 bits red, 8 bits green, 8 bits blue), and convert it to
>     an 8-bit-per-pixel color image along with a suitable
>     256-entry colormap (each entry has 8 bits of red, 8 of green,
>     and 8 of blue).
>
>Thanks,
>Bill
>(reachable on Internet at wjc@xn.ll.mit.edu)

	I have written two different programs that do the thing that you ask
for.  They are written for color SUNs that they have a colormap of 256 entries.
The first method is a bit stupid.  You define, for example, 3 bits for red,
3 bits for green and 2 bits for blue (unfortunately 256 is 2^8 and not 2^9),
take the 3 (or 2) most significant bits of the primary colors, create a byte
out of it and index it to the colormap.  In other words, the colormap is fixed.
The other method is a bit smarter, but it depends on the size and the number
of original colors of the image.  You start with an empty colormap, and
check each incoming pixel if its color is in the colormap. If it is, use that
index as its color, else add it to the end of the colormap.  Unfortunately
for pictures bigger than 128*128 the colormap runs out pretty quickly, so
you can try eliminating the 1,2 or how many you want least significant bits
of the input data, until everything fits in the colormap.  Of course you 
can elimimate a different number of bits for different primary colors.
	Hope that I helped you..


*******************************************************
*George C. Kyriazis                                   *    Gravity is a myth
*userfe0e@mts.rpi.edu or userfe0e@rpitsmts.bitnet     *        \    /
*Electrical and Computer Systems Engineering Dept.    *         \  /
*Rensselear Polytechnic Institute, Troy, NY 12180     *          ||
*******************************************************      Earth sucks.

dwight@akelei.UUCP (Dwight Melcher) (03/12/88)

I've implemented slight variations on the algorithm presented in 

	Heckbert, "Color Image Quantization for Frame Buffer Display",
	Computer Graphics, V16 #3 (July 1982).

It gives quite good results, was fairly easy to implement, and the
performance is reasonable on SUN3 machines.  If you're interested in
having the code, and not too picky about dealing with grungy stuff
(this was a Saturday afternoon quick-hack special), send me a note and I
can mail you a copy of the C source.
-- 
Dwight Melcher
Columbine Data Systems, Inc.
3300 Mitchell Lane Suite 380
Boulder, CO 80301	 	{ hao!boulder , sun!sunpeaks }!akelei!dwight

kurtk@tekcae.TEK.COM (Kurt Krueger) (03/12/88)

It seems to me that the real question is how to represent a 24bit deep image
on a device that is only 8 bits deep without throwing away any more information
than necessary.  The solutions I've seen so far throw away more info than
needed, and indeed, can run into some severe problems if you run out of color
map before you run out of colors.

Some solutions that work very well are to divide up the available color map
as well as possible.  Something to remember is that the human eye has poor
spacial resolution with blue light, but very good color resolution (you can't
see much detail in blue areas but you can sure tell the difference between
shades of blue).  You then either dither to the available colors or use an
error difusion method.  With 256 shades of grey I've been able to error diffuse
images and not be able to see any color steps.  Having ~1300 pixels across, of
course, helps.

A warning on dither techniques:  If your output display is not "gamma corrected"
you will have to include this correction in your rendering code.  A device that
is not gamma corrected will not have equally perceived brightness changes
across the available range (i.e. you ask for 2x change and you actually get
3x or 1.5x).

jbm@eos.UUCP (Jeffrey Mulligan) (03/15/88)

From article <1528@tekcae.TEK.COM>, by kurtk@tekcae.TEK.COM (Kurt Krueger):
> A warning on dither techniques:  If your output display is not "gamma corrected"
> you will have to include this correction in your rendering code.  A device that
> is not gamma corrected will not have equally perceived brightness changes
> across the available range (i.e. you ask for 2x change and you actually get
> 3x or 1.5x).

It can get worse that; since very few displays themselves are linear,
gamma correction must be done in software to compensate for the
physical nonlinearity.  This is fine.

A separate problem is the failure of spatial independence.
When this rears its ugly head, it can cause
a 50% density of black and white dots to be darker that
the (gamma corrected) mean of the white and black levels.
It should be emphasized that without gamma correction, the dithered
pattern would be brighter than the mid-level setting, since
the gamma nonlinearity is positively accelerating.

My theory about this is that it reflects inadequate video bandwidth;
i.e. the monitor can be modeled as a low pass filter followed by
a nonlinearity (the gamma function).  A simple way to measure this is
to put up alternating black and white lines, either horizontal or vertical.
The vertical line pattern can have a substantially lower mean luminance.

If anyone is interested, Lee Stone and myself are working on a manuscript
which contains a section on this topic.

ksbooth@watcgl.waterloo.edu (Kelly Booth) (03/16/88)

In article <927@xn.LL.MIT.EDU> wjc@XN.LL.MIT.EDU (William J. Chiarchiaro) writes:
>
>Does anyone know of an algorithm for doing the following:
>
>	Take a digitized, color image composed of 24-bit pixels
>     (8 bits red, 8 bits green, 8 bits blue), and convert it to
>     an 8-bit-per-pixel color image along with a suitable
>     256-entry colormap (each entry has 8 bits of red, 8 of green,
>     and 8 of blue).

Paul Heckbert had a paper on this in SIGGRAPH '82.  No, this is not a joke.
He really did have a (serious) paper.  This is another of the semi-monthly
items that appear in comp.graphics.  It's been beaten to death.  Read the
original paper then check the literature for subsequent work.

P.S.  The work was done while Paul was an undergrad at M.I.T., I think as
an honors thesis.  Your school should have a copy :).

anc@camcon.uucp (Adrian Cockcroft) (03/23/88)

>Does anyone know of an algorithm for doing the following:
>
>	Take a digitized, color image composed of 24-bit pixels
>     (8 bits red, 8 bits green, 8 bits blue), and convert it to
>     an 8-bit-per-pixel color image along with a suitable
>     256-entry colormap (each entry has 8 bits of red, 8 of green,
>     and 8 of blue).
>
>Thanks,
>Bill
>(reachable on Internet at wjc@xn.ll.mit.edu)

I did this as well. We have a CRS 512 by 512 frame grabber and I grabbed
a picture of a Ferrari 308GTO via red, green, blue filters and wanted to
see a red ferrari. The camera was B&W with auto gain control (yuk) so
my algorithm let me balance the red, green, blue.

It is a better algorithm than taking the top few bits but not the best.
It takes about 15 minutes to run on a SUN 3/160.

First histogram the rgb combinations.
Second take the top 256 entries in the histogram and remap all other
 entries to the nearest one in the top. I use a primitive notion of
 nearest, the best algorithms are more sophisticated.
Remap all the pixels in the picture using the top 256 entries and write
out the combined picture as a lookup table and an 8 bit deep picture.

Since we need to sort the histogram at the end it makes sense to sort
it a few times while histograming so that we don't have to search so far.

Its only ~200 lines so here it is. A few mods for SUN rasterfiles will be
needed. Note that this was hacked together for my own use and anyone
can do anything with it.

#!/bin/sh
# Archived: Tue Mar 22 18:10:20 GMT 1988
#
# Contents:
#  image.c
#
echo x - image.c
sed 's/^X//' >image.c <<'*-*-END-of-image.c-*-*'
X/* combine r,g,b and produce lut table 28/1/87 anc */
X
X#include "stdio.h"
X
XFILE *fred,*fgreen,*fblue;
XFILE *fout,*flut;
X
Xstruct clr
X        {
X        unsigned red:8;
X        unsigned green:8;
X        unsigned blue:8;
X        };
X
Xstruct clr histval[512*512]; /* could be up to 512*512 different colours */
Xint histcnt[512*512];
Xint histsize = 0;
X
X#define abs(x) ((x)>=0?(x):-(x))
X
Xmain(argc,argv)
X        int     argc;
X        char    *argv[];
X        {
X        char    oname[20];
X        char    lname[20];
X        int     i,n;
X        struct  clr col;
X        short   rmul,gmul,bmul;
X        short   notfound;
X        if (argc < 4)
X                {
X                printf("image red*256 green*256 blue*256\n");
X                exit(0);
X                }
X        rmul = atoi(argv[1]);
X        gmul = atoi(argv[2]);
X        bmul = atoi(argv[3]);
X        sprintf(oname,"f%d%d%d.I",rmul,gmul,bmul);
X        sprintf(lname,"f%d%d%d.OUT",rmul,gmul,bmul);
X        printf("Using red=%d/256 green=%d/256 blue=%d/256\n",rmul,gmul,bmul);
X        fred = fopen("fred.I","r");
X        fgreen = fopen("fgreen.I","r");
X        fblue = fopen("fblue.I","r");
X        fout = fopen(oname,"w");
X        flut = fopen(lname,"w");
X        if (fred==0 || fgreen==0 || fblue==0)
X                {
X                printf("cant open input files\n");
X                exit(0);
X                }
X        /* histogram the input files */
X        for(n=0;n < 512*512; ++n)
X                {
X                /* make weighted 24 bit colour */
X                col.red =   (fgetc(fred)*rmul)>>8;
X                col.green = (fgetc(fgreen)*gmul)>>8;
X                col.blue =  (fgetc(fblue)*bmul)>>8;
X                notfound = 1;
X#ifdef DEBUG
X                printf("r=%x g=%x b=%x col=%x",col.red,col.green,col.blue,col);
X#endif
X                for(i=0; i<histsize; ++i)        /* search existing colours */
X                        {
X                        if (col == histval[i])
X                                {
X                                ++histcnt[i];   /* existing colour */
X                                notfound = 0;
X                                break;
X                                }
X                        }
X                if (notfound)
X                        {
X#ifdef DEBUG
X                        printf(" notfound ");
X#endif
X                        histval[histsize] = col; /* new colour */
X                        histcnt[histsize++] = 1;
X                        }
X#ifdef DEBUG
X                printf(" histsize=%d\n",histsize);
X#endif
X                /* sort the histogram every now and again */
X                if ((n & 0x000007FF) == 0)   /* every 2048 pixels */
X#ifdef DEBUGS
X                        sort(1);
X#else
X                        sort(0);
X#endif
X                }
X        printf("histsize = %d\n",histsize);
X        sort(1);        /* print out the table */
X        remap();        /* collapse the lut to 256 entries */
X        printf("remapped:\n");
X        for(i=256;i<histsize;++i)
X                printf("val=%x lut=%d\n",histval[i],histcnt[i]);
X        /* write the output file */
X        rewind(fred);
X        rewind(fgreen);
X        rewind(fblue);  /* run through image again */
X        for(n=0;n < 512*512; ++n)
X                {
X                notfound = 1;
X                /* make weighted 24 bit colour */
X                col.red =   (fgetc(fred)*rmul)>>8;
X                col.green = (fgetc(fgreen)*gmul)>>8;
X                col.blue =  (fgetc(fblue)*bmul)>>8;
X                for(i=0; i<histsize; ++i)        /* search existing colours */
X                        {
X                        if (col == histval[i])
X                                {
X                                if (i<256)
X                                        fputc(i,fout);  /* true entry */
X                                else
X                                        fputc(histcnt[i],fout); /* remapped */
X                                notfound = 0;
X                                break;
X                                }
X                        }
X                if (notfound)
X                        {
X                        printf("panic: pixel %d not found\n",n);
X                        }
X                }
X
X        }
X
Xsort(print)
X        int print;
X/* bubble sort histogram to put most common at top */
X        {
X        register int     i=1;
X        register int     tmp;
X        struct   clr     ctmp;
X        while(i < histsize)
X                {
X                if (histcnt[i] > histcnt[i-1])
X                        {
X                        /* swap */
X                        tmp = histcnt[i];
X                        histcnt[i] = histcnt[i-1];
X                        histcnt[i-1] = tmp;
X                        ctmp = histval[i];
X                        histval[i] = histval[i-1];
X                        histval[i-1] = ctmp;
X                        if (i > 1)
X                                --i;
X                        }
X                else
X                        ++i;
X                }
X        if (print)
X                for(i=0; i<histsize; ++i)
X                        printf("%d count=%d val=%x\n",i,histcnt[i],histval[i]);
X        }
X
X#define MUL 4   /* brighten result by 4, for use with mults of 0-63 */
X
Xremap()
X/* collapse lut to 256 entries and write to file */
X        {
X        int i,n,diff,mindiff,minno;
X        for(n=256; n<histsize; ++n)
X                {
X                minno = 0;
X                mindiff = coldiff(histval[n],histval[0]);
X                for(i=1; i<256; ++i)
X                        {
X                        diff = coldiff(histval[n],histval[i]);
X                        if (diff < mindiff)
X                                {
X                                mindiff = diff;
X                                minno = i;
X                                }
X                        }
X                histcnt[n] = minno;     /* reuse cnt with nearest clut index */
X                }
X        /* clut now remapped - write it out */
X        for(i=0; i<256;++i)
X                {
X                putc(0,flut);   /* 16 bit values with top byte zero */
X                putc(histval[i].red*MUL,flut);       /* red */
X                }
X        for(i=0; i<256;++i)
X                {
X                putc(0,flut);
X                putc(histval[i].green*MUL,flut);        /* green */
X                }
X        for(i=0; i<256;++i)
X                {
X                putc(0,flut);
X                putc(histval[i].blue*MUL,flut);             /* blue */
X                }
X        }
X
Xint coldiff(c1,c2)
X        struct clr c1,c2;
X        {
X        return abs(c1.red - c2.red) +
X               abs(c1.green - c2.green) +
X               abs(c1.blue - c2.blue);
X        }
X
X
*-*-END-of-image.c-*-*
echo End of Archive
exit
-- 
  |   Adrian Cockcroft anc@camcon.uucp  ..!seismo!mcvax!ukc!camcon!anc
-[T]- Cambridge Consultants Ltd, Science Park, Cambridge CB4 4DW,
  |   England, UK                                        (0223) 358855
      (You are in a maze of twisty little C004's, all alike...)

michael@orcisi.UUCP (Michael Herman) (03/27/88)

An interesting way to histogram a raster image is to load it into a
frame buffer, quicksort it and then run-length encode it.  The RLE
output is your histogram.

It's also visually interesting to watch.