Milán Major

netscape-revival

1 branch
Code

lib/libimg/color.c

/* -*- Mode: C; tab-width: 4 -*-
   color.c --- Responsible for conversion from image depth to screen depth.
               Includes dithering for B&W displays, but not dithering
               for PseudoColor displays which can be found in dither.c.
               
   Copyright © 1995 Netscape Communications Corporation, all rights reserved.

   $Id: color.c,v 1.57 1996/05/24 19:48:13 fur Exp $
*/

#include "xp.h"
#include "if.h"

#if defined(__sun)
# include <nspr/sunos4.h>
#endif /* __sun */

#ifdef PROFILE
#pragma profile on
#endif


static void
ConvertRGBToCI(il_container *ic,
               const uint8 *mask,
               const uint8 *sp,
               int x_offset,
               int num,
               void XP_HUGE *vout)
{
    uint r, g, b, pixel;
    il_colorspace *cs = ic->cs;
    uint8 XP_HUGE *out = (uint8 XP_HUGE *) vout + x_offset;
    const uint8 *end = sp + 3*num;
    uint8 cbase = cs->alloc_base;
    uint8 *rm = cs->rmap;
    uint8 *gm = cs->gmap;
    uint8 *bm = cs->bmap;
    uint8 *indirect_map = cs->current_indirect_map;

    XP_ASSERT(indirect_map);
    if (!indirect_map)
        return;

    if (!mask)
    {
        while (sp < end) 
        {
            r = sp[0];
            g = sp[1];
            b = sp[2];
            pixel = rm[r] + gm[g] + bm[b] + cbase;
            *out = indirect_map[pixel];
            out++;
            sp += 3;
        }
    }
    else
    {
        while (sp < end) 
        {
            if (*mask++) {
                r = sp[0];
                g = sp[1];
                b = sp[2];
                pixel = rm[r] + gm[g] + bm[b] + cbase;
                *out = indirect_map[pixel];
            }
            out++;
            sp += 3;
        }
    }
}

static void
DitherConvertRGBToCI(il_container *ic,
                     const uint8 *mask,
                     const uint8 *sp,
                     int x_offset,
                     int num,
                     void XP_HUGE *vout)
{
    il_colorspace *cs = ic->cs;
    uint8 XP_HUGE *out = (uint8 XP_HUGE *) vout + x_offset;
    const uint8 XP_HUGE *end = out + num;
    uint8 *indirect_map = cs->current_indirect_map;

    XP_ASSERT(indirect_map);
    if (!indirect_map)
        return;

    il_quantize_fs_dither(ic, mask, sp, x_offset, (uint8 XP_HUGE *) vout,
                          ic->cs->alloc_base, num);
    if (mask) {
        while (out < end) {
            if (*mask++)
                *out = indirect_map[*out];
            out++;
        }
    } else {
        while (out < end) {
            *out = indirect_map[*out];
            out++;
        }
    }
}

struct fs_data {
    long* err1;
    long* err2;
    long* err3;
    uint8 *greypixels;
    uint8 *bwpixels;
	int width;
    int direction;
    long threshval, sum;
};


static struct fs_data *
init_fs_dither(il_container *ic)
{
	struct fs_data *fs;
	fs = XP_NEW_ZAP(struct fs_data);
    if (! fs)
        return NULL;

	fs->width = ic->image->width;
	fs->direction = 1;
	fs->err1 = (long*) XP_CALLOC(fs->width+2, sizeof(long));
	fs->err2 = (long*) XP_CALLOC(fs->width+2, sizeof(long));
	fs->greypixels = (uint8 *)XP_CALLOC(fs->width+7, 1);
	fs->bwpixels = (uint8 *)XP_CALLOC(fs->width+7, 1);
#ifdef XP_UNIX
    {
        int i;
        /* XXX should not be unix only */
        for(i=0; i<fs->width+2; i++)
            {
                fs->err1[i] = (XP_RANDOM() % 1024 - 512)/4;
            }
    }
#endif
	fs->threshval = 512;
	ic->quantize = (void *)fs;
	return fs;
}

