#include <u.h>
#include <libc.h>
#include <bio.h>
#include <draw.h>
#include <memdraw.h>

#define DBG if(0)
#define RGB2K(r,g,b)    ((299*((ulong)(r))+587*((ulong)(g))+114*((ulong)(b)))/1000)

/*
* This program tests the 'memimagedraw' primitive stochastically.
* It tests the combination aspects of it thoroughly, but since the
* three images it uses are disjoint, it makes no check of the
* correct behavior when images overlap.  That is, however, much
* easier to get right and to test.
*/

void    drawonepixel(Memimage*, Point, Memimage*, Point, Memimage*, Point);
void    verifyone(void);
void    verifyline(void);
void    verifyrect(void);
void    verifyrectrepl(int, int);
void putpixel(Memimage *img, Point pt, ulong nv);
ulong rgbatopix(uchar, uchar, uchar, uchar);

char *dchan, *schan, *mchan;
int dbpp, sbpp, mbpp;

int     seed;
int     niters = 100;
int     dbpp;   /* bits per pixel in destination */
int     sbpp;   /* bits per pixel in src */
int     mbpp;   /* bits per pixel in mask */
int     dpm;    /* pixel mask at high part of byte, in destination */
int     nbytes; /* in destination */

int     Xrange  = 64;
int     Yrange  = 8;

Memimage        *dst;
Memimage        *src;
Memimage        *mask;
Memimage        *stmp;
Memimage        *mtmp;
Memimage        *ones;
uchar   *dstbits;
uchar   *srcbits;
uchar   *maskbits;
ulong   *savedstbits;

void
main(int argc, char *argv[])
{
       memimageinit();
       seed = time(0);

       ARGBEGIN{
       case 'x':
               Xrange = atoi(ARGF());
               break;
       case 'y':
               Yrange = atoi(ARGF());
               break;
       case 'n':
               niters = atoi(ARGF());
               break;
       case 's':
               seed = atoi(ARGF());
               break;
       }ARGEND

       dchan = "r8g8b8";
       schan = "r8g8b8";
       mchan = "r8g8b8";
       switch(argc){
       case 3: mchan = argv[2];
       case 2: schan = argv[1];
       case 1: dchan = argv[0];
       case 0: break;
       default:        goto Usage;
       Usage:
               fprint(2, "usage: dtest [dchan [schan [mchan]]]\n");
               exits("usage");
       }

//      fmtinstall('b', numbconv);      /* binary! */

       fprint(2, "%s -x %d -y %d -s 0x%x %s %s %s\n", argv0, Xrange, Yrange, seed, dchan, schan, mchan);
       srand(seed);

       dst = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(dchan));
       src = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(schan));
       mask = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(mchan));
       stmp = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(schan));
       mtmp = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(mchan));
       ones = allocmemimage(Rect(0, 0, Xrange, Yrange), strtochan(mchan));
//      print("chan %lux %lux %lux %lux %lux %lux\n", dst->chan, src->chan, mask->chan, stmp->chan, mtmp->chan, ones->chan);
       if(dst==0 || src==0 || mask==0 || mtmp==0 || ones==0) {
       Alloc:
               fprint(2, "dtest: allocation failed: %r\n");
               exits("alloc");
       }
       nbytes = (4*Xrange+4)*Yrange;
       srcbits = malloc(nbytes);
       dstbits = malloc(nbytes);
       maskbits = malloc(nbytes);
       savedstbits = malloc(nbytes);
       if(dstbits==0 || srcbits==0 || maskbits==0 || savedstbits==0)
               goto Alloc;
       dbpp = dst->depth;
       sbpp = src->depth;
       mbpp = mask->depth;
       dpm = 0xFF ^ (0xFF>>dbpp);
       memset(ones->data->bdata, 0xFF, ones->width*sizeof(ulong)*Yrange);


       fprint(2, "dtest: verify single pixel operation\n");
       verifyone();

       fprint(2, "dtest: verify full line non-replicated\n");
       verifyline();

       fprint(2, "dtest: verify full rectangle non-replicated\n");
       verifyrect();

       fprint(2, "dtest: verify full rectangle source replicated\n");
       verifyrectrepl(1, 0);

       fprint(2, "dtest: verify full rectangle mask replicated\n");
       verifyrectrepl(0, 1);

       fprint(2, "dtest: verify full rectangle source and mask replicated\n");
       verifyrectrepl(1, 1);

       exits(0);
}

