#include <../source/TAL/objbase.h>
#include <../source/PAL/dataman.h>
/*
 * bitmap.c  - raster image helpers.
 */

/*
 * Under Win32 we need system headers for GDI functions.
 */
#if defined(CONFIG_CLIPBOARD) || defined(CONFIG_WIN32_DIBOBJECT)
#include <../source/PAL/oshead.h>
#endif

#if		defined(CONFIG_WIN32_DIBOBJECT) && !defined(__WIN32__)
#error	GDI calls not available on target platform.
#endif	/* CONFIG_WIN32_DIBOBJECT && !__WIN32__ */


/*
 * imgDefaultAlpha(): sets default value for alpha channel.
 * Raster input routines uses this value for alpha channel if source
 * image contains only RGB information.
 *
 * TODO: move to another file
 * TODO?: Implement 'alpha generators':
 *
 * a) src_rgba_ref stored with zero alpha: overrides 'constant_alpha'
 * void	imgColorKey(u32_t src_rgba_ref,u8_t alpha_to_store);
 *
 * b) callback to use with xxxLoad(): overrides color key.
 * typedef u8_t (V_CALL *alphagen_t)(u32_t x,u32_t y,u32_t rgba);
 * void	imgAlphaGen(alphagen_t alphagen);
 */ 
u8_t	V_GLOBAL
imgDefaultAlpha(u8_t new_alpha)
{
	thread_data_t *td;
	u8_t old_alpha;
	td=GetThreadData();
	old_alpha=td->constant_alpha;
	td->constant_alpha=new_alpha;
	return old_alpha;
}

/*
 * imgDownsample(): helper function for mip mapping.
 * TODO: move to another file
 */	
u8_p	V_GLOBAL
imgDownsample(u8_t *src,u32_t width,u32_t height,vboolean keepon)
{
	u32_t x,y,ad_src,w_s=width;
	u32_t r[4],g[4],b[4],a[4];
	u32_t offset;
	u8_t *dest;
	offset=0;
	if(width<2 || height<2 || !IS_POWER_TWO(width) 
		|| !IS_POWER_TWO(height) || !src)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"imgDownsample(%X,%d,%d): incorrect parameter(s).\n",
			src,width,height);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	/* four times smaller than original */
	dest=(u8_t*)memAlloc(width * height);
	width>>=1;
	height>>=1;
	for(y=0;y<height;y++)
	{ 
		for(x=0;x<width;x++)
		{
			ad_src = (x<<1) + (y<<1) * w_s;
			r[0]=src[(ad_src<<2)];
			g[0]=src[(ad_src<<2)+1];
			b[0]=src[(ad_src<<2)+2];
			a[0]=src[(ad_src<<2)+3];
			
			r[1]=src[((ad_src+1)<<2)];
			g[1]=src[((ad_src+1)<<2)+1];
			b[1]=src[((ad_src+1)<<2)+2];
			a[1]=src[((ad_src+1)<<2)+3];
			
			r[2]=src[((ad_src+w_s)<<2)];
			g[2]=src[((ad_src+w_s)<<2)+1];
			b[2]=src[((ad_src+w_s)<<2)+2];
			a[2]=src[((ad_src+w_s)<<2)+3];
			
			r[3]=src[((ad_src+w_s+1)<<2)];
			g[3]=src[((ad_src+w_s+1)<<2)+1];
			b[3]=src[((ad_src+w_s+1)<<2)+2];
			a[3]=src[((ad_src+w_s+1)<<2)+3];
			
			dest[offset++]=(u8_t)((r[0]+r[1]+r[2]+r[3])>>2);
			dest[offset++]=(u8_t)((g[0]+g[1]+g[2]+g[3])>>2);
			dest[offset++]=(u8_t)((b[0]+b[1]+b[2]+b[3])>>2);
			dest[offset++]=(u8_t)((a[0]+a[1]+a[2]+a[3])>>2);
		}
	}
	if(!keepon)
		memFree(src);
	return dest;
}

#ifdef	CONFIG_RASTER_TARGA

/* 
 *	Based on code from Quake II.
 */
