/*****
* quantize.c : XmHTML color quantization routines
*
* This file Version $Revision: 1.5 $
*
* Creation date: Fri Sep 26 16:46:35 GMT+0100 1997
* Last modification: $Date: 1999/07/29 01:26:29 $
* By: $Author: sopwith $
* Current State: $State: Exp $
*
* Author: newt
*
* Copyright (C) 1994-1997 by Ripley Software Development
* All Rights Reserved
*
* This file is part of the XmHTML Widget library.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* The following copyright notice applies to ppm_quant and its supporting
* functions:
*
* ppmquant.c - quantize the colors in a pixmap down to a specified number
*
* Copyright (C) 1989, 1991 by Jef Poskanzer.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose and without fee is hereby granted, provided
* that the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. This software is provided "as is" without express or
* implied warranty.
*
* It also contains the following note to the above:
*
* Many people get confused by this legalese, especially the part about
* "without fee". Does this mean you can't charge for any product that
* uses PBMPLUS? No. All it means is that you don't have to pay me.
* You can do what you want with this software. Build it into your
* package, steal code from it, whatever. Just be sure to let people
* know where it came from.
*
*****/
/*****
* ChangeLog
* $Log: quantize.c,v $
* Revision 1.5 1999/07/29 01:26:29 sopwith
*
*
* Fix all warnings.
*
* Revision 1.4 1998/02/12 03:09:44 unammx
* Merge to Koen's XmHTML 1.1.2 + following fixes:
*
* Wed Feb 11 20:27:19 1998 Miguel de Icaza
*
* * gtk-forms.c (freeForm): gtk_destroy_widget is no longer needed
* with the refcounting changes.
*
* * gtk-xmhtml.c (gtk_xmhtml_remove): Only god knows why I was
* adding the just removed widget.
*
* Revision 1.3 1997/12/29 22:16:36 unammx
* This version does:
*
* - Sync with Koen to version Beta 1.1.2c of the XmHTML widget.
* Includes various table fixes.
*
* - Callbacks are now properly checked for the Gtk edition (ie,
* signals).
*
* Revision 1.2 1997/12/18 00:39:23 unammx
* It compiles and links -miguel
*
* Revision 1.1 1997/11/28 03:38:58 gnomecvs
* Work in progress port of XmHTML; No, it does not compile, don't even try -mig
*
* Revision 1.5 1997/10/23 00:25:17 newt
* XmHTML Beta 1.1.0 release
*
*****/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#define _WANT_TIMINGS /* we want timings, see XmHTMLfuncs.h */
#include "XmHTMLP.h"
#include "XmHTMLfuncs.h"
#ifdef WITH_MOTIF
#include "XCCP.h"
/*** External Function Prototype Declarations ***/
extern Byte XCCGetIndexFromPalette(XCC _xcc, int *_red, int *_green,
int *_blue, Boolean *failed);
#endif
/*** Public Variable Declarations ***/
/*** Private Datatype Declarations ****/
typedef struct{
Byte r;
Byte g;
Byte b;
}pixel;
/* Color histogram stuff. */
struct chist_item{
pixel color;
int value;
};
struct chist_list_item{
struct chist_item ch;
struct chist_list_item *next;
};
struct box{
int index;
int colors;
int sum;
};
typedef struct chist_item* chist_vec;
typedef struct chist_list_item* chist_list;
typedef chist_list* chash_table;
typedef struct box* box_vector;
/*** Private Function Prototype Declarations ****/
static void my_bcopy(char *src, char *dst, size_t len);
static Boolean QuickRGB(Byte *rgb, XmHTMLRawImageData *img_data,
int max_colors);
static void QuickQuantize(Byte *rgb, XmHTMLRawImageData *img_data);
/* ppm_quant supporting functions */
static void ppm_freechist(chist_vec);
static void ppm_freechash(chash_table);
static int redcompare(const void*, const void*);
static int greencompare(const void*, const void*);
static int bluecompare(const void*, const void*);
static int sumcompare(const void*, const void*);
static chist_vec mediancut(chist_vec, int, int, int, int);
static chist_vec ppm_computechist(pixel **, int, int, int, int*);
static chist_vec ppm_chashtochist(chash_table, int);
static chash_table ppm_computechash(pixel **, int, int, int, int*);
static chash_table ppm_allocchash(void);
/* the quantizer itself */
static int ppm_quant(Byte *pic24, pixel **pix, XmHTMLRawImageData *img_data,
int max_colors);
/* fast rgb converter */
static Boolean QuickRGB(Byte *rgb, XmHTMLRawImageData *img_data,
int max_colors);
/* fast quantizer to a fixed palette */
static void QuickQuantize(Byte *rgb, XmHTMLRawImageData *img_data);
/* various macros used by the quantization routines */
#define PPM_GETR(p) ((p).r)
#define PPM_GETG(p) ((p).g)
#define PPM_GETB(p) ((p).b)
#define PPM_ASSIGN(p,red,grn,blu) \
{ (p).r = (red); (p).g = (grn); (p).b = (blu); }
/* color compare */
#define PPM_EQUAL(p,q) ( (p).r == (q).r && (p).g == (q).g && (p).b == (q).b )
/* Color scaling macro -- to make writing ppmtowhatever easier. */
#define PPM_DEPTH(newp,p,oldmaxval,newmaxval) \
PPM_ASSIGN( (newp), \
(int) PPM_GETR(p) * (newmaxval) / ((int)oldmaxval), \
(int) PPM_GETG(p) * (newmaxval) / ((int)oldmaxval), \
(int) PPM_GETB(p) * (newmaxval) / ((int)oldmaxval) )
/* Luminance macro, using only integer ops. Returns an int (*256) */
#define PPM_LUMIN(p) \
( 77 * PPM_GETR(p) + 150 * PPM_GETG(p) + 29 * PPM_GETB(p) )
/* compute a hashvalue */
#define ppm_hashpixel(p) ((((int)PPM_GETR(p) * 33023 + \
(int)PPM_GETG(p) * 30013 + (int)PPM_GETB(p) * 27011) & 0x7fffffff) \
% HASH_SIZE)
/*** Private Variable Declarations ***/
#define MAXCOLORS 32767 /* max. no of colors to take into account */
#define HASH_SIZE 6553 /* color hashtable size */
/*****
* defines & macros for quick 24bit (RGB) image to 8bit (paletted) image
* We use a 3/3/2 colorcube.
*****/
#define RMASK 0xe0
#define RSHIFT 0
#define GMASK 0xe0
#define GSHIFT 3
#define BMASK 0xc0
#define BSHIFT 6
/* division speedup: multiply by this instead of dividing by 16 */
#define SIXTEENTH 0.0625
/*****
* Name: my_bcopy
* Return Type: void
* Description: safe bcopy, areas may overlap.
* In:
* src: data to be copied;
* dst: destination;
* len: no of bytes to copy;
* Returns:
* nothing.
*****/
static void
my_bcopy(char *src, char *dst, size_t len)
{
/* areas are the same */
if(src == dst || len <= 0)
return;
if(src < dst && src+len > dst)
{
/* do a backward copy */
src = src + len - 1;
dst = dst + len - 1;
for(; len > 0; len--, src--, dst--)
*dst = *src;
}
else
{
/* do a forward copy */
for(; len > 0; len--, src++, dst++)
*dst = *src;
}
}
/*****
* Name: QuickRGB
* Return Type: Boolean
* Description: attemps a quick 24 to 8 bit image conversion
* In:
* rgb: raw image data, RGB format;
* img_data: destination;
* max_colors: maximum no of colors allowed;
* Returns:
* True when image was converted, False if not.
*****/
static Boolean
QuickRGB(Byte *rgb, XmHTMLRawImageData *img_data, int max_colors)
{
unsigned long colors[MAX_IMAGE_COLORS],col;
int i, num_colors, low, high, mid, width, height;
Byte *p, *pix;
SetTimer;
width = img_data->width;
height = img_data->height;
/* put the first color in the table by hand */
num_colors = 0;
mid = 0;
for(i = width*height, p = rgb; i; i--)
{
/* make truecolor pixel val */
col = (((Pixel) *p++) << 16);
col += (((Pixel) *p++) << 8);
col += *p++;
/* binary search the 'colors' array to see if it's in there */
low = 0;
high = num_colors - 1;
while (low <= high)
{
mid = (low+high)/2;
if(col < colors[mid])
high = mid - 1;
else if(col > colors[mid])
low = mid + 1;
else
break;
}
if(high < low)
{
/* didn't find color in list, add it. */
if(num_colors >= max_colors)
{
ShowTimer(13, ("quantize.c: QuickRGB end, more colors than "
"allowed."));
return(False); /* more colors than allowed */
}
my_bcopy((char *)&colors[low], (char*)&colors[low+1],
(num_colors - low) * sizeof(unsigned long));
colors[low] = col;
num_colors++;
}
}
/* Pixelize data: map pixel values of the RGB image in the colormap */
for(i = width*height, p = rgb, pix = img_data->data; i; i--,pix++)
{
col = (((Pixel)*p++) << 16);
col += (((Pixel)*p++) << 8);
col += *p++;
/* binary search the 'colors' array. It *IS* in there */
low = 0;
high = num_colors - 1;
while(low <= high)
{
mid = (low+high)/2;
if(col < colors[mid])
high = mid - 1;
else if(col > colors[mid])
low = mid + 1;
else
break;
}
*pix = mid;
}
/* allocate colormap */
AllocRawImageCmap(img_data, num_colors);
/* and fill it */
for(i = 0; i < num_colors; i++)
{
img_data->cmap[i].red = ( colors[i] >> 16);
img_data->cmap[i].green = ((colors[i] >> 8 ) & 0xff);
img_data->cmap[i].blue = ( colors[i] & 0xff);
}
ShowTimer(13, ("quantize.c: QuickRGB success."));
return(True);
}
/*****
* Name: QuickQuantize
* Return Type: Byte*
* Description: quick conversion of RGB image with > 256 colors to a
* paletted image using a 256 colors imagemap.
* In:
* rgb: rgb image data (3 bytes per pixel, rgb order), pixel 0 at
* top left corner;
* data: buffer for reduced image, filled upon return.
* width,height:
* image dimensions;
* cols: colormap, filled upon return;
* Returns:
* 8bit pixelized image data. Palette data in *cols.
* Note:
* This routine does a very quick quantization using a fixed palette and is
* based on the routine quick_quant as found in xv28to8.c from xv-3.10 by
* John Bradley. Used by permission.
*****/
static void
QuickQuantize(Byte *rgb, XmHTMLRawImageData *img_data)
{
Byte *pp;
int r, g, b; /* RGB color components */
int *thisLine, *nextLine, *thisPtr, *nextPtr, *tmpPtr;
int i, j, val, size;
int imax, jmax;
int width = img_data->width;
int height = img_data->height;
TColor *cols;
SetTimer;
pp = img_data->data;
size = width*3; /* a single RGB scanline */
imax = height-1;
jmax = width-1;
/* allocate a full colormap for this image */
AllocRawImageCmap(img_data, MAX_IMAGE_COLORS);
cols = img_data->cmap;
/* fill colormap */
for(i = 0; i < MAX_IMAGE_COLORS; i++)
{
cols[i].red =(((i << RSHIFT) & RMASK)*255 + 0.5*RMASK)/RMASK;
cols[i].green=(((i << GSHIFT) & GMASK)*255 + 0.5*GMASK)/GMASK;
cols[i].blue =(((i << BSHIFT) & BMASK)*255 + 0.5*BMASK)/BMASK;
}
/* temporary work memory */
thisLine = (int*)malloc(size*sizeof(int));
nextLine = (int*)malloc(size*sizeof(int));
/* get first line of picture */
for(j = size, tmpPtr = nextLine; j; j--)
*tmpPtr++ = (int)*rgb++;
for(i = 0; i < height; i++)
{
tmpPtr = thisLine;
thisLine = nextLine;
nextLine = tmpPtr; /* swap */
/* get next line */
if(i != imax)
for(j = size, tmpPtr = nextLine; j; j--)
*tmpPtr++ = (int)*rgb++;
/* convert RGB scanline to indexed scanline */
for(j = 0, thisPtr = thisLine, nextPtr = nextLine; j < width;
j++, pp++)
{
/* get RGB values */
r = *thisPtr++;
g = *thisPtr++;
b = *thisPtr++;
/* check validity of component ranges */
RANGE(r,0,255);
RANGE(g,0,255);
RANGE(b,0,255);
/* choose actual pixel value */
val = (((r & RMASK) >> RSHIFT) | ((g & GMASK) >> GSHIFT) |
((b & BMASK) >> BSHIFT));
*pp = val;
/* compute color errors */
r -= cols[val].red;
g -= cols[val].green;
b -= cols[val].blue;
/* Add error fractions to adjacent pixels using a 3x3 matrix. */
/* adjust RIGHT pixel */
if(j != jmax)
{
thisPtr[0] += SIXTEENTH * (r*7);
thisPtr[1] += SIXTEENTH * (g*7);
thisPtr[2] += SIXTEENTH * (b*7);
}
/* do BOTTOM pixel */
if(i != imax)
{
nextPtr[0] += SIXTEENTH * (r*5);
nextPtr[1] += SIXTEENTH * (g*5);
nextPtr[2] += SIXTEENTH * (b*5);
/* do BOTTOM LEFT pixel */
if(j > 0)
{
nextPtr[-3] += SIXTEENTH * (r*3);
nextPtr[-2] += SIXTEENTH * (g*3);
nextPtr[-1] += SIXTEENTH * (b*3);
}
/* do BOTTOM RIGHT pixel */
if(j != jmax)
{
nextPtr[3] += SIXTEENTH * (r);
nextPtr[4] += SIXTEENTH * (g);
nextPtr[5] += SIXTEENTH * (b);
}
nextPtr += 3;
}
}
}
/* free it */
free(thisLine);
free(nextLine);
ShowTimer(13, ("quantize.c: QuickQuantize end."));
}
/***************************************************************/
/* The following code based on code from the 'pbmplus' package */
/* written by Jef Poskanzer */
/***************************************************************/
static int
ppm_quant(Byte *pic24, pixel **pix, XmHTMLRawImageData *img_data,
int max_colors)
{
pixel **pixels;
chist_vec chv, colormap;
chash_table cht;
int i, cols, row, rows, colors;
Byte *pic8 = img_data->data;
Byte *picptr, maxval, newmaxval;
register int index, col, limitcol;
register pixel *pP;
SetTimer;
index = 0;
maxval = 255;
cols = img_data->width;
rows = img_data->height;
/* reformat RGB image data into a 2D array of pixel values */
if(pix == (pixel**)NULL)
{
pixels = (pixel **)malloc(rows * sizeof(pixel*));
for(row = 0; row < rows; row++)
{
pixels[row] = (pixel *) malloc(cols * sizeof(pixel));
for(col=0, pP=pixels[row]; colr = *pic24++;
pP->g = *pic24++;
pP->b = *pic24++;
}
}
}
else
pixels = pix;
/* create unclustered color histogram */
for( ; ; )
{
chv = ppm_computechist(pixels, cols, rows, MAXCOLORS, &colors);
if(chv != (chist_vec) 0)
break;
newmaxval = maxval / 2;
for(row=0; rownext)
{
if(PPM_EQUAL(chl->ch.color, *pP))
{
index = chl->ch.value;
break;
}
}
if(!chl)
{
/* Not preset, search colormap for closest match. */
register int i, r1, g1, b1, r2, g2, b2;
register long dist, newdist;
r1 = PPM_GETR(*pP);
g1 = PPM_GETG(*pP);
b1 = PPM_GETB(*pP);
dist = 2000000000;
for(i=0; ich.color = *pP;
chl->ch.value = index;
chl->next = cht[hash];
cht[hash] = chl;
}
*picptr++ = index;
++col;
++pP;
}
while (col != limitcol);
}
/* free the pixels array */
for(i = 0; i < rows; i++)
free(pixels[i]);
free(pixels);
/* rescale colormap and save return colormap */
if(img_data->cmapsize)
free(img_data->cmap);
AllocRawImageCmap(img_data, max_colors);
for(i = 0; i < max_colors; i++)
{
PPM_DEPTH(colormap[i].color, colormap[i].color, maxval, 255);
img_data->cmap[i].red = PPM_GETR(colormap[i].color);
img_data->cmap[i].green = PPM_GETG(colormap[i].color);
img_data->cmap[i].blue = PPM_GETB(colormap[i].color);
img_data->cmap[i].pixel = i;
}
/* free cht and colormap */
ppm_freechist(colormap);
ppm_freechash(cht);
ShowTimer(13, ("quantize.c: ppm_quant end."));
return(0);
}
/*****
* Name: mediancut
* Return Type: chist_vec
* Description: Here is the fun part, the median-cut colormap generator.
* This is based on Paul Heckbert's paper "Color Image
* Quantization for Frame Buffer Display",
* SIGGRAPH '82 Proceedings, page 297.
* In:
*
* Returns:
*
*****/
static chist_vec
mediancut(chist_vec chv, int colors, int sum, int maxval, int max_colors)
{
chist_vec colormap;
box_vector bv;
int boxes, rl, gl, bl;
pixel p;
register int bi, i;
bv = (box_vector)malloc(sizeof(struct box) * max_colors);
colormap = (chist_vec)malloc(sizeof(struct chist_item) * max_colors);
/* reset to zero */
for(i=0; i maxr) maxr = v;
v = PPM_GETG( chv[indx + i].color );
if (v < ming) ming = v;
if (v > maxg) maxg = v;
v = PPM_GETB( chv[indx + i].color );
if (v < minb) minb = v;
if (v > maxb) maxb = v;
}
/* Find the largest dimension, and sort by that component. */
PPM_ASSIGN(p, maxr - minr, 0, 0);
rl = PPM_LUMIN(p);
PPM_ASSIGN(p, 0, maxg - ming, 0);
gl = PPM_LUMIN(p);
PPM_ASSIGN(p, 0, 0, maxb - minb);
bl = PPM_LUMIN(p);
if(rl >= gl && rl >= bl)
qsort((char*)&(chv[indx]), (size_t)clrs,
sizeof(struct chist_item), redcompare);
else if(gl >= bl)
qsort((char*)&(chv[indx]), (size_t)clrs,
sizeof(struct chist_item), greencompare);
else
qsort((char*)&(chv[indx]), (size_t)clrs,
sizeof(struct chist_item), bluecompare);
/*****
* Now find the median based on the counts, so that about half the
* pixels (not colors, pixels) are in each subdivision.
*****/
lowersum = chv[indx].value;
halfsum = sm / 2;
for (i=1; i= halfsum)
break;
lowersum += chv[indx + i].value;
}
/* Split the box, and sort to bring the biggest boxes to the top. */
bv[bi].colors = i;
bv[bi].sum = lowersum;
bv[boxes].index = indx + i;
bv[boxes].colors = clrs - i;
bv[boxes].sum = sm - lowersum;
++boxes;
qsort((char*)bv, (size_t)boxes, sizeof(struct box),sumcompare);
} /* while (boxes ... */
/* Now choose a representative color for each box. */
for(bi = 0; bi < boxes; bi++)
{
register int indx = bv[bi].index;
register int clrs = bv[bi].colors;
register long r = 0, g = 0, b = 0, sum = 0;
for(i = 0; i < clrs; i++)
{
r += PPM_GETR( chv[indx + i].color ) * chv[indx + i].value;
g += PPM_GETG( chv[indx + i].color ) * chv[indx + i].value;
b += PPM_GETB( chv[indx + i].color ) * chv[indx + i].value;
sum += chv[indx + i].value;
}
r = r / sum;
if (r>maxval)
r = maxval; /* avoid math errors */
g = g / sum;
if (g>maxval)
g = maxval;
b = b / sum;
if (b>maxval)
b = maxval;
PPM_ASSIGN( colormap[bi].color, r, g, b );
}
free(bv);
return colormap;
}
static int
redcompare(const void *p1, const void *p2)
{
return((int)PPM_GETR(((chist_vec)p1)->color) -
(int)PPM_GETR(((chist_vec)p2)->color));
}
static int
greencompare(const void *p1, const void *p2)
{
return((int) PPM_GETG( ((chist_vec)p1)->color) -
(int) PPM_GETG( ((chist_vec)p2)->color));
}
static int
bluecompare(const void *p1, const void *p2)
{
return((int) PPM_GETB( ((chist_vec)p1)->color) -
(int) PPM_GETB( ((chist_vec)p2)->color));
}
static int
sumcompare(const void *p1, const void *p2)
{
return(((box_vector) p2)->sum - ((box_vector) p1)->sum);
}
static chist_vec
ppm_computechist(pixel **pixels, int cols, int rows, int maxcolors,
int *colorsP)
{
chash_table cht;
chist_vec chv;
cht = ppm_computechash(pixels, cols, rows, maxcolors, colorsP);
if(!cht)
return((chist_vec)0);
chv = ppm_chashtochist(cht, maxcolors);
ppm_freechash(cht);
return(chv);
}
static chash_table
ppm_computechash(pixel **pixels, int cols, int rows, int maxcolors,
int *colorsP )
{
chash_table cht;
register pixel* pP;
chist_list chl;
int col, row, hash;
cht = ppm_allocchash();
*colorsP = 0;
/* Go through the entire image, building a hash table of colors. */
for(row = 0; row < rows; row++)
{
for (col = 0, pP = pixels[row]; col < cols; col++, pP++)
{
hash = ppm_hashpixel(*pP);
for(chl = cht[hash]; chl != (chist_list) 0; chl = chl->next)
{
if(PPM_EQUAL(chl->ch.color, *pP))
break;
}
if(chl != (chist_list)0)
++(chl->ch.value);
else
{
if((*colorsP)++ > maxcolors)
{
ppm_freechash(cht);
return((chash_table)0);
}
chl = (chist_list)malloc(sizeof(struct chist_list_item));
chl->ch.color = *pP;
chl->ch.value = 1;
chl->next = cht[hash];
cht[hash] = chl;
}
}
}
return(cht);
}
static chash_table
ppm_allocchash(void)
{
chash_table cht;
int i;
cht = (chash_table)malloc(HASH_SIZE * sizeof(chist_list));
for(i = 0; i < HASH_SIZE; i++)
cht[i] = (chist_list) 0;
return(cht);
}
static chist_vec
ppm_chashtochist(chash_table cht, int maxcolors)
{
chist_vec chv;
chist_list chl;
int i, j;
/* Now collate the hash table into a simple chist array. */
chv = (chist_vec)malloc(maxcolors * sizeof(struct chist_item));
/* Loop through the hash table. */
j = 0;
for(i = 0; i < HASH_SIZE; i++)
{
for(chl = cht[i]; chl != (chist_list) 0; chl = chl->next)
{
/* Add the new entry. */
chv[j] = chl->ch;
++j;
}
}
return(chv);
}
static void ppm_freechist(chist_vec chv)
{
free((char*)chv);
}
static void
ppm_freechash(chash_table cht)
{
int i;
chist_list chl, chlnext;
for(i = 0; i < HASH_SIZE; i++)
{
for(chl = cht[i]; chl != (chist_list) 0; chl = chlnext)
{
chlnext = chl->next;
free( (char*) chl );
}
}
free((char*)cht);
}
/*****
* Name: _XmHTMLConvert24to8
* Return Type: void
* Description: transforms a 24bit RGB image to an 8bit paletted image
* In:
* data: original image data (in RGB format)
* img_data: raw image data. Will receive paletted image data and colormap.
* max_colors: maximum no of colors to use. Only used by ppm_quant.
* mode: rgb conversion mode to use.
* Returns:
* nothing but img_data will contain the indexed image data and a correct
* colormap with at most 256 colors.
*****/
void
_XmHTMLConvert24to8(Byte *data, XmHTMLRawImageData *img_data, int max_colors,
Byte mode)
{
Boolean done = False;
#if 0
if(!done) /* quantize image */
ppm_quant(data, NULL, img_data, max_colors);
#endif
_XmHTMLDebug(13, ("quantize.c: _XmHTMLConvert24to8, start for %i colors "
"maximum.\n", max_colors));
/*****
* If this image isn't RGB, there's a good chance that this image
* has less than 256 colors in it. So we first make a quick check
* to see if this is indeed true. If true, this will produce the best
* possible results as no quantization is performed.
*****/
if((mode == XmBEST || mode == XmQUICK) &&
img_data->color_class != XmIMAGE_COLORSPACE_RGB)
done = QuickRGB(data, img_data, max_colors);
if(!done)
{
if(mode == XmBEST || mode == XmSLOW)
ppm_quant(data, NULL, img_data, max_colors);
else
QuickQuantize(data, img_data);
}
_XmHTMLDebug(13, ("quantize.c: _XmHTMLConvert24to8, end\n"));
}
/*****
* Name: _XmHTMLQuantizeImage
* Return Type: void
* Description: quantizes an image to max_colors
* In:
* img_data: image to be quantized;
* max_colors: max. no of colors allowed;
* Returns:
* nothing, but img_data is updated.
*****/
void
_XmHTMLQuantizeImage(XmHTMLRawImageData *img_data, int max_colors)
{
Byte *ptr;
int col, row;
pixel **pixels;
register pixel *pP;
/* reformat image data into a 2D array of pixel values */
pixels = (pixel **)malloc(img_data->height * sizeof(pixel*));
ptr = img_data->data;
for(row = 0; row < img_data->height; row++)
{
pixels[row] = (pixel *) malloc(img_data->width * sizeof(pixel));
for(col = 0, pP = pixels[row]; col < img_data->width; col++, pP++)
{
pP->r = img_data->cmap[*ptr].red;
pP->g = img_data->cmap[*ptr].green;
pP->b = img_data->cmap[*ptr++].blue;
}
}
/* quantize it */
ppm_quant(NULL, pixels, img_data, max_colors);
/* no need to free pixels, ppm_quant does that for us */
}
/*****
* Name: _XmHTMLPixelizeRGB
* Return Type: void
* Description: converts RGB data to paletted data. Doesn't do any quantizing.
* In:
* rgb: raw rgb data;
* img_data: destination.
* Returns:
* nothing, but upon return img_data contains a valid colormap (with possibly
* more than MAX_IMAGE_COLORS colors) and the data field has been pixelized.
*****/
void
_XmHTMLPixelizeRGB(Byte *rgb, XmHTMLRawImageData *img_data)
{
Pixel *colors, col, max_colors;
int i, num_colors, low, high, mid, width, height;
Byte *p, *pix;
width = img_data->width;
height = img_data->height;
/* initialize colors array */
max_colors = MAX_IMAGE_COLORS;
colors = (Pixel*)malloc(max_colors * sizeof(Pixel));
/* put the first color in the table by hand */
num_colors = 0;
mid = 0;
for(i = width*height, p = rgb; i; i--)
{
/* make truecolor pixel val */
col = (((Pixel) *p++) << 16);
col += (((Pixel) *p++) << 8);
col += *p++;
/* binary search the 'colors' array to see if it's in there */
low = 0;
high = num_colors - 1;
while (low <= high)
{
mid = (low+high)/2;
if(col < colors[mid])
high = mid - 1;
else if(col > colors[mid])
low = mid + 1;
else
break;
}
if(high < low)
{
/* didn't find color in list, add it. */
if(num_colors >= max_colors)
{
/* enlarge colors array */
max_colors *= 2;
colors = (Pixel*)realloc(colors, max_colors * sizeof(Pixel));
}
my_bcopy((char *)&colors[low], (char*)&colors[low+1],
(num_colors - low) * sizeof(Pixel));
colors[low] = col;
num_colors++;
}
}
/* destination buffer */
if(img_data->data == (Byte*)NULL)
img_data->data = (Byte*)malloc(width*height*sizeof(Byte));
/* Pixelize data: map pixel values of the RGB image in the colormap */
for(i = width*height, p = rgb, pix = img_data->data; i; i--,pix++)
{
col = (((Pixel)*p++) << 16);
col += (((Pixel)*p++) << 8);
col += *p++;
/* binary search the 'colors' array. It *IS* in there */
low = 0;
high = num_colors - 1;
while(low <= high)
{
mid = (low+high)/2;
if(col < colors[mid])
high = mid - 1;
else if(col > colors[mid])
low = mid + 1;
else
break;
}
*pix = mid;
}
/* allocate colormap */
AllocRawImageCmap(img_data, num_colors);
/* and fill it */
for(i = 0; i < num_colors; i++)
{
img_data->cmap[i].red = ( colors[i] >> 16);
img_data->cmap[i].green = ((colors[i] >> 8 ) & 0xff);
img_data->cmap[i].blue = ( colors[i] & 0xff);
}
/* no longer needed */
free(colors);
}
void
_XmHTMLDitherImage(XmHTMLWidget html, XmHTMLRawImageData *img_data)
{
int r, g, b, er, eg, eb;
int i, j, size, ex, used[MAX_IMAGE_COLORS];
int *error = NULL, *er1 = NULL, *er2 = NULL, *ter;
Dimension width = img_data->width;
Dimension height = img_data->height;
Pixel val;
Byte *ptr = img_data->data;
TColor *cmap = img_data->cmap;
XCC xcc = html->html.xcc;
XCCDither *dm = xcc->fast_dither;
#ifdef WITH_MOTIF
Boolean f;
#else
int f;
#endif
size = width * height;
if(html->html.map_to_palette == XmBEST ||
html->html.map_to_palette == XmSLOW)
{
error = (int*)malloc(6*width*sizeof(int));
er1 = error;
er2 = error + width*3;
memset(error, 0, 6*width*sizeof(int));
}
else
{
for(i = 0; i < MAX_IMAGE_COLORS; i++)
used[i] = -1;
}
switch(html->html.map_to_palette)
{
case XmQUICK: /* closest match, no error correction */
for(i = 0; i < size; i++, ptr++)
{
if(used[(int)*ptr] == -1)
{
r = cmap[(int)*ptr].red;
g = cmap[(int)*ptr].green;
b = cmap[(int)*ptr].blue;
used[(int)*ptr] = (int)XCCGetIndexFromPalette(xcc, &r,
&g, &b, &f);
}
*ptr = (Byte)used[(int)*ptr];
}
break;
case XmBEST: /* predefined color & error matrices, FS */
{
for(i = 0; i < height; i++)
{
ter = er1;
er1 = er2;
er2 = ter;
memset(er2, 0, width*3*sizeof(int));
ex = 0;
for(j = 0; j < width; j++)
{
r = cmap[(int)*ptr].red;
g = cmap[(int)*ptr].green;
b = cmap[(int)*ptr].blue;
er = r + er1[ex++];
eg = g + er1[ex++];
eb = b + er1[ex++];
RANGE(er,0,255);
RANGE(eg,0,255);
RANGE(eb,0,255);
val = (Pixel)dm->fast_rgb[er>>3][eg>>3][eb>>3];
r = dm->fast_err[er>>3][eg>>3][eb>>3];
g = dm->fast_erg[er>>3][eg>>3][eb>>3];
b = dm->fast_erb[er>>3][eg>>3][eb>>3];
er = r;
eg = g;
eb = b;
if(j < (width - 1))
{
er1[ex+0] += SIXTEENTH * (er*7);
er1[ex+1] += SIXTEENTH * (eg*7);
er1[ex+2] += SIXTEENTH * (eb*7);
}
if(i < (height - 1))
{
er2[ex - 3] += SIXTEENTH * (er*5);
er2[ex - 2] += SIXTEENTH * (eg*5);
er2[ex - 1] += SIXTEENTH * (eb*5);
if(j > 0)
{
er2[ex - 6] += SIXTEENTH * (er*3);
er2[ex - 5] += SIXTEENTH * (eg*3);
er2[ex - 4] += SIXTEENTH * (eb*3);
}
if(j < (width - 1))
{
er2[ex + 0] = SIXTEENTH * (er);
er2[ex + 1] = SIXTEENTH * (eg);
er2[ex + 2] = SIXTEENTH * (eb);
}
}
*ptr++ = (Byte)val;
}
}
}
break;
case XmFAST: /* predefined color matrix, no error correction */
for(i = 0; i < size; i++, ptr++)
{
if(used[(int)*ptr] == -1)
{
r = cmap[(int)*ptr].red;
g = cmap[(int)*ptr].green;
b = cmap[(int)*ptr].blue;
used[(int)*ptr] = (int)dm->fast_rgb[r>>3][g>>3][b>>3];
}
*ptr = (Byte)used[(int)*ptr];
}
break;
case XmSLOW: /* closest match & dynamic error matrices, FS */
{
for(i = 0; i < height; i++)
{
ter = er1;
er1 = er2;
er2 = ter;
memset(er2, 0, width*3*sizeof(int));
ex = 0;
for(j = 0; j < width; j++)
{
r = cmap[(int)*ptr].red;
g = cmap[(int)*ptr].green;
b = cmap[(int)*ptr].blue;
er = r + er1[ex++];
eg = g + er1[ex++];
eb = b + er1[ex++];
RANGE(er,0,255);
RANGE(eg,0,255);
RANGE(eb,0,255);
val = XCCGetIndexFromPalette(xcc, &er, &eg, &eb, &f);
if(j < (width - 1))
{
er1[ex+0] += SIXTEENTH * (er*7);
er1[ex+1] += SIXTEENTH * (eg*7);
er1[ex+2] += SIXTEENTH * (eb*7);
}
if(i < (height - 1))
{
er2[ex - 3] += SIXTEENTH * (er*5);
er2[ex - 2] += SIXTEENTH * (eg*5);
er2[ex - 1] += SIXTEENTH * (eb*5);
if(j > 0)
{
er2[ex - 6] += SIXTEENTH * (er*3);
er2[ex - 5] += SIXTEENTH * (eg*3);
er2[ex - 4] += SIXTEENTH * (eb*3);
}
if(j < (width - 1))
{
er2[ex + 0] = SIXTEENTH * (er);
er2[ex + 1] = SIXTEENTH * (eg);
er2[ex + 2] = SIXTEENTH * (eb);
}
}
*ptr++ = (Byte)val;
}
}
}
break;
default:
return;
}
/* release error matrix if we've used one */
if(html->html.map_to_palette == XmBEST ||
html->html.map_to_palette == XmSLOW)
free(error);
/* replace colormap with palette */
img_data->cmapsize = xcc->num_palette;
img_data->cmap = (TColor*)realloc(img_data->cmap,
xcc->num_palette*sizeof(TColor));
memcpy(img_data->cmap, xcc->palette, xcc->num_palette*sizeof(TColor));
}