/*
* Dump out an ASCII representation of an image.  The label specifies
* a list of characters to put at various points in the picture.
*/
static void
Bprintr5g6b5(Biobuf *bio, char*, ulong v)
{
       int r,g,b;
       r = (v>>11)&31;
       g = (v>>5)&63;
       b = v&31;
       Bprint(bio, "%.2x%.2x%.2x", r,g,b);
}

static void
Bprintr5g5b5a1(Biobuf *bio, char*, ulong v)
{
       int r,g,b,a;
       r = (v>>11)&31;
       g = (v>>6)&31;
       b = (v>>1)&31;
       a = v&1;
       Bprint(bio, "%.2x%.2x%.2x%.2x", r,g,b,a);
}

void
dumpimage(char *name, Memimage *img, void *vdata, Point labelpt)
{
       Biobuf b;
       uchar *data;
       uchar *p;
       char *arg;
       void (*fmt)(Biobuf*, char*, ulong);
       int npr, x, y, nb, bpp;
       ulong v, mask;
       Rectangle r;

       fmt = nil;
       arg = nil;
       switch(img->depth){
       case 1:
       case 2:
       case 4:
               fmt = (void(*)(Biobuf*,char*,ulong))Bprint;
               arg = "%.1ux";
               break;
       case 8:
               fmt = (void(*)(Biobuf*,char*,ulong))Bprint;
               arg = "%.2ux";
               break;
       case 16:
               arg = nil;
               if(img->chan == RGB16)
                       fmt = Bprintr5g6b5;
               else{
                       fmt = (void(*)(Biobuf*,char*,ulong))Bprint;
                       arg = "%.4ux";
               }
               break;
       case 24:
               fmt = (void(*)(Biobuf*,char*,ulong))Bprint;
               arg = "%.6lux";
               break;
       case 32:
               fmt = (void(*)(Biobuf*,char*,ulong))Bprint;
               arg = "%.8lux";
               break;
       }
       if(fmt == nil){
               fprint(2, "bad format\n");
               abort();
       }

       r  = img->r;
       Binit(&b, 2, OWRITE);
       data = vdata;
       bpp = img->depth;
       Bprint(&b, "%s\t%d\tr %R clipr %R repl %d data %p *%P\n", name, r.min.x, r, img->clipr, (img->flags&Frepl) ? 1 : 0, vdata, labelpt);
       mask = (1ULL<<bpp)-1;
//      for(y=r.min.y; y<r.max.y; y++){
       for(y=0; y<Yrange; y++){
               nb = 0;
               v = 0;
               p = data+(byteaddr(img, Pt(0,y))-(uchar*)img->data->bdata);
               Bprint(&b, "%-4d\t", y);
//              for(x=r.min.x; x<r.max.x; x++){
               for(x=0; x<Xrange; x++){
                       if(x==0)
                               Bprint(&b, "\t");

                       if(x != 0 && (x%8)==0)
                               Bprint(&b, " ");

                       npr = 0;
                       if(x==labelpt.x && y==labelpt.y){
                               Bprint(&b, "*");
                               npr++;
                       }
                       if(npr == 0)
                               Bprint(&b, " ");

                       while(nb < bpp){
                               v &= (1<<nb)-1;
                               v |= (ulong)(*p++) << nb;
                               nb += 8;
                       }
                       nb -= bpp;
//                      print("bpp %d v %.8lux mask %.8lux nb %d\n", bpp, v, mask, nb);
                       fmt(&b, arg, (v>>nb)&mask);
               }
               Bprint(&b, "\n");
       }
       Bterm(&b);
}