#include <../source/PAL/pack0.h>
typedef struct _TargaHeader 
{
	u8_t	id_length, colormap_type, image_type;
	u16_t	colormap_index, colormap_length;
	u8_t	colormap_size;
	u16_t	x_origin, y_origin, width, height;
	u8_t	pixel_size, attributes;
}	ATTRIBUTE_PACKED TargaHeader;
#include <../source/PAL/ppack.h>

u8_p	V_GLOBAL
tgaLoad(char *name,u32_t *width,u32_t *height)
{
	u32_t	columns, rows, numPixels;
	u8_t	*pixbuf;
	u8_t	red,green,blue,alphabyte;
	u32_t row, column;
	file_t	f;
	u8_t	*targa_rgba;
	TargaHeader targa_header;
	if(!name)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"tgaLoad('%s',%X,%X): incorrect parameter(s).\n",
			dbs(name),width,height);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	f=fileOpenRead(name);
	if(!f)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"tgaLoad('%s'): file not found.\n",name);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	targa_header.id_length = fileGetc(f);
	targa_header.colormap_type = fileGetc(f);
	targa_header.image_type = fileGetc(f);
	targa_header.colormap_index = fileGetLittleShort(f);
	targa_header.colormap_length = fileGetLittleShort(f);
	targa_header.colormap_size = fileGetc(f);
	targa_header.x_origin = fileGetLittleShort(f);
	targa_header.y_origin = fileGetLittleShort(f);
	targa_header.width = fileGetLittleShort(f);
	targa_header.height = fileGetLittleShort(f);
	targa_header.pixel_size = fileGetc(f);
	targa_header.attributes = fileGetc(f);
	if(targa_header.image_type!=2 && targa_header.image_type!=10)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"tgaLoad('%s'): image type '%d' not supported.\n",
			name,targa_header.image_type);
#endif	/* CHECKED_BUILD */
		fileClose(f);
		return NULL;
	}
	if(targa_header.colormap_type !=0 
		|| (targa_header.pixel_size!=32 && targa_header.pixel_size!=24))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"tgaLoad('%s'): only 32 or 24 bit images supported.\n",
			name);
#endif	/* CHECKED_BUILD */
		fileClose(f);
		return NULL;
	}
	columns = targa_header.width;
	rows = targa_header.height;
	numPixels = columns * rows;
	if(width)
		*width = columns;
	if(height)
		*height = rows;
	targa_rgba = (u8_t*)memAlloc(numPixels * 4);
	if(!targa_rgba)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"tgaLoad('%s'): can't allocate %d bytes.\n",
			name,numPixels*4);
#endif	/* CHECKED_BUILD */
		fileClose(f);
		return NULL;
	}
	alphabyte=GetThreadData()->constant_alpha;
	/* skip TARGA image comment */
	if(targa_header.id_length != 0)
		fileSeek(f,targa_header.id_length,SEEK_CUR);
	if(targa_header.image_type==2)
	{
		/* Uncompressed, RGB images */
		for(row=rows-1;;row--)
		{
			pixbuf = targa_rgba + row*columns*4;
			for(column=0; column<columns; column++)
			{
				switch(targa_header.pixel_size)
				{
					case 24:
						blue = fileGetc(f);
						green = fileGetc(f);
						red = fileGetc(f);
						*pixbuf++ = red;
						*pixbuf++ = green;
						*pixbuf++ = blue;
						*pixbuf++ = alphabyte;
						break;
					case 32:
						blue = fileGetc(f);
						green = fileGetc(f);
						red = fileGetc(f);
						alphabyte = fileGetc(f);
						*pixbuf++ = red;
						*pixbuf++ = green;
						*pixbuf++ = blue;
						*pixbuf++ = alphabyte;
						break;
				}
			}
			if(!row)
				break;
		}
	}
	else /* targa_header.image_type==10 */
	{
		/* Runlength encoded RGB images */
		u8_t packetHeader,packetSize,j;
		red=0;
		green=0;
		blue=0;
		for(row=rows-1;;row--)
		{
			pixbuf = targa_rgba + row*columns*4;
			for(column=0; column<columns;)
			{
				packetHeader=fileGetc(f);
				packetSize = (u8_t)(1 + (packetHeader & 0x7f));
				if(packetHeader & 0x80)
				{
					/* run-length packet */
					switch (targa_header.pixel_size)
					{
						case 24:
							blue = fileGetc(f);
							green = fileGetc(f);
							red = fileGetc(f);
							break;
						case 32:
							blue = fileGetc(f);
							green = fileGetc(f);
							red = fileGetc(f);
							alphabyte = fileGetc(f);
							break;
					}
					for(j=0;j<packetSize;j++)
					{
						*pixbuf++=red;
						*pixbuf++=green;
						*pixbuf++=blue;
						*pixbuf++=alphabyte;
						column++;
						if(column==columns)
						{
							/* run spans across rows */
							column=0;
							if(row>0)
								row--;
							else
								goto breakOut;
							pixbuf = targa_rgba + row*columns*4;
						}
					}
				}
				else	/* if(packetHeader & 0x80) */
				{
					/* non run-length packet */
					for(j=0;j<packetSize;j++)
					{
						switch (targa_header.pixel_size)
						{
							case 24:
								blue = fileGetc(f);
								green = fileGetc(f);
								red = fileGetc(f);
								*pixbuf++ = red;
								*pixbuf++ = green;
								*pixbuf++ = blue;
								*pixbuf++ = 255;
								break;
							case 32:
								blue = fileGetc(f);
								green = fileGetc(f);
								red = fileGetc(f);
								alphabyte = fileGetc(f);
								*pixbuf++ = red;
								*pixbuf++ = green;
								*pixbuf++ = blue;
								*pixbuf++ = alphabyte;
								break;
						}
						column++;
						if(column==columns)
						{
							/* pixel packet run spans across rows */
							column=0;
							if(row>0)
								row--;
							else
								goto breakOut;
							pixbuf = targa_rgba + row*columns*4;
						}           
					}
				}	/* if(packetHeader & 0x80) else */
			}	/* for column */
			if(!row)
				break;
		}	/* for(row) */;
		breakOut:;
	}
	fileClose(f);
	return targa_rgba;
}

