#include <../source/TAL/objbase.h>
#include <../source/PAL/dataman.h>

/*
 * bitmap.c  - raster image helpers.
 * 09/29/2004 - fixed bug in handling grayscale JPEG images.
 * TODO: numerous cleanups.
 */

GLOBAL_STRING(libv_clipboard_name,LIBV_CLIPBOARD_NAME)

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

#ifdef	CONFIG_RASTER_TARGA

/* 
 *	Based on Quake II implementation.
 */

typedef struct _TargaHeader /* FIXME: get rid of it, it is not necessary */
{
	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;
}	TargaHeader; /* _no_ need to pack fields - see below */

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;
	vhandle_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",
			strSafe(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;
	}
	/* get all fields sequentially, in endian - independent manner */
	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;
	vhandle_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",
			strSafe(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,true);
}

#endif	/* CONFIG_RASTER_TARGA */

#ifdef	CONFIG_RASTER_BITMAP

/*
 *	TODO: implement loading from clipboard (implement separate function
 *	'bmpPasteFromClipboard()', but don't export it; instead, call it
 *	from bmpLoad() if name is libv_clipboard_name).
 */

#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>


#ifndef	BI_RGB
#define BI_RGB	0
#endif

#ifndef	BI_RLE8
#define BI_RLE8	1
#endif

V_LOCAL void bmp_decode_rle8_image(u8_t *dest_buf,u8_t *src,s32_t dest_width,
	s32_t dest_height,u8_t alpha_byte,rgbquad_t *palette)
{
	s32_t x, y;
	u8_t b1, b2, color_index, *dest;
	x = 0;
	for(y = dest_height-1;y >= 0;)
	{
		dest = dest_buf + ((y * dest_width + x) << 2);
		/* Read two-byte description of the compression method */
		b1 = *src; src++;
		b2 = *src; src++;
		if(b1)
		{
			/* 1st byte is non-zero: encoded mode */
			while(b1--)
			{
				if(x == dest_width)
				{
					if(!y)
						return;
					y--;
					x = 0;
					dest = dest_buf + ((y * dest_width) << 2);
				}
				dest[0] = palette[b2].red;
				dest[1] = palette[b2].green;
				dest[2] = palette[b2].blue;
				dest[3] = alpha_byte;
				dest += 4;
				x++;
			}
		}
		else
		{
			/* 1st byte is zero: either escape or absolute mode */
			switch(b2)
			{
				case 0: /* escape: EOL */
					y--;
					x = 0;
					continue;
				case 1: /* escape: EOF */
					return;
				case 2: /* delta */
					/* read offsets */
					x += *src; src++;
					y -= *src; src++;
					continue;
				default: /* absolute mode */
					b1 = b2;
        			while(b2--)
        			{
        				if(x == dest_width)
        				{
        					if(!y)
        						return;
        					y--;
        					x = 0;
        					dest = dest_buf + ((y * dest_width) << 2);
        				}
        				color_index = *src; src++;
        				dest[0] = palette[color_index].red;
        				dest[1] = palette[color_index].green;
        				dest[2] = palette[color_index].blue;
        				dest[3] = alpha_byte;
        				dest += 4;
        				x++;
        			}
        			if(b1 & 1) /* each run must be aligned on word boundary */
        				src++;
					break;
			}
		}
	}
}

V_LOCAL void bmp_decode_mono_row(u8_t *dest,u8_t *src,
	u32_t row_len,u8_t alpha_byte,rgbquad_t *palette)
{
	u32_t x, color_index;
	u8_t byte = *src, bit_num = 7;
	for(x=0;x < row_len;x++)
	{
		color_index = (byte & (1 << bit_num)) ? 1 : 0;
		if(!bit_num)
		{
			src++;
			byte = *src;
			bit_num = 7;
		}
		else
			bit_num--;
		dest[0] = palette[color_index].red;
		dest[1] = palette[color_index].green;
		dest[2] = palette[color_index].blue;
		dest[3] = alpha_byte;
		dest += 4;
	}
}

V_LOCAL void bmp_decode_paletted_row(u8_t *dest,u8_t *src,u32_t row_len,u8_t alpha_byte,
	rgbquad_t *palette)
{
	u32_t x, color_index;
	for(x=0;x<row_len;x++)
	{
		color_index = src[x];
		dest[0] = palette[color_index].red;
		dest[1] = palette[color_index].green;
		dest[2] = palette[color_index].blue;
		dest[3] = alpha_byte;
		dest += 4;
	}
}

