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