From: garyo@masscomp.UUCP (Gary Oberbrunner) Subject: Re: Advanced Dither Needed Date: 7 Jan 88 03:03:58 GMT In article <3703@ames.arpa> watson@ames.UUCP (John S. Watson) writes: > I'm looking for either references to, or code for a really >nice color dithering algorithm. The algorithm produces 8-bit dithered >images that are almost indistinguishable from the original 24-bit ima Floyd & Steinberg's dithering algorithm is described in the following paper: Floyd, R.W. and Steinberg, L. "An Adaptive Algorithm for Spatial Gray Scale." SID 75m Int. Symp. Dig. Tech. Papers (1975), p. 36. I don't actually have this paper, just a reference to it in Heckbert's "Color Image Quantization for Frame Buffer Display (Siggraph proceedings p. 297). You probably want to check this paper out too. But the way F/S dither works is like this (it's so simple...) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - First you come up with a color map - use uniform segmentation for spee Heckbert's method for quality. Then you work from the top left to the b right of the image (pixel order), as follows: Find the closest match in the color map to the true pixel color. Use that color map entry to represent that pixel. Compute the r,g,b error resulting from the above approximation. Add 3/8 of that error to the pixel to the right, and 3/8 to the pixel below the current pixel. The remaining 1/4 goes to the pixel diagonally below. Don't forget to clip the rgb, as well as check your boundary conditions. And that's it - compute the next pixel! No dither maps, no muss no fuss no bother. And it's real fast since you can do the 3/8 and 1/4 by shifting. On some images it can help to add some random noise; for instance if you have a smooth (flat-shaded) surface that's just below one of the color the map, the error will build up slowly until you get a line of brighter color somewhere in the surface. A bit o' noise gets rid of that line - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Of course choosing the color map is the fun part. :-) :-) Gary Oberbrunner Here is a hack C program which implements both Heckbert's algorithm and F/S dither (no claims of any kind are made for this program - in fact t ended above with my name, and this is all line nois)@(#*&... No seriously, this code needs real help - it was written for a one-shot deal, and it is very inefficient in many places. But it does work ok. The worst part is where every possible color mapping is computed - SLOW! And Remap() ain't a speed-demon either, but that's where Floyd et. al. come in... Enjoy (watch out for possible signature at end...) ---------------------------------------------------------------------------- Remember, -Truth is not beauty; Information is not knowledge; / Beauty is not love; Gary Oberbrunner Knowledge is not wisdom; / Love is not music; ...!masscomp!garyo Wisdom is not truth; ----/ Music is the best. - FZ ---------------------------------------------------------------------------- ---------------------- c u t h e r e -------------------------- #! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create: # quantize.c (most everything in here) # split.c (the color-space-splitting algorithm) # quantize.h (structs & externs) # std.h (my "gotta have it" stuff) # This archive created: Wed Jan 6 21:58:18 1988 export PATH; PATH=/bin:/usr/bin:$PATH if test -f 'quantize.c' then echo shar: "will not over-write existing file 'quantize.c'" else cat << \SHAR_EOF > 'quantize.c' /*---------------------------------------------------------------------- * Color image quantizer, from Paul Heckbert's paper in * Computer Graphics, vol.16 #3, July 1982 (Siggraph proceedings), * pp. 297-304. * By Gary Oberbrunner, copyright c. 1988. *---------------------------------------------------------------------- */ #include #include #include #include "quantize.h" static char SCCSId[] = "SCCS ID: %W% %G% %U"; int NCOLS = 0; char *progname; int debug = 1; int dither = 0; /* This turns on dithering */ int fs_dither = 1; /* This turns on Floyd-Steinberg dithering */ #define DITH 8 int Dither[DITH][DITH] = { { 0, 32, 8, 40, 2, 34, 10, 42}, {48, 16, 56, 24, 50, 18, 58, 26}, {12, 44, 4, 36, 14, 46, 6, 38}, {60, 28, 52, 20, 62, 30, 54, 22}, { 3, 35, 11, 43, 1, 33, 9, 41}, {51, 19, 59, 27, 49, 17, 57, 25}, {15, 47, 7, 39, 13, 45, 5, 37}, {63, 31, 55, 23, 61, 29, 53, 21} }; /* This is only temporarily global: */ out_pix_t *quant_image; /* the quantized image - cmap indices */ main (argc, argv) int argc; char **argv; { char *filename, *outfilename; int rows = 0, NCOLS = 0, cmaplen = 0; rgb_pix_t *image; /* rgb image storage */ cmap_t *cmap; /* color map; rgb byte triples */ unsigned int *hist; /* histogram of color values: indices are formed from 5 bits each of r,g,b */ unsigned int *maptable; /* quantized color map table: indices are colors, values are color map indices */ int i; /* temp! */ progname = argv[0]; if (argc != 6) { dbug1("Usage: %s infile rows cols colormaplen outfile\n", argv[0]); dbug0("Converts 24-bit rgb image to n-bit colormapped image.\n"); exit(1); } filename = argv[1]; rows = atoi(argv[2]); NCOLS = atoi(argv[3]); cmaplen = atoi(argv[4]); outfilename = argv[5]; /* dynamically allocate all the various structures */ NEW(image, rgb_pix_t, rows * NCOLS * RGB_BYTES); NEW(quant_image, out_pix_t, rows * NCOLS); NEW(cmap, cmap_t, 3 * cmaplen); NEW(hist, unsigned int, HIST_SIZE); maptable = hist; /* reuse the same storage for the map table */ /* Perform the algorithm */ ReadImage(filename, image, rows, NCOLS); MakeHist(image, rows * NCOLS, hist); /* ShowHist(hist);*/ MakeCmap(hist, cmap, cmaplen); ComputeMapping(cmap, cmaplen, maptable); RemapImage(image, rows, NCOLS, cmap, cmaplen, maptable, quant_image); WriteImage(outfilename, quant_image, rows, NCOLS, cmap, cmaplen); /* Show the result */ #ifdef GRAPHICS mgiasngp(0,0); for (i = 0; i < cmaplen*3; i+=3) { printf("Before LoadMap: cmap[%4d] = %3d, %3d, %3d\n", i/3, cmap[i], cmap[i+1], cmap[i+2]); } LoadMap(cmap, cmaplen); Draw(quant_image, rows, NCOLS); mgideagp(); #endif } /* This assumes that the image is stored in packed r,g,b byte triples in */ /* row-major order. */ ReadImage(name, array, rows, cols) char *name; rgb_pix_t *array; int rows, cols; { int file; int xfercount; file = open(name, O_RDONLY); if (file == -1) { dbug2("%s: Can't open file %s for reading.\n", progname, name); perror(name); exit(1); } dbug0("Reading file..."); xfercount = read(file, array, RGB_BYTES * rows * cols); if (xfercount == -1) { dbug2("%s: Can't read file %s.\n", progname, name); perror(name); exit(1); } if (xfercount != RGB_BYTES * rows * cols) { dbug2("File %s ended prematurely after %d bytes: continuing.\n", name, xfercount); } dbug0("Done.\n"); } /* Write out the quantized image */ WriteImage(name, array, rows, cols, cmap, cmaplen) char *name; out_pix_t *array; int rows, cols; cmap_t *cmap; int cmaplen; { int file; int xfercount; struct FileData { int rows, cols, cmaplen, dummy; } fdata; int i; fdata.rows = rows; fdata.cols = cols; fdata.cmaplen = cmaplen; fdata.dummy = 0; file = open(name, O_WRONLY | O_TRUNC | O_CREAT, 0666); if (file == -1) { dbug2("%s: Can't open file %s for writing.\n", progname, name); perror(name); exit(1); } dbug0("Writing file..."); dbug1("Header is %d bytes.\n", sizeof(fdata)); xfercount = write(file, &fdata, sizeof(fdata)); if (xfercount != sizeof(fdata)) { dbug3("%s: Can't write file %s's header. Wrote %d bytes.\n", progname, name, xfercount); if (xfercount == -1) perror(name); exit(1); } dbug1("Color map is %d bytes.\n", 3 * cmaplen); xfercount = write(file, cmap, 3 * cmaplen); if (xfercount != 3*cmaplen) { dbug3("%s: Can't write file %s's color map. Wrote %d bytes.\n", progname, name, xfercount); if (xfercount == -1) perror(name); exit(1); } xfercount = write(file, array, rows * cols); if (xfercount != rows * cols) { dbug3("%s: Can't write image to file %s. Wrote %d bytes.\n", progname, name, xfercount); if (xfercount == -1) perror(name); exit(1); } dbug0("Done.\n"); close(file); } MakeHist(image, npix, hist) rgb_pix_t *image; int npix; unsigned int *hist; { register int pix; /* pixel index */ register rgb_pix_t *ppix; /* pointer to the actual triple */ /* Clear out the histogram array */ for (pix = HIST_SIZE - 1; pix >= 0; pix--) hist[pix] = 0; dbug0("Making histogram...\n"); for (pix = npix-1; pix >= 0; pix--) { if (pix % 25000 == 0) dbug1("%d \r", pix); ppix = PIXPIX(image, pix); /* Increment the histogram bucket corresponding to the value at pix: Only five bits of the color value are used to increase clumping in the histogram. The histogram is indexed by the five bits each of red, green and blue all strung together. */ hist[(*ppix >> HIST_SHIFT << (HIST_QUANT_BITS * 2)) | (*(ppix+1) >> HIST_SHIFT << HIST_QUANT_BITS) | (*(ppix+2) >> HIST_SHIFT)]++; } dbug0("Done.\n"); } ShowHist(hist) unsigned int *hist; { register int i, nmaps = 0; printf("RED\tGREEN\tBLUE\n"); for (i = 0; i < HIST_SIZE; i++) { if (hist[i] != 0) { nmaps++; printf("%-d\t%-d\t%-d\t= %d.\n", i >> 10 << 3, (i >> 5 & 0x1f) << 3, (i & 0x1f) << 3, hist[i]); } } dbug1("%d different pixel values (5-bit quantized).\n", nmaps); } /* Compute the map color for each original color */ /* This should be done dynamically while remapping... */ ComputeMapping(cmap, cmaplen, maptable) cmap_t *cmap; int cmaplen; unsigned int *maptable; { register int mapcolor, d, mindist, minmap; int orig_color; for (orig_color = 0; orig_color < HIST_SIZE; orig_color++) { mindist = BIG_DISTANCE; for (mapcolor = 3*(cmaplen-1); mapcolor >= 0; mapcolor -= 3) { if ((d = DIST(((orig_color >> 10) & 0x1f) << 3, ((orig_color >> 5) & 0x1f) << 3, (orig_color & 0x1f) << 3, cmap[mapcolor], cmap[mapcolor+1], cmap[mapcolor+2])) < mindist) { mindist = d; minmap = mapcolor; } } maptable[orig_color] = minmap/3; if ((orig_color & 0x3ff) == 0) dbug1("%d/31 \r", orig_color >> 10); } } /* Pick color map values that are close to the real ones, using the map * computed above in ComputeMapping(). */ /* Now with optional dither! */ RemapImage(image, rows, cols, cmap, cmaplen, maptable, quant_image) rgb_pix_t *image; /* original image (rgb) */ int rows, cols; /* size of the image */ cmap_t *cmap; /* color map (rgb) */ int cmaplen; /* number of entries in cmap */ unsigned int *maptable; /* mapping table from rgb5 to cmap indices */ out_pix_t *quant_image; /* quantized image */ { int npix = rows * cols; register unsigned int pix; register int mapentry; int nearestcm; dbug0("Remapping image..."); for (pix = 0; pix < npix; pix++) { mapentry = ((PIXRED(image, pix) >> 3 & 0x1f) << 10) | ((PIXGREEN(image, pix) >> 3 & 0x1f) << 5) | (PIXBLUE(image, pix) >> 3 & 0x1f); ASSERT (mapentry >= 0 && mapentry < HIST_SIZE) nearestcm = maptable[mapentry]; if (fs_dither) { register int r, g, b; register int errr, errg, errb; /* Compute error in current pixel */ errr = PIXRED(image, pix) - cmap[nearestcm * 3]; errg = PIXGREEN(image, pix) - cmap[nearestcm * 3 + 1]; errb = PIXBLUE(image, pix) - cmap[nearestcm * 3 + 2]; /* Propagate errors right and down */ if (pix % cols != cols - 1) { /* right gets 3/8 of the error */ r = (int) PIXRED(image,pix+1) + errr * 3 / 8; g = (int) PIXGREEN(image,pix+1) + errg * 3 / 8; b = (int) PIXBLUE(image,pix+1) + errb * 3 / 8; PIXRED(image,pix+1) = clamp(r, 0, 255); PIXGREEN(image,pix+1) = clamp(g, 0, 255); PIXBLUE(image,pix+1) = clamp(b, 0, 255); } if (pix < npix - cols) { /* down gets another 3/8 */ r = (int) PIXRED(image,pix+cols) + errr * 3 / 8; g = (int) PIXGREEN(image,pix+cols) + errg * 3 / 8; b = (int) PIXBLUE(image,pix+cols) + errb * 3 / 8; PIXRED(image,pix+cols) = clamp(r, 0, 255); PIXGREEN(image,pix+cols) = clamp(g, 0, 255); PIXBLUE(image,pix+cols) = clamp(b, 0, 255); } if (pix < npix - cols - 1) { /* and diagonally down gets the last 1/4. */ r = (int) PIXRED(image,pix+cols+1) + errr / 4; g = (int) PIXGREEN(image,pix+cols+1) + errg / 4; b = (int) PIXBLUE(image,pix+cols+1) + errb / 4; PIXRED(image,pix+cols+1) = clamp(r, 0, 255); PIXGREEN(image,pix+cols+1) = clamp(g, 0, 255); PIXBLUE(image,pix+cols+1) = clamp(b, 0, 255); } /* Store the current pixel */ quant_image[pix] = nearestcm; } else if (dither) { register unsigned int r, g, b; register unsigned int nr, ng, nb; register int oppr, oppg, oppb; int oppnrcm; /* true rgb value for pixel */ r = PIXRED(image, pix); g = PIXGREEN(image, pix); b = PIXBLUE(image, pix); /* rgb values for nearest color */ nr = cmap[nearestcm * 3]; ng = cmap[nearestcm * 3 + 1]; nb = cmap[nearestcm * 3 + 2]; /* Color as far from rgb as nrngnb but in the other direction */ oppr = min(255, max(0, 2 * (int) r - (int) nr)); oppg = min(255, max(0, 2 * (int) g - (int) ng)); oppb = min(255, max(0, 2 * (int) b - (int) nb)); /* Nearest match for opposite color: */ oppnrcm = maptable[((oppr >> 3 & 0x1f) << 10) | ((oppg >> 3 & 0x1f) << 5) | (oppb >> 3 & 0x1f)]; /* If they're not the same, dither between them. */ /* Dither constant is measured by where the true color lies between the two nearest approximations. Since the most nearly opposite color is not necessarily on the line from the nearest through the true color, some triangulation error can be introduced. In the worst case the r-nr distance can actually be less than the nr-oppr distance. */ if (oppnrcm != nearestcm) { register int dither_const; int oppcmr = cmap[oppnrcm * 3]; int oppcmg = cmap[oppnrcm * 3 + 1]; int oppcmb = cmap[oppnrcm * 3 + 2]; dither_const = min(63, (64 * DIST(r, g, b, nr, ng, nb)) / DIST(nr, ng, nb, oppcmr, oppcmg, oppcmb)); if (!(dither_const >= 0 && dither_const < 64)) { dbug2("Pix: %d, Dither constant: %d\n", pix, dither_const); fprintf(stderr,"Near: %d %d %d\tTrue: %d %d %d\tOpp: %d %d %d / %d %d % nr, ng, nb, r, g, b, oppr, oppg, oppb, oppcmr, oppcmg, oppcmb) } ASSERT (dither_const >= 0 && dither_const < 64); if (Dither[(pix/cols)%DITH][(pix%cols)%DITH] < dither_const) quant_image[pix] = oppnrcm; else quant_image[pix] = nearestcm; } else quant_image[pix] = nearestcm; } else { quant_image[pix] = nearestcm; } } dbug0("Done.\n"); /* Cmap is still ok here */ } /* Load up the frame buffer color map - device specific */ LoadMap(cmap, cmaplen) cmap_t *cmap; int cmaplen; { register int i; dbug1("Loading %d color map entries...", cmaplen); #ifdef GRAPHICS for (i = 0; i < cmaplen*3; i+=3) { register unsigned int r = cmap[i], g = cmap[i+1], b = cmap[i+2]; mgicm(i/3, (r << 16) | (g << 8) | b); dbug2("Color %d is 0x%x.\n", i/3, (r << 16) | (g << 8) | b); } #endif dbug0("done.\n"); } /* Draw the remapped image - device specific */ Draw(image, rows, cols) out_pix_t *image; int rows, cols; { #ifdef GRAPHICS mgiimage(image, 0, 0, cols-1, rows-1); #endif } SHAR_EOF fi if test -f 'split.c' then echo shar: "will not over-write existing file 'split.c'" else cat << \SHAR_EOF > 'split.c' /*---------------------------------------------------------------------- * split up color space for quantize program *---------------------------------------------------------------------- */ #include #include #include "quantize.h" /* Pick your choice of algorithm: */ /* MIDPOINT_CUT or POPULARITY */ /* (Choose in makefile) */ #ifdef MIDPOINT_CUT typedef unsigned int color_index_t; /* My method here is to allocate a clist array big enough for all the * possible colors, and then recursively split it. Since the recursion * is depth-first and we're building a LIST, not a TREE of boxes, we ca * scramble the colors within a parent box because that parent won't be * any more. We end up with a list of boxes into which all the existin * colors are partitioned, as described in Heckbert's paper. */ struct Box { struct Box *next; int rmin, rmax, gmin, gmax, bmin, bmax; color_index_t *clist; /* an array of color indices */ int clist_len; /* how many cindexes in the list */ }; static int nboxes, maxboxes; /* number of boxes so far and max needed */ MakeCmap(hist, cmap, cmaplen) unsigned int *hist; cmap_t *cmap; int cmaplen; { register int i; struct Box *box; nboxes = 1; maxboxes = cmaplen; NEW(box, struct Box, 1); dbug0("Making color boxes.\n"); MakeTopBox(box, hist); /* Make the first (top-level) box */ while (nboxes < maxboxes) SplitBoxes(box); FindColors(box, cmap, cmaplen); } MakeTopBox(box, hist) /* Make the top box from the histogram */ struct Box *box; color_index_t *hist; { register int i; ASSERT(box != NULL); ASSERT(hist != NULL); /* Allocate enough color cells to hold all the colors */ NEW(box->clist, color_index_t, HIST_SIZE); box->clist_len = 0; box->next = NULL; /* For each possible rgb color */ for (i = 0; i < HIST_SIZE; i++) { if (hist[i] > 0) /* if any pixels are that color, */ box->clist[box->clist_len++] = i; } Compact(box); fprintf(stderr,"Top box at 0x%x: %d colors, R (%d -> %d) G (%d -> %d) B (%d -> %d)\n", box, box->clist_len, box->rmin, box->rmax, box->gmin, box->gmax, box->bmin, box->bmax); } /* Shrink the box limits until it just encloses the points within it. */ Compact(box) struct Box *box; { register int i; ASSERT(box != NULL); box->rmin = 256; box->gmin = 256; box->bmin = 256; /* Greater than any value */ box->rmax = -1; box->gmax = -1; box->bmax = -1; /* Less than any rgb value */ for (i = box->clist_len-1; i >= 0; i--) { int r = (box->clist[i] >> 10) & 0x1f; /* colors associated with 'i' */ int g = (box->clist[i] >> 5) & 0x1f; int b = (box->clist[i]) & 0x1f; /* Expand the box until it just fits the colors tightly */ if (r < box->rmin) box->rmin = r; if (g < box->gmin) box->gmin = g; if (b < box->bmin) box->bmin = b; if (r > box->rmax) box->rmax = r; if (g > box->gmax) box->gmax = g; if (b > box->bmax) box->bmax = b; } } /* Split all the boxes once each, stop if we've got enough */ SplitBoxes(box) struct Box *box; { struct Box *nextbox; dbug1("Splitting all the boxes with %d done so far.\n", nboxes); while (box != NULL && nboxes < maxboxes) { nextbox = box->next; SplitBox(box); /* SplitBox changes box->next! */ box = nextbox; } } static char longdim; /* char representing the longest dimension */ int CompareColors(c1, c2) color_index_t *c1, *c2; { switch (longdim) { case 'r': return ((*c1 >> 10 & 0x1f) - (*c2 >> 10 & 0x1f)); case 'g': return ((*c1 >> 5 & 0x1f) - (*c2 >> 5 & 0x1f)); case 'b': return ((*c1 & 0x1f) - (*c2 & 0x1f)); default: fprintf(stderr,"Illegal longdim 0x%x in CompareColors.\n", longdim); exit(1); } } /* Divide the box across its longest dimension at its median point * This is NOT recursive - we split all the boxes once, then if there's * more colors to be assigned we split all of them again, and so on. */ SplitBox(box) struct Box *box; { struct Box *newbox; /* the 'other half' of the split box */ ASSERT(box != NULL); if (nboxes > maxboxes) return; if (box->clist_len <= 1) return; nboxes++; if (box->rmax - box->rmin >= box->gmax - box->gmin && box->rmax - box->rmin >= box->bmax - box->bmin) longdim = 'r'; else if (box->gmax - box->gmin >= box->rmax - box->rmin && box->gmax - box->gmin >= box->bmax - box->bmin) longdim = 'g'; else if (box->bmax - box->bmin >= box->gmax - box->gmin && box->bmax - box->bmin >= box->rmax - box->rmin) longdim = 'b'; else { fprintf(stderr,"No longest dimension in SplitBox??!\n"); fprintf(stderr,"This box: R (%d -> %d) G (%d -> %d) B (%d -> %d)\n", box->rmin, box->rmax, box->gmin, box->gmax, box->bmin, box- exit(1); } qsort(box->clist, box->clist_len, sizeof(color_index_t), CompareColors); /* Set up the new box and put it after the old box */ NEW(newbox, struct Box, 1); newbox->next = box->next; box->next = newbox; /* If len is odd, put the extra one in the old box */ newbox->clist_len = box->clist_len / 2; box->clist_len -= newbox->clist_len; newbox->clist = box->clist + box->clist_len; Compact(box); Compact(newbox); } /* Find the representative color in each box and put it into cmap. */ FindColors(box, cmap, cmaplen) /* here box is a list of boxes */ struct Box *box; cmap_t *cmap; int cmaplen; { register int r, g, b; register int i; int cml = 0; while (box != NULL) { r = 0, g = 0, b = 0; for (i=0; i < box->clist_len; i++) { r += (box->clist[i] >> 10 & 0x1f) << 3; g += (box->clist[i] >> 5 & 0x1f) << 3; b += (box->clist[i] & 0x1f) << 3; } ASSERT(r/box->clist_len < 256); ASSERT(g/box->clist_len < 256); ASSERT(b/box->clist_len < 256); cmap[cml*3] = r / box->clist_len; cmap[cml*3+1] = g / box->clist_len; cmap[cml*3+2] = b / box->clist_len; box = box->next; cml++; } ASSERT(cmaplen == cml); /* since there should be 'cmaplen' boxes */ for (i = 0; i < cmaplen*3; i+=3) { printf("Cmap[%4d] = %3d, %3d, %3d\n", i/3, cmap[i], cmap[i+1], cmap[i+2]); } } #endif #ifdef POPULARITY static unsigned int *HiSt; CompareHist(v1, v2) int *v1, *v2; { return (HiSt[*v2] - HiSt[*v1]); } MakeCmap(hist, cmap, cmaplen) unsigned int *hist; cmap_t *cmap; int cmaplen; { register int i; unsigned int *hptrs; /* This is the popularity algorithm (Boyle & Lippman, Archmac, 1978) */ /* First we sort the histogram: set up a bunch of pointers and dbug1("Getting %d ints for hptrs...", HIST_SIZE); /* NEW(hptrs, unsigned int, HIST_SIZE);*/ hptrs = (unsigned int *)quant_image; /* use existing storage temporarily */ dbug0("initting it..."); for (i = 0; i < HIST_SIZE; i++) hptrs[i] = i; HiSt = hist; dbug0("Sorting..."); qsort (hptrs, HIST_SIZE, sizeof(unsigned int), CompareHist); dbug0("making color map..."); for (i = 0; i < cmaplen; i++) { cmap[3*i] = hptrs[i] >> 10 << 3; cmap[3*i+1] = (hptrs[i] >> 5 & 0x1f) << 3; cmap[3*i+2] = (hptrs[i] & 0x1f) << 3; } dbug0("done.\n"); dbug1("Max pixels in one bucket: %d.\n", hist[hptrs[0]]); } #endif POPULARITY SHAR_EOF fi if test -f 'quantize.h' then echo shar: "will not over-write existing file 'quantize.h'" else cat << \SHAR_EOF > 'quantize.h' /*---------------------------------------------------------------------- * header file for color-quantizer * by Gary Oberbrunner 1/5/88 *---------------------------------------------------------------------- */ #define HIST_QUANT_BITS 5 /* Histogram clump quantization */ #define HIST_SHIFT (8 - HIST_QUANT_BITS) #define HIST_SIZE (1 << (HIST_QUANT_BITS * 3)) #define RGB_BYTES 4 /* Bytes per rgb pixel (4 for alpha channel) */ /* We need the next definition to reference a variable-size array */ /* NCOLS is the number of columns in the array */ /* Each pixel is stored as an rgb byte triplet */ /* This is the address of the pixel triplet: */ #define RCPIX(array, row, col) (array + RGB_BYTES * (row * NCOLS + col)) #define PIXPIX(array, pixel) (array + RGB_BYTES * (pixel)) /* These are the pixel components: */ #define RED(array, row, col) (*(RCPIX(array, row, col))) #define GREEN(array, row, col) (*(RCPIX(array, row, col) + 1)) #define BLUE(array, row, col) (*(RCPIX(array, row, col) + 2)) #define PIXRED(array, pix) (*(PIXPIX(array, pix))) #define PIXGREEN(array, pix) (*(PIXPIX(array, pix) + 1)) #define PIXBLUE(array, pix) (*(PIXPIX(array, pix) + 2)) /* Here is the rgb-space distance metric: */ #define DIST(r1,g1,b1,r2,g2,b2) \ (int) \ (3 * ((r1)-(r2)) * ((r1)-(r2)) + \ 4 * ((g1)-(g2)) * ((g1)-(g2)) + \ 2 * ((b1)-(b2)) * ((b1)-(b2))) #define BIG_DISTANCE 1000000 /* bigger than any real distance */ typedef unsigned char cmap_t; typedef unsigned char rgb_pix_t; typedef unsigned char out_pix_t; extern out_pix_t *quant_image; /* the quantized image - cmap indices */ extern int debug; SHAR_EOF fi if test -f 'std.h' then echo shar: "will not over-write existing file 'std.h'" else cat << \SHAR_EOF > 'std.h' /* Standard include file with lots of generally useful stuff */ /* by Gary Oberbrunner */ #define min(x,y) ((x) < (y) ? (x) : (y)) #define max(x,y) ((x) > (y) ? (x) : (y)) #define clamp(x, mn, mx) (((x) <= (mn)) ? (mn) : (((x) >= (mx)) ? (mx) : (x))) #define SECURITY #ifdef SECURITY #define ASSERT(exp) { if (!(exp)) { fprintf(stderr,\ "Assertion error: %s, line %d. Assertion exp failed.\n",\ __FILE__, __LINE__);\ exit(69); } } #else #define ASSERT(exp) #endif #define dbug0(str) \ if (debug) fprintf(stderr, str); #define dbug1(str, arg1) \ if (debug) fprintf(stderr, str, arg1); #define dbug2(str, arg1, arg2) \ if (debug) fprintf(stderr, str, arg1, arg2); #define dbug3(str, arg1, arg2, arg3) \ if (debug) fprintf(stderr, str, arg1, arg2, arg3); #define dbug4(str, arg1, arg2, arg3, arg4) \ if (debug) fprintf(stderr, str, arg1, arg2, arg3, arg4); extern char *malloc(); /* * NEW is a macro to malloc 'n' new variables of type 'type'. */ #define NEW(var, type, num) \ if ((var = (type *) malloc((num) * sizeof(type)))==NULL) \ { fprintf(stderr,\ "ERROR: Out of memory.\nLast request:\n");\ fprintf(stderr,\ "\tNumber: num\n\tType : type\n\tName : var.\n");\ fprintf(stderr,"File %s, line %d\n", __FILE__, __LINE__);\ fprintf(stderr,\ "\tTotal requested: num=%d x sizeof(type)=%d\n\t =%d bytes.\n",\ num, sizeof(type), (num) * sizeof(type));\ exit(99); } SHAR_EOF fi exit 0 # End of shell archive -- Remember, -Truth is not beauty; Information is not knowledge; / Beauty is not love; Gary Oberbrunner Knowledge is not wisdom; / Love is not music; ...!masscomp!garyo Wisdom is not truth; ----/ Music is the best. - FZ ----------------------------------------------------------------------------- From: awpaeth@watcgl.waterloo.edu (Alan W. Paeth) Subject: Re: Advanced Dither Needed Date: 8 Jan 88 22:41:45 GMT A comment on dither - the "quick and dirty" approach to uniform division of the color space (so that R, G and B can be treated separably) very slices up 8 bit pixels as 3 bits for red and green, 2 bits for blue (where the eye is least sensitive). This is an unnecessary oversimplification that leaves blue with only two mid-range intensities. It suggests itself because we tend to think in of "bits" per color at the hardware level, not "discrete number of inten steps". This approach is further suggested because the pixel channels R G and B can be written separably through the proper setting of the wri mask, but in practice, the pixel is normally written as a single byte not as three consecutive "inking" passes. One useful property of this ap is that there are a number of "greys" in the color space, as the chann precisions are all multiples of each other. A better approach is to form a color table containing a Cartesian product of intensites on a range that is not necessarily a power of two. For we take red = [0..5], green = [0..6], blue = [0..4] (suitably normalized to represent intensities on the range [0.0..1.0] then there are 6*7*5 table entries, and noticibly better representation in blue. Our Graphics Lab takes this approach for writing on our VAX GPX's under X. We must use a uniform space, because many simultaneous image (window be on the display, and each would have a different histogram (if Heckbert' approach were used), potentially overflowing the color table. In our X-write IM tool takes color images of arbitrary precision and Floyd-Steinb converts them into this space of 6,7,5 steps, and thus all color ima identical entries within the color table (which is a consequence of how X manages requests for color table entries). B/W images typically alloc level grey ramp which can co-exist within the remaining space, leaving 14 (256=210+32+14) entries for use by other applications, should they re colors other than saturated yellow, blue, white, etc. On GPX's with smaller color tables, the same approach is used. Once again, ~3/4 of the hardware color table is allocated, giving 12 free color the program divides as 2*3*2 in RGB. Other useful sets have been 6*6*6, which gives the largest uniform allocation in 256 entries, thus allowin greys, and 6*7*6, which uses 252 out of 256 colors and is much better overall than the 8*8*4 set which is used implicitly when one allocates us "three bits red and green, two for blue" scheme. /Alan Paeth Computer Graphics Lab University of Waterloo ----------------------------------------------------------------------------- From: lou@aramis.rutgers.edu (Lou Steinberg) Subject: Re: Advanced Dither Needed Date: 11 Jan 88 04:46:33 GMT In article <2741@masscomp.UUCP> garyo@masscomp.UUCP (Gary Oberbrunner) writes: > [for] Floyd & Steinberg's dithering algorithm [...] > On some images it can help to add some random noise; for instance if you > have a smooth (flat-shaded) surface that's just below one of the c > the map, the error will build up slowly until you get a line of brighter > color somewhere in the surface. A bit o' noise gets rid of that l The other way to get rid of it is to alternate scanning left to right and right to left, i.e. even scan lines one way, odd scan lines the other. The line of brighter color essentially comes because pixels on that line do not communicate - there is no path for the error value at one to affect the value chosen for the other. By scanning in a zig zag fashion, you ensure that each pixel communicates with every other pixel scanned after it. -- Lou Steinberg uucp: {pretty much any major site}!rutgers!aramis.rutgers.edu!lou arpa: lou@aramis.rutgers.edu ----------------------------------------------------------------------------- From: lou@aramis.rutgers.edu (Lou Steinberg) Subject: Re: Advanced Dither Needed Date: 11 Jan 88 05:02:13 GMT In article <2741@masscomp.UUCP> garyo@masscomp.UUCP (Gary Oberbrunner) writes: > But the wayd [Floyd/Steinberg] dither works is like this (it's [...] > Add 3/8 of that error to the pixel to the right, and 3/8 to the pixel > below the current pixel. The remaining 1/4 goes to the pixel diagona > below. I should point out that the actual fractions we used were, assuming you are at X, moving left to right: X 7/16 3/16 5/16 1/16 Note that the error goes to four neighbors, not three. I think this will probably do better (at least for black and white) than the 3/8-3/8-1/4 distribution, at the cost of greater processing. I have seen the 3/8-3/8-1/4 distribution described as "our" algorithm before, but I have no idea who the credit really belongs to. Also, I should add that if you do zig-zag scanning (see my immediately previous message), it is sufficient (but not quite as good) to send half the error one pixel ahead (e.g. to the right on lines you scan left to right), and half one pixel straight down. Again, this is for black and white; I've not tried it with color. -- Lou Steinberg uucp: {pretty much any major site}!rutgers!aramis.rutgers.edu!lou arpa: lou@aramis.rutgers.edu Note: In a seperate message, another person confirmed that this method works well with color too.