diff options
Diffstat (limited to 'src/runtime/rimage.r')
-rw-r--r-- | src/runtime/rimage.r | 930 |
1 files changed, 930 insertions, 0 deletions
diff --git a/src/runtime/rimage.r b/src/runtime/rimage.r new file mode 100644 index 0000000..775b836 --- /dev/null +++ b/src/runtime/rimage.r @@ -0,0 +1,930 @@ +/* + * File: rimage.c + * Functions and data for reading and writing GIF images + */ + +#ifdef Graphics + +#define GifSeparator 0x2C /* (',') beginning of image */ +#define GifTerminator 0x3B /* (';') end of image */ +#define GifExtension 0x21 /* ('!') extension block */ +#define GifControlExt 0xF9 /* graphic control extension label */ +#define GifEmpty -1 /* internal flag indicating no prefix */ + +#define GifTableSize 4096 /* maximum number of entries in table */ +#define GifBlockSize 255 /* size of output block */ + +typedef struct lzwnode { /* structure of LZW encoding tree node */ + unsigned short tcode; /* token code */ + unsigned short child; /* first child node */ + unsigned short sibling; /* next sibling */ + } lzwnode; + +static int gfread (char *fn, int p); +static int gfheader (FILE *f); +static int gfskip (FILE *f); +static void gfcontrol (FILE *f); +static int gfimhdr (FILE *f); +static int gfmap (FILE *f, int p); +static int gfsetup (void); +static int gfrdata (FILE *f); +static int gfrcode (FILE *f); +static void gfinsert (int prev, int c); +static int gffirst (int c); +static void gfgen (int c); +static void gfput (int b); + +static int gfwrite (wbp w, char *filename, + int x, int y, int width, int height); +static void gfmktree (lzwnode *tree); +static void gfout (int tcode); +static void gfdump (void); + +static int medcut (long hlist[], struct palentry plist[], int ncolors); + +static FILE *gf_f; /* input file */ + +static int gf_gcmap, gf_lcmap; /* global color map? local color map? */ +static int gf_nbits; /* number of bits per pixel */ +static int gf_ilace; /* interlace flag */ +static int gf_width, gf_height; /* image size */ + +static short *gf_prefix, *gf_suffix; /* prefix and suffix tables */ +static int gf_free; /* next free position */ + +static struct palentry *gf_paltbl; /* palette table */ +static unsigned char *gf_string; /* incoming image data */ +static short *gf_pixels; /* outgoing image data */ +static unsigned char *gf_nxt, *gf_lim; /* store pointer and its limit */ +static int gf_row, gf_step; /* current row and step size */ + +static int gf_cdsize; /* code size */ +static int gf_clear, gf_eoi; /* values of CLEAR and EOI codes */ +static int gf_lzwbits, gf_lzwmask; /* current bits per code */ + +static unsigned char *gf_obuf; /* output buffer */ +static unsigned long gf_curr; /* current partial byte(s) */ +static int gf_valid; /* number of valid bits */ +static int gf_rem; /* remaining bytes in this block */ + +/* + * readGIF(filename, p, imd) - read GIF file into image data structure + * + * p is a palette number to which the GIF colors are to be coerced; + * p=0 uses the colors exactly as given in the GIF file. + */ +int readGIF(filename, p, imd) +char *filename; +int p; +struct imgdata *imd; + { + int r; + + r = gfread(filename, p); /* read image */ + + if (gf_prefix) free((pointer)gf_prefix); /* deallocate temp memory */ + if (gf_suffix) free((pointer)gf_suffix); + if (gf_f) fclose(gf_f); + + if (r != Succeeded) { /* if no success, free mem */ + if (gf_paltbl) free((pointer) gf_paltbl); + if (gf_string) free((pointer) gf_string); + return r; /* return Failed or Error */ + } + + imd->width = gf_width; /* set return variables */ + imd->height = gf_height; + imd->paltbl = gf_paltbl; + imd->data = gf_string; + + return Succeeded; /* return success */ + } + +/* + * gfread(filename, p) - read GIF file, setting gf_ globals + */ +static int gfread(filename, p) +char *filename; +int p; + { + int i; + + gf_f = NULL; + gf_prefix = NULL; + gf_suffix = NULL; + gf_string = NULL; + + if (!(gf_paltbl = (struct palentry *)malloc(256 * sizeof(struct palentry)))) + return Failed; + + if ((gf_f = fopen(filename, "rb")) == NULL) + return Failed; + + for (i = 0; i < 256; i++) /* init palette table */ + gf_paltbl[i].used = gf_paltbl[i].valid = gf_paltbl[i].transpt = 0; + + if (!gfheader(gf_f)) /* read file header */ + return Failed; + if (gf_gcmap) /* read global color map, if any */ + if (!gfmap(gf_f, p)) + return Failed; + if (!gfskip(gf_f)) /* skip to start of image */ + return Failed; + if (!gfimhdr(gf_f)) /* read image header */ + return Failed; + if (gf_lcmap) /* read local color map, if any */ + if (!gfmap(gf_f, p)) + return Failed; + if (!gfsetup()) /* prepare to read image */ + return Error; + if (!gfrdata(gf_f)) /* read image data */ + return Failed; + while (gf_row < gf_height) /* pad if too short */ + gfput(0); + + return Succeeded; + } + +/* + * gfheader(f) - read GIF file header; return nonzero if successful + */ +static int gfheader(f) +FILE *f; + { + unsigned char hdr[13]; /* size of a GIF header */ + int b; + + if (fread((char *)hdr, sizeof(char), sizeof(hdr), f) != sizeof(hdr)) + return 0; /* header short or missing */ + if (strncmp((char *)hdr, "GIF", 3) != 0 || + !isdigit(hdr[3]) || !isdigit(hdr[4])) + return 0; /* not GIFnn */ + + b = hdr[10]; /* flag byte */ + gf_gcmap = b & 0x80; /* global color map flag */ + gf_nbits = (b & 7) + 1; /* number of bits per pixel */ + return 1; + } + +/* + * gfskip(f) - skip intermediate blocks and locate image + */ +static int gfskip(f) +FILE *f; + { + int c, n; + + while ((c = getc(f)) != GifSeparator) { /* look for start-of-image flag */ + if (c == EOF) + return 0; + if (c == GifExtension) { /* if extension block is present */ + c = getc(f); /* get label */ + if ((c & 0xFF) == GifControlExt) + gfcontrol(f); /* process control subblock */ + while ((n = getc(f)) != 0) { /* read blks until empty one */ + if (n == EOF) + return 0; + n &= 0xFF; /* ensure positive count */ + while (n--) /* skip block contents */ + getc(f); + } + } + } + return 1; + } + +/* + * gfcontrol(f) - process control extension subblock + */ +static void gfcontrol(f) +FILE *f; + { + int i, n, c, t; + + n = getc(f) & 0xFF; /* subblock length (s/b 4) */ + for (i = t = 0; i < n; i++) { + c = getc(f) & 0xFF; + if (i == 0) + t = c & 1; /* transparency flag */ + else if (i == 3 && t != 0) { + gf_paltbl[c].transpt = 1; /* set flag for transpt color */ + gf_paltbl[c].valid = 0; /* color is no longer "valid" */ + } + } + } + +/* + * gfimhdr(f) - read image header + */ +static int gfimhdr(f) +FILE *f; + { + unsigned char hdr[9]; /* size of image hdr excl separator */ + int b; + + if (fread((char *)hdr, sizeof(char), sizeof(hdr), f) != sizeof(hdr)) + return 0; /* header short or missing */ + gf_width = hdr[4] + 256 * hdr[5]; + gf_height = hdr[6] + 256 * hdr[7]; + b = hdr[8]; /* flag byte */ + gf_lcmap = b & 0x80; /* local color map flag */ + gf_ilace = b & 0x40; /* interlace flag */ + if (gf_lcmap) + gf_nbits = (b & 7) + 1; /* if local map, reset nbits also */ + return 1; + } + +/* + * gfmap(f, p) - read GIF color map into paltbl under control of palette p + */ +static int gfmap(f, p) +FILE *f; +int p; + { + int ncolors, i, r, g, b, c; + struct palentry *stdpal = 0; + + if (p) + stdpal = palsetup(p); + + ncolors = 1 << gf_nbits; + + for (i = 0; i < ncolors; i++) { + r = getc(f); + g = getc(f); + b = getc(f); + if (r == EOF || g == EOF || b == EOF) + return 0; + if (p) { + c = *(unsigned char *)(rgbkey(p, r / 255.0, g / 255.0, b / 255.0)); + gf_paltbl[i].clr = stdpal[c].clr; + } + else { + gf_paltbl[i].clr.red = 257 * r; /* 257 * 255 -> 65535 */ + gf_paltbl[i].clr.green = 257 * g; + gf_paltbl[i].clr.blue = 257 * b; + } + if (!gf_paltbl[i].transpt) /* if not transparent color */ + gf_paltbl[i].valid = 1; /* mark as valid/opaque */ + } + + return 1; + } + +/* + * gfsetup() - prepare to read GIF data + */ +static int gfsetup() + { + int i; + word len; + + len = (word)gf_width * (word)gf_height; + gf_string = (unsigned char *)malloc(len); + gf_prefix = (short *)malloc(GifTableSize * sizeof(short)); + gf_suffix = (short *)malloc(GifTableSize * sizeof(short)); + if (!gf_string || !gf_prefix || !gf_suffix) + return 0; + for (i = 0; i < GifTableSize; i++) { + gf_prefix[i] = GifEmpty; + gf_suffix[i] = i; + } + + gf_row = 0; /* current row is 0 */ + gf_nxt = gf_string; /* set store pointer */ + + if (gf_ilace) { /* if interlaced */ + gf_step = 8; /* step rows by 8 */ + gf_lim = gf_string + gf_width; /* stop at end of one row */ + } + else { + gf_lim = gf_string + len; /* do whole image at once */ + gf_step = gf_height; /* step to end when full */ + } + + return 1; + } + +/* + * gfrdata(f) - read GIF data + */ +static int gfrdata(f) +FILE *f; + { + int curr, prev, c; + + if ((gf_cdsize = getc(f)) == EOF) + return 0; + gf_clear = 1 << gf_cdsize; + gf_eoi = gf_clear + 1; + gf_free = gf_eoi + 1; + + gf_lzwbits = gf_cdsize + 1; + gf_lzwmask = (1 << gf_lzwbits) - 1; + + gf_curr = 0; + gf_valid = 0; + gf_rem = 0; + + prev = curr = gfrcode(f); + while (curr != gf_eoi) { + if (curr == gf_clear) { /* if reset code */ + gf_lzwbits = gf_cdsize + 1; + gf_lzwmask = (1 << gf_lzwbits) - 1; + gf_free = gf_eoi + 1; + prev = curr = gfrcode(f); + gfgen(curr); + } + else if (curr < gf_free) { /* if code is in table */ + gfgen(curr); + gfinsert(prev, gffirst(curr)); + prev = curr; + } + else if (curr == gf_free) { /* not yet in table */ + c = gffirst(prev); + gfgen(prev); + gfput(c); + gfinsert(prev, c); + prev = curr; + } + else { /* illegal code */ + if (gf_nxt == gf_lim) + return 1; /* assume just extra stuff after end */ + else + return 0; /* more badly confused */ + } + curr = gfrcode(f); + } + + return 1; + } + +/* + * gfrcode(f) - read next LZW code + */ +static int gfrcode(f) +FILE *f; + { + int c, r; + + while (gf_valid < gf_lzwbits) { + if (--gf_rem <= 0) { + if ((gf_rem = getc(f)) == EOF) + return gf_eoi; + } + if ((c = getc(f)) == EOF) + return gf_eoi; + gf_curr |= ((c & 0xFF) << gf_valid); + gf_valid += 8; + } + r = gf_curr & gf_lzwmask; + gf_curr >>= gf_lzwbits; + gf_valid -= gf_lzwbits; + return r; + } + +/* + * gfinsert(prev, c) - insert into table + */ +static void gfinsert(prev, c) +int prev, c; + { + + if (gf_free >= GifTableSize) /* sanity check */ + return; + + gf_prefix[gf_free] = prev; + gf_suffix[gf_free] = c; + + /* increase code size if code bits are exhausted, up to max of 12 bits */ + if (++gf_free > gf_lzwmask && gf_lzwbits < 12) { + gf_lzwmask = gf_lzwmask * 2 + 1; + gf_lzwbits++; + } + + } + +/* + * gffirst(c) - return the first pixel in a map structure + */ +static int gffirst(c) +int c; + { + int d; + + if (c >= gf_free) + return 0; /* not in table (error) */ + while ((d = gf_prefix[c]) != GifEmpty) + c = d; + return gf_suffix[c]; + } + +/* + * gfgen(c) - generate and output prefix + */ +static void gfgen(c) +int c; + { + int d; + + if ((d = gf_prefix[c]) != GifEmpty) + gfgen(d); + gfput(gf_suffix[c]); + } + +/* + * gfput(b) - add a byte to the output string + */ +static void gfput(b) +int b; + { + if (gf_nxt >= gf_lim) { /* if current row is full */ + gf_row += gf_step; + while (gf_row >= gf_height && gf_ilace && gf_step > 2) { + if (gf_step == 4) { + gf_row = 1; + gf_step = 2; + } + else if ((gf_row % 8) != 0) { + gf_row = 2; + gf_step = 4; + } + else { + gf_row = 4; + /* gf_step remains 8 */ + } + } + + if (gf_row >= gf_height) { + gf_step = 0; + return; /* too much data; ignore it */ + } + gf_nxt = gf_string + ((word)gf_row * (word)gf_width); + gf_lim = gf_nxt + gf_width; + } + + *gf_nxt++ = b; /* store byte */ + gf_paltbl[b].used = 1; /* mark color entry as used */ + } + +/* + * writeGIF(w, filename, x, y, width, height) - write GIF image + * + * Returns Succeeded, Failed, or Error. + * We assume that the area specified is within the window. + */ +int writeGIF(w, filename, x, y, width, height) +wbp w; +char *filename; +int x, y, width, height; + { + int r; + + r = gfwrite(w, filename, x, y, width, height); + if (gf_f) fclose(gf_f); + if (gf_pixels) free((pointer)gf_pixels); + return r; + } + +/* + * gfwrite(w, filename, x, y, width, height) - write GIF file + * + * We write GIF87a format (not 89a) for maximum acceptability and because + * we don't need any of the extensions of GIF89. + */ + +static int gfwrite(w, filename, x, y, width, height) +wbp w; +char *filename; +int x, y, width, height; + { + unsigned char obuf[GifBlockSize]; + short *p, *q; + int i, c, cur, nc; + long h, npixels, hlist[1<<15]; + LinearColor *cp; + struct palentry paltbl[GIFMAX]; + lzwnode tree[GifTableSize + 1]; + + npixels = (long)width * (long)height; /* total length of data */ + + if (!(gf_f = fopen(filename, "wb"))) + return Failed; + if (!(gf_pixels = malloc(npixels * sizeof(short)))) + return Error; + + if (!capture(w, x, y, width, height, gf_pixels)) /* get data (rgb15) */ + return Error; + + memset(hlist, 0, sizeof(hlist)); + for (h = 0; h < npixels; h++) /* make histogram */ + hlist[gf_pixels[h]]++; + + nc = medcut(hlist, paltbl, GIFMAX); /* make palette using median cut alg */ + if (nc == 0) + return Error; + + gf_nbits = 1; /* figure out gif bits for nc colors */ + while ((1 << gf_nbits) < nc) + gf_nbits++; + if (gf_nbits < 2) + gf_cdsize = 2; + else + gf_cdsize = gf_nbits; + + gf_clear = 1 << gf_cdsize; /* set encoding variables */ + gf_eoi = gf_clear + 1; + gf_free = gf_eoi + 1; + gf_lzwbits = gf_cdsize + 1; + + /* + * Write the header, global color table, and image descriptor. + */ + + fprintf(gf_f, "GIF87a%c%c%c%c%c%c%c", width, width >> 8, height, height >> 8, + 0x80 | ((gf_nbits - 1) << 4) | (gf_nbits - 1), 0, 0); + + for (i = 0; i < (1 << gf_nbits); i++) { /* output color table */ + if (i < GIFMAX && i < nc) { + cp = &paltbl[i].clr; + putc(cp->red >> 8, gf_f); + putc(cp->green >> 8, gf_f); + putc(cp->blue >> 8, gf_f); + } + else { + putc(0, gf_f); + putc(0, gf_f); + putc(0, gf_f); + } + } + + fprintf(gf_f, "%c%c%c%c%c%c%c%c%c%c%c", GifSeparator, 0, 0, 0, 0, + width, width >> 8, height, height >> 8, gf_nbits - 1, gf_cdsize); + + /* + * Encode and write the image. + */ + gf_obuf = obuf; /* initialize output state */ + gf_curr = 0; + gf_valid = 0; + gf_rem = GifBlockSize; + + gfmktree(tree); /* initialize encoding tree */ + + gfout(gf_clear); /* start with CLEAR code */ + + p = gf_pixels; + q = p + npixels; + cur = hlist[*p++]; /* first pixel is special */ + while (p < q) { + c = hlist[*p++]; /* get code */ + for (i = tree[cur].child; i != 0; i = tree[i].sibling) + if (tree[i].tcode == c) /* find as suffix of previous string */ + break; + if (i != 0) { /* if found in encoding tree */ + cur = i; /* note where */ + continue; /* and accumulate more */ + } + gfout(cur); /* new combination -- output prefix */ + tree[gf_free].tcode = c; /* make node for new combination */ + tree[gf_free].child = 0; + tree[gf_free].sibling = tree[cur].child; + tree[cur].child = gf_free; + cur = c; /* restart string from single pixel */ + ++gf_free; /* grow tree to account for new node */ + if (gf_free > (1 << gf_lzwbits)) { + if (gf_free > GifTableSize) { + gfout(gf_clear); /* table is full; reset to empty */ + gf_lzwbits = gf_cdsize + 1; + gfmktree(tree); + } + else + gf_lzwbits++; /* time to make output one bit wider */ + } + } + + /* + * Finish up. + */ + gfout(cur); /* flush accumulated prefix */ + gfout(gf_eoi); /* send EOI code */ + gf_lzwbits = 7; + gfout(0); /* force out last partial byte */ + gfdump(); /* dump final block */ + putc(0, gf_f); /* terminate image (block of size 0) */ + putc(GifTerminator, gf_f); /* terminate file */ + + fflush(gf_f); + if (ferror(gf_f)) + return Failed; + else + return Succeeded; /* caller will close file */ + } + +/* + * gfmktree() - initialize or reinitialize encoding tree + */ + +static void gfmktree(tree) +lzwnode *tree; + { + int i; + + for (i = 0; i < gf_clear; i++) { /* for each basic entry */ + tree[i].tcode = i; /* code is pixel value */ + tree[i].child = 0; /* no suffixes yet */ + tree[i].sibling = i + 1; /* next code is sibling */ + } + tree[gf_clear - 1].sibling = 0; /* last entry has no sibling */ + gf_free = gf_eoi + 1; /* reset next free entry */ + } + +/* + * gfout(code) - output one LZW token + */ +static void gfout(tcode) +int tcode; + { + gf_curr |= tcode << gf_valid; /* add to current word */ + gf_valid += gf_lzwbits; /* count the bits */ + while (gf_valid >= 8) { /* while we have a byte to output */ + gf_obuf[GifBlockSize - gf_rem] = gf_curr; /* put in buffer */ + gf_curr >>= 8; /* remove from word */ + gf_valid -= 8; + if (--gf_rem == 0) /* flush buffer when full */ + gfdump(); + } + } + +/* + * gfdump() - dump output buffer + */ +static void gfdump() + { + int n; + + n = GifBlockSize - gf_rem; + putc(n, gf_f); /* write block size */ + fwrite((pointer)gf_obuf, 1, n, gf_f); /*write block */ + gf_rem = GifBlockSize; /* reset buffer to empty */ + } + +/* + * Median cut quantization code, based on the classic algorithm from + * Color Image Quantization for Frame Buffer Display + * Paul Heckbert + * SIGGRAPH '82, July 1982 (vol 16 no 3), pp297-307 + */ + +typedef struct box { /* 3-D RGB region for median cut algorithm */ + struct box *next; /* next box in chain */ + long count; /* number of occurrences in this region */ + char maxaxis; /* indication of longest axis */ + char maxdim; /* length along longest axis */ + char cutpt; /* cut point along that axis */ + char rmin, gmin, bmin; /* minimum r, g, b values (5-bit color) */ + char rmax, gmax, bmax; /* maximum r, g, b values (5-bit color) */ + } box; + +#define MC_QUANT 5 /* quantize colors to 5 bits for median cut */ +#define MC_MAXC ((1 << MC_QUANT) - 1) /* so the maximum color value is 31 */ + +#define MC_RED (2 * MC_QUANT) /* red shift */ +#define MC_GRN (1 * MC_QUANT) /* green shift */ +#define MC_BLU (0 * MC_QUANT) /* blue shift */ + +static void mc_shrink(box *bx); +static void mc_cut(box *bx); +static void mc_setcolor(box *bx, struct palentry *pe, int i); +static void mc_median(box *bx, int axis, long counts[], int min, int max); +static void mc_remove(box *bx); +static void mc_insert(box *bx); + +static long *mc_hlist; /* current histogram list */ +static box *mc_blist; /* current box list */ +static int mc_nboxes = 0; /* number of boxes allocated so far */ + +static box *mc_bfirst; /* first box on linked list */ + +/* + * medcut(hlist, plist, n) -- perform median-cut color quantization. + * + * On entry, hlist is a histogram of 32768 entries (5-bit color), + * plist is an array of n palentry structs to be filled in, + * and n is the number of colors desired in the result. + * + * On exit, up to n entries in plist have been filled in, and each + * hlist entry is an index into plist for the corresponding color. + * + * medcut returns the number of entries actually used. + * This is usually n if the histogram has that many nonzero entries. + * A return code of 0 indicates an allocation failure. + */ +int medcut(long hlist[], struct palentry plist[], int ncolors) { + box *bx; + int i; + + if ((mc_blist = malloc(ncolors * sizeof(box))) == NULL) + return 0; + mc_nboxes = 0; + mc_hlist = hlist; + + bx = &mc_blist[mc_nboxes++]; /* create initial box */ + bx->next = NULL; + bx->rmin = bx->gmin = bx->bmin = 0; + bx->rmax = bx->gmax = bx->bmax = 31; + mc_shrink(bx); /* set box statistics */ + mc_bfirst = bx; /* put as first and only box on chain */ + + while (mc_nboxes < ncolors && mc_bfirst->maxdim > 1) + mc_cut(mc_bfirst); /* split box with longest dimension */ + + for (i = 0; i < mc_nboxes; i++) /* for every box created */ + mc_setcolor(&mc_blist[i], &plist[i], i); /* set palette entry */ + + free(mc_blist); + return mc_nboxes; + } + +/* + * mc_shrink(bx) -- shrink a box to tightly enclose its contents. + * + * Adjusts rmin, gmin, bmin, rmax, gmax, bmax. + * Calculates count, maxaxis, maxdim, and cutpt + * (while the necessary statistics are handy). + */ +static void mc_shrink(box *bx) { + int i, n, r, g, b, t, dr, dg, db; + long rcounts[MC_MAXC+1], gcounts[MC_MAXC+1], bcounts[MC_MAXC+1]; + + memset(rcounts, 0, (MC_MAXC + 1) * sizeof(long)); + memset(gcounts, 0, (MC_MAXC + 1) * sizeof(long)); + memset(bcounts, 0, (MC_MAXC + 1) * sizeof(long)); + + /* + * Simultaneously count cross-sections along r, g, and b axes. + */ + t = n = 0; + for (r = bx->rmin; r <= bx->rmax; r++) { + for (g = bx->gmin; g <= bx->gmax; g++) { + for (b = bx->bmin; b <= bx->bmax; b++) { + i = (r << MC_RED) + (g << MC_GRN) + (b << MC_BLU); + n = mc_hlist[i]; + t += n; + rcounts[r] += n; + gcounts[g] += n; + bcounts[b] += n; + } + } + } + bx->count = t; + + /* + * Adjust min/mas bounds to tightly enclose the data we found. + */ + while (rcounts[bx->rmin] == 0) bx->rmin++; + while (rcounts[bx->rmax] == 0) bx->rmax--; + while (gcounts[bx->gmin] == 0) bx->gmin++; + while (gcounts[bx->gmax] == 0) bx->gmax--; + while (bcounts[bx->bmin] == 0) bx->bmin++; + while (bcounts[bx->bmax] == 0) bx->bmax--; + + /* + * Find and record the axis of longest dimension. + */ + dr = bx->rmax - bx->rmin; + dg = bx->gmax - bx->gmin; + db = bx->bmax - bx->bmin; + if (db > dg && db > dr) + mc_median(bx, MC_BLU, bcounts, bx->bmin, bx->bmax); + else if (dr > dg) + mc_median(bx, MC_RED, rcounts, bx->rmin, bx->rmax); + else + mc_median(bx, MC_GRN, gcounts, bx->gmin, bx->gmax); + } + +/* + * mc_median(bx, axis, counts, cmin, cmax) -- find median and set box values. + */ +static void mc_median(box *bx, int axis, long counts[], int cmin, int cmax) { + int lower, upper; + + bx->maxaxis = axis; + bx->maxdim = cmax - cmin + 1; + lower = counts[cmin]; + upper = counts[cmax]; + + /* + * Approach from both ends to find the median bin. + */ + while (cmin < cmax) { + if (lower < upper) + lower += counts[++cmin]; + else + upper += counts[--cmax]; + } + + /* + * Have counted the median bin in both upper and lower halves. + * Remove it from the larger of those two. + */ + if (lower < upper) + upper -= counts[cmax++]; + else + lower -= counts[cmin--]; + + bx->cutpt = cmax; + bx->count = lower + upper; + } + +/* + * mc_cut(bx) -- split box at previously recorded cutpoint. + */ +static void mc_cut(box *b1) { + box *b2; + + mc_remove(b1); /* unlink box */ + b2 = &mc_blist[mc_nboxes++]; /* allocate new box */ + *b2 = *b1; /* duplicate the contents */ + + switch (b1->maxaxis) { + case MC_RED: b1->rmax = b1->cutpt - 1; b2->rmin = b2->cutpt; break; + case MC_GRN: b1->gmax = b1->cutpt - 1; b2->gmin = b2->cutpt; break; + case MC_BLU: b1->bmax = b1->cutpt - 1; b2->bmin = b2->cutpt; break; + } + mc_shrink(b1); /* recomputes box statistics */ + mc_shrink(b2); + + mc_insert(b1); /* put both boxes back on list */ + mc_insert(b2); + } + +/* + * mc_remove(bx) -- remove box from global linked list. + * + * This is fast in practice because we always remove the first entry. + */ +static void mc_remove(box *bx) { + box **bp; + + for (bp = &mc_bfirst; *bp != NULL; bp = &(*bp)->next) { + if (*bp == bx) { + *bp = bx->next; + return; + } + } + } + +/* + * mc_insert(bx) -- insert box in list, preserving decreasing maxdim ordering. + */ +static void mc_insert(box *bx) { + box **bp; + + for (bp = &mc_bfirst; *bp != NULL; bp = &(*bp)->next) { + if (bx->maxdim > (*bp)->maxdim + || (bx->maxdim == (*bp)->maxdim && bx->count >= (*bp)->count)) + break; + } + bx->next = *bp; + *bp = bx; + } + +/* + * mc_setcolor(bx, pe, i) -- set palette entry to box color. + * + * Also sets the associated hlist entries to i, the palette index. + */ +static void mc_setcolor(box *bx, struct palentry *pe, int i) { + int j, r, g, b; + long n, t = 0, rtotal = 0, gtotal = 0, btotal = 0; + + /* + * Calculate a weighted sum of the colors in the box. + */ + for (r = bx->rmin; r <= bx->rmax; r++) { + for (g = bx->gmin; g <= bx->gmax; g++) { + for (b = bx->bmin; b <= bx->bmax; b++) { + j = (r << MC_RED) + (g << MC_GRN) + (b << MC_BLU); + n = mc_hlist[j]; + t += n; + rtotal += n * r; + gtotal += n * g; + btotal += n * b; + mc_hlist[j] = i; + } + } + } + + /* + * Scale colors using floating arithmetic to avoid overflow. + */ + pe->clr.red = (65535. / MC_MAXC) * rtotal / t + 0.5; + pe->clr.green = (65535. / MC_MAXC) * gtotal / t + 0.5; + pe->clr.blue = (65535. / MC_MAXC) * btotal / t + 0.5; + pe->used = 1; + pe->valid = 1; + pe->transpt = 0; + } + +#endif /* Graphics */ |