/* -------------------------------------------------------------------------- *\
   BITMAP24.CPP
   Copyright © 1997 by Jarno van der Linden
   jarno@kcbbs.gen.nz

   24 bit bitmap handling, including IFF24 saving

   This program is Freeware, and all usual Freeware rules apply.

   21 Feb 1997: Project started
\* -------------------------------------------------------------------------- */

/* -------------------------------- Includes -------------------------------- */
#include <dos/dos.h>

#include <proto/dos.h>

#include <string.h>

#include "bitmap24.h"

/* ------------------------------ Definitions ------------------------------- */

/* --------------------------------- Macros --------------------------------- */

/* -------------------------------- Typedefs -------------------------------- */
typedef UBYTE Masking;
#define mskNone	0
typedef UBYTE Compression;
#define cmpNone		0
#define cmpByteRun	1

typedef struct {
	UWORD w,h;
	WORD x,y;
	UBYTE nPlanes;
	Masking masking;
	Compression compression;
	UBYTE reserved1;
	UWORD transparentColor;
	UBYTE xAspect,yAspect;
	WORD pageWidth,pageHeight;
} BitMapHeader;


/* ------------------------------ Proto Types ------------------------------- */

/* -------------------------------- Structs --------------------------------- */

/* -------------------------------- Globals --------------------------------- */

/* ---------------------------------- Code ---------------------------------- */
BitMap24::BitMap24(UWORD width_arg, UWORD height_arg)
{
	error = BITMAP24_ERROR_NONE;

	width = width_arg;
	height = height_arg;

	width += (16-(width & 15)) & 15;
	bytewidth = width >> 3;

	bitmap = new Colour[width*height];
	if(bitmap)
		memset(bitmap,0,width*height*sizeof(Colour));
	else
		error = BITMAP24_ERROR_NOBITMAP;
}


BitMap24::~BitMap24()
{
	if(bitmap)
		delete bitmap;
}


int BitMap24::GetError()
{
	return error;
}


BOOL BitMap24::HasError()
{
	return error != BITMAP24_ERROR_NONE;
}


char *BitMap24::GetErrorStr()
{
	switch(error)
	{
		case BITMAP24_ERROR_NONE:
			return "No error";
		case BITMAP24_ERROR_NOBITMAP:
			return "BitMap couldn't be allocated";
		case BITMAP24_ERROR_FILEOPEN:
			return "Error opening output file";
		case BITMAP24_ERROR_OUTOFBOUNDS:
			return "Out of bounds coordinate used";
	}

	return "Unknown error";
}



UWORD BitMap24::GetWidth()
{
	return width;
}


UWORD BitMap24::GetHeight()
{
	return height;
}


BOOL BitMap24::BoundsCheck(int x,int y)
{
	if((x < 0) || (x >= width) || (y < 0) || (y >= height))
	{
		error = BITMAP24_ERROR_OUTOFBOUNDS;
		return FALSE;
	}

	return TRUE;
}


void BitMap24::GetColour(Colour *c,int x,int y)
{
	if(BoundsCheck(x,y))
		*c = bitmap[y*width+x];
}


UBYTE BitMap24::GetRed(int x,int y)
{
	if(BoundsCheck(x,y))
		return bitmap[y*width+x].r;

	return 0;
}


UBYTE BitMap24::GetGreen(int x,int y)
{
	if(BoundsCheck(x,y))
		return bitmap[y*width+x].g;

	return 0;
}


UBYTE BitMap24::GetBlue(int x,int y)
{
	if(BoundsCheck(x,y))
		return bitmap[y*width+x].b;

	return 0;
}


void BitMap24::SetColour(UBYTE r,UBYTE g,UBYTE b,int x,int y)
{
	Colour *bp;

	if(BoundsCheck(x,y))
	{
		bp = &(bitmap[y*width+x]);
		bp->r = r;
		bp->g = g;
		bp->b = b;
	}
}


void BitMap24::WriteV(BPTR fh,ULONG v)
{
	FWrite(fh,&v,sizeof(v),1);
}


void BitMap24::WriteB(BPTR fh,UBYTE b)
{
	FWrite(fh,&b,sizeof(b),1);
}

void BitMap24::WriteC(BPTR fh, BYTE b)
{
	FWrite(fh,&b,sizeof(b),1);
}


void BitMap24::WriteP(BPTR fh,void *p,int size)
{
	FWrite(fh,p,size,1);
}


void BitMap24::WriteS(BPTR fh,char *s)
{
	FWrite(fh,s,strlen(s),1);
}