/*
* Verify that the destination pixel has the specified value.
* The value is in the high bits of v, suitably masked, but must
* be extracted from the destination Memimage.
*/
void
checkone(Point p, Point sp, Point mp)
{
       int delta;
       uchar *dp, *sdp;

       delta = (uchar*)byteaddr(dst, p)-(uchar*)dst->data->bdata;
       dp = (uchar*)dst->data->bdata+delta;
       sdp = (uchar*)savedstbits+delta;

       if(memcmp(dp, sdp, (dst->depth+7)/8) != 0) {
               fprint(2, "dtest: one bad pixel drawing at dst %P from source %P mask %P\n", p, sp, mp);
               fprint(2, " %.2ux %.2ux %.2ux %.2ux should be %.2ux %.2ux %.2ux %.2ux\n",
                       dp[0], dp[1], dp[2], dp[3], sdp[0], sdp[1], sdp[2], sdp[3]);
               fprint(2, "addresses dst %p src %p mask %p\n", dp, byteaddr(src, sp), byteaddr(mask, mp));
               dumpimage("src", src, src->data->bdata, sp);
               dumpimage("mask", mask, mask->data->bdata, mp);
               dumpimage("origdst", dst, dstbits, p);
               dumpimage("dst", dst, dst->data->bdata, p);
               dumpimage("gooddst", dst, savedstbits, p);
               abort();
       }
}

/*
* Verify that the destination line has the same value as the saved line.
*/
#define RECTPTS(r) (r).min.x, (r).min.y, (r).max.x, (r).max.y
void
checkline(Rectangle r, Point sp, Point mp, int y, Memimage *stmp, Memimage *mtmp)
{
       ulong *dp;
       int nb;
       ulong *saved;

       dp = wordaddr(dst, Pt(0, y));
       saved = savedstbits + y*dst->width;
       if(dst->depth < 8)
               nb = Xrange/(8/dst->depth);
       else
               nb = Xrange*(dst->depth/8);
       if(memcmp(dp, saved, nb) != 0){
               fprint(2, "dtest: bad line at y=%d; saved %p dp %p\n", y, saved, dp);
               fprint(2, "draw dst %R src %P mask %P\n", r, sp, mp);
               dumpimage("src", src, src->data->bdata, sp);
               if(stmp) dumpimage("stmp", stmp, stmp->data->bdata, sp);
               dumpimage("mask", mask, mask->data->bdata, mp);
               if(mtmp) dumpimage("mtmp", mtmp, mtmp->data->bdata, mp);
               dumpimage("origdst", dst, dstbits, r.min);
               dumpimage("dst", dst, dst->data->bdata, r.min);
               dumpimage("gooddst", dst, savedstbits, r.min);
               abort();
       }
}

/*
* Fill the bits of an image with random data.
* The Memimage parameter is used only to make sure
* the data is well formatted: only ucbits is written.
*/
void
fill(Memimage *img, uchar *ucbits)
{
       int i, x, y;
       ushort *up;
       uchar alpha, r, g, b;
       void *data;

       if((img->flags&Falpha) == 0){
               up = (ushort*)ucbits;
               for(i=0; i<nbytes/2; i++)
                       *up++ = lrand() >> 7;
               if(i+i != nbytes)
                       *(uchar*)up = lrand() >> 7;
       }else{
               data = img->data->bdata;
               img->data->bdata = ucbits;

               for(x=img->r.min.x; x<img->r.max.x; x++)
               for(y=img->r.min.y; y<img->r.max.y; y++){
                       alpha = rand() >> 4;
                       r = rand()%(alpha+1);
                       g = rand()%(alpha+1);
                       b = rand()%(alpha+1);
                       putpixel(img, Pt(x,y), rgbatopix(r,g,b,alpha));
               }
               img->data->bdata = data;
       }

}

