Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#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 drawdebug=0;
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
rdb(void)
{
}

int
iprint(char *fmt, ...)
{
        int n;  
        va_list va;
        char buf[1024];

        va_start(va, fmt);
        n = vseprint(buf, buf+sizeof buf, fmt, va) - buf;
        va_end(va);

        write(1,buf,n);
        return 1;
}

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)/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)/255;
                dr = dg = db = dk;
                da = (sa*m + da*M)/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)/255;
                dg = (sg*m + dg*M)/255;
                db = (sb*m + db*M)/255;
                da = (sa*m + da*M)/255;
        }

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