UBYTE BitMap24::GatherBits(int row, int c, int b, int x)
{
	UBYTE *bp;
	UBYTE v;

	x <<= 3;
	b = 1<<b;

	bp = ((UBYTE *)&bitmap[row*width+x]+c);

	v = (UBYTE)((*bp & b)!=0);
	bp+=3;
	v = (UBYTE)((v<<1) | ((*bp & b)!=0));
	bp+=3;
	v = (UBYTE)((v<<1) | ((*bp & b)!=0));
	bp+=3;
	v = (UBYTE)((v<<1) | ((*bp & b)!=0));
	bp+=3;
	v = (UBYTE)((v<<1) | ((*bp & b)!=0));
	bp+=3;
	v = (UBYTE)((v<<1) | ((*bp & b)!=0));
	bp+=3;
	v = (UBYTE)((v<<1) | ((*bp & b)!=0));
	bp+=3;
	v = (UBYTE)((v<<1) | ((*bp & b)!=0));

	return v;
}


void BitMap24::WriteRun(BPTR fh, int row, int c, int b, int runstart, int runend)
{
	int x,n;
	UBYTE v;

	v = GatherBits(row,c,b,runstart);

	for(x=runstart; x<=runend; x+=128)
	{
		n = (runend-x+1);
		if(n > 128)
			n = 128;

		WriteC(fh, (BYTE)(-(n-1)));
		WriteB(fh, v);
	}
}


void BitMap24::WriteDump(BPTR fh, int row, int c, int b, int runstart, int runend)
{
	int xx,x,n;

	for(x=runstart; x<=runend; x+=128)
	{
		n = (runend-x+1);
		if(n > 128)
			n = 128;

		WriteC(fh, (BYTE)(n-1));
		for(xx=0; xx<n; xx++)
			WriteB(fh, GatherBits(row,c,b,xx+x));
	}
}



void BitMap24::FindRun(int row, int c, int b, int start, int *runstart, int *runlength)
{
	int x;
	UBYTE vs,v;

	*runstart = x = start;
	v = GatherBits(row,c,b,x);

	while(*runstart < bytewidth)
	{
		vs = v;
		x++;
		while((x < bytewidth) && ((v = GatherBits(row,c,b,x)) == vs))
			x++;

		if((*runlength = x-*runstart) > 2)
			return;

		*runstart = x;
	}
}


void BitMap24::WriteBitMap(char *file)
{
	BPTR fh;
	BitMapHeader bmheader;
	int y,c,b;
	int runstart,runlength,runend;
	long lastpos;

	bmheader.w = width;
	bmheader.h = height;
	bmheader.x = 0;
	bmheader.y = 0;
	bmheader.nPlanes = 24;
	bmheader.masking = mskNone;
	bmheader.compression = cmpByteRun;
	bmheader.reserved1 = 0;
	bmheader.transparentColor = 0;
	bmheader.xAspect = 1;
	bmheader.yAspect = 1;
	bmheader.pageWidth = (WORD)width;
	bmheader.pageHeight = (WORD)height;

	fh = Open(file,MODE_NEWFILE);
	if(!fh)
	{
		error = BITMAP24_ERROR_FILEOPEN;
		return;
	}

	WriteS(fh,"FORM");
	WriteV(fh,0);
	WriteS(fh,"ILBMBMHD");
	WriteV(fh,sizeof(bmheader));
	WriteP(fh,&bmheader,sizeof(bmheader));
	WriteS(fh,"BODY");
	WriteV(fh,0);

	for(y=0; y<height; y++)
	{
		for(c=0; c<3; c++)
		{
			for(b=0; b<8; b++)
			{
				runend = 0;

				while(runend < bytewidth)
				{
					FindRun(y,c,b, runend, &runstart, &runlength);
					WriteDump(fh, y,c,b, runend,runstart-1);
					runend = runstart+runlength;
					if(runstart < bytewidth)
						WriteRun(fh, y,c,b, runstart,runend-1);
				}
			}
		}
	}

	lastpos = Seek(fh, 0, OFFSET_CURRENT);
	if(lastpos & 1)
		WriteB(fh,0);
	lastpos = Seek(fh, 4, OFFSET_BEGINNING);
	WriteV(fh,lastpos-Seek(fh, 0, OFFSET_CURRENT)-4);
	Seek(fh, 4+4+8+4+sizeof(bmheader)+4, OFFSET_BEGINNING);
	WriteV(fh, lastpos-Seek(fh, 0, OFFSET_CURRENT)-4);

	Close(fh);
}