#include <../source/PAL/argsused.h>
V_LOCAL void bmp_decode_rgb_row(u8_t *dest,u8_t *src,u32_t row_len,
	u8_t alpha_byte,rgbquad_t *palette)
{
	u32_t x;
	for(x=0;x<row_len;x++)
	{
		dest[0] = src[2];
		dest[1] = src[1];
		dest[2] = src[0];
		dest[3] = alpha_byte;
		dest += 4;
		src += 3;
	}
}

u8_p	V_GLOBAL
bmpLoad(char *file_name,u32_t *width_ret,u32_t *height_ret)
{
	vhandle_t infile;
	u32_t input_start,input_row_bytes,input_row_stride,num_pal_entries;
	u32_t output_row_stride,output_row_offset;
	u8_t dummy[64];
	void (*decode_row)(u8_t *dest,u8_t *src,u32_t row_len,u8_t alpha_byte,rgbquad_t *palette);
	u16_t htype;
	u32_t width,height,compression;
	u16_t bitcount;
	u8_t *pixels,*row_bits;
	rgbquad_t palette[256];
	u8_t alpha_byte;
	if(!file_name)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"bmpLoad(NULL): incorrect parameter.\n");
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	infile = fileOpenRead(file_name);
	if(!infile)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"bmpLoad('%s'): file not found.\n",file_name);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	/* Read header magic */
	htype = fileGetLittleShort(infile);
	/* Check if it's a valid Windows bitmap */
	if(htype != 0x4D42)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"bmpLoad('%s'): isn't a bitmap file - magic wrong.\n",file_name);
#endif	/* CHECKED_BUILD */
		fileClose(infile);
		return NULL;
	}
	/* Skip useless fields of header (long size; short res1, res2) */
	fileRead(infile,dummy,8);
	/* Read in offset of the bits */
	input_start = fileGetLittleLong(infile);
	/* ------------------------------------------------------------ */
	/* TODO: bmp_load_dib(infile,input_start,&width,&height, ...)   */
	/* ------------------------------------------------------------ */
	fileRead(infile,dummy,4); /* skip biSize */
	width = fileGetLittleLong(infile);
	height = fileGetLittleLong(infile);
	if(height & (1 << 31))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"bmpLoad('%s'): only bottom-up DIBs suported.\n",file_name);
#endif	/* CHECKED_BUILD */
		fileClose(infile);
		return NULL;
	}
	fileRead(infile,dummy,2); /* skip biPlanes */
	bitcount = fileGetLittleShort(infile);
	if(bitcount!=1 && bitcount!=8 && bitcount!=24)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"bmpLoad('%s'): only 2, 256 or 16M-color images supported.\n",
			file_name);
#endif	/* CHECKED_BUILD */
		fileClose(infile);
		return NULL;
	}
	compression = fileGetLittleLong(infile);
	if(compression!=BI_RGB && compression!=BI_RLE8)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"bmpLoad('%s'): only uncompressed and "
			"RLE8-compressed images supported.\n",file_name);
#endif	/* CHECKED_BUILD */
		fileClose(infile);
		return NULL;
	}
	/* Skip other fields of bitmapinfoheader */
	fileRead(infile,dummy,20);
	/* Read palette */
	num_pal_entries = (bitcount == 24) ? 0 : 1 << bitcount;
	if(num_pal_entries)
		fileRead(infile,palette,num_pal_entries * sizeof(rgbquad_t));
	alpha_byte = GetThreadData()->constant_alpha;
	/* Read pixels */
	pixels = (u8_t*)memAlloc(4 * width * height);
	/* Now, we must take into account that input row length isn't equal 
	 * to width * bitcount / 8, but aligned to the next dword boundary */
	input_row_bytes = (width*bitcount) >> 3;
	if(width*bitcount != (input_row_bytes << 3))
		input_row_bytes++;
	input_row_stride = INT_ALIGN(input_row_bytes); /* length of one line in file */
	/* Start reading bits */
	if(fileTell(infile) != input_start)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_WARNING,
			"bmpLoad('%s'): palette end at %u, but bfOffBits is %u.\n",
			file_name,fileTell(infile),input_start);