vboolean	V_GLOBAL
tgaSaveEx(char *name,u32_t width,u32_t height,u8_t *pixels,
	vboolean save_alpha)
{
	u8_p ptr;
	file_t f;
	u32_t x,y;
	u16_t w,h;
	if(!name || !width || !height || !pixels)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"tgaSaveEx('%s',%d,%d,%X,%d): incorrect parameter(s).\n",
			dbs(name),width,height,pixels,save_alpha);
#endif	/* CHECKED_BUILD */
		return false;
	}
	f=fileOpenWrite(name);
	if(!f)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"tgaSaveEx('%s'): can't create file.\n",name);
#endif	/* CHECKED_BUILD */
		return false;
	}
	filePutc(f,0);			/* no id field */
	filePutc(f,0);			/* no color map */
	filePutc(f,2);			/* type = True color uncompressed */
	filePutLittleShort(f,0);	/* first color map entry */
	filePutLittleShort(f,0);	/* color map length */
	filePutc(f,0);			/* color map entry size */
	filePutLittleShort(f,0);	/* origin x & y */
	filePutLittleShort(f,0);
	w=(u16_t)width;
	h=(u16_t)height;
	filePutLittleShort(f,w);	/* image width & height */
	filePutLittleShort(f,h);
	if(save_alpha)
		filePutc(f,32);		/* bits per pixel */
	else
		filePutc(f,24);		/* bits per pixel */
	filePutc(f,0);
	if(save_alpha)
	{
		for(y=height-1;;y--)
		{
			ptr=pixels+(y*width*4);
			for(x=0;x<width;x++)
			{
				filePutc(f,ptr[2]);
				filePutc(f,ptr[1]);
				filePutc(f,ptr[0]);
				filePutc(f,ptr[3]);
				ptr+=4;
			}
			if(!y)
				break;
		}
	}
	else
	{  
		for(y=height-1;;y--)
		{
			ptr=pixels+(y*width*4);
			for(x=0;x<width;x++)
			{
				filePutc(f,ptr[2]);
				filePutc(f,ptr[1]);
				filePutc(f,ptr[0]);
				ptr+=4;
			}
			if(!y)
				break;
		}
	}
	fileClose(f);
	return true;
}

vboolean	V_GLOBAL
tgaSave(char *name,u32_t width,u32_t height,u8_t *pixels)
{
	return tgaSaveEx(name,width,height,pixels,false);
}

