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

#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

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

#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)
{
	file_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;
	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;
	u8_p scan, sptr;
	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;
	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 */
	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 */

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_BITMAP */
	{NULL,NULL}
};

/*
 * Loads raster image
 */
u8_p V_GLOBAL
rasterLoad(char *file_name,u32_t *width_ret, u32_t *height_ret)
{
	u32_t lnum;
    char ext[_MAX_EXT], *ref;
	if(!file_name || !file_name[0])
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"rasterLoad(): wrong file name.\n");
#endif	/* CHECKED_BUILD */
    	return NULL;
    }
    fsSplitPath(file_name,NULL,NULL,ext);
	if(!ext[0])
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"rasterLoad('%s'): name without extension.\n",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);

	/* Unknown extension !
	in=fileOpenRead(file_name);
    if(!in)
    	return -1;
	magic=fileGetLittleShort(in);
    fileClose(in);
    switch(magic)
    {
    	case 0x4D42: return 2;
        case 0xD8FF: return 1;
        case 0x0000: return 0; // TGA: no id, no colormap ?
	}
    return -1;
	*/
	
#ifdef	CHECKED_BUILD
	conlPrintf(LOG_ERROR,
		"rasterLoad('%s'): no appropriate loader found.\n",file_name);
#endif	/* CHECKED_BUILD */
	return NULL;	
}


#if 0

typedef struct
{
	u8_p	src_pixels;
	u32_t	src_width;
	u32_t	src_height;
	vec_t	x1, y1;
	vec_t	x2, y2;
	vec_t	sx, sy, r_area; /* r_area = 1 / (sx * sy) */
	u32_t	first_col, last_col, first_row, last_row;
	u32_t	r,  c;
	vec_t	coverage;
	vec4_t	filtered;
}	box_filter_t, *box_filter_p;

V_LOCAL	void
box_coverage(box_filter_p arg)
{
	vec_t c1 = arg->c;
	vec_t r1 = arg->r;
	vec_t c2 = c1 + 1;
	vec_t r2 = r1 + 1;
	vec_t l,t,r,b;
	l = (arg->x1 < c1) ? c1 : arg->x1;
	r = (arg->x2 > c2) ? c2 : arg->x2;
	t = (arg->y1 < r1) ? r1 : arg->y1;
	b = (arg->y2 > r2) ? r2 : arg->y2;
	arg->coverage = (b-t) * (r-l);
}

V_LOCAL void
box_filter(box_filter_p arg)
{
	u8_p ptr;
	vec4_t rgba, sum;
	arg->x2 = arg->x1 + arg->sx;
	arg->y2 = arg->y1 + arg->sy;
	arg->first_col = (u32_t)(floor(arg->x1));
	arg->first_row = (u32_t)(floor(arg->y1));
	arg->last_col = (u32_t)(floor(arg->x2));
	if(IsEqualEpsilon(arg->x2,arg->last_col,(vec_t)0.01))
	{
		if(arg->last_col >= arg->src_width)
			arg->last_col = arg->src_width - 1;
		else
			arg->last_col--;
		if(arg->first_col > arg->last_col)
			arg->first_col = arg->last_col;
	}
	arg->last_row = (u32_t)(floor(arg->y2));
	if(IsEqualEpsilon(arg->y2,arg->last_row,(vec_t)0.01))
	{
		if(arg->last_row >= arg->src_height)
			arg->last_row = arg->src_height - 1;
		else
			arg->last_row--;
		if(arg->first_row > arg->last_row)
			arg->first_row = arg->last_row;
	}
	vec4Zero(sum);
	for(arg->r=arg->first_row;arg->r<=arg->last_row;arg->r++)
	{
		ptr = arg->src_pixels + ((arg->r * arg->src_width + arg->first_col) << 2);
		for(arg->c=arg->first_col;arg->c<=arg->last_col;arg->c++,ptr+=4)
		{
			box_coverage(arg);
			rgbaFromByte(rgba,ptr);
			vec4Scale(rgba,rgba,arg->coverage);
			vec4Add(sum,sum,rgba);
		}
	}
	vec4Scale(arg->filtered,sum,arg->r_area);
}