#endif	/* CHECKED_BUILD */
		fileSeek(infile,input_start,SEEK_SET);
	}
	output_row_stride = width << 2; /* output format is 4-byte RGBA */
	output_row_offset = height * output_row_stride;
	if(compression == BI_RLE8)
	{
		u32_t src_len = fileSize(infile) - fileTell(infile);
		row_bits = (u8_t*)memAlloc(src_len);
		fileRead(infile,row_bits,src_len);
		memZero32(pixels,width * height);
		bmp_decode_rle8_image(pixels,row_bits,(s32_t)width,(s32_t)height,alpha_byte,palette);
	}
	else
	{
    	switch(bitcount)
    	{
    		case 1:
    			decode_row = bmp_decode_mono_row;
    			break;
    		case 8:
    			decode_row = bmp_decode_paletted_row;
    			break;
    		default:
    			decode_row = bmp_decode_rgb_row;
    	}
		row_bits = (u8_t*)memAlloc(input_row_stride);
		do
		{
			output_row_offset -= output_row_stride;
			fileRead(infile,row_bits,input_row_stride);
			(*decode_row)(&pixels[output_row_offset],row_bits,width,alpha_byte,palette);
		} while(output_row_offset);
	}
	fileClose(infile);
	memFree(row_bits);
	if(width_ret)
		*width_ret = width;
	if(height_ret)
		*height_ret = height;
	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;
	vhandle_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)
{
	vhandle_t outfile;
	u32_t x,y,zeros,stride_adjust,offset;
	u8_t *ptr;
	u8_p scan, sptr;
	if(!name || !width || !height || !pixels)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"bmpSaveEx('%s',%d,%d,%X): incorrect parameter(s).\n",
			strSafe(name),width,height,pixels);
#endif	/* CHECKED_BUILD */
		return false;
	}
#ifdef	CONFIG_CLIPBOARD
	if(!strcmp(name,libv_clipboard_name))
		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;
	scan = (u8_p)memAlloc(width * 3);
	for(y=0;y<height;y++)
	{
		offset -= width << 2;
		ptr = pixels+offset;
		sptr = scan;
		for(x=0;x<width;x++)
		{
			sptr[0] = ptr[2];
			sptr[1] = ptr[1];
			sptr[2] = ptr[0];
			ptr += 4;
			sptr += 3;
		}
		fileWrite(outfile,scan,width * 3);
		if(stride_adjust)
			fileWrite(outfile,&zeros,stride_adjust);
	}
	memFree(scan);
	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/jpeglib/src/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 */
	vhandle_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,vhandle_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);
}

V_LOCAL void
convert_rgb_to_rgba(unsigned char *dst,unsigned char *src,
	int triples,u8_t alphabyte)
{
	int i;
	unsigned char *dptr, *sptr;
	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] = alphabyte;
	}
}

V_LOCAL void
convert_gray_to_rgba(unsigned char *dst,unsigned char *src,
	int triples,u8_t alphabyte)
{
	int i;
	unsigned char *dptr, *sptr;
	for(i=0,dptr=dst,sptr=src;i<triples;i++,dptr+=4,sptr++)
	{
		dptr[0] = *sptr;
		dptr[1] = *sptr;
		dptr[2] = *sptr;
		dptr[3] = alphabyte;
	}
}

/*
 * The wrapper.
 */
u8_p	V_GLOBAL
jpgLoad(char *name,u32_t *width,u32_t *height)
{
	CALLER_NAME(jpgLoad)
	DEBUG_MESSAGE(wrong_out_comps,"image must be either RGB or grayscale")
	DEBUG_MESSAGE(file_not_found,"can't load: ")
	unsigned char default_alpha;
	struct jpeg_decompress_struct cinfo;
	int row_stride;
	vhandle_t infile;
	struct new_error_mgr jerr;
	JSAMPARRAY buffer;
	unsigned char *outbuf = NULL;
	unsigned char *out = NULL;
	void (*conv_routine)(unsigned char *,unsigned char *,int,u8_t);
	default_alpha = GetThreadData()->constant_alpha;
	memZero(&cinfo,sizeof(struct jpeg_decompress_struct));
	memZero(&jerr,sizeof(struct jpeg_error_mgr));
	cinfo.err = jpeg_std_error(&jerr.pub);
	if(!name)
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return NULL;
	}
	infile = fileOpenRead(name);
	if(!infile)
	{
		ERROR_GENERIC_2ARG(file_not_found,name);
		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);
		/* GCC 2.95.3 issues warning here; fixme ? */
		if(outbuf)
			memFree(outbuf);
		return NULL;
	}
	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;
	switch(cinfo.output_components)
	{
		case 1:
			conv_routine = convert_gray_to_rgba;
			break;
		case 3:
			conv_routine = convert_rgb_to_rgba;
			break;
		default:
			ERROR_GENERIC(wrong_out_comps);
			jpeg_destroy_decompress(&cinfo);
			fileClose(infile);
			return NULL;
	}
	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);
		(*conv_routine)(out,buffer[0],cinfo.output_width,default_alpha);
		out += (cinfo.output_width << 2);
	}
	jpeg_finish_decompress(&cinfo);
	jpeg_destroy_decompress(&cinfo);
	fileClose(infile);
	return outbuf;
}