#endif	/* CONFIG_RASTER_TARGA */

#ifdef	CONFIG_RASTER_BITMAP

#include <../source/PAL/pack0.h>
typedef struct
{
    u8_t    blue; 
    u8_t    green; 
    u8_t    red; 
    u8_t    reserved; 
}	ATTRIBUTE_PACKED rgbquad_t;
#include <../source/PAL/ppack.h>

/*
 *	TODO: implement loading from clipboard (implement separate function
 *	'bmpPasteFromClipboard()', but don't export it; instead, call it
 *	from bmpLoad() if name is "//./clipboard").
 */
u8_p	V_GLOBAL
bmpLoad(char *name,u32_t *width,u32_t *height)
{
	file_t f;
	u32_t x,y,stride,adjust,offset,dummy;
	unsigned char c;
	u8_t *pixels,*ptr;
	/* The only useful header field */
	u16_t htype;
	/* Useful info fields */
	u32_t iwidth;
	u32_t iheight;
	u16_t ibitcount;
	u32_t icompression;
	/* palette */
	rgbquad_t pal[256];
	thread_data_t *td;
	if(!name)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"bmpLoad('%s'): incorrect parameter.\n",
			dbs(name));
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	f=fileOpenRead(name);
	if(!f)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"bmpLoad('%s'): file not found.\n",name);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	/* Read header magic */
	htype=fileGetLittleShort(f);
	/* Check if valid Windows bitmap */
	if(htype!=0x4D42)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"bmpLoad('%s'): isn't a bitmap file, magic wrong.\n",name);
#endif	/* CHECKED_BUILD */
		fileClose(f);
		return NULL;
	}
	/* Skip useless fields of header */
	/* long size; short res1, res2; long offbits */
	fileSeek(f,12,SEEK_CUR);
	/* Read bitmapinfo */
	/* TODO: use biSize to locate bits ! */
	fileGetLittleLong(f);		/* assuming NO comment */
	iwidth=fileGetLittleLong(f);
	iheight=fileGetLittleLong(f);
	fileGetLittleShort(f);
	ibitcount=fileGetLittleShort(f);
	icompression=fileGetLittleLong(f);
	/* Skip other fields of bitmapinfo */
	fileSeek(f,20,SEEK_CUR);
	if(icompression)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"bmpLoad('%s'): compressed images not supported.\n",
			name);
#endif	/* CHECKED_BUILD */
		fileClose(f);
		return NULL;
	}
	if(ibitcount!=8 && ibitcount!=24)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"bmpLoad('%s'): only 256 and 16M-color images supported.\n",
			name);
#endif	/* CHECKED_BUILD */
		fileClose(f);
		return NULL;
	}
	/* Read pixels */
	td=GetThreadData();
	if(width)
		*width=iwidth;
	if(height)
		*height=iheight;
	pixels=(u8_t*)memAlloc(4*iwidth*iheight);
	stride=iwidth * 4;
	adjust=iwidth*3; /* ... Hmmm ... */
	adjust=INT_ALIGN(adjust);
	adjust-=iwidth*3;
	offset=iheight * stride;
	if(ibitcount==8)
	{
		fileRead(f,pal,256*sizeof(rgbquad_t));
		for(y=0;y<iheight;y++)
		{
			offset-=stride;
			ptr=pixels+offset;
			for(x=0;x<iwidth;x++)
			{
				c=fileGetc(f);
				ptr[0]=pal[c].red;
				ptr[1]=pal[c].green;
				ptr[2]=pal[c].blue;
				ptr[3]=td->constant_alpha;
				ptr+=4;
			}
			if(adjust)
				fileRead(f,&dummy,adjust);
		}
	}
	else /* truecolor 24-bit image */
	{
		for(y=0;y<iheight;y++)
		{
			offset-=stride;
			ptr=pixels+offset;
			for(x=0;x<iwidth;x++)
			{
				ptr[2]=fileGetc(f);
				ptr[1]=fileGetc(f);
				ptr[0]=fileGetc(f);
				ptr[3]=td->constant_alpha;
				ptr+=4;
			}
			if(adjust)
				fileRead(f,&dummy,adjust);
		}
	}
	fileClose(f);
	return pixels;
}

/*
 *	Returns size in millimeters
 */