static void
ConvertRGBToBW(il_container *ic,
               const uint8 *mask,
               const uint8 *sp,
               int x_offset,
               int num,
               void XP_HUGE *vout)
{
    uint32 fgmask32, bgmask32;
    uint32 *m;
    int mask_bit;
	struct fs_data *fs = (struct fs_data *)ic->quantize;
    uint8 XP_HUGE *out = (uint8 XP_HUGE *)vout;
	uint8 *gp, *bp;
	int col, limitcol;
	long grey;
	long sum;

	if(!fs)
		fs = init_fs_dither(ic);

    /* Silently fail if memory exhausted */
    if (! fs)
        return;

	gp = fs->greypixels;
	for(col=0; col<fs->width; col++)
	{
        /* CCIR 709 */
		uint8 r = *sp++;
		uint8 g = *sp++;
		uint8 b = *sp++;
        grey = ((uint)(0.299 * 4096) * r +
                (uint)(0.587 * 4096) * g +
                (uint)(0.114 * 4096) * b) / 4096;

		*gp++ = (uint8)grey;
	}

#if 0
	/* thresholding */
	gp = fs->greypixels;
	bp = fs->bwpixels;
	for(col=0; col<fs->width; col++)
	{
		*bp++ = (*gp++<128);
	}

#else

	for(col=0; col<fs->width+2; col++)
	{
		fs->err2[col] =0;
	}

	if (fs->direction)
	{
		col = 0;
		limitcol = fs->width;
		gp = fs->greypixels;
		bp = fs->bwpixels;
	}
	else
	{
		col = fs->width - 1;
		limitcol = -1;
		gp = &(fs->greypixels[col]);
		bp = &(fs->bwpixels[col]);
	}

	do
	{
		sum = ( (long) *gp * 1024 ) / 256 + fs->err1[col + 1];
        if ( sum >= 512)
        {
            *bp = 0;
            sum = sum - 1024;
        }
        else
            *bp = 1;
    
		if ( fs->direction )
		{
			fs->err1[col + 2] += ( sum * 7 ) / 16;
			fs->err2[col    ] += ( sum * 3 ) / 16;
			fs->err2[col + 1] += ( sum * 5 ) / 16;
			fs->err2[col + 2] += ( sum     ) / 16;
			col++; gp++; bp++;
		}
		else
		{
			fs->err1[col    ] += ( sum * 7 ) / 16;
			fs->err2[col + 2] += ( sum * 3 ) / 16;
			fs->err2[col + 1] += ( sum * 5 ) / 16;
			fs->err2[col    ] += ( sum     ) / 16;
			col--; gp--; bp--;
		}
	}
	while ( col != limitcol );

	fs->err3 = fs->err1;
	fs->err1 = fs->err2;
	fs->err2 = fs->err3;

	fs->direction = !fs->direction;

#endif
    
	bp = fs->bwpixels;
    bgmask32 = 0;        /* 32-bit temporary mask accumulators */
    fgmask32 = 0;

    m = ((uint32*)out) + (x_offset >> 5);
    mask_bit = ~x_offset & 0x1f; /* next bit to write in 32-bit mask */

/* Add a bit to the row of mask bits.  Flush accumulator to memory if full. */
#define SHIFT_IMAGE_MASK(opaqueness, pixel)					  		          \
    {																		  \
        fgmask32 |=  ((uint32)pixel & opaqueness) << M32(mask_bit);           \
        bgmask32 |=  ((uint32)((pixel ^ 1) & opaqueness)) << M32(mask_bit);   \
																			  \
        /* Filled up 32-bit mask word.  Write it to memory. */				  \
        if (mask_bit-- == 0) {                                                \
            uint32 mtmp = *m;                                                 \
            mtmp |= fgmask32;                                                 \
            mtmp &= ~bgmask32;                                                \
            *m++ = mtmp;                                                      \
            mask_bit = 31;													  \
            bgmask32 = 0;                                                     \
            fgmask32 = 0;                                                     \
        }																	  \
    }

    for(col=0; col < num; col++)
    {
        int opaqueness = 1;
        int pixel = *bp++;

        if (mask)
            opaqueness = *mask++;
        SHIFT_IMAGE_MASK(opaqueness, pixel);
    }

    /* End of scan line. Write out any remaining mask bits. */ 
    if (mask_bit < 31) {
        uint32 mtmp = *m;
        mtmp |= fgmask32;
        mtmp &= ~bgmask32; 
        *m = mtmp; 
    }
}

