Path: utzoo!utgpu!watserv1!watmath!att!news.cs.indiana.edu!julius.cs.uiuc.edu!wuarchive!cs.utexas.edu!sun-barr!newstop!exodus!appserv!halibut.cis.upenn.edu From: bradley@halibut.cis.upenn.edu (John Bradley) Newsgroups: comp.sources.x Subject: v10i086: xv - display and manipulate images, Part08/10 Message-ID: <324@appserv.Eng.Sun.COM> Date: 27 Nov 90 20:08:41 GMT References: Sender: news@exodus.Eng.Sun.COM Lines: 992 Approved: argv@sun.com Submitted-by: bradley@halibut.cis.upenn.edu (John Bradley) Posting-number: Volume 10, Issue 86 Archive-name: xv/part08 #!/bin/sh # to extract, remove the header and type "sh filename" if `test ! -s ./xv24to8.c` then echo "writting ./xv24to8.c" cat > ./xv24to8.c << '\BARFOO\' /* * xv24to8.c - contains the 24-to-8-bit Conv24to8 procedure * * The Conv24to8 procedure takes a pointer to a 24-bit image (loaded * previously). The image will be a w * h * 3 byte array of * bytes. The image will be arranged with 3 bytes per pixel (in order * R, G, and B), pixel 0 at the top left corner. (As normal.) * The procedure also takes a maximum number of colors to use (numcols) * * The Conv24to8 procedure will set up the following: it will allocate and * create 'pic', a pWIDE*pHIGH 8-bit picture. it will set up pWIDE and pHIGH. * it will load up the r[], g[], and b[] colormap arrays. it will NOT * calculate numcols, since the cmap sort procedure has to be called anyway * * Conv24to8 returns '0' if successful, '1' if it failed (presumably on a * malloc()) * * contains: * Cont24to8() * InitFSDTables() */ /* * Copyright 1989, 1990 by the University of Pennsylvania * * Permission to use, copy, and distribute for non-commercial purposes, * is hereby granted without fee, providing that the above copyright * notice appear in all copies and that both the copyright notice and this * permission notice appear in supporting documentation. * * The software may be modified for your own purposes, but modified versions * may not be distributed. * * This software is provided "as is" without any express or implied warranty. */ #include "xv.h" #define MAX_CMAP_SIZE 256 #define COLOR_DEPTH 8 #define MAX_COLOR 256 #define B_DEPTH 5 /* # bits/pixel to use */ #define B_LEN (1<0; i--, pp++, p24+=3) *pp = (p24[0]*11 + p24[1]*16 + p24[2]*5) >> 5; /* pp=.33R+.5G+.17B */ return(0); } if (!noqcheck && QuickCheck(pic24,w,h,nc)) { /* see if it's a <256 color RGB pic */ SetISTR(ISTR_INFO,"No color compression was necessary.\n"); return 0; } else if (!slow24) { SetISTR(ISTR_INFO,"Doing quick 24-bit to 8-bit conversion."); return(Quick24to8(pic24,w,h)); } else SetISTR(ISTR_INFO,"Doing full 24-bit to 8-bit conversion."); /**** STEP 1: create empty boxes ****/ usedboxes = NULL; box_list = freeboxes = (CBOX *) malloc(num_colors * sizeof(CBOX)); if (box_list == NULL) { fprintf(stderr,"%s: Conv24to8() - failed to allocate 'freeboxes'\n",cmd); return(1); } for (i=0; inext; if (freeboxes) freeboxes->prev = NULL; ptr->next = usedboxes; usedboxes = ptr; if (ptr->next) ptr->next->prev = ptr; get_histogram(ptr); /**** STEP 3: continually subdivide boxes until no more free boxes remain */ while (freeboxes) { ptr = largest_box(); if (ptr) splitbox(ptr); else break; } /**** STEP 4: assign colors to all boxes ****/ for (i=0, ptr=usedboxes; inext) { assign_color(ptr, &r[i], &g[i], &b[i]); } /* We're done with the boxes now */ num_colors = i; free(box_list); box_list = freeboxes = usedboxes = NULL; /**** STEP 5: scan histogram and map all values to closest color */ /* 5a: create cell list as described in Heckbert[2] */ ColorCells = (CCELL **) calloc(C_LEN*C_LEN*C_LEN, sizeof(CCELL *)); /* 5b: create mapping from truncated pixel space to color table entries */ map_colortable(); /**** STEP 6: scan image, match input values to table entries */ i=quant_fsdither(); /* free everything that can be freed */ free(ColorCells); return i; } /****************************/ static void get_histogram(box) CBOX *box; /****************************/ { int i,j,r,g,b,*ptr; byte *p; box->rmin = box->gmin = box->bmin = 999; box->rmax = box->gmax = box->bmax = -1; box->total = WIDE * HIGH; /* zero out histogram */ ptr = &histogram[0][0][0]; for (i=B_LEN*B_LEN*B_LEN; i>0; i-- ) *ptr++ = 0; /* calculate histogram */ p = pic24; for (i=0; i> (COLOR_DEPTH-B_DEPTH); g = (*p++) >> (COLOR_DEPTH-B_DEPTH); b = (*p++) >> (COLOR_DEPTH-B_DEPTH); if (r < box->rmin) box->rmin = r; if (r > box->rmax) box->rmax = r; if (g < box->gmin) box->gmin = g; if (g > box->gmax) box->gmax = g; if (b < box->bmin) box->bmin = b; if (b > box->bmax) box->bmax = b; histogram[r][g][b]++; } } /******************************/ static CBOX *largest_box() /******************************/ { CBOX *tmp, *ptr; int size = -1; tmp = usedboxes; ptr = NULL; while (tmp) { if ( (tmp->rmax > tmp->rmin || tmp->gmax > tmp->gmin || tmp->bmax > tmp->bmin) && tmp->total > size ) { ptr = tmp; size = tmp->total; } tmp = tmp->next; } return(ptr); } /******************************/ static void splitbox(ptr) CBOX *ptr; /******************************/ { int hist2[B_LEN], first, last, i, rdel, gdel, bdel; CBOX *new; int *iptr, *histp, ir, ig, ib; int rmin,rmax,gmin,gmax,bmin,bmax; enum {RED,GREEN,BLUE} which; /* * see which axis is the largest, do a histogram along that * axis. Split at median point. Contract both new boxes to * fit points and return */ first = last = 0; /* shut RT hcc compiler up */ rmin = ptr->rmin; rmax = ptr->rmax; gmin = ptr->gmin; gmax = ptr->gmax; bmin = ptr->bmin; bmax = ptr->bmax; rdel = rmax - rmin; gdel = gmax - gmin; bdel = bmax - bmin; if (rdel>=gdel && rdel>=bdel) which = RED; else if (gdel>=bdel) which = GREEN; else which = BLUE; /* get histogram along longest axis */ switch (which) { case RED: histp = &hist2[rmin]; for (ir=rmin; ir<=rmax; ir++) { *histp = 0; for (ig=gmin; ig<=gmax; ig++) { iptr = &histogram[ir][ig][bmin]; for (ib=bmin; ib<=bmax; ib++) { *histp += *iptr; ++iptr; } } ++histp; } first = rmin; last = rmax; break; case GREEN: histp = &hist2[gmin]; for (ig=gmin; ig<=gmax; ig++) { *histp = 0; for (ir=rmin; ir<=rmax; ir++) { iptr = &histogram[ir][ig][bmin]; for (ib=bmin; ib<=bmax; ib++) { *histp += *iptr; ++iptr; } } ++histp; } first = gmin; last = gmax; break; case BLUE: histp = &hist2[bmin]; for (ib=bmin; ib<=bmax; ib++) { *histp = 0; for (ir=rmin; ir<=rmax; ir++) { iptr = &histogram[ir][gmin][ib]; for (ig=gmin; ig<=gmax; ig++) { *histp += *iptr; iptr += B_LEN; } } ++histp; } first = bmin; last = bmax; break; } /* find median point */ { int sum, sum2; histp = &hist2[first]; sum2 = ptr->total/2; histp = &hist2[first]; sum = 0; for (i=first; i<=last && (sum += *histp++)next; if (freeboxes) freeboxes->prev = NULL; if (usedboxes) usedboxes->prev = new; new->next = usedboxes; usedboxes = new; { int sum1,sum2,j; histp = &hist2[first]; sum1 = 0; for (j = first; j < i; ++j) sum1 += *histp++; sum2 = 0; for (j = i; j <= last; ++j) sum2 += *histp++; new->total = sum1; ptr->total = sum2; } new->rmin = rmin; new->rmax = rmax; new->gmin = gmin; new->gmax = gmax; new->bmin = bmin; new->bmax = bmax; switch (which) { case RED: new->rmax = i-1; ptr->rmin = i; break; case GREEN: new->gmax = i-1; ptr->gmin = i; break; case BLUE: new->bmax = i-1; ptr->bmin = i; break; } shrinkbox(new); shrinkbox(ptr); } /****************************/ static void shrinkbox(box) CBOX *box; /****************************/ { int *histp,ir,ig,ib; int rmin,rmax,gmin,gmax,bmin,bmax; rmin = box->rmin; rmax = box->rmax; gmin = box->gmin; gmax = box->gmax; bmin = box->bmin; bmax = box->bmax; if (rmax>rmin) { for (ir=rmin; ir<=rmax; ir++) for (ig=gmin; ig<=gmax; ig++) { histp = &histogram[ir][ig][bmin]; for (ib=bmin; ib<=bmax; ib++) if (*histp++ != 0) { box->rmin = rmin = ir; goto have_rmin; } } have_rmin: if (rmax>rmin) for (ir=rmax; ir>=rmin; --ir) for (ig=gmin; ig<=gmax; ig++) { histp = &histogram[ir][ig][bmin]; for (ib=bmin; ib<=bmax; ib++) if (*histp++ != 0) { box->rmax = rmax = ir; goto have_rmax; } } } have_rmax: if (gmax>gmin) { for (ig=gmin; ig<=gmax; ig++) for (ir=rmin; ir<=rmax; ir++) { histp = &histogram[ir][ig][bmin]; for (ib=bmin; ib<=bmax; ib++) if (*histp++ != 0) { box->gmin = gmin = ig; goto have_gmin; } } have_gmin: if (gmax>gmin) for (ig=gmax; ig>=gmin; --ig) for (ir=rmin; ir<=rmax; ir++) { histp = &histogram[ir][ig][bmin]; for (ib=bmin; ib<=bmax; ib++) if (*histp++ != 0) { box->gmax = gmax = ig; goto have_gmax; } } } have_gmax: if (bmax>bmin) { for (ib=bmin; ib<=bmax; ib++) for (ir=rmin; ir<=rmax; ir++) { histp = &histogram[ir][gmin][ib]; for (ig=gmin; ig<=gmax; ig++) { if (*histp != 0) { box->bmin = bmin = ib; goto have_bmin; } histp += B_LEN; } } have_bmin: if (bmax>bmin) for (ib=bmax; ib>=bmin; --ib) for (ir=rmin; ir<=rmax; ir++) { histp = &histogram[ir][gmin][ib]; for (ig=gmin; ig<=gmax; ig++) { if (*histp != 0) { bmax = ib; goto have_bmax; } histp += B_LEN; } } } have_bmax: return; } /*******************************/ static void assign_color(ptr,rp,gp,bp) CBOX *ptr; byte *rp,*gp,*bp; /*******************************/ { *rp = ((ptr->rmin + ptr->rmax) << (COLOR_DEPTH - B_DEPTH)) / 2; *gp = ((ptr->gmin + ptr->gmax) << (COLOR_DEPTH - B_DEPTH)) / 2; *bp = ((ptr->bmin + ptr->bmax) << (COLOR_DEPTH - B_DEPTH)) / 2; } /*******************************/ static CCELL *create_colorcell(r1,g1,b1) int r1,g1,b1; /*******************************/ { register int i,tmp, dist; register CCELL *ptr; register byte *rp,*gp,*bp; int ir,ig,ib, mindist; ir = r1 >> (COLOR_DEPTH-C_DEPTH); ig = g1 >> (COLOR_DEPTH-C_DEPTH); ib = b1 >> (COLOR_DEPTH-C_DEPTH); r1 &= ~1 << (COLOR_DEPTH-C_DEPTH); g1 &= ~1 << (COLOR_DEPTH-C_DEPTH); b1 &= ~1 << (COLOR_DEPTH-C_DEPTH); ptr = (CCELL *) malloc(sizeof(CCELL)); *(ColorCells + ir*C_LEN*C_LEN + ig*C_LEN + ib) = ptr; ptr->num_ents = 0; /* step 1: find all colors inside this cell, while we're at it, find distance of centermost point to furthest corner */ mindist = 99999999; rp=r; gp=g; bp=b; for (i=0; i>(COLOR_DEPTH-C_DEPTH) == ir && *gp>>(COLOR_DEPTH-C_DEPTH) == ig && *bp>>(COLOR_DEPTH-C_DEPTH) == ib) { ptr->entries[ptr->num_ents][0] = i; ptr->entries[ptr->num_ents][1] = 0; ++ptr->num_ents; tmp = *rp - r1; if (tmp < (MAX_COLOR/C_LEN/2)) tmp = MAX_COLOR/C_LEN-1 - tmp; dist = tmp*tmp; tmp = *gp - g1; if (tmp < (MAX_COLOR/C_LEN/2)) tmp = MAX_COLOR/C_LEN-1 - tmp; dist += tmp*tmp; tmp = *bp - b1; if (tmp < (MAX_COLOR/C_LEN/2)) tmp = MAX_COLOR/C_LEN-1 - tmp; dist += tmp*tmp; if (dist < mindist) mindist = dist; } /* step 3: find all points within that distance to box */ rp=r; gp=g; bp=b; for (i=0; i> (COLOR_DEPTH-C_DEPTH) != ir || *gp >> (COLOR_DEPTH-C_DEPTH) != ig || *bp >> (COLOR_DEPTH-C_DEPTH) != ib) { dist = 0; if ((tmp = r1 - *rp)>0 || (tmp = *rp - (r1 + MAX_COLOR/C_LEN-1)) > 0 ) dist += tmp*tmp; if( (tmp = g1 - *gp)>0 || (tmp = *gp - (g1 + MAX_COLOR/C_LEN-1)) > 0 ) dist += tmp*tmp; if( (tmp = b1 - *bp)>0 || (tmp = *bp - (b1 + MAX_COLOR/C_LEN-1)) > 0 ) dist += tmp*tmp; if( dist < mindist ) { ptr->entries[ptr->num_ents][0] = i; ptr->entries[ptr->num_ents][1] = dist; ++ptr->num_ents; } } /* sort color cells by distance, use cheap exchange sort */ { int n, next_n; n = ptr->num_ents - 1; while (n>0) { next_n = 0; for (i=0; ientries[i][1] > ptr->entries[i+1][1]) { tmp = ptr->entries[i][0]; ptr->entries[i][0] = ptr->entries[i+1][0]; ptr->entries[i+1][0] = tmp; tmp = ptr->entries[i][1]; ptr->entries[i][1] = ptr->entries[i+1][1]; ptr->entries[i+1][1] = tmp; next_n = i; } } n = next_n; } } return (ptr); } /***************************/ static void map_colortable() /***************************/ { int ir,ig,ib, *histp; CCELL *cell; histp = &histogram[0][0][0]; for (ir=0; ir>(B_DEPTH-C_DEPTH)) << C_DEPTH*2) + ((ig>>(B_DEPTH-C_DEPTH)) << C_DEPTH) + (ib>>(B_DEPTH-C_DEPTH)) ) ); if (cell==NULL) cell = create_colorcell(ir<<(COLOR_DEPTH-B_DEPTH), ig<<(COLOR_DEPTH-B_DEPTH), ib<<(COLOR_DEPTH-B_DEPTH)); dist = 9999999; for (i=0; inum_ents && dist>cell->entries[i][1]; i++) { j = cell->entries[i][0]; d2 = r[j] - (ir << (COLOR_DEPTH-B_DEPTH)); d2 *= d2; tmp = g[j] - (ig << (COLOR_DEPTH-B_DEPTH)); d2 += tmp*tmp; tmp = b[j] - (ib << (COLOR_DEPTH-B_DEPTH)); d2 += tmp*tmp; if( d2 < dist ) { dist = d2; *histp = j; } } } histp++; } } /*****************************/ static int quant_fsdither() /*****************************/ { register int *thisptr, *nextptr; int *thisline, *nextline, *tmpptr; int r1, g1, b1, r2, g2, b2; int i, j, imax, jmax, oval; byte *inptr, *outptr, *tmpbptr; int lastline, lastpixel; imax = HIGH - 1; jmax = WIDE - 1; thisline = (int *) malloc(WIDE * 3 * sizeof(int)); nextline = (int *) malloc(WIDE * 3 * sizeof(int)); if (thisline == NULL || nextline == NULL) { fprintf(stderr,"%s: unable to allocate stuff for the 'dither' routine\n", cmd); return 1; } inptr = (byte *) pic24; outptr = (byte *) pic; /* get first line of picture */ for (j=WIDE * 3, tmpptr=nextline, tmpbptr=inptr; j; j--) *tmpptr++ = (int) *tmpbptr++; for (i=0; i=MAX_COLOR) r2=MAX_COLOR-1; if (g2<0) g2=0; else if (g2>=MAX_COLOR) g2=MAX_COLOR-1; if (b2<0) b2=0; else if (b2>=MAX_COLOR) b2=MAX_COLOR-1; r1 = r2; g1 = g2; b1 = b2; r2 >>= (COLOR_DEPTH-B_DEPTH); g2 >>= (COLOR_DEPTH-B_DEPTH); b2 >>= (COLOR_DEPTH-B_DEPTH); if ( (oval=histogram[r2][g2][b2]) == -1) { int ci, cj, dist, d2, tmp; CCELL *cell; cell = *( ColorCells + ( ((r2>>(B_DEPTH-C_DEPTH)) << C_DEPTH*2) + ((g2>>(B_DEPTH-C_DEPTH)) << C_DEPTH ) + (b2>>(B_DEPTH-C_DEPTH)) ) ); if (cell==NULL) cell = create_colorcell(r1,g1,b1); dist = 9999999; for (ci=0; cinum_ents && dist>cell->entries[ci][1]; ci++) { cj = cell->entries[ci][0]; d2 = (r[cj] >> (COLOR_DEPTH-B_DEPTH)) - r2; d2 *= d2; tmp = (g[cj] >> (COLOR_DEPTH-B_DEPTH)) - g2; d2 += tmp*tmp; tmp = (b[cj] >> (COLOR_DEPTH-B_DEPTH)) - b2; d2 += tmp*tmp; if (d2>3)&0x1c) | (b1>>6); if (j!=jmax) { /* adjust RIGHT pixel */ thisptr[0] += tbl7[rerr]; thisptr[1] += tbl7[gerr]; thisptr[2] += tbl7[berr]; } if (i!=imax) { /* do BOTTOM pixel */ nextptr[0] += tbl5[rerr]; nextptr[1] += tbl5[gerr]; nextptr[2] += tbl5[berr]; if (j>0) { /* do BOTTOM LEFT pixel */ nextptr[-3] += tbl3[rerr]; nextptr[-2] += tbl3[gerr]; nextptr[-1] += tbl3[berr]; } if (j!=jmax) { /* do BOTTOM RIGHT pixel */ nextptr[3] += tbl1[rerr]; nextptr[4] += tbl1[gerr]; nextptr[5] += tbl1[berr]; } nextptr += 3; } } } return 0; } /****************************/ void InitFSDTables() /****************************/ { int i; for (i=0; i<256; i++) { /* initialize Floyd-Steinberg division tables */ tbl1[i] = i/16; tbl3[i] = (3*i)/16; tbl5[i] = (5*i)/16; tbl7[i] = (7*i)/16; } } /****************************/ static int QuickCheck(pic24,w,h,maxcol) byte *pic24; int w,h,maxcol; { /* scans picture until it finds more than 'maxcol' different colors. If it finds more than 'maxcol' colors, it returns '0'. If it DOESN'T, it does the 24-to-8 conversion by simply sticking the colors it found into a colormap, and changing instances of a color in pic24 into colormap indicies (in pic) */ unsigned long colors[256],col; int i, nc, low, high, mid; byte *p, *pix; if (maxcol>256) maxcol = 256; /* put the first color in the table by hand */ nc = 0; mid = 0; for (i=w*h,p=pic24; i; i--) { col = (*p++ << 16); col += (*p++ << 8); col += *p++; /* binary search the 'colors' array to see if it's in there */ low = 0; high = nc-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. */ /* WARNING: this is an overlapped memory copy. memcpy doesn't do it correctly, hence 'bcopy', which claims to */ if (nc>=maxcol) return 0; bcopy(&colors[low], &colors[low+1], (nc - low) * sizeof(unsigned long)); colors[low] = col; nc++; } } /* run through the data a second time, this time mapping pixel values in pic24 into colormap offsets into 'colors' */ for (i=w*h,p=pic24, pix=pic; i; i--,pix++) { col = (*p++ << 16); col += (*p++ << 8); col += *p++; /* binary search the 'colors' array. It *IS* in there */ low = 0; high = nc-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) { fprintf(stderr,"QuickCheck: impossible!\n"); exit(1); } *pix = mid; } /* and load up the 'desired colormap' */ for (i=0; i>16; g[i] = (colors[i]>>8) & 0xff; b[i] = colors[i] & 0xff; } return 1; } \BARFOO\ else echo "will not over write ./xv24to8.c" fi echo "Finished archive 8 of 10" exit dan ---------------------------------------------------- O'Reilly && Associates argv@sun.com / argv@ora.com Opinions expressed reflect those of the author only. -- dan ---------------------------------------------------- O'Reilly && Associates argv@sun.com / argv@ora.com Opinions expressed reflect those of the author only.