/*
* 3to1 -- convert a 24 bit picture to 8 bits
* also does color-map replacement on 8 bit inputs
*/
#include <u.h>
#include <libc.h>
#include <libg.h>
#include <fb.h>
#define NCMAP 256 /* how many color map entries? */
#define NCOLOR 256 /* 0<=r,g,b<NCOLOR */
void initcindex(uchar [][3], int);
int fclook(int, int, int);
int clook(int, int, int);
void main(int argc, char *argv[]){
PICFILE *in, *out;
char *infile, *s, *inbuf, *outbuf, *ip, *op, *eop;
int wid, hgt, nchan, offs, y, mapped, i, t;
uchar cmap[NCMAP][3];
uchar icmap[NCMAP][3];
short *e, *error, herror[3];
int ncmap;
switch(getflags(argc, argv, "en:1[ncolor]")){
default:
usage("cmap [picture]");
case 3:
infile=argv[2];
break;
case 2:
infile="IN";
break;
}
if(!getcmap(argv[1], (uchar *)cmap)){
fprint(2, "%s: can't get colormap %s\n", argv[0], argv[1]);
exits("no cmap");
}
ncmap=flag['n']?atoi(flag['n'][0]):NCMAP;
if(ncmap>NCMAP){
fprint(2, "%s: sorry, can't handle ncolor>%d\n", argv[0], NCMAP);
exits("range error");
}
in=picopen_r(infile);
if(in==0){
perror(infile);
exits("no input");
}
s=strstr(in->chan, "rgb");
if(s==0){
mapped=1;
s=strstr(in->chan, "m");
if(s==0){
fprint(2, "%s: can't convert CHAN=%s\n", argv[0], in->chan);
exits("unk chan");
}
if(in->cmap) memmove(icmap, in->cmap, 256*3);
else for(y=0;y!=256;y++) icmap[y][0]=icmap[y][1]=icmap[y][2]=y;
}
else
mapped=0;
offs=s-in->chan;
wid=PIC_WIDTH(in);
hgt=PIC_HEIGHT(in);
nchan=PIC_NCHAN(in);
inbuf=malloc(wid*nchan);
outbuf=malloc(wid);
error=malloc((wid+1)*3*sizeof(short));
memset(error, 0, (wid+1)*3*sizeof(short));
if(inbuf==0 || outbuf==0 || error==0){
fprint(2, "%s: can't malloc\n", argv[0]);
exits("no malloc");
}
out=picopen_w("OUT", in->type, PIC_XOFFS(in), PIC_YOFFS(in), wid, hgt, "m", 0,
(char *)cmap);
initcindex(cmap, ncmap);
eop=outbuf+wid;
for(y=0;y!=hgt;y++){
picread(in, inbuf);
if(flag['e']){
if(mapped){
for(op=outbuf,ip=inbuf+offs;op!=eop;op++,ip+=nchan)
*op=fclook(icmap[*ip&255][0]&255,
icmap[*ip&255][1]&255,
icmap[*ip&255][2]&255);
}
else{
for(op=outbuf,ip=inbuf+offs;op!=eop;op++,ip+=nchan)
*op=fclook(ip[0]&255, ip[1]&255, ip[2]&255);
}
}
else if(mapped){
herror[0]=herror[1]=herror[2]=0;
for(op=outbuf,ip=inbuf+offs,e=error+3;op!=eop;op++,ip+=nchan,e+=3){
*op=clook((icmap[*ip&255][0]&255)+e[0],
(icmap[*ip&255][1]&255)+e[1],
(icmap[*ip&255][2]&255)+e[2]);
for(i=0;i!=3;i++){
t=(icmap[*ip&255][i]&255)-cmap[*op&255][i];
e[i]=3*t/8+herror[i];
herror[i]=t/4;
e[i+3]+=t-(5*t/8);
}
}
}
else{
herror[0]=herror[1]=herror[2]=0;
for(op=outbuf,ip=inbuf+offs,e=error+3;op!=eop;op++,ip+=nchan,e+=3){
*op=clook((ip[0]&255)+e[0],
(ip[1]&255)+e[1],
(ip[2]&255)+e[2]);
for(i=0;i!=3;i++){
t=(ip[i]&255)-cmap[*op&255][i];
e[i]=3*t/8+herror[i];
herror[i]=t/4;
e[i+3]+=t-(5*t/8);
}
}
}
picwrite(out, outbuf);
}
}
/*
* Color-map reverse indexing using a bucket list
*
* cindex[r>>RSH][g>>GSH][b>>BSH] points to a list of colors
* that might be the closest to (r,g,b).
*/
#define RSH 4 /* tune this for speed */
#define GSH 4 /* tune this for speed */
#define BSH 4 /* tune this for speed */
#define NR (NCOLOR>>RSH)
#define NG (NCOLOR>>GSH)
#define NB (NCOLOR>>BSH)
typedef struct Interval Interval;
typedef struct Color Color;
struct Interval{
int lo, hi;
};
struct Color{
uchar r, g, b, i;
Color *next;
};
Color *cindex[NR][NG][NB];
/*
* Look (r,g,b) up by indexing
* cindex and walking through the list
* of possible closest colors
*/
int clook(int r, int g, int b){
Color *cp, *bestp;
int bestd, dr, dg, db, d;
if(r<0) r=0; else if(r>=NCOLOR) r=NCOLOR-1;
if(g<0) g=0; else if(g>=NCOLOR) g=NCOLOR-1;
if(b<0) b=0; else if(b>=NCOLOR) b=NCOLOR-1;
cp=cindex[r>>RSH][g>>GSH][b>>BSH];
bestp=cp;
if(cp->next){
dr=cp->r-r;
dg=cp->g-g;
db=cp->b-b;
bestd=dr*dr+dg*dg+db*db;
while((cp=cp->next)!=0){
dr=cp->r-r;
dg=cp->g-g;
db=cp->b-b;
d=dr*dr+dg*dg+db*db;
if(d<bestd){
bestd=d;
bestp=cp;
}
}
}
return bestp->i;
}
/*
* Same as above, but without range check
*/
int fclook(int r, int g, int b){
Color *cp, *bestp;
int bestd, dr, dg, db, d;
cp=cindex[r>>RSH][g>>GSH][b>>BSH];
bestp=cp;
if(cp->next){
dr=cp->r-r;
dg=cp->g-g;
db=cp->b-b;
bestd=dr*dr+dg*dg+db*db;
while((cp=cp->next)!=0){
dr=cp->r-r;
dg=cp->g-g;
db=cp->b-b;
d=dr*dr+dg*dg+db*db;
if(d<bestd){
bestd=d;
bestp=cp;
}
}
}
return bestp->i;
}
/*
* Interval addition
*/
Interval iadd(Interval a, Interval b){
a.lo+=b.lo;
a.hi+=b.hi;
return a;
}
/*
* Interval square
*/
Interval isq(Interval a){
int t;
if(a.hi<=0){
t=a.hi*a.hi;
a.hi=a.lo*a.lo;
a.lo=t;
}
else if(a.lo>=0){
a.lo*=a.lo;
a.hi*=a.hi;
}
else{
if(-a.lo>=a.hi)
a.hi=a.lo*a.lo;
else
a.hi*=a.hi;
a.lo=0;
}
return a;
}
/*
* interval ((r<<RSH)-c.r)^2 + ((g<<GSH)-c.g)^2 + ((b<<BSH)-c.b)^2
*
* This tells us how far c can be from any point in
* the box (r,g,b)
*/
Interval rsq(Color c, Interval r, Interval g, Interval b){
r.lo=(r.lo<<RSH)-c.r;
r.hi=(r.hi<<RSH)-c.r;
g.lo=(g.lo<<GSH)-c.g;
g.hi=(g.hi<<GSH)-c.g;
b.lo=(b.lo<<BSH)-c.b;
b.hi=(b.hi<<BSH)-c.b;
return iadd(iadd(isq(r), isq(g)), isq(b));
}
/*
* map[ncmap] is a list of colors that might be the closest
* color to some point in the interval box (r,g,b).
*
* First, we trim the list to remove colors whose minimum distance
* to a point in (r,g,b) is larger than the maximum of some other
* color, since those can never be the closest to any point in (r,g,b).
*
* If there is only one color in the list, or if there is only one point
* in the box, we're done.
*
* Otherwise, we subdivide the (r,g,b) box in its longest dimension and
* proceed recursively.
*/
void rangemap(Color map[NCMAP], int ncmap, Interval r, Interval g, Interval b){
Interval range[NCMAP], new;
Color submap[NCMAP], *clist;
int i, j, k, minhi, nsubmap, dr, dg, db;
minhi=255*255*3+1;
for(i=0;i!=ncmap;i++){
range[i]=rsq(map[i], r, g, b);
if(range[i].hi<minhi)
minhi=range[i].hi;
}
nsubmap=0;
for(i=0;i!=ncmap;i++){
if(range[i].lo<=minhi){
submap[nsubmap]=map[i];
nsubmap++;
}
}
dr=r.hi-r.lo;
dg=g.hi-g.lo;
db=b.hi-b.lo;
if(nsubmap==1 || dr*dg*db<=1){
clist=malloc(nsubmap*sizeof(Color));
memmove(clist, submap, nsubmap*sizeof(Color));
for(i=1;i!=nsubmap;i++) clist[i-1].next=clist+i;
clist[nsubmap-1].next=0;
for(i=r.lo;i!=r.hi;i++) for(j=g.lo;j!=g.hi;j++) for(k=b.lo;k!=b.hi;k++)
cindex[i][j][k]=clist;
}
else if(dr>=dg && dr>=db){
new.lo=r.lo;
new.hi=r.lo+dr/2;
rangemap(submap, nsubmap, new, g, b);
new.lo=new.hi;
new.hi=r.hi;
rangemap(submap, nsubmap, new, g, b);
}
else if(dg>=db){
new.lo=g.lo;
new.hi=g.lo+dg/2;
rangemap(submap, nsubmap, r, new, b);
new.lo=new.hi;
new.hi=g.hi;
rangemap(submap, nsubmap, r, new, b);
}
else{
new.lo=b.lo;
new.hi=b.lo+db/2;
rangemap(submap, nsubmap, r, g, new);
new.lo=new.hi;
new.hi=b.hi;
rangemap(submap, nsubmap, r, g, new);
}
}
Interval rrange={0,NR}, grange={0,NG}, brange={0,NB};
void initcindex(uchar input[NCMAP][3], int ncmap){
int i;
Color map[NCMAP];
for(i=0;i!=ncmap;i++){
map[i].r=input[i][0];
map[i].g=input[i][1];
map[i].b=input[i][2];
map[i].i=i;
}
rangemap(map, ncmap, rrange, grange, brange);
}