/*
* Mask is preset; do the rest
*/
void
verifyonemask(void)
{
       Point dp, sp, mp;

       fill(dst, dstbits);
       fill(src, srcbits);
       memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);
       memmove(src->data->bdata, srcbits, src->width*sizeof(ulong)*Yrange);
       memmove(mask->data->bdata, maskbits, mask->width*sizeof(ulong)*Yrange);

       dp.x = nrand(Xrange);
       dp.y = nrand(Yrange);

       sp.x = nrand(Xrange);
       sp.y = nrand(Yrange);

       mp.x = nrand(Xrange);
       mp.y = nrand(Yrange);

       drawonepixel(dst, dp, src, sp, mask, mp);
       memmove(mask->data->bdata, maskbits, mask->width*sizeof(ulong)*Yrange);
       memmove(savedstbits, dst->data->bdata, dst->width*sizeof(ulong)*Yrange);

       memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);
       memimagedraw(dst, Rect(dp.x, dp.y, dp.x+1, dp.y+1), src, sp, mask, mp, SoverD);
       memmove(mask->data->bdata, maskbits, mask->width*sizeof(ulong)*Yrange);

       checkone(dp, sp, mp);
}

void
verifyone(void)
{
       int i;

       /* mask all zeros */
       memset(maskbits, 0, nbytes);
       for(i=0; i<niters; i++)
               verifyonemask();

       /* mask all ones */
       memset(maskbits, 0xFF, nbytes);
       for(i=0; i<niters; i++)
               verifyonemask();

       /* random mask */
       for(i=0; i<niters; i++){
               fill(mask, maskbits);
               verifyonemask();
       }
}

/*
* Mask is preset; do the rest
*/
void
verifylinemask(void)
{
       Point sp, mp, tp, up;
       Rectangle dr;
       int x;

       fill(dst, dstbits);
       fill(src, srcbits);
       memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);
       memmove(src->data->bdata, srcbits, src->width*sizeof(ulong)*Yrange);
       memmove(mask->data->bdata, maskbits, mask->width*sizeof(ulong)*Yrange);

       dr.min.x = nrand(Xrange-1);
       dr.min.y = nrand(Yrange-1);
       dr.max.x = dr.min.x + 1 + nrand(Xrange-1-dr.min.x);
       dr.max.y = dr.min.y + 1;

       sp.x = nrand(Xrange);
       sp.y = nrand(Yrange);

       mp.x = nrand(Xrange);
       mp.y = nrand(Yrange);

       tp = sp;
       up = mp;
       for(x=dr.min.x; x<dr.max.x && tp.x<Xrange && up.x<Xrange; x++,tp.x++,up.x++)
               memimagedraw(dst, Rect(x, dr.min.y, x+1, dr.min.y+1), src, tp, mask, up, SoverD);
       memmove(savedstbits, dst->data->bdata, dst->width*sizeof(ulong)*Yrange);

       memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);

       memimagedraw(dst, dr, src, sp, mask, mp, SoverD);
       checkline(dr, drawrepl(src->r, sp), drawrepl(mask->r, mp), dr.min.y, nil, nil);
}

void
verifyline(void)
{
       int i;

       /* mask all ones */
       memset(maskbits, 0xFF, nbytes);
       for(i=0; i<niters; i++)
               verifylinemask();

       /* mask all zeros */
       memset(maskbits, 0, nbytes);
       for(i=0; i<niters; i++)
               verifylinemask();

       /* random mask */
       for(i=0; i<niters; i++){
               fill(mask, maskbits);
               verifylinemask();
       }
}

/*
* Mask is preset; do the rest
*/
void
verifyrectmask(void)
{
       Point sp, mp, tp, up;
       Rectangle dr;
       int x, y;

       fill(dst, dstbits);
       fill(src, srcbits);
       memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);
       memmove(src->data->bdata, srcbits, src->width*sizeof(ulong)*Yrange);
       memmove(mask->data->bdata, maskbits, mask->width*sizeof(ulong)*Yrange);

       dr.min.x = nrand(Xrange-1);
       dr.min.y = nrand(Yrange-1);
       dr.max.x = dr.min.x + 1 + nrand(Xrange-1-dr.min.x);
       dr.max.y = dr.min.y + 1 + nrand(Yrange-1-dr.min.y);

       sp.x = nrand(Xrange);
       sp.y = nrand(Yrange);

       mp.x = nrand(Xrange);
       mp.y = nrand(Yrange);

       tp = sp;
       up = mp;
       for(y=dr.min.y; y<dr.max.y && tp.y<Yrange && up.y<Yrange; y++,tp.y++,up.y++){
               for(x=dr.min.x; x<dr.max.x && tp.x<Xrange && up.x<Xrange; x++,tp.x++,up.x++)
                       memimagedraw(dst, Rect(x, y, x+1, y+1), src, tp, mask, up, SoverD);
               tp.x = sp.x;
               up.x = mp.x;
       }
       memmove(savedstbits, dst->data->bdata, dst->width*sizeof(ulong)*Yrange);

       memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);

       memimagedraw(dst, dr, src, sp, mask, mp, SoverD);
       for(y=0; y<Yrange; y++)
               checkline(dr, drawrepl(src->r, sp), drawrepl(mask->r, mp), y, nil, nil);
}

