[comp.sys.amiga] RGB to HAM, dithering

hue@netcom.UUCP (Jonathan Hue) (10/12/89)

24-bit RGB to HAM is very straightforward.  My first cut at this got
pretty good results (see Fish disk #196).  I only came up with one more
improvement after that, which was to propagate errors in uvL rather than RGB.

First of all, you must realize that RGB is useless for measing color
differences, so you have to convert to uvL for measuring color differences.

1)  Start with 24-bit data.  For each pixel, figure out what is the
closest HAM pixel to that color (by Euclidean distance in uvL space).
Propagate the error using a serpentine Floyd-Steinberg dither.

2)  Now, compare each HAM pixel to the original 24-bit data and find
the 256 worst differences in uvL space.  Put these in a sorted array.
Next, take the 24-bit color which represents the target color where the
worst difference occurs, and put it in your color map (I usually add 8 then
shift right by 4).  Draw a little sphere around this color in uvL space, and
eliminate any colors in the sorted array which lie inside this sphere.  The
radius of this sphere are up to you, I made it a command line option for
experimentation.  Continue, working your way down the array until your color
map is full.  If you want to reserve some color map entries, do this before
you start filling the color map.

3)  Now, repeat step 1, except when a color map entry is closer in uvL
space than the HAM pixel, use it instead.

Dithering N-bit data to n-bit is also straightforward.  Here are a couple
functions which do 8-bit to 1-bit and 8-bit to 2-bit serpentine Floyd-Steinberg
dithering.  Given these, you should be able to easily extend it to 8-bit to
4-bit dithering.


-Jonathan

(If it's not obvious, you take your pixel data, your error data, add them
 together, clear the error data, and call the dither function for each row
 of pixels.  The result is packed into the outdata array)
------------------------------------------------------------------
/*
 * F-S dither 8-bit data to 1-bit
 */
void
FSDither81(register short *indata, register short *errorTerms,
	   register u_char *outdata, short row, short npixels)
{
    register u_char pattern;
    register short i, error;

    if (row & 1)			/* odd, go right to left */
    {
	indata += npixels;		/* point to last pixel */
	errorTerms += npixels;
	outdata += ((npixels + 7) >> 3) - 1;
	if ((npixels & 0x7) == 0)
	    pattern = 0x01;
	else
	    pattern = 1 << (8 - (npixels & 0x7));
	for (i = 0; i < npixels; i++, indata--, errorTerms--)
	{
	    if (*indata < 0x80)
	    	error = *indata;
	    else
	    {
		*outdata |= pattern;
		error = *indata - 0xff;
	    }
	    if (error)		/* propagate it */
	    {
		*(indata - 1) += (7 * error) / 16;  /* error can be neg. */
		*(errorTerms + 1) += (3 * error) / 16;
		*errorTerms += (5 * error) / 16;
		*(errorTerms - 1) += error / 16;
	    }	    
 	    if (pattern == 0x80)
	    {
	    	pattern = 0x01;	 /* reset pattern, point to next output pixel */
		outdata--;
	    }
	    else
	        pattern <<= 1;
	}
    }
    else			/* even, go left to right */
    {
	pattern = 0x80;
	indata++;
	errorTerms++;
	for (i = 0; i < npixels; i++, indata++, errorTerms++)
	{
	    if (*indata < 0x80)
	        error = *indata;
	    else
	    {
		*outdata |= pattern;
		error = *indata - 0xff;
	    }
	    if (error)		/* propagate it */
	    {
		*(indata + 1) += (7 * error) / 16;	/* error can be neg. */
		*(errorTerms + 1) += error / 16;
		*errorTerms += (5 * error) / 16;
		*(errorTerms - 1) += (3 * error) / 16;
	    }	    
	    if (pattern == 0x01)
	    {
		outdata++;	/* reset pattern, point to next output pixel */
		pattern = 0x80;
	    }
	    else
	        pattern >>= 1;	
	}
    }
}


/*
 * F-S dither 8-bit data to 2-bit
 */
void
FSDither82(register short *indata, register short *errorTerms,
	   register u_char *outdata, short row, short npixels)
{
    register u_char pattern, mask;
    register short i, error;

    if (row & 1)			/* odd, go right to left */
    {
	indata += npixels;		/* point to last pixel */
	errorTerms += npixels;
	outdata += ((npixels + 3) >> 2) - 1;
	if ((npixels & 0x3) == 0)
	    pattern = 0x03;
	else
	    pattern = 0x03 << (2 * (4 - (npixels & 0x3)));
	for (i = 0; i < npixels; i++, indata--, errorTerms--)
	{
	    if (*indata < 0x2b)
	    	error = *indata;
	    else if (*indata < 0x80)
	    {
		mask = 0x55;
		*outdata |= mask & pattern;
		error = *indata - 0x55;
	    }
	    else if (*indata < 0xd5)
	    {
		mask = 0xaa;
		*outdata |= mask & pattern;
		error = *indata - 0xaa;
	    }
	    else
	    {
		mask = 0xff;
		*outdata |= mask & pattern;
		error = *indata - 0xff;
	    }
	    if (error)		/* propagate it */
	    {
		*(indata - 1) += (7 * error) / 16;  /* error can be neg. */
		*(errorTerms + 1) += (3 * error) / 16;
		*errorTerms += (5 * error) / 16;
		*(errorTerms - 1) += error / 16;
	    }	    
 	    if (pattern == 0xc0)
	    {
	    	pattern = 0x03;	 /* reset mask, point to next output pixel */
		*outdata--;
	    }
	    else
	        pattern <<= 2;
	}
    }
    else			/* even, go left to right */
    {
	pattern = 0xc0;
	indata++;
	errorTerms++;
	for (i = 0; i < npixels; i++, indata++, errorTerms++)
	{
	    if (*indata < 0x2b)
	    	error = *indata;
	    else if (*indata < 0x80)
	    {
		mask = 0x55;
		*outdata |= mask & pattern;
		error = *indata - 0x55;
	    }
	    else if (*indata < 0xd5)
	    {
		mask = 0xaa;
		*outdata |= mask & pattern;
		error = *indata - 0xaa;
	    }
	    else
	    {
		mask = 0xff;
		*outdata |= mask & pattern;
		error = *indata - 0xff;
	    }
	    if (error)		/* propagate it */
	    {
		*(indata + 1) += (7 * error) / 16;	/* error can be neg. */
		*(errorTerms + 1) += error / 16;
		*errorTerms += (5 * error) / 16;
		*(errorTerms - 1) += (3 * error) / 16;
	    }	    
	    if (pattern == 0x03)
	    {
		outdata++;	/* reset pattern, point to next output pixel */
		pattern = 0xc0;
	    }
	    else
	        pattern >>= 2;	
	}
    }
}