/*
* 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 */
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);
}
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;
/*
* 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;
/*
* 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;
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;
/*
* 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);
}
/*
* 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();
}
#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;
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;
}