void
verifyrect(void)
{
       int i;

       /* mask all zeros */
       memset(maskbits, 0, nbytes);
       for(i=0; i<niters; i++)
               verifyrectmask();

       /* mask all ones */
       memset(maskbits, 0xFF, nbytes);
       for(i=0; i<niters; i++)
               verifyrectmask();

       /* random mask */
       for(i=0; i<niters; i++){
               fill(mask, maskbits);
               verifyrectmask();
       }
}

Rectangle
randrect(void)
{
       Rectangle r;

       r.min.x = nrand(Xrange-1);
       r.min.y = nrand(Yrange-1);
       r.max.x = r.min.x + 1 + nrand(Xrange-1-r.min.x);
       r.max.y = r.min.y + 1 + nrand(Yrange-1-r.min.y);
       return r;
}

/*
* Return coordinate corresponding to x withing range [minx, maxx)
*/
int
tilexy(int minx, int maxx, int x)
{
       int sx;

       sx = (x-minx) % (maxx-minx);
       if(sx < 0)
               sx += maxx-minx;
       return sx+minx;
}

void
replicate(Memimage *i, Memimage *tmp)
{
       Rectangle r, r1;
       int x, y, nb;

       /* choose the replication window (i->r) */
       r.min.x = nrand(Xrange-1);
       r.min.y = nrand(Yrange-1);
       /* make it trivial more often than pure chance allows */
       switch(lrand()&0){
       case 1:
               r.max.x = r.min.x + 2;
               r.max.y = r.min.y + 2;
               if(r.max.x < Xrange && r.max.y < Yrange)
                       break;
               /* fall through */
       case 0:
               r.max.x = r.min.x + 1;
               r.max.y = r.min.y + 1;
               break;
       default:
               if(r.min.x+3 >= Xrange)
                       r.max.x = Xrange;
               else
                       r.max.x = r.min.x+3 + nrand(Xrange-(r.min.x+3));

               if(r.min.y+3 >= Yrange)
                       r.max.y = Yrange;
               else
                       r.max.y = r.min.y+3 + nrand(Yrange-(r.min.y+3));
       }
       assert(r.min.x >= 0);
       assert(r.max.x <= Xrange);
       assert(r.min.y >= 0);
       assert(r.max.y <= Yrange);
       /* copy from i to tmp so we have just the replicated bits */
       nb = tmp->width*sizeof(ulong)*Yrange;
       memset(tmp->data->bdata, 0, nb);
       memimagedraw(tmp, r, i, r.min, ones, r.min, SoverD);
       memmove(i->data->bdata, tmp->data->bdata, nb);
       /* i is now a non-replicated instance of the replication */
       /* replicate it by hand through tmp */
       memset(tmp->data->bdata, 0, nb);
       x = -(tilexy(r.min.x, r.max.x, 0)-r.min.x);
       for(; x<Xrange; x+=Dx(r)){
               y = -(tilexy(r.min.y, r.max.y, 0)-r.min.y);
               for(; y<Yrange; y+=Dy(r)){
                       /* set r1 to instance of tile by translation */
                       r1.min.x = x;
                       r1.min.y = y;
                       r1.max.x = r1.min.x+Dx(r);
                       r1.max.y = r1.min.y+Dy(r);
                       memimagedraw(tmp, r1, i, r.min, ones, r.min, SoverD);
               }
       }
       i->flags |= Frepl;
       i->r = r;
       i->clipr = randrect();
//      fprint(2, "replicate [[%d %d] [%d %d]] [[%d %d][%d %d]]\n", r.min.x, r.min.y, r.max.x, r.max.y,
//              i->clipr.min.x, i->clipr.min.y, i->clipr.max.x, i->clipr.max.y);
       tmp->clipr = i->clipr;
}