static void
ConvertRGBToGrey8(il_container *ic,
                  const uint8 *mask,
                  const uint8 *sp,
                  int x_offset,
                  int num,
                  void XP_HUGE *vout)
{
    uint r, g, b;
    uint8 XP_HUGE *out = (uint8 XP_HUGE *)vout + x_offset;
    const uint8 *end = sp + num*3;
    uint32 grey;

    if (!mask)
    {
        while (sp < end)
        {
            /* CCIR 709 */
            r = sp[0];
            g = sp[1];
            b = sp[2];
            grey = ((uint)(0.299 * 4096) * r +
                    (uint)(0.587 * 4096) * g +
                    (uint)(0.114 * 4096) * b) / 4096;

            *out = (uint8)grey;
            out++;
            sp += 3;
        }
    }
    else
    {
        while (sp < end)
        {
            if (*mask++)
            {
                
                /* CCIR 709 */
                r = sp[0];
                g = sp[1];
                b = sp[2];
                grey = ((uint)(0.299 * 4096) * r +
                        (uint)(0.587 * 4096) * g +
                        (uint)(0.114 * 4096) * b) / 4096;
                *out = (uint8)grey;
            }
            out++;
            sp += 3;
        }
    }
}

static void
ConvertRGBToRGB8(il_container *ic,
                 const uint8 *mask,
                 const uint8 *sp,
                 int x_offset,
                 int num,
                 void XP_HUGE *vout)
{
    uint r, g, b, pixel;
    il_colorspace *cs = ic->cs;
    uint8 XP_HUGE *out = (uint8 XP_HUGE *) vout + x_offset;
    const uint8 *end = sp + num*3;
    uint8 *rm = (uint8*)cs->rtom;
    uint8 *gm = (uint8*)cs->gtom;
    uint8 *bm = (uint8*)cs->btom;
    if (!mask)
    {
        while (sp < end) 
        {
            r = sp[0];
            g = sp[1];
            b = sp[2];
            pixel = rm[r] + gm[g] + bm[b];
            *out = pixel;
            out++;
            sp+=3;
        }
    }
    else
    {
        while (sp < end) 
        {
            if (*mask++) {
                r = sp[0];
                g = sp[1];
                b = sp[2];
                pixel = rm[r] + gm[g] + bm[b];
                *out = pixel;
            }
            out++;
            sp += 3;
        }
    }
}

static void
ConvertRGBToRGB16(il_container *ic,
                  const uint8 *mask,
                  const uint8 *sp,
                  int x_offset,
                  int num,
                  void XP_HUGE *vout)
{
    uint r, g, b, pixel;
    il_colorspace *cs = ic->cs;
    uint16 XP_HUGE *out = (uint16 XP_HUGE *) vout + x_offset;
    const uint8 *end = sp + num*3;
    uint16 *rm = (uint16*)cs->rtom;
    uint16 *gm = (uint16*)cs->gtom;
    uint16 *bm = (uint16*)cs->btom;
    if (!mask)
    {
        while (sp < end) 
        {
            r = sp[0];
            g = sp[1];
            b = sp[2];
            pixel = rm[r] + gm[g] + bm[b];
            *out = pixel;
            out++;
            sp+=3;
        }
    }
    else
    {
        while (sp < end) 
        {
            if (*mask++) {
                r = sp[0];
                g = sp[1];
                b = sp[2];
                pixel = rm[r] + gm[g] + bm[b];
                *out = pixel;
            }
            out++;
            sp += 3;
        }
    }
}

static void
ConvertRGBToRGB24(il_container *ic,
                  const uint8 *mask,
                  const uint8 *sp,
                  int x_offset,
                  int num,
                  void XP_HUGE *vout)
{
    uint8 XP_HUGE *out = (uint8 XP_HUGE *) vout + (3 * x_offset);
    const uint8 *end = sp + num*3;
	/* XXX this is a hack because it ignores the shifts */

    if (!mask)
    {
        while (sp < end) {
            out[2] = sp[0];
            out[1] = sp[1];
            out[0] = sp[2];
            out+=3;
            sp+=3;
        }
    } else {
        while (sp < end) {
            if (*mask++)
            {
                out[2] = sp[0];
                out[1] = sp[1];
                out[0] = sp[2];
            }
            out+=3;
            sp+=3;
        }
    }
}

static void
ConvertRGBToRGB32(il_container *ic,
                  const uint8 *mask,
                  const uint8 *sp,
                  int x_offset,
                  int num,
                  void XP_HUGE *vout)
{
    uint r, g, b, pixel;
    il_colorspace *cs = ic->cs;
    uint32 XP_HUGE *out = (uint32 XP_HUGE *) vout + x_offset;
    const uint8 *end = sp + num*3;
    uint32 *rm = (uint32*)cs->rtom;
    uint32 *gm = (uint32*)cs->gtom;
    uint32 *bm = (uint32*)cs->btom;
    if (!mask)
    {
        while (sp < end) 
        {
            r = sp[0];
            g = sp[1];
            b = sp[2];
            pixel = rm[r] + gm[g] + bm[b];
            *out = pixel;
            out++;
            sp+=3;
        }
    }
    else
    {
        while (sp < end) 
        {
            if (*mask++) {
                r = sp[0];
                g = sp[1];
                b = sp[2];
                pixel = rm[r] + gm[g] + bm[b];
                *out = pixel;
            }
            out++;
            sp += 3;
        }
    }
}