#endif	/* CONFIG_RASTER_JPEG */

typedef u8_p (V_CALL *imgload_t)(char *name,u32_t *width,u32_t *height);

V_LOCAL struct
{
	char		*file_extension;
	imgload_t	loader;
}	raster_image_loaders[] = {
#ifdef	CONFIG_RASTER_TARGA
	{"tga",tgaLoad},
#endif	/* CONFIG_RASTER_TARGA */
#ifdef	CONFIG_RASTER_BITMAP
	{"bmp",bmpLoad},
#endif	/* CONFIG_RASTER_BITMAP */
#ifdef	CONFIG_RASTER_JPEG
	{"jpg",jpgLoad},
	{"jpeg",jpgLoad},
#endif	/* CONFIG_RASTER_JPEG */
	{NULL,NULL}
};

/*
 * Loads raster image
 */
u8_p V_GLOBAL
rgbaLoad(char *file_name,u32_t *width_ret, u32_t *height_ret)
{
	CALLER_NAME(rgbaLoad)
	u32_t lnum;
    char ext[_MAX_EXT], *ref;
	if(!file_name || !file_name[0])
	{
		ERROR_GENERIC(error_incorrect_parameters);
    	return NULL;
    }
    fsSplitPath(file_name,NULL,NULL,ext);
	if(!ext[0])
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s('%s'): unable to determine image format.\n",
			caller_name,file_name);
#endif	/* CHECKED_BUILD */
    	return NULL;
    }
    lnum = 0;
    ref = raster_image_loaders[0].file_extension;
    for(;ref;lnum++,ref=raster_image_loaders[lnum].file_extension)
    	if(!strcasecmp(ext,ref))
    		return (*(raster_image_loaders[lnum].loader))(file_name,width_ret,height_ret);
#ifdef	CHECKED_BUILD
	conlPrintf(LOG_ERROR,
		"%s('%s'): no appropriate loader found.\n",caller_name,file_name);
#endif	/* CHECKED_BUILD */
	return NULL;	
}

typedef vboolean (V_CALL *imgsave_t)(char *name,u32_t width,u32_t height,u8_t *pixels);

V_LOCAL struct
{
	char		*file_extension;
	imgsave_t	exporter;
}	raster_image_exporters[] = {
#ifdef	CONFIG_RASTER_TARGA
	{"tga",tgaSave},
#endif	/* CONFIG_RASTER_TARGA */
#ifdef	CONFIG_RASTER_BITMAP
	{"bmp",bmpSave},
#endif	/* CONFIG_RASTER_BITMAP */
	{NULL,NULL}
};

vboolean V_GLOBAL
rgbaSaveEx(char *file_name,u32_t width, u32_t height,u8_p pixels,
	u32_t mmWidth,u32_t mmHeight)
{
	CALLER_NAME(rgbaSaveEx)
	imgsave_t save_routine = NULL;
	u32_t lnum;
    char ext[_MAX_EXT], *ref;
	if(!file_name || !file_name[0])
	{
		ERROR_GENERIC(error_incorrect_parameters);
    	return false;
    }
	if(strcasecmp(file_name,libv_clipboard_name))
	{
	    fsSplitPath(file_name,NULL,NULL,ext);
		if(!ext[0])
		{
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"%s('%s'): unable to determine image format.\n",
				caller_name,file_name);
#endif	/* CHECKED_BUILD */
	    	return false;
	    }
	    lnum = 0;
	    ref = raster_image_exporters[0].file_extension;
	    for(;ref;lnum++,ref = raster_image_exporters[lnum].file_extension)
	    	if(!strcasecmp(ext,ref))
	    		save_routine = raster_image_exporters[lnum].exporter;
	}
	else
		save_routine = bmpSave;
	if(!save_routine)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s('%s'): no appropriate export routine found.\n",
			caller_name,file_name);
#endif	/* CHECKED_BUILD */
		return false;
	}
	if(mmWidth && mmHeight)
	{
		if(save_routine == bmpSave)
			return bmpSaveEx(file_name,width,height,pixels,mmWidth,mmHeight);
	}
	return (*save_routine)(file_name,width,height,pixels);
}

vboolean V_GLOBAL
rgbaSave(char *file_name,u32_t width, u32_t height,u8_p pixels)
{
	return rgbaSaveEx(file_name,width,height,pixels,0,0);
}