V_LOCAL void
scale_ubyte4(u32_t width,u32_t height,u8_p src,
	u32_t new_width,u32_t new_height,u8_p pixels)
{
	u8_p ptr;
	u32_t dest_r, dest_c;
	box_filter_t arg;
	arg.src_pixels = src;
	arg.src_width = width;
	arg.src_height = height;
	arg.sx = (vec_t)width / (vec_t)new_width;
	arg.sy = (vec_t)height / (vec_t)new_height;
	arg.r_area = 1 / (arg.sx * arg.sy);
	arg.y1 = 0;
	for(dest_r=0;dest_r < new_height;dest_r++)
	{
		ptr = pixels + ((dest_r * new_width) << 2);
		arg.x1 = 0;
		for(dest_c=0;dest_c<new_width;dest_c++)
		{
			box_filter(&arg);
			rgbaByteFromFloat(ptr,arg.filtered);
			ptr += 4;
			arg.x1 += arg.sx;
		}
		arg.y1 += arg.sy;
	}
}

u8_p V_GLOBAL
_imgScale(u8_t *src,u32_t width,u32_t height,
	u32_t new_width,u32_t new_height,vboolean keepon)
{
	u32_t cksize;
	u8_p pixels;
	if(!src || !width || !height || !new_width || !new_height)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"imgScale(%X,(%ux%u)->(%ux%u)): incorrect parameter(s).\n",
			src,width,height,new_width,new_height);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	cksize = new_width * new_height * 4;
	pixels = (u8_p)memAlloc(cksize);
	if(!pixels)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"imgScale(%X,(%ux%u)->(%ux%u)): memAlloc(%u) failed.\n",
			src,width,height,new_width,new_height,cksize);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	scale_ubyte4(width,height,src,new_width,new_height,pixels);
	if(!keepon)
		memFree(src);
	return pixels;
}

/*
 * Replaces m x n image by (2m-1) x (2n-1) image
 */
u8_p	V_GLOBAL
imgUpSample(u8_t *src,u32_t width,u32_t height,vboolean keepon)
{
	u8_p src_ptr, dst_ptr;
	u32_t nw = 2 * width - 1, nh = 2 * height - 1;
	u8_p dst = (u8_p)memAllocZ(nw * nh * 4);
	u32_t r, c;
	u8_p c1, c2;
	u8_p box[9];
	u32_t stride = nw << 2;
	/* fill nodes */
	for(r = 0;r < height;r++)
	{
		src_ptr = src + ((r * width) << 2);
		dst_ptr = dst + ((r << 1) * stride);
		for(c = 0;c < width; c++)
		{
			*((u32_p)dst_ptr) = *((u32_p)src_ptr);
			dst_ptr += 8;
			src_ptr += 4;
		}
	}
	if(!keepon)
		memFree(src);
	/* column interpolation */
	for(r = 0;r < height;r++)
	{
		c1 = dst + ((r << 1) * stride);
		dst_ptr = c1 + 4;
		for(c = 1;c < width; c++)
		{
			c2 = dst_ptr + 4;
			/* lerp color */
			dst_ptr[0] = (u8_t)((c1[0] + c2[0]) / 2);
			dst_ptr[1] = (u8_t)((c1[1] + c2[1]) / 2);
			dst_ptr[2] = (u8_t)((c1[2] + c2[2]) / 2);
			dst_ptr[3] = (u8_t)((c1[3] + c2[3]) / 2);
			c1 = c2;
			dst_ptr = c1 + 4;
		}
	}
	/* row interpolation */
	for(c = 0;c < nw;c += 2)
	{
		for(r = 1;r < nh;r += 2)
		{
			dst_ptr = dst + ((r * nw + c) << 2);
			c1 = dst_ptr - stride;
			c2 = dst_ptr + stride;
			/* lerp color */
			dst_ptr[0] = (u8_t)((c1[0] + c2[0]) / 2);
			dst_ptr[1] = (u8_t)((c1[1] + c2[1]) / 2);
			dst_ptr[2] = (u8_t)((c1[2] + c2[2]) / 2);
			dst_ptr[3] = (u8_t)((c1[3] + c2[3]) / 2);
		}
	}
	/* midpoint interpolation */
	for(r = 1;r < nh;r += 2)
	{
		for(c = 0;c < nw; c+= 2)
		{
			int k;
			box[3] = dst + ((r * nw + c) << 2);
			box[4] = box[3] + 4;
			box[5] = box[4] + 4;
			box[0] = box[3] - stride;
			box[1] = box[4] - stride;
			box[2] = box[5] - stride;
			box[6] = box[3] + stride;
			box[7] = box[4] + stride;
			box[8] = box[5] + stride;
			/* lerp color */
			for(k=0;k<4;k++)
			{
				box[4][k] = (u8_t)(
					(4 * (box[1][k]+box[3][k]+box[5][k]+box[7][k]) +
					 (box[0][k]+box[2][k]+box[6][k]+box[8][k]))/20);
			}
		}
	}
	return dst;
}

#endif