/* Sorting predicate for qsort() */
static int
compare_uint32(const void *a, const void *b)
{
    uint32 a1 = *(uint32*)a;
    uint32 b1 = *(uint32*)b;
    
    return (a1 < b1) ? -1 : ((a1 > b1) ? 1 : 0);
}

/* Count colors in color map.  Remove duplicate colors. */
static int
unique_map_colors(IL_Image *image)
{
    uint i;
    uint32 map[256];
    uint max_colors = image->colors;
    uint unique_colors = 1;
    
    XP_ASSERT(max_colors <= 256);
    XP_ASSERT(image->map);

    if (image->unique_colors)
        return image->unique_colors;

    /* Slightly slimy way of converting packed RGB structs to uint32s */
    for (i = 0; i < max_colors; i++) {
        map[i] = *((uint32*)&image->map[i]);
    }

    /* Sort by color, so identical colors will be grouped together. */
    qsort(map, max_colors, sizeof(*map), compare_uint32);

    /* Look for adjacent colors with different values */
    for (i = 0; i < max_colors-1; i++)
        if (map[i] != map[i + 1])
            unique_colors++;

    image->unique_colors = unique_colors;
    return unique_colors;
}
        
/*
 * A greater number of colors than this causes dithering to be used
 * rather than closest-color rendering if dither_mode == ilAuto.
 */
#define AUTO_DITHER_COLOR_THRESHOLD    16

/* XXX - need to do this for every client of a container*/
void
il_setup_color_space_converter(il_container *ic)
{
    il_colorspace *cs = ic->cs;

    if (ic->type == IL_GIF) {
        
        /*
         * Colormap sizes are rounded up to a power of two for GIFS, so
         * figure out how many colors are *really* in the colormap.
         */
        unique_map_colors(ic->image);
    }
    
#ifdef XP_MAC
    if ((ic->type == IL_GIF) && (ic->image->depth != 1)) {
        ic->converter = NULL;

        /* Allow for custom color palettes */
        il_set_color_palette(ic->cx, ic);
        
        return;
    }
#endif

    ic->dither_mode = ilClosestColor;
    /* Is this a pseudo-color display ? ... */
	if (cs->mode == ilCI) {
        switch (ic->ip->dither_mode) {
        case ilAuto:
            if (ic->type == IL_GIF) {
                int unique_colors = ic->image->unique_colors;
                /* Use a simple heuristic to decide whether or not we dither. */
                if ((unique_colors <= AUTO_DITHER_COLOR_THRESHOLD) &&
                    (unique_colors <= (cs->default_map_size / 2))) {
                    ic->converter = ConvertRGBToCI;
                    ILTRACE(1, ("Dithering turned off; Image has %d colors: %s",
                              unique_colors, ic->url ? ic->url->address : ""));
                    break;
                }
            }

            /* Fall through ... */
            
        case ilDither:
            ic->dither_mode = ilDither;
            ic->converter = DitherConvertRGBToCI;
            break;

        case ilClosestColor:
            if (ic->type == IL_JPEG)
                ic->converter = DitherConvertRGBToCI;
            else
                ic->converter = ConvertRGBToCI;
            break;

        default:
            XP_ASSERT(0);
        }

        /* Allow for custom color palettes */
        il_set_color_palette(ic->cx, ic);
        
    } else {
		il_converter conv;

        /* Is this a true-color display ? ... */
		if (cs->mode == ilRGB) {
            /* no dithering */
			switch (cs->bytes_per_pixel) 
			{
				case 0:
	    			conv = ConvertRGBToBW;
					break;
				case 1:
					conv = ConvertRGBToRGB8;
					break;
				case 2:
					conv = ConvertRGBToRGB16;
					break;
				case 3:
					conv = ConvertRGBToRGB24;
					break;
				case 4:
					conv = ConvertRGBToRGB32;
					break;
			}
		} else {
			XP_ASSERT(cs->mode == ilGrey);
			switch (cs->bytes_per_pixel) 
			{
				case 0:
                    ic->dither_mode = ilDither;
	    			conv = ConvertRGBToBW;
					break;
				case 1:
					conv = ConvertRGBToGrey8;
					break;
			}
		}
		ic->converter = conv;
	}
}

#ifdef PROFILE
#pragma profile off
#endif