/*
* Mask is preset; do the rest
*/
void
verifyrectmaskrepl(int srcrepl, int maskrepl)
{
       Point sp, mp, tp, up;
       Rectangle dr;
       int x, y;
       Memimage *s, *m;

//      print("verfrect %d %d\n", srcrepl, maskrepl);
       src->flags &= ~Frepl;
       src->r = Rect(0, 0, Xrange, Yrange);
       src->clipr = src->r;
       stmp->flags &= ~Frepl;
       stmp->r = Rect(0, 0, Xrange, Yrange);
       stmp->clipr = src->r;
       mask->flags &= ~Frepl;
       mask->r = Rect(0, 0, Xrange, Yrange);
       mask->clipr = mask->r;
       mtmp->flags &= ~Frepl;
       mtmp->r = Rect(0, 0, Xrange, Yrange);
       mtmp->clipr = mask->r;

       fill(dst, dstbits);
       fill(src, srcbits);

       memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);
       memmove(src->data->bdata, srcbits, src->width*sizeof(ulong)*Yrange);
       memmove(mask->data->bdata, maskbits, mask->width*sizeof(ulong)*Yrange);

       if(srcrepl){
               replicate(src, stmp);
               s = stmp;
       }else
               s = src;
       if(maskrepl){
               replicate(mask, mtmp);
               m = mtmp;
       }else
               m = mask;

       dr = randrect();

       sp.x = nrand(Xrange);
       sp.y = nrand(Yrange);

       mp.x = nrand(Xrange);
       mp.y = nrand(Yrange);

DBG     print("smalldraws\n");
       for(tp.y=sp.y,up.y=mp.y,y=dr.min.y; y<dr.max.y && tp.y<Yrange && up.y<Yrange; y++,tp.y++,up.y++)
               for(tp.x=sp.x,up.x=mp.x,x=dr.min.x; x<dr.max.x && tp.x<Xrange && up.x<Xrange; x++,tp.x++,up.x++)
                       memimagedraw(dst, Rect(x, y, x+1, y+1), s, tp, m, up, SoverD);
       memmove(savedstbits, dst->data->bdata, dst->width*sizeof(ulong)*Yrange);

       memmove(dst->data->bdata, dstbits, dst->width*sizeof(ulong)*Yrange);

DBG     print("bigdraw\n");
       memimagedraw(dst, dr, src, sp, mask, mp, SoverD);
       for(y=0; y<Yrange; y++)
               checkline(dr, drawrepl(src->r, sp), drawrepl(mask->r, mp), y, srcrepl?stmp:nil, maskrepl?mtmp:nil);
}

void
verifyrectrepl(int srcrepl, int maskrepl)
{
       int i;

       /* mask all ones */
       memset(maskbits, 0xFF, nbytes);
       for(i=0; i<niters; i++)
               verifyrectmaskrepl(srcrepl, maskrepl);

       /* mask all zeros */
       memset(maskbits, 0, nbytes);
       for(i=0; i<niters; i++)
               verifyrectmaskrepl(srcrepl, maskrepl);

       /* random mask */
       for(i=0; i<niters; i++){
               fill(mask, maskbits);
               verifyrectmaskrepl(srcrepl, maskrepl);
       }
}

/*
* Trivial draw implementation.
* Color values are passed around as ulongs containing ααRRGGBB
*/

/*
* Convert v, which is nhave bits wide, into its nwant bits wide equivalent.
* Replicates to widen the value, truncates to narrow it.
*/
ulong
replbits(ulong v, int nhave, int nwant)
{
       v &= (1<<nhave)-1;
       for(; nhave<nwant; nhave*=2)
               v |= v<<nhave;
       v >>= (nhave-nwant);
       return v & ((1<<nwant)-1);
}

