#include #include #include #include #include #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 = doprint(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<data->bdata); Bprint(&b, "%-4d\t", y); // for(x=r.min.x; x>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> 7; if(i+i != nbytes) *(uchar*)up = lrand() >> 7; }else{ data = img->data->bdata; img->data->bdata = ucbits; for(x=img->r.min.x; xr.max.x; x++) for(y=img->r.min.y; yr.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; idata->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; xdata->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; idata->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; ydata->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; yr, sp), drawrepl(mask->r, mp), y, nil, nil); } void verifyrect(void) { int i; /* mask all zeros */ memset(maskbits, 0, nbytes); for(i=0; ir) */ 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(; xflags |= 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; ydata->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; yr, 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>= (nhave-nwant); return v & ((1<>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<chan; c; c>>=8){ nbits = NBITS(c); bits = v & ((1<>= 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<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<>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)); }