u32_t	V_GLOBAL
bmpMMFromPixels(u32_t num_pixels,u32_t device_dpi)
{
	return (u32_t)((25.2 * num_pixels) / device_dpi);
}

#ifdef	CONFIG_WIN32_DIBOBJECT

/*
 * Creates packed DIB structure in memory.
 * TODO: move common part into other proc.
 */
void_p	V_GLOBAL
bmpCreateDIBObject(u8_t *rgba,u32_t width,u32_t height,
	u32_t mmWidth,u32_t mmHeight)
{
	u32_t x,y,stride,offset,cksize;
	u8_t *bgr,*ptr;
	BITMAPINFOHEADER *head;
	stride=3*width;
	while(stride % 4)
		stride++;
	cksize=sizeof(BITMAPINFOHEADER)+height*stride;
	head=(BITMAPINFOHEADER*)memAlloc(cksize);
	if(!head)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"bmpCreateDIBObject(): can't memAlloc() %d bytes.\n",
			cksize);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	memZero(head,cksize);
	/* Set head fields */
	head->biSize=sizeof(BITMAPINFOHEADER);
	head->biWidth=width;
	head->biHeight=height;
	head->biPlanes=1;
	head->biBitCount=24;
	head->biCompression=0;
	head->biSizeImage=width*height*3;
	if(mmWidth)
		head->biXPelsPerMeter=(1000 * width) / mmWidth;
	else
		head->biXPelsPerMeter=0;
	if(mmHeight)
		head->biYPelsPerMeter=(1000 * height) / mmHeight;
	else
		head->biYPelsPerMeter=0;
	head->biClrUsed=0;
	head->biClrImportant=0;
	/* Store bits */
	bgr=(u8_t*)(head+1);
	offset=height*stride;
	for(y=0;y<height;y++)
	{
		offset-=stride;
		ptr=bgr+offset;
		for(x=0;x<width;x++)
		{
			ptr[0]=rgba[2];
			ptr[1]=rgba[1];
			ptr[2]=rgba[0];
			ptr+=3;
			rgba+=4;
		}
	}
	return head;
}

#endif	/* CONFIG_WIN32_DIBOBJECT */

#ifdef	CONFIG_CLIPBOARD

/*
 * bmpCopyToClipboard() copies bitmap to the system clipboard.
 */

#ifdef __WIN32__

vboolean	V_LOCAL
bmpCopyToClipboard(u8_t *rgba,u32_t width,u32_t height,
	u32_t mmWidth,u32_t mmHeight)
{
	u32_t x,y,stride,offset,cksize;
	u8_t *bgr,*ptr;
	HGLOBAL pic;
	BITMAPINFOHEADER *head;
	stride=3*width;
	while(stride % 4)
		stride++;
	cksize=sizeof(BITMAPINFOHEADER)+height*stride;
	if(!OpenClipboard(NULL))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"bmpSaveEx(): OpenClipboard() failed (code %d).\n",
			GetLastError());
#endif	/* CHECKED_BUILD */
		return false;
	}
	/* Allocate piece of GMEM_DDESHARE'd memory */
	pic=GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE,cksize);
	head=(BITMAPINFOHEADER*)GlobalLock(pic);
	if(!head)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"bmpSaveEx(): can't GlobalAlloc() %d bytes (code %d).\n",
			cksize,GetLastError());
#endif	/* CHECKED_BUILD */
		return false;
	}
	memZero(head,cksize);
	EmptyClipboard();
	/* Set head fields */
	head->biSize=sizeof(BITMAPINFOHEADER);
	head->biWidth=width;
	head->biHeight=height;
	head->biPlanes=1;
	head->biBitCount=24;
	head->biCompression=0;
	head->biSizeImage=width*height*3;
	if(mmWidth)
		head->biXPelsPerMeter=(1000 * width) / mmWidth;
	else
		head->biXPelsPerMeter=0;
	if(mmHeight)
		head->biYPelsPerMeter=(1000 * height) / mmHeight;
	else
		head->biYPelsPerMeter=0;
	head->biClrUsed=0;
	head->biClrImportant=0;
	/* Store bits */
	bgr=(u8_t*)(head+1);
	offset=height*stride;
	for(y=0;y<height;y++)
	{
		offset-=stride;
		ptr=bgr+offset;
		for(x=0;x<width;x++)
		{
			ptr[0]=rgba[2];
			ptr[1]=rgba[1];
			ptr[2]=rgba[0];
			ptr+=3;
			rgba+=4;
		}
	}
	/* Release HGLOBAL */
	GlobalUnlock(pic);
	/* Pass it to system */
	SetClipboardData(CF_DIB,pic);
	CloseClipboard();
	return true;
}