/*
* Decode a pixel into the uchar* values.
*/
void
pixtorgba(ulong v, uchar *r, uchar *g, uchar *b, uchar *a)
{
       *a = v>>24;
       *r = v>>16;
       *g = v>>8;
       *b = v;
}

/*
* Convert uchar channels into ulong pixel.
*/
ulong
rgbatopix(uchar r, uchar g, uchar b, uchar a)
{
       return (a<<24)|(r<<16)|(g<<8)|b;
}

/*
* Retrieve the pixel value at pt in the image.
*/
ulong
getpixel(Memimage *img, Point pt)
{
       uchar r, g, b, a, *p;
       int nbits, npack, bpp;
       ulong v, c, rbits, bits;

       r = g = b = 0;
       a = ~0; /* default alpha is full */

       p = byteaddr(img, pt);
       v = p[0]|(p[1]<<8)|(p[2]<<16)|(p[3]<<24);
       bpp = img->depth;
       if(bpp<8){
               /*
                * Sub-byte greyscale pixels.
                *
                * We want to throw away the top pt.x%npack pixels and then use the next bpp bits
                * in the bottom byte of v.  This madness is due to having big endian bits
                * but little endian bytes.
                */
               npack = 8/bpp;
               v >>= 8 - bpp*(pt.x%npack+1);
               v &= (1<<bpp)-1;
               r = g = b = replbits(v, bpp, 8);
       }else{
               /*
                * General case.  We need to parse the channel descriptor and do what it says.
                * In all channels but the color map, we replicate to 8 bits because that's the
                * precision that all calculations are done at.
                *
                * In the case of the color map, we leave the bits alone, in case a color map
                * with less than 8 bits of index is used.  This is currently disallowed, so it's
                * sort of silly.
                */

               for(c=img->chan; c; c>>=8){
                       nbits = NBITS(c);
                       bits = v & ((1<<nbits)-1);
                       rbits = replbits(bits, nbits, 8);
                       v >>= nbits;
                       switch(TYPE(c)){
                       case CRed:
                               r = rbits;
                               break;
                       case CGreen:
                               g = rbits;
                               break;
                       case CBlue:
                               b = rbits;
                               break;
                       case CGrey:
                               r = g = b = rbits;
                               break;
                       case CAlpha:
                               a = rbits;
                               break;
                       case CMap:
                               p = img->cmap->cmap2rgb + 3*bits;
                               r = p[0];
                               g = p[1];
                               b = p[2];
                               break;
                       case CIgnore:
                               break;
                       default:
                               fprint(2, "unknown channel type %lud\n", TYPE(c));
                               abort();
                       }
               }
       }
       return rgbatopix(r, g, b, a);
}

/*
* Return the greyscale equivalent of a pixel.
*/
uchar
getgrey(Memimage *img, Point pt)
{
       uchar r, g, b, a;
       pixtorgba(getpixel(img, pt), &r, &g, &b, &a);
       return RGB2K(r, g, b);
}

/*
* Return the value at pt in image, if image is interpreted
* as a mask.  This means the alpha channel if present, else
* the greyscale or its computed equivalent.
*/
uchar
getmask(Memimage *img, Point pt)
{
       if(img->flags&Falpha)
               return getpixel(img, pt)>>24;
       else
               return getgrey(img, pt);
}
#undef DBG