#else /* !__WIN32__ */

vboolean	V_LOCAL
bmpCopyToClipboard(u8_t *rgba,u32_t width,u32_t height,
	u32_t mmWidth,u32_t mmHeight)
{
	u8_t *ptr;
	u32_t count;
	int i,rows,rest;
	file_t clip;
	V_ASSERT(width && height);
	clip = fileOpenWrite("/tmp/libv-bitmap.clip");
	if(!clip)
		return false;
	filePrintf(clip,
		"int width=%u;\n"
		"int height=%u;\n"
		"int mmWidth=%u;\n"
		"int mmHeight=%u;\n",width,height,mmWidth,mmHeight);
	count = 4*width*height;
	filePrintf(clip,"unsigned char rgba_data[%u] = {\n",count);
	ptr = rgba;
	rows = count / 16;
	rest = count - (rows * 16);
	if(!rest)
	{
		rows--;
		rest=16;
	}
	for(i=0;i<rows;i++,ptr+=16)
		filePrintf(clip,
			"\t0x%02X,0x%02X,0x%02X,0x%02X,"
			"0x%02X,0x%02X,0x%02X,0x%02X,"
			"0x%02X,0x%02X,0x%02X,0x%02X,"
			"0x%02X,0x%02X,0x%02X,0x%02X,\n",
			ptr[0x0],ptr[0x1],ptr[0x2],ptr[0x3],
			ptr[0x4],ptr[0x5],ptr[0x6],ptr[0x7],
			ptr[0x8],ptr[0x9],ptr[0xA],ptr[0xB],
			ptr[0xC],ptr[0xD],ptr[0xE],ptr[0xF]);
	rest--;
	filePuts(clip,"\t");
	for(i=0;i<rest;i++)
		filePrintf(clip,"0x%02X,",ptr[i]);
	filePrintf(clip,"0x%02X\n};\n",ptr[i]);
	fileClose(clip);
	return true;
}

#endif	/* !__WIN32__ */

#endif	/* CONFIG_CLIPBOARD */

/*
 *	bmpSaveEx
 */
#define	BMP_HEADER_SIZE	(3*2+2*4)
#define	BMP_INFO_SIZE	(2*2+9*4)

vboolean	V_GLOBAL
bmpSaveEx(char *name,u32_t width,u32_t height,u8_t *pixels,
	u32_t mmWidth,u32_t mmHeight)
{
	file_t outfile;
	u32_t x,y,zeros,stride_adjust,offset;
	u8_t *ptr;
	if(!name || !width || !height || !pixels)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"bmpSaveEx('%s',%d,%d,%X): incorrect parameter(s).\n",
			dbs(name),width,height,pixels);
#endif	/* CHECKED_BUILD */
		return false;
	}
#ifdef	CONFIG_CLIPBOARD
	if(!strcmp(name,"//./clipboard"))
		return bmpCopyToClipboard(pixels,width,height,mmWidth,mmHeight);
#endif	/* CONFIG_CLIPBOARD */
	outfile=fileOpenWrite(name);
	if(!outfile)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"bmpSaveEx('%s'): can't open file.\n",name);
#endif	/* CHECKED_BUILD */
		return false;
	}
	/* Write header */
	filePutLittleShort(outfile,0x4D42);
	filePutLittleLong(outfile,BMP_HEADER_SIZE+BMP_INFO_SIZE+4*width*height);
	filePutLittleShort(outfile,0);
	filePutLittleShort(outfile,0);
	filePutLittleLong(outfile,BMP_HEADER_SIZE+BMP_INFO_SIZE);
	/* Write bitmapinfo */
	filePutLittleLong(outfile,BMP_INFO_SIZE);
	filePutLittleLong(outfile,width);
	filePutLittleLong(outfile,height);
	filePutLittleShort(outfile,1);
	filePutLittleShort(outfile,24);
	filePutLittleLong(outfile,0);
	filePutLittleLong(outfile,width*height);
	if(mmWidth)
		filePutLittleLong(outfile,(1000 * width) / mmWidth);
	else
		filePutLittleLong(outfile,0);
	if(mmHeight)
		filePutLittleLong(outfile,(1000 * height) / mmHeight);
	else
		filePutLittleLong(outfile,0);
	filePutLittleLong(outfile,0);
	filePutLittleLong(outfile,0);
	/* Write pixels */
	zeros=0;
	x=stride_adjust=width*3;
	while(stride_adjust % 4)
		stride_adjust++;
	stride_adjust-=x;
	offset=width*height*4;
	for(y=0;y<height;y++)
	{
		offset-=width << 2;
		ptr=pixels+offset;
		for(x=0;x<width;x++)
		{
			filePutc(outfile,ptr[2]);
			filePutc(outfile,ptr[1]);
			filePutc(outfile,ptr[0]);
			ptr+=4;
		}
		if(stride_adjust)
			fileWrite(outfile,&zeros,stride_adjust);
	}
	fileClose(outfile);
	return true;
}

vboolean	V_GLOBAL
bmpSave(char *name,u32_t width,u32_t height,u8_t *pixels)
{
	return bmpSaveEx(name,width,height,pixels,100,100);
}

#endif	/* CONFIG_RASTER_BITMAP */

#ifdef		CONFIG_RASTER_JPEG

#include	<setjmp.h>
#include	"../support/jpeglib6b/jpeglib.h"

/*
 *	Part 1. Source managment (stolen from jdatasrc.c).
 *	We want to create jpeg source manager which can be able to read
 *	data from both normal and VFS files.
 */

typedef struct
{
	struct	jpeg_source_mgr pub;	/* public fields */
	file_t	infile;
	char	infile_name[_MAX_PATH];
	JOCTET	*buffer;
	boolean start_of_file;
}	new_source_mgr;

typedef new_source_mgr *new_src_ptr;

#define	JPEG_INPUT_BUF_SIZE		(16 * 4096)

/*
 *	Source manager methods.
 */
METHODDEF(void)
init_source(j_decompress_ptr cinfo)
{
	new_src_ptr src = (new_src_ptr) cinfo->src;
	src->start_of_file = TRUE;
}

METHODDEF(boolean)
fill_input_buffer (j_decompress_ptr cinfo)
{
	new_src_ptr src = (new_src_ptr) cinfo->src;
	size_t nbytes;
	nbytes = fileRead(src->infile, src->buffer, JPEG_INPUT_BUF_SIZE);
	if (nbytes <= 0)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"jpgLoad(): fill_input_buffer(): can't read from '%s'.\n",
			src->infile_name);
#endif	/* CHECKED_BUILD */
		src->buffer[0] = (JOCTET) 0xFF;
		src->buffer[1] = (JOCTET) JPEG_EOI;
		nbytes = 2;
	}
	src->pub.next_input_byte = src->buffer;
	src->pub.bytes_in_buffer = nbytes;
	src->start_of_file = FALSE;
	return TRUE;
}

METHODDEF(void)
skip_input_data (j_decompress_ptr cinfo,long num_bytes)
{
	new_src_ptr src = (new_src_ptr) cinfo->src;
	if (num_bytes > 0)
	{
		while (num_bytes > (long) src->pub.bytes_in_buffer)
		{
			num_bytes -= (long) src->pub.bytes_in_buffer;
			(void) fill_input_buffer(cinfo);
		}
		src->pub.next_input_byte += (size_t) num_bytes;
		src->pub.bytes_in_buffer -= (size_t) num_bytes;
	}
}

#include <../source/PAL/argsused.h>
METHODDEF(void)
term_source (j_decompress_ptr cinfo)
{
	/* no work necessary here */
}