#define DBG if(0)
/*
* Write a pixel to img at point pt.
*
* We do this by reading a 32-bit little endian
* value from p and then writing it back
* after tweaking the appropriate bits.  Because
* the data is little endian, we don't have to worry
* about what the actual depth is, as long as it is
* less than 32 bits.
*/
void
putpixel(Memimage *img, Point pt, ulong nv)
{
       uchar r, g, b, a, *p, *q;
       ulong c, mask, bits, v;
       int bpp, sh, npack, nbits;

       pixtorgba(nv, &r, &g, &b, &a);

       p = byteaddr(img, pt);
       v = p[0]|(p[1]<<8)|(p[2]<<16)|(p[3]<<24);
       bpp = img->depth;
DBG print("v %.8lux...", v);
       if(bpp < 8){
               /*
                * Sub-byte greyscale pixels.  We need to skip the leftmost pt.x%npack pixels,
                * which is equivalent to skipping the rightmost npack - pt.x%npack - 1 pixels.
                */
               npack = 8/bpp;
               sh = bpp*(npack - pt.x%npack - 1);
               bits = RGB2K(r,g,b);
DBG print("repl %lux 8 %d = %lux...", bits, bpp, replbits(bits, 8, bpp));
               bits = replbits(bits, 8, bpp);
               mask = (1<<bpp)-1;
DBG print("bits %lux mask %lux sh %d...", bits, mask, sh);
               mask <<= sh;
               bits <<= sh;
DBG print("(%lux & %lux) | (%lux & %lux)", v, ~mask, bits, mask);
               v = (v & ~mask) | (bits & mask);
       } else {
               /*
                * General case.  We need to parse the channel descriptor again.
                */
               sh = 0;
               for(c=img->chan; c; c>>=8){
                       nbits = NBITS(c);
                       switch(TYPE(c)){
                       case CRed:
                               bits = r;
                               break;
                       case CGreen:
                               bits = g;
                               break;
                       case CBlue:
                               bits = b;
                               break;
                       case CGrey:
                               bits = RGB2K(r, g, b);
                               break;
                       case CAlpha:
                               bits = a;
                               break;
                       case CIgnore:
                               bits = 0;
                               break;
                       case CMap:
                               q = img->cmap->rgb2cmap;
                               bits = q[(r>>4)*16*16+(g>>4)*16+(b>>4)];
                               break;
                       default:
                               SET(bits);
                               fprint(2, "unknown channel type %lud\n", TYPE(c));
                               abort();
                       }

DBG print("repl %lux 8 %d = %lux...", bits, nbits, replbits(bits, 8, nbits));
                       if(TYPE(c) != CMap)
                               bits = replbits(bits, 8, nbits);
                       mask = (1<<nbits)-1;
DBG print("bits %lux mask %lux sh %d...", bits, mask, sh);
                       bits <<= sh;
                       mask <<= sh;
                       v = (v & ~mask) | (bits & mask);
                       sh += nbits;
               }
       }
DBG print("v %.8lux\n", v);
       p[0] = v;
       p[1] = v>>8;
       p[2] = v>>16;
       p[3] = v>>24;
}
#undef DBG

#define DBG if(0)
void
drawonepixel(Memimage *dst, Point dp, Memimage *src, Point sp, Memimage *mask, Point mp)
{
       uchar m, M, sr, sg, sb, sa, sk, dr, dg, db, da, dk;

       pixtorgba(getpixel(dst, dp), &dr, &dg, &db, &da);
       pixtorgba(getpixel(src, sp), &sr, &sg, &sb, &sa);
       m = getmask(mask, mp);
       M = 255-(sa*m + 127)/255;

DBG print("dst %x %x %x %x src %x %x %x %x m %x = ", dr,dg,db,da, sr,sg,sb,sa, m);
       if(dst->flags&Fgrey){
               /*
                * We need to do the conversion to grey before the alpha calculation
                * because the draw operator does this, and we need to be operating
                * at the same precision so we get exactly the same answers.
                */
               sk = RGB2K(sr, sg, sb);
               dk = RGB2K(dr, dg, db);
               dk = (sk*m + dk*M + 127)/255;
               dr = dg = db = dk;
               da = (sa*m + da*M + 127)/255;
       }else{
               /*
                * True color alpha calculation treats all channels (including alpha)
                * the same.  It might have been nice to use an array, but oh well.
                */
               dr = (sr*m + dr*M + 127)/255;
               dg = (sg*m + dg*M + 127)/255;
               db = (sb*m + db*M + 127)/255;
               da = (sa*m + da*M + 127)/255;
       }

DBG print("%x %x %x %x\n", dr,dg,db,da);
       putpixel(dst, dp, rgbatopix(dr, dg, db, da));
}