LOCAL(void)
jpeg_file_src(j_decompress_ptr cinfo,file_t infile,char *filename)
{
	new_src_ptr src;
	if (cinfo->src == NULL)
	{
		/* first time for this JPEG object? */
		cinfo->src = (struct jpeg_source_mgr *)
			(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
				sizeof(new_source_mgr));
		src = (new_src_ptr) cinfo->src;
		src->buffer = (JOCTET *)
			(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
				JPEG_INPUT_BUF_SIZE * sizeof(JOCTET));
	}
	src = (new_src_ptr) cinfo->src;
	src->pub.init_source = init_source;
	src->pub.fill_input_buffer = fill_input_buffer;
	src->pub.skip_input_data = skip_input_data;
	src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
	src->pub.term_source = term_source;
	src->infile = infile;
	strCopy(src->infile_name,filename,_MAX_PATH);
	src->pub.bytes_in_buffer = 0;
	src->pub.next_input_byte = NULL;
}

/* Part 2. Error handling (stolen from jpeglib 'example.c') */
struct new_error_mgr
{
	struct	jpeg_error_mgr pub;	/* "public" fields */
	jmp_buf	setjmp_buffer;		/* for return to caller */
};

typedef struct new_error_mgr *new_error_ptr;

METHODDEF(void)
new_output_message (j_common_ptr cinfo)
{
	char buffer[JMSG_LENGTH_MAX];
	(*cinfo->err->format_message) (cinfo, buffer);
#ifdef	CHECKED_BUILD
	conlPrintf(LOG_ERROR,"JPEGLIB error: '%s'\n", buffer);
#endif /* CHECKED_BUILD */
}

METHODDEF(void)
new_error_exit(j_common_ptr cinfo)
{
	new_error_ptr newerr = (new_error_ptr) cinfo->err;
	(*cinfo->err->output_message) (cinfo);
	longjmp(newerr->setjmp_buffer, 1);
}

LOCAL(void)
convert_rgb_to_rgba(unsigned char *dst,unsigned char *src,int triples)
{
	int i;
	unsigned char default_alpha;
	unsigned char *dptr, *sptr;
	default_alpha=GetThreadData()->constant_alpha;
	for(i=0,dptr=dst,sptr=src;i<triples;i++,dptr+=4,sptr+=3)
	{
		dptr[0]=sptr[0];
		dptr[1]=sptr[1];
		dptr[2]=sptr[2];
		dptr[3]=default_alpha;
	}
}

/*
 * The wrapper.
 */
u8_p	V_GLOBAL
jpgLoad(char *name,u32_t *width,u32_t *height)
{
	struct jpeg_decompress_struct cinfo;
	int row_stride;
	file_t infile;
	struct new_error_mgr jerr;
	JSAMPARRAY buffer;
	unsigned char *outbuf,*out;
	memZero(&cinfo,sizeof(struct jpeg_decompress_struct));
	memZero(&jerr,sizeof(struct jpeg_error_mgr));
	cinfo.err = jpeg_std_error(&jerr.pub);
	if(!name)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"jpegLoad('%s'): incorrect parameter.\n",
			dbs(name));
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	infile = fileOpenRead(name);
	if(!infile)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"jpegLoad('%s'): can't open file.\n",name);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	jerr.pub.error_exit = new_error_exit;
	jerr.pub.output_message = new_output_message;
	if (setjmp(jerr.setjmp_buffer))
	{
		/* If we get here, the JPEG code has signaled an error.
		 * We need to clean up the JPEG object, close the input file,
		 * and return.
		 */
		jpeg_destroy_decompress(&cinfo);
		fileClose(infile);
		return 0;
	}
	jpeg_create_decompress(&cinfo);
	jpeg_file_src(&cinfo,infile,name);
	jpeg_read_header(&cinfo,TRUE);
	jpeg_start_decompress(&cinfo);

	row_stride = cinfo.output_width * cinfo.output_components;
	if(width)
		*width=cinfo.output_width;
	if(height)
		*height=cinfo.output_height;

	buffer = (*cinfo.mem->alloc_sarray)
		((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);

	outbuf=(u8_t*)memAlloc(cinfo.output_width*cinfo.output_height*4);
	out=outbuf;
	while (cinfo.output_scanline < cinfo.output_height)
	{
		jpeg_read_scanlines(&cinfo, buffer, 1);
		convert_rgb_to_rgba(out,buffer[0],cinfo.output_width);
		out+=cinfo.output_width << 2;
	}
	jpeg_finish_decompress(&cinfo);
	jpeg_destroy_decompress(&cinfo);
	fileClose(infile);
	return outbuf;
}

#endif	/* CONFIG_RASTER_JPEG */
