/*
 * bitmap.c
 * 
 * Copyright (c) 2003 Alexandre Pigolkine
 * Copyright (C) 2007 Novell, Inc (http://www.novell.com)
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
 * subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial 
 * portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 * Authors:
 *   Alexandre Pigolkine (pigolkine@gmx.de)
 *   Vladimir Vukicevic (vladimir@pobox.com)
 *   Jordi Mas (jordi@ximian.com)
 *   Jonathan Gilbert (logic@deltaq.org)
 *   Sebastien Pouliot  <sebastien@ximian.com>
 */

#include "gdiplus-private.h"
#include "bitmap-private.h"
#include "bmpcodec.h"
#include "general-private.h"
#include "graphics-private.h"
#include "metafile-private.h"


static GpStatus gdip_bitmap_clone_data_rect (ActiveBitmapData *srcData, Rect *srcRect, ActiveBitmapData *destData, Rect *destRect);


/* The default indexed palettes. This code was generated by a tiny C# program.
 */

static const unsigned int default_Format1bppIndexed_palette[2] = {
	0xFF000000, 0xFFFFFFFF
};

static const unsigned int default_Format4bppIndexed_palette[16] = {
	0xFF000000, 0xFF800000, 0xFF008000, 0xFF808000, 0xFF000080, 0xFF800080, 0xFF008080, 0xFF808080, 0xFFC0C0C0, 0xFFFF0000, 0xFF00FF00, 0xFFFFFF00, 0xFF0000FF, 0xFFFF00FF, 0xFF00FFFF, 0xFFFFFFFF 
};

static const unsigned int default_Format8bppIndexed_palette[256] = {
	0xFF000000, 0xFF800000, 0xFF008000, 0xFF808000, 0xFF000080, 0xFF800080, 0xFF008080, 0xFF808080, 0xFFC0C0C0, 0xFFFF0000, 0xFF00FF00, 0xFFFFFF00, 0xFF0000FF, 0xFFFF00FF, 0xFF00FFFF, 0xFFFFFFFF, 
	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 
	0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFF000000, 0xFF000033, 0xFF000066, 0xFF000099, 0xFF0000CC, 0xFF0000FF, 0xFF003300, 0xFF003333, 
	0xFF003366, 0xFF003399, 0xFF0033CC, 0xFF0033FF, 0xFF006600, 0xFF006633, 0xFF006666, 0xFF006699, 0xFF0066CC, 0xFF0066FF, 0xFF009900, 0xFF009933, 0xFF009966, 0xFF009999, 0xFF0099CC, 0xFF0099FF, 
	0xFF00CC00, 0xFF00CC33, 0xFF00CC66, 0xFF00CC99, 0xFF00CCCC, 0xFF00CCFF, 0xFF00FF00, 0xFF00FF33, 0xFF00FF66, 0xFF00FF99, 0xFF00FFCC, 0xFF00FFFF, 0xFF330000, 0xFF330033, 0xFF330066, 0xFF330099, 
	0xFF3300CC, 0xFF3300FF, 0xFF333300, 0xFF333333, 0xFF333366, 0xFF333399, 0xFF3333CC, 0xFF3333FF, 0xFF336600, 0xFF336633, 0xFF336666, 0xFF336699, 0xFF3366CC, 0xFF3366FF, 0xFF339900, 0xFF339933, 
	0xFF339966, 0xFF339999, 0xFF3399CC, 0xFF3399FF, 0xFF33CC00, 0xFF33CC33, 0xFF33CC66, 0xFF33CC99, 0xFF33CCCC, 0xFF33CCFF, 0xFF33FF00, 0xFF33FF33, 0xFF33FF66, 0xFF33FF99, 0xFF33FFCC, 0xFF33FFFF, 
	0xFF660000, 0xFF660033, 0xFF660066, 0xFF660099, 0xFF6600CC, 0xFF6600FF, 0xFF663300, 0xFF663333, 0xFF663366, 0xFF663399, 0xFF6633CC, 0xFF6633FF, 0xFF666600, 0xFF666633, 0xFF666666, 0xFF666699, 
	0xFF6666CC, 0xFF6666FF, 0xFF669900, 0xFF669933, 0xFF669966, 0xFF669999, 0xFF6699CC, 0xFF6699FF, 0xFF66CC00, 0xFF66CC33, 0xFF66CC66, 0xFF66CC99, 0xFF66CCCC, 0xFF66CCFF, 0xFF66FF00, 0xFF66FF33, 
	0xFF66FF66, 0xFF66FF99, 0xFF66FFCC, 0xFF66FFFF, 0xFF990000, 0xFF990033, 0xFF990066, 0xFF990099, 0xFF9900CC, 0xFF9900FF, 0xFF993300, 0xFF993333, 0xFF993366, 0xFF993399, 0xFF9933CC, 0xFF9933FF, 
	0xFF996600, 0xFF996633, 0xFF996666, 0xFF996699, 0xFF9966CC, 0xFF9966FF, 0xFF999900, 0xFF999933, 0xFF999966, 0xFF999999, 0xFF9999CC, 0xFF9999FF, 0xFF99CC00, 0xFF99CC33, 0xFF99CC66, 0xFF99CC99, 
	0xFF99CCCC, 0xFF99CCFF, 0xFF99FF00, 0xFF99FF33, 0xFF99FF66, 0xFF99FF99, 0xFF99FFCC, 0xFF99FFFF, 0xFFCC0000, 0xFFCC0033, 0xFFCC0066, 0xFFCC0099, 0xFFCC00CC, 0xFFCC00FF, 0xFFCC3300, 0xFFCC3333, 
	0xFFCC3366, 0xFFCC3399, 0xFFCC33CC, 0xFFCC33FF, 0xFFCC6600, 0xFFCC6633, 0xFFCC6666, 0xFFCC6699, 0xFFCC66CC, 0xFFCC66FF, 0xFFCC9900, 0xFFCC9933, 0xFFCC9966, 0xFFCC9999, 0xFFCC99CC, 0xFFCC99FF, 
	0xFFCCCC00, 0xFFCCCC33, 0xFFCCCC66, 0xFFCCCC99, 0xFFCCCCCC, 0xFFCCCCFF, 0xFFCCFF00, 0xFFCCFF33, 0xFFCCFF66, 0xFFCCFF99, 0xFFCCFFCC, 0xFFCCFFFF, 0xFFFF0000, 0xFFFF0033, 0xFFFF0066, 0xFFFF0099, 
	0xFFFF00CC, 0xFFFF00FF, 0xFFFF3300, 0xFFFF3333, 0xFFFF3366, 0xFFFF3399, 0xFFFF33CC, 0xFFFF33FF, 0xFFFF6600, 0xFFFF6633, 0xFFFF6666, 0xFFFF6699, 0xFFFF66CC, 0xFFFF66FF, 0xFFFF9900, 0xFFFF9933, 
	0xFFFF9966, 0xFFFF9999, 0xFFFF99CC, 0xFFFF99FF, 0xFFFFCC00, 0xFFFFCC33, 0xFFFFCC66, 0xFFFFCC99, 0xFFFFCCCC, 0xFFFFCCFF, 0xFFFFFF00, 0xFFFFFF33, 0xFFFFFF66, 0xFFFFFF99, 0xFFFFFFCC, 0xFFFFFFFF
};


/*
	Those are the only pixel formats that we really support	
*/
static BOOL
gdip_is_a_supported_pixelformat (PixelFormat fmt)
{
	switch (fmt) {
	case PixelFormat1bppIndexed:
	case PixelFormat4bppIndexed:
	case PixelFormat8bppIndexed:
	case PixelFormat24bppRGB:
	case PixelFormat32bppARGB:
	case PixelFormat32bppPARGB:
	case PixelFormat32bppRGB:
		return TRUE;
	default:
		return FALSE;
	}
}

static BOOL
gdip_is_an_alpha_pixelformat (PixelFormat format)
{
	return ((format & PixelFormatAlpha) != 0);
}

/*
	Returns TRUE if the Bitmap contains indexed (palettized) data.
*/
BOOL
gdip_is_an_indexed_pixelformat (PixelFormat fmt)
{
	return ((fmt & PixelFormatIndexed) != 0);
}

void 
gdip_bitmap_init (GpBitmap *bitmap)
{
	if (bitmap == NULL)
		return;

	memset (bitmap, 0, sizeof (GpBitmap));

	bitmap->type = ImageTypeBitmap;
	bitmap->image_format = INVALID;
}

static GpStatus
gdip_propertyitems_clone(PropertyItem *src, PropertyItem **dest, int count)
{
	PropertyItem	*result;
	int		i;
	int		j;

	if (dest == NULL) {
		return InvalidParameter;
	}

	if (src == NULL) {
		*dest = NULL;
		return Ok;
	}
	result = GdipAlloc(sizeof(PropertyItem) * count);
	if (result == NULL) {
		return OutOfMemory;
	}

	for (i = 0; i < count; i++) {
		result[i].id = src[i].id;
		result[i].length = src[i].length;
		result[i].type = src[i].type;
		if ((src[i].value != NULL) && (src[i].length > 0)) {
			result[i].value = GdipAlloc(src[i].length);
			if (result[i].value == NULL) {
				for (j = 0; j < i; j++) {
					if (result[j].value != NULL) {
						GdipFree(result[j].value);
					}
				}
				GdipFree (result);
				return OutOfMemory;
			}
			memcpy(result[i].value, src[i].value, src[i].length);	
		} else {
			result[i].value = NULL;
		}
	}
	
	*dest = result;
	return Ok;
}

static GpStatus
gdip_propertyitems_dispose(PropertyItem *property, int count)
{
	int i;

	if (property == NULL) {
		return Ok;
	}
	for (i = 0; i < count; i++) {
		if (property[i].value != NULL) {
			GdipFree(property[i].value);
			property[i].value = NULL;
		}
	}
	GdipFree(property);

	return Ok;
}

static GpStatus
gdip_bitmapdata_init (ActiveBitmapData *data)
{
	if (data != NULL) {
		memset(data, 0, sizeof (ActiveBitmapData));
		return Ok;
	}
	return InvalidParameter;
}

GpStatus
gdip_property_get_short(int offset, void *value, unsigned short *result)
{
	BYTE *ptr = (BYTE*)value;

	*result = ptr[offset] + (ptr[offset + 1] << 8);

	return Ok;
}

GpStatus
gdip_property_get_long(int offset, void *value, guint32 *result)
{
	BYTE *ptr = (BYTE*)value;

	*result = ptr[offset] + (ptr[offset + 1] << 8) + (ptr[offset + 2] << 16) + ((guint32)ptr[offset + 3] << 24);

	return Ok;
}

GpStatus
gdip_property_get_srational(int offset, void *value, unsigned short *numerator, unsigned short *denominator)
{
	BYTE *ptr = (BYTE*)value;

	*numerator = ptr[offset] + (ptr[offset + 1] << 8);
	*denominator = ptr[offset + 2] + (ptr[offset + 3] << 8);

	return Ok;
}

GpStatus
gdip_property_get_rational(int offset, void *value, guint32 *numerator, guint32 *denominator)
{
	BYTE *ptr = (BYTE*)value;

	*numerator = ptr[offset] + (ptr[offset + 1] << 8) + (ptr[offset + 2] << 16) + ((guint32)ptr[offset + 3] << 24);
	*denominator = ptr[offset + 4] + (ptr[offset + 5] << 8) + (ptr[offset + 6] << 16) + ((guint32)ptr[offset + 7] << 24);

	return Ok;
}

GpStatus
gdip_bitmapdata_property_add_long(ActiveBitmapData *bitmap_data, PROPID id, guint32 value)
{
	BYTE buffer[4];

	buffer[0] = value & 0xff;
	buffer[1] = (value & 0x0000ff00) >> 8;
	buffer[2] = (value & 0x00ff0000) >> 16;
	buffer[3] = (value & 0xff000000) >> 24;

	return gdip_bitmapdata_property_add (bitmap_data, id, 4, PropertyTagTypeLong, buffer);
}

GpStatus
gdip_bitmapdata_property_add_ASCII(ActiveBitmapData *bitmap_data, PROPID id, BYTE *value)
{
	return gdip_bitmapdata_property_add (bitmap_data, id, strlen((char *)value) + 1, PropertyTagTypeASCII, value);
}

GpStatus
gdip_bitmapdata_property_add_byte (ActiveBitmapData *bitmap_data, PROPID id, BYTE value)
{
	return gdip_bitmapdata_property_add (bitmap_data, id, 1, PropertyTagTypeByte, &value);
}

GpStatus
gdip_bitmapdata_property_add_short(ActiveBitmapData *bitmap_data, PROPID id, unsigned short value)
{
	BYTE buffer[2];

	buffer[0] = value & 0xff;
	buffer[1] = (value & 0x0ff00) >> 8;

	return gdip_bitmapdata_property_add (bitmap_data, id, 2, PropertyTagTypeShort, buffer);
}

GpStatus
gdip_bitmapdata_property_add_rational(ActiveBitmapData *bitmap_data, PROPID id, guint32 numerator, guint32 denominator)
{
	BYTE buffer[8];

	buffer[0] = numerator & 0xff;
	buffer[1] = (numerator & 0x0000ff00) >> 8;
	buffer[2] = (numerator & 0x00ff0000) >> 16;
	buffer[3] = (numerator & 0xff000000) >> 24;

	buffer[4] = denominator & 0xff;
	buffer[5] = (denominator & 0x0000ff00) >> 8;
	buffer[6] = (denominator & 0x00ff0000) >> 16;
	buffer[7] = (denominator & 0xff000000) >> 24;

	return gdip_bitmapdata_property_add (bitmap_data, id, 8, PropertyTagTypeRational, buffer);
}

GpStatus
gdip_bitmapdata_property_add_srational(ActiveBitmapData *bitmap_data, PROPID id, unsigned short numerator, unsigned short denominator)
{
	BYTE buffer[4];

	buffer[0] = numerator & 0xff;
	buffer[1] = (numerator & 0x0ff00) >> 8;

	buffer[2] = denominator & 0xff;
	buffer[3] = (denominator & 0xff00) >> 8;

	return gdip_bitmapdata_property_add (bitmap_data, id, 8, PropertyTagTypeRational, buffer);
}

GpStatus
gdip_bitmapdata_property_add(ActiveBitmapData *bitmap_data, PROPID id, ULONG length, WORD type, VOID *value)
{
	int	property_count;

	if (bitmap_data == NULL) {
		return InvalidParameter;
	}

	property_count = bitmap_data->property_count;

	if (bitmap_data->property == NULL) {
		bitmap_data->property = GdipAlloc(sizeof(PropertyItem));
	} else {
		bitmap_data->property = gdip_realloc (bitmap_data->property, sizeof(PropertyItem) * (property_count + 1));
	}
	if (bitmap_data->property == NULL) {
		bitmap_data->property_count = 0;
		return OutOfMemory;
	}

	if ((value != NULL) && (length > 0)) {
		bitmap_data->property[property_count].value = GdipAlloc(length);
		if (bitmap_data->property[property_count].value == NULL) {
			return OutOfMemory;
		}
		memcpy(bitmap_data->property[property_count].value, value, length);
	} else {
		bitmap_data->property[property_count].value = NULL;
	}

	bitmap_data->property[property_count].id = id;
	bitmap_data->property[property_count].length = length;
	bitmap_data->property[property_count].type = type;
	bitmap_data->property_count++;
	return Ok;
}

GpStatus
gdip_bitmapdata_property_remove_id(ActiveBitmapData *bitmap_data, PROPID id)
{
	int	i;

	for (i = 0; i < bitmap_data->property_count; i++) {
		if (bitmap_data->property[i].id == id) {
			return gdip_bitmapdata_property_remove_index(bitmap_data, i);
		}
	}

	return GenericError;
}

GpStatus
gdip_bitmapdata_property_remove_index(ActiveBitmapData *bitmap_data, int index)
{
	if (index >= bitmap_data->property_count) {
		return PropertyNotFound;
	}

	/* We don't realloc the array, more overhead than savings */
	if ((index + 1) < bitmap_data->property_count) {
		if (bitmap_data->property[index].value != NULL) {
			GdipFree(bitmap_data->property[index].value);
			bitmap_data->property[index].value = NULL;
		}
		memmove(&bitmap_data->property[index], &bitmap_data->property[index + 1], (bitmap_data->property_count - index - 1) * sizeof(PropertyItem));
	}
	bitmap_data->property_count--;

	return Ok;
}

GpStatus
gdip_bitmapdata_property_find_id(ActiveBitmapData *bitmap_data, PROPID id, int *index)
{
	int	i;

	if (index == NULL) {
		return InvalidParameter;
	}

	for (i = 0; i < bitmap_data->property_count; i++) {
		if (bitmap_data->property[i].id == id) {
			*index = i;
			return Ok;
		}
	}

	return PropertyNotFound;
}

GpStatus
gdip_bitmapdata_clone (ActiveBitmapData *src, ActiveBitmapData **dest, int count)
{
	GpStatus	status;
	ActiveBitmapData	*result;
	int		i;
	unsigned long long int size;

	if (!dest)
		return InvalidParameter;

	if (src == NULL) {
		*dest = NULL;
		return Ok;
	}

	result = GdipAlloc (sizeof (ActiveBitmapData) * count);
	if (!result)
		return OutOfMemory;

	for (i = 0; i < count; i++) {
		result[i].width = src[i].width;
		result[i].height = src[i].height;
		result[i].stride = src[i].stride;
		result[i].pixel_format = src[i].pixel_format;
		result[i].reserved = GBD_OWN_SCAN0;	/* We're duplicating SCAN0, we always own it*/
		result[i].dpi_horz = src[i].dpi_horz;
		result[i].dpi_vert = src[i].dpi_vert;
		result[i].image_flags = src[i].image_flags;
		result[i].top = src[i].top;
		result[i].left = src[i].left;
		result[i].x = src[i].x;
		result[i].y = src[i].y;
		result[i].transparent = src[i].transparent;

		if (src[i].scan0 != NULL) {
			size = (unsigned long long int)src[i].stride * src[i].height;
			if (size > G_MAXINT32) {
				GdipFree(result);
				return OutOfMemory;
			}
			result[i].scan0 = GdipAlloc(size);
			if (result[i].scan0 == NULL) {
				GdipFree(result);
				return OutOfMemory;
			}
			memcpy(result[i].scan0, src[i].scan0, size);
		} else {
			result[i].scan0 = NULL;
		}

		result[i].palette = gdip_palette_clone (src[i].palette);

		result[i].property_count = src[i].property_count;
		status = gdip_propertyitems_clone(src[i].property, &result[i].property, src[i].property_count);
		if (status != Ok) {
			int j;

			for (j = 0; j < i; j++) {
				if (result[j].scan0 != NULL) {
					GdipFree(result[j].scan0);
				}
				if (result[j].property != NULL) {
					gdip_propertyitems_dispose(result[j].property, result[j].property_count);
				}
			}

			GdipFree(result);
			return status;
		}
	}

	*dest = result;
	return Ok;
}

static GpStatus
gdip_bitmapdata_dispose(ActiveBitmapData *bitmap, int count)
{
	int		index;

	if (bitmap == NULL) {
		return Ok;
	}

	for (index = 0; index < count; index++) {
		if ((bitmap[index].scan0 != NULL) && ((bitmap[index].reserved & GBD_OWN_SCAN0) != 0)) {
			GdipFree(bitmap[index].scan0);
			bitmap[index].scan0 = NULL;
		}

		if (bitmap[index].palette != NULL) {
			GdipFree(bitmap[index].palette);
			bitmap[index].palette = NULL;
		}

		gdip_propertyitems_dispose(bitmap[index].property, bitmap[index].property_count);
	}

	GdipFree(bitmap);
	return Ok;
}

/* Add a new frame for the given dimension, if it does not exist. Return pointer to frame for dimension */
FrameData *
gdip_frame_add(GpBitmap *bitmap, const GUID *dimension)
{
	int		i;

	if (bitmap == NULL) {
		return NULL;
	}

	if (bitmap->frames == NULL) {
		bitmap->frames = GdipAlloc(sizeof(FrameData));
		if (bitmap->frames == NULL) {
			return NULL;
		}

		bitmap->num_of_frames = 1;
		bitmap->frames[0].count = 0;
		bitmap->frames[0].bitmap = NULL;
		bitmap->frames[0].frame_dimension = *dimension;

		return bitmap->frames;
	} else {
		/* Check if this dimension already exists, if not, add it */
		for (i = 0; i < bitmap->num_of_frames; i++) {
			if (memcmp(&bitmap->frames[i].frame_dimension, dimension, sizeof(GUID)) == 0) {
				return &bitmap->frames[i];
			}
		}

		/* No frame for the requested dimension exists */
		bitmap->num_of_frames++;
		bitmap->frames = gdip_realloc (bitmap->frames, sizeof(FrameData) * (bitmap->num_of_frames));
		if (bitmap->frames == NULL) {
			return NULL;
		}
		bitmap->frames[bitmap->num_of_frames - 1].count = 0;
		bitmap->frames[bitmap->num_of_frames - 1].bitmap = NULL;
		bitmap->frames[bitmap->num_of_frames - 1].frame_dimension = *dimension;
			
		return &bitmap->frames[bitmap->num_of_frames - 1];
	}
}

/* Add a new ActiveBitmapData structure to an existing frame, return pointer to the new structure */
ActiveBitmapData *
gdip_frame_add_bitmapdata(FrameData *frame)
{
	if (frame == NULL) {
		return NULL;
	}

	if (frame->bitmap == NULL) {
		frame->bitmap = GdipAlloc (sizeof (ActiveBitmapData));
		if (frame->bitmap == NULL) {
			return NULL;
		}
		gdip_bitmapdata_init(frame->bitmap);
		frame->count++;
		return frame->bitmap;
	}

	frame->bitmap = gdip_realloc (frame->bitmap, sizeof (ActiveBitmapData) * (frame->count + 1));
	if (frame->bitmap == NULL) {
		return NULL;
	}
	gdip_bitmapdata_init(&frame->bitmap[frame->count]);
	frame->count++;
	return &frame->bitmap[frame->count - 1];
}

ColorPalette*
gdip_palette_clone (ColorPalette *original)
{
	if (!original)
		return NULL;

	/* ColorPalette definition already includes a (single) ARGB value */
	int size = sizeof (ColorPalette) + sizeof (ARGB) * (original->Count - 1);
	ColorPalette *result = GdipAlloc (size);
	if (result)
		memcpy (result, original, size);

	return result;
}

GpStatus
gdip_bitmap_setactive(GpBitmap *bitmap, const GUID *dimension, int index)
{
	int	i;

	if (bitmap == NULL) {
		return InvalidParameter;
	}

	/* Invalidate the cached surface */
	gdip_bitmap_flush_surface (bitmap);
	gdip_bitmap_invalidate_surface (bitmap);

	if ((bitmap->num_of_frames == 0) || (bitmap->frames == NULL)) {
		bitmap->active_frame = 0;
		bitmap->active_bitmap_no = 0;
		bitmap->active_bitmap = NULL;
		return Ok;
	}

	if (dimension == NULL) {
		if (bitmap->frames[0].count <= index) {
			return InvalidParameter;
		}
		bitmap->active_frame = 0;
		bitmap->active_bitmap_no = index;
		bitmap->active_bitmap = &bitmap->frames[0].bitmap[index];
		return Ok;
	}

	for (i = 0; i < bitmap->num_of_frames; i++) {
		if (memcmp(&bitmap->frames[i].frame_dimension, dimension, sizeof(GUID)) == 0) {
			if (bitmap->frames[i].count <= index) {
				return Win32Error;
			}
			bitmap->active_frame = i;
			bitmap->active_bitmap_no = index;
			bitmap->active_bitmap = &bitmap->frames[i].bitmap[index];
			return Ok;
		}
	}

	bitmap->active_frame = 0;
	bitmap->active_bitmap_no = 0;
	bitmap->active_bitmap = NULL;
	return InvalidParameter;
}

GpStatus
gdip_bitmap_clone (GpBitmap *bitmap, GpBitmap **clonedbitmap)
{
	GpBitmap	*result;
	int		frame;
	GpStatus	status;

	result = (GpBitmap *) GdipAlloc (sizeof (GpBitmap));
	if (result == NULL) {
		return OutOfMemory;
	}

	gdip_bitmap_flush_surface (bitmap);

	/* Copy simple types */
	result->type = bitmap->type;
	result->image_format = bitmap->image_format;
	result->num_of_frames = bitmap->num_of_frames;
	result->active_frame = bitmap->active_frame;
	result->active_bitmap_no = bitmap->active_bitmap_no;
	result->active_bitmap = NULL;
	result->cairo_format = bitmap->cairo_format;
	result->surface = NULL;

	/* Allocate and copy frames, properties and bitmap data */
	if (bitmap->frames != NULL) {
		result->frames = GdipAlloc(sizeof (FrameData) * result->num_of_frames);
		if (!result->frames) {
			status = OutOfMemory;
			goto fail;
		}

		for (frame = 0; frame < result->num_of_frames; frame++) {
			result->frames[frame].count = bitmap->frames[frame].count;
			result->frames[frame].frame_dimension = bitmap->frames[frame].frame_dimension;
			result->frames[frame].bitmap = NULL;

			status = gdip_bitmapdata_clone (bitmap->frames[frame].bitmap, &result->frames[frame].bitmap, bitmap->frames[frame].count);
			if (status != Ok)
				goto fail;
		}
		result->active_bitmap = &result->frames[result->active_frame].bitmap[result->active_bitmap_no];
	} else {
		bitmap->frames = NULL;
	}

	*clonedbitmap = result;
	return Ok;

fail:
	gdip_bitmap_dispose(result);
	return status;
}


GpBitmap *
gdip_bitmap_new(void)
{
	GpBitmap *result;

	result = (GpBitmap *) GdipAlloc (sizeof (GpBitmap));
	if (result != NULL) {
		gdip_bitmap_init (result);
	}

	return result;
}

/* Create a new bitmap, and create a frame with the given dimension */
GpBitmap *
gdip_bitmap_new_with_frame (const GUID *dimension, BOOL add_bitmapdata)
{
	GpBitmap	*result;
	FrameData	*frame;

	if (dimension == NULL) {
		dimension = &gdip_image_frameDimension_page_guid;
	}

	result = gdip_bitmap_new();
	if (result != NULL) {
		frame = gdip_frame_add(result, dimension);
		if ((frame != NULL) && add_bitmapdata) {
			ActiveBitmapData	*bitmap_data;

			bitmap_data = gdip_frame_add_bitmapdata(frame);
			if (bitmap_data != NULL) {
				result->active_bitmap = bitmap_data;
			}
		}
	}
	return result;
}

GpStatus
gdip_bitmap_dispose (GpBitmap *bitmap)
{
	/* Might be called with partially filled bitmaps, so check values to be freed instead of assuming */
	if (!bitmap)
		return Ok;

	gdip_bitmap_invalidate_surface (bitmap);

	if (bitmap->frames) {
		int frame;
		for (frame = 0; frame < bitmap->num_of_frames; frame++) {
			gdip_bitmapdata_dispose (bitmap->frames[frame].bitmap, bitmap->frames[frame].count);
		}
		GdipFree (bitmap->frames);
		bitmap->frames = NULL;
	}

	GdipFree (bitmap);
	return Ok;
}

GpStatus
GdipCreateBitmapFromStream (void *stream, GpBitmap **bitmap)
{
	if (!gdiplusInitialized)
		return GdiplusNotInitialized;

	if (!stream || !bitmap)
		return InvalidParameter;
	
	// This will never be implemented, as 'stream' is a COM IStream.
	return NotImplemented;
}

/* coverity[+alloc : arg-*1] */
GpStatus
GdipCreateBitmapFromFile (GDIPCONST WCHAR* filename, GpBitmap **bitmap)
{
	GpImage *image;
	GpStatus status;

	status = GdipLoadImageFromFile (filename, &image);
	if (status != Ok)
		return status == OutOfMemory ? InvalidParameter : status;

	switch (image->type) {
	case ImageTypeBitmap:
		*bitmap = (GpBitmap *)image;
		return Ok;
	case ImageTypeMetafile:
		status = gdip_get_bitmap_from_metafile ((GpMetafile *) image, 0, 0, (GpImage **) bitmap);
		GdipDisposeImage (image);
		return status;
	default:
		GdipDisposeImage (image);
		return GenericError;
	}
}

GpStatus
GdipCreateBitmapFromStreamICM (void *stream, GpBitmap **bitmap)
{
	/* ICM isn't supported */
	return GdipCreateBitmapFromStream (stream, bitmap);
}

/* coverity[+alloc : arg-*1] */
GpStatus
GdipCreateBitmapFromFileICM (GDIPCONST WCHAR* filename, GpBitmap **bitmap)
{
	/* ICM isn't supported */
	return GdipCreateBitmapFromFile (filename, bitmap);
}

/* coverity[+alloc : arg-*5] */
GpStatus
GdipCreateBitmapFromScan0 (INT width, INT height, INT stride, PixelFormat format, BYTE *scan0, GpBitmap **bitmap)
{
	FrameData	*frame;
	GpBitmap	*result;
	ActiveBitmapData	*bitmap_data;
	int		cairo_format;
	int flags = 0;

	if (!gdiplusInitialized)
		return GdiplusNotInitialized;
	
	if (width <= 0 || height <= 0 || !bitmap)
		return InvalidParameter;
	
	switch (format) {
		case PixelFormat24bppRGB:
			cairo_format = CAIRO_FORMAT_RGB24;  
			break;

		case PixelFormat32bppARGB:
		case PixelFormat32bppPARGB:
			flags = ImageFlagsHasAlpha;
			cairo_format = CAIRO_FORMAT_ARGB32;
			break;

		case PixelFormat32bppRGB:
			cairo_format = CAIRO_FORMAT_ARGB32;
			break;

		case PixelFormat16bppRGB555:
		case PixelFormat16bppRGB565:
			/* fake them as 32bpp RGB as Cairo deprecated CAIRO_FORMAT_RGB16_565 support */
			/* why 32bpp ? because that's the result of MS GDI+ when loading them, even if the bitmap is empty */
			cairo_format = CAIRO_FORMAT_ARGB32;
			break;

		case PixelFormat8bppIndexed:
		case PixelFormat4bppIndexed:
			flags = ImageFlagsHasAlpha;
			cairo_format = CAIRO_FORMAT_A8;        
			break;

		case PixelFormat1bppIndexed:
			flags = ImageFlagsHasAlpha;
			cairo_format = CAIRO_FORMAT_A1;
			break;
			
		case PixelFormat16bppGrayScale:
		case PixelFormat16bppARGB1555:
		case PixelFormat48bppRGB:
		case PixelFormat64bppARGB:
		case PixelFormat64bppPARGB:
		case PixelFormat32bppCMYK:
			*bitmap = NULL;
			return NotImplemented;

		default:
			return InvalidParameter;
	}
	
	if (scan0 && (stride == 0 || stride % 4))
		return InvalidParameter;

	result = gdip_bitmap_new ();
	if (!result)
		return OutOfMemory;

	result->image_format = MEMBMP;
	result->cairo_format = cairo_format;
	result->surface = NULL;
	result->active_frame = 0;
	result->active_bitmap_no = 0;
	result->active_bitmap = NULL;

	/* Create single 'page' frame, with single bitmap */
	frame = gdip_frame_add (result, &gdip_image_frameDimension_page_guid);
	if (!frame) {
		gdip_bitmap_dispose (result);
		return OutOfMemory;
	}

	bitmap_data = gdip_frame_add_bitmapdata (frame);
	if (!bitmap_data) {
		gdip_bitmap_dispose (result);
		return OutOfMemory;
	}

	/* populate first bitmap in first frame */
	bitmap_data->width = width;
	bitmap_data->height = height;
	bitmap_data->pixel_format = format;
	bitmap_data->image_flags = flags;

	if (stride == 0) {
		if (gdip_is_an_indexed_pixelformat(format)) {
			stride = ((gdip_get_pixel_format_depth(format) * width) + 7) / 8;
		} else {
			stride = (gdip_get_pixel_format_components (format) * gdip_get_pixel_format_depth (format) * width) / 8;
		}

		/* make sure the stride aligns the next row to a 32 bits boundary */
		gdip_align_stride (stride);
	}
	bitmap_data->stride = stride;

	if (!scan0) {
		unsigned long long int size = (unsigned long long int)stride * height;
		if (size > G_MAXINT32) {
			gdip_bitmap_dispose (result);
			return OutOfMemory;
		}
		scan0 = GdipAlloc (size);
		if (!scan0) {
			gdip_bitmap_dispose (result);
			return OutOfMemory;
		}

		if ((gdip_get_pixel_format_bpp(format) < 32) || gdip_is_an_alpha_pixelformat(format)) {
			memset (scan0, 0, size);
		} else {
			/* Since the pixel format is not an alpha pixel format (i.e., it is
			 * PixelFormat32bppRGB), the image should be initially black, not
			 * initially transparent. Thus, we need to set the alpha channel,
			 * which the user code doesn't think exists but Cairo is still paying
			 * attention to, to 0xFF.
			 */
			for (int y = 0; y < height; y++) {
				ARGB *scan = (ARGB *) (scan0 + y * stride);
				for (int x = 0; x < width; x++) {
					scan[x] = 0xFF000000;
				}
			}
		}
		bitmap_data->reserved = GBD_OWN_SCAN0;	
	}
	
	bitmap_data->scan0 = scan0;

	/* Make sure indexed images have a palette */
	if (gdip_is_an_indexed_pixelformat (format)) {
		int			palette_entries;
		int			header_size;
		int			bytes_needed;
		const unsigned int	*default_palette;
#if WORDS_BIGENDIAN
		int			i;
#endif

		palette_entries = 1 << gdip_get_pixel_format_depth(format);
		header_size = sizeof(ColorPalette) - sizeof(ARGB);
		bytes_needed = header_size + palette_entries * sizeof(ARGB);

		bitmap_data->palette = GdipAlloc (bytes_needed);
		if (!bitmap_data->palette) {
			gdip_bitmap_dispose (result);
			return OutOfMemory;
		}

		bitmap_data->palette->Count = palette_entries;
		bitmap_data->palette->Flags = COLOR_PALETTE_FLAGS_DEFAULT;

		switch (format) {
		case PixelFormat1bppIndexed:
			default_palette = default_Format1bppIndexed_palette;
			bitmap_data->palette->Flags |= COLOR_PALETTE_FLAGS_GREYSCALE;
			break;
		case PixelFormat4bppIndexed:
			default_palette = default_Format4bppIndexed_palette;
			break;
		case PixelFormat8bppIndexed:
			default_palette = default_Format8bppIndexed_palette;
			bitmap_data->palette->Flags |= COLOR_PALETTE_FLAGS_HALFTONE;
			break;
		default:
			default_palette = NULL;	/* Suppress unassigned val used warning */
			palette_entries = 0;	/* make sure we don't try to access default_palette later */
		}

#if WORDS_BIGENDIAN
		for (i=0; i < palette_entries; i++) {
			set_pixel_bgra (bitmap_data->palette->Entries, (i << 2), default_palette[i], 
				(default_palette[i] >>  8), (default_palette[i] >> 16), (default_palette[i] >> 24));
		}
#else
		memcpy (bitmap_data->palette->Entries, default_palette, palette_entries * 4);
#endif
	} else {
		bitmap_data->palette = NULL;
	}

	gdip_bitmap_setactive(result, NULL, 0);
	*bitmap = result;
	return Ok;
}

/* coverity[+alloc : arg-*3] */
GpStatus
GdipCreateBitmapFromGraphics (int width, int height, GpGraphics *graphics, GpBitmap **bitmap)
{
	GpStatus status;
	GpBitmap *result;

	if (!gdiplusInitialized)
		return GdiplusNotInitialized;

	if (!graphics || !bitmap)
		return InvalidParameter;

	status = GdipCreateBitmapFromScan0 (width, height, 0, PixelFormat32bppPARGB, NULL, &result);
	if (status != Ok)
		return status;

	GdipGetDpiX (graphics, &result->active_bitmap->dpi_horz);
	GdipGetDpiY (graphics, &result->active_bitmap->dpi_vert);

	*bitmap = result;
	return Ok;
}

GpStatus
GdipCreateBitmapFromHBITMAP (HBITMAP hbm, HPALETTE hpal, GpBitmap** bitmap)
{
	if (!gdiplusInitialized)
		return GdiplusNotInitialized;

	if (!bitmap || !hbm)
		return InvalidParameter;

	return GdipCloneImage ((GpImage *)hbm, (GpImage**)bitmap);
}

GpStatus
GdipCreateHBITMAPFromBitmap (GpBitmap* bitmap, HBITMAP *hbmReturn, ARGB background)
{
	if (!gdiplusInitialized)
		return GdiplusNotInitialized;

	/*
	 * Note: the handle must survive disposing the bitmap. This means that there's no interoperable way to free this memory. 
	 * For libgdiplus you must use GdipDisposeImage on the handle, while on Windows you should call the DeleteObject function.
	 */
	GpStatus status = GdipCloneImage ((GpImage *)bitmap, (GpImage**)hbmReturn);
	if (status != Ok)
		return status;
	((GpImage*)*hbmReturn)->image_format = MEMBMP;
	((GpImage*)*hbmReturn)->active_bitmap->image_flags |= ImageFlagsUndocumented;	/* 0x00040000 */
	((GpImage*)*hbmReturn)->active_bitmap->image_flags &= ~ImageFlagsHasAlpha;	/* 0x00000002 */
	return Ok;
}

GpStatus
GdipCreateBitmapFromHICON (HICON hicon, GpBitmap** bitmap)
{
	GpStatus status;
	GpImage *result;

	if (!gdiplusInitialized)
		return GdiplusNotInitialized;

	if (!hicon || !bitmap)
		return InvalidParameter;

#if defined(WIN32)
		return NotImplemented;
#endif

	status = GdipCloneImage ((GpImage *)hicon, &result);
	if (status != Ok)
		return status;

	if (result->active_bitmap->palette) {
		GdipFree (result->active_bitmap->palette);
		result->active_bitmap->palette = NULL;
	}

	result->image_format = MEMBMP;
	result->active_bitmap->image_flags |= ImageFlagsUndocumented;/* 0x00040000 */
	result->active_bitmap->image_flags &= ~ImageFlagsHasAlpha;	/* 0x00000002 */

	*bitmap = (GpBitmap *) result;
	return Ok;
}

GpStatus
GdipCreateHICONFromBitmap (GpBitmap* bitmap, HICON *hbmReturn)
{
	if (!gdiplusInitialized)
		return GdiplusNotInitialized;

	/*
	 * Note: the handle must survive disposing the bitmap. This means that there's no interoperable way to free this memory. 
	 * For libgdiplus you must use GdipDisposeImage on the handle, while on Windows you should call the DestroyIcon function.
	 */
	return GdipCloneImage ((GpImage *)bitmap, (GpImage**)hbmReturn);
}

GpStatus WINGDIPAPI
GdipCreateBitmapFromResource (HINSTANCE hInstance, GDIPCONST WCHAR *lpBitmapName, GpBitmap** bitmap)
{
	if (!gdiplusInitialized)
		return GdiplusNotInitialized;

	return(NotImplemented);
}

/* coverity[+alloc : arg-*6] */
GpStatus WINGDIPAPI
GdipCloneBitmapAreaI (INT x, INT y, INT width, INT height, PixelFormat format,
					  GpBitmap *original, GpBitmap **bitmap)
{
	GpBitmap	*result;
	Rect		sr = { x, y, width, height };
	Rect		dr = { 0, 0, width, height };
	GpStatus	status;

	if ((original == NULL) || (bitmap == NULL) || (original->active_bitmap == NULL)) {
		return InvalidParameter;
	}

	if (((x + width) > original->active_bitmap->width) || ((y + height) > original->active_bitmap->height)) {
		return InvalidParameter;
	}

	result = gdip_bitmap_new_with_frame(NULL, TRUE);
	if (result == NULL) {
		return OutOfMemory;
	}

	result->image_format = original->image_format;

	gdip_bitmap_flush_surface (original);

	status = gdip_bitmap_clone_data_rect (original->active_bitmap, &sr, result->active_bitmap, &dr);
	if (status != Ok) {
		goto fail;
	}

	result->cairo_format = original->cairo_format;

	// Preserve the resolution values.  See https://bugzilla.xamarin.com/show_bug.cgi?id=44127.
	result->active_bitmap->dpi_horz = original->active_bitmap->dpi_horz;
	result->active_bitmap->dpi_vert = original->active_bitmap->dpi_vert;
	result->active_bitmap->image_flags |= (original->active_bitmap->image_flags & ImageFlagsHasRealDPI);

	*bitmap = result;
	return Ok;

fail:
	gdip_bitmap_dispose (result);
	return status;
}

/* coverity[+alloc : arg-*6] */
GpStatus WINGDIPAPI
GdipCloneBitmapArea (REAL x, REAL y, REAL w, REAL h, PixelFormat format,
					 GpBitmap *original, GpBitmap **bitmap)
{
	return GdipCloneBitmapAreaI ((int) x, (int) y, (int) w, (int) h, format,
								 original, bitmap);
}

static void
gdip_copy_strides (BYTE *dst, int dstStride, BYTE *src, int srcStride, int realBytes, int height)
{
	int i;
	for (i = 0; i < height; i++) {
		memcpy (dst, src, realBytes);
		dst += dstStride;
		src += srcStride;
	}
}

/*
 * Copy srcRect region in srcData to destRect region in destData.  No conversion is done.  Assumes
 * ActiveBitmapData is straight from a GpBitmap.  src and dest rects must be the same width/height and
 * bits must be of the same PixelFormat.
 */
static GpStatus
gdip_bitmap_clone_data_rect (ActiveBitmapData *srcData, Rect *srcRect, ActiveBitmapData *destData, Rect *destRect)
{
	int dest_components;
	int dest_depth; 

	if ((srcData == NULL) || (srcRect == NULL) || (destData == NULL) || (destRect == NULL) || (srcRect->Width != destRect->Width) || (srcRect->Height != destRect->Height)) {
		return InvalidParameter;
	}
	
	if (!gdip_is_a_supported_pixelformat (srcData->pixel_format)) {
		return NotImplemented;
	}

	dest_components = gdip_get_pixel_format_components (destData->pixel_format);
	
	if (destData->scan0 == NULL) {
		dest_components = gdip_get_pixel_format_components (srcData->pixel_format);
		dest_depth = gdip_get_pixel_format_depth (srcData->pixel_format);
		destData->pixel_format = srcData->pixel_format;

		destData->stride = ((destRect->Width * dest_components * dest_depth) >> 3);
		gdip_align_stride (destData->stride);

		unsigned long long int size = (unsigned long long int)destData->stride * destRect->Height;
		if (size > G_MAXINT32) {
			return OutOfMemory;
		}
		destData->scan0 = GdipAlloc (size);
		if (destData== NULL) {
			return OutOfMemory;
		}
		
		destData->width = destRect->Width;
		destData->height = destRect->Height;
		destData->pixel_format = srcData->pixel_format;
		destData->reserved = GBD_OWN_SCAN0;

		if (srcData->palette) {
			destData->palette = gdip_palette_clone (srcData->palette);
			if (!destData->palette) {
				GdipFree (destData->scan0);
				destData->scan0 = NULL;
				return OutOfMemory;
			}
		}
	}


	if (!gdip_is_an_indexed_pixelformat (srcData->pixel_format)) {
		gdip_copy_strides (destData->scan0, destData->stride,
			srcData->scan0 + (srcData->stride * srcRect->Y) + (gdip_get_pixel_format_components (srcData->pixel_format) 
			* srcRect->X), srcData->stride,   destRect->Width * dest_components,  destRect->Height);
	} else {
		int src_depth;
		int src_first_x_bit_index;
		int width_bits;
		int src_first_x_bit_offset_into_byte;

		src_depth = gdip_get_pixel_format_depth (srcData->pixel_format);

		/* first, check if the bits are aligned onto a byte boundary */
		src_first_x_bit_index = srcRect->X * src_depth;
		width_bits = destRect->Width * src_depth;
		src_first_x_bit_offset_into_byte = src_first_x_bit_index & 7;

		if (src_first_x_bit_offset_into_byte == 0) {
			/* the fast path: no mid-byte bit mangling required :-)
			 * this will always be the case for 8-bit images.
			 * basically, the source bits are aligned to the destination
			 * bytes, and it doesn't matter if the width isn't a multiple
			 * of 8 bits, because the remainder is guaranteed to be
			 * allocated by the stride, and will be ignored because the
			 * width will indicate fewer pixels than actually end up being
			 * copied.
			 */
			gdip_copy_strides (
				destData->scan0, destData->stride,
				srcData->scan0 + (src_first_x_bit_index / 8) + (srcData->stride * srcRect->Y),
				srcData->stride, width_bits / 8, destRect->Height);
		} else {
			/* the not-so-fast path: no bits are aligned, so the entire image requires bit juggling. */

			BYTE *src_scan;
			BYTE *src_scan0;
			BYTE *dest_scan;
			BYTE *dest_scan0;
			unsigned short	buffer;
			int		left_shift;
			int		x;
			int		y;

			src_scan0 = srcData->scan0;
			dest_scan0 = destData->scan0;
			left_shift = src_first_x_bit_offset_into_byte;

			/* move the src_scan0 up to the first byte with pixel data involved in the copy */
			src_scan0 += srcRect->Y * srcData->stride;
			src_scan0 += (src_first_x_bit_offset_into_byte / 8);

			for (y=0; y < destRect->Height; y++) {
				src_scan = src_scan0 + y * srcData->stride;
				dest_scan = dest_scan0 + y * destData->stride;

				/* jump-start the packing function. it avoids double-sampling the source bits by
				 * using buffer as a shift register; bits 8-15 are the current packed dest pixel,
				 * and some of bits 0-7 are not used, to permit alignment of the pixel data.
				 */
				buffer = src_scan[0] << left_shift;

				for (x = 1; x < destRect->Width; x++) {
					buffer <<= 8;
					buffer |= src_scan[x] << left_shift;
					dest_scan[0] = (buffer >> 8);
				}
			}
		}
	}

	return Ok;
}

/*
 * According to GDI+ testing,
 * RGB[A] PixelFormats can be converted to/from any other RGB[A] pixel format, with or without alpha.
 * We should support all of these:
 *
 * 32bpp argb - 16bpp argb 1555
 * 32bpp argb - 16bpp rgb 555
 * 32bpp argb - 16bpp rgb 565
 * 32bpp argb - 24bpp rgb 888
 * 32bpp argb - 32bpp Pargb
 * 32bpp argb - 32bpp rgb
 *
 * Upconversion is allowed (e.g 8bpp indexed to 32bpp rgb), but no downconversion (32bpp to 8bpp indexed)
 *
 */

static int
gdip_is_pixel_format_conversion_valid (PixelFormat src, PixelFormat dest)
{
	if (src == dest) {
		return 1;
	}

	/* non-GDI supported formats can't be converted */
	if (!(src & PixelFormatGDI)) {
		return 0;
	}

	/* We don't allow converting *to* indexed formats */
	if (dest & PixelFormatIndexed) {
		return 0;
	}

	/* These are the RGB formats */
	if ((src & PixelFormatGDI) && !(src & PixelFormatExtended)) {
		/* all of these should be supported, but we only report the
		 * ones we really can do for now */
		/* We can't handle converting to/from the 565/555/1555 ones */
		if ((src & 0xff00) == PixelFormatMax || (dest & 0xff00) == PixelFormatMax) {
			return 0;
		}
		return 1;
	}

	return 0;
}

GpStatus
gdip_init_pixel_stream (StreamingState *state, ActiveBitmapData *data, int x, int y, int w, int h)
{
	if ((state == NULL) || (data == NULL) || (data->scan0 == NULL)) {
		return InvalidParameter;
	}

	/* Ensure that the rectangle requested is legal. */
	if ((x < 0) || (y < 0) || ((x + w) > data->width) || ((y + h) > data->height)) {
		return InvalidParameter;
	}

	/* Initialize the StreamingState structure to point at the first pixel. */
	state->region.X = x;
	state->region.Y = y;
	state->region.Width = w;
	state->region.Height = h;

	state->x = x;
	state->y = y;

	state->p = -1; /* ensure that the buffer will be preloaded on the first call, for indexed formats */

	/* The following computation will compute the byte pointer that _contains_ the first
	 * pixel; this doesn't necessarily mean that the pixel is aligned to the byte. This
	 * will be handled in gdip_pixel_stream_get_next () each time it starts a new row.
	 */
	state->scan = (BYTE*)(data->scan0) + y * data->stride;

	switch (data->pixel_format) {
	case PixelFormat1bppIndexed:
		state->one_pixel_mask = 0x01;
		state->one_pixel_shift = 1;
		state->pixels_per_byte = 8;
		state->scan += (x >> 3);		/* x * 1 / 8 */
		break;
	case PixelFormat4bppIndexed:
		state->one_pixel_mask = 0x0F;
		state->one_pixel_shift = 4;
		state->pixels_per_byte = 2;
		state->scan += (x >> 1); 		/* x * 4 / 8 */
		break;
	case PixelFormat8bppIndexed:
		state->one_pixel_mask = 0xFF;
		state->one_pixel_shift = 8;
		state->pixels_per_byte = 1;
		state->scan += x; 			/* x * 8 / 8 */
		break;
	case PixelFormat24bppRGB:
		/* GDI+ use 3 bytes for 24 bpp while Cairo use 4 bytes */
		if (data->reserved & GBD_TRUE24BPP) {
			state->pixels_per_byte = -3;
			state->scan += ((x * 3) >> 3);	/* x * 3 / 8 */
			break;
		}
		/* fall through */
	case PixelFormat32bppRGB:
	default:
		/* indicate full RGB processing */
		state->pixels_per_byte = -(gdip_get_pixel_format_bpp (data->pixel_format) >> 3); 
		state->scan -= x * state->pixels_per_byte;
		break;
	}

	state->data = data;

	return Ok;
}

static BOOL
gdip_pixel_stream_has_next (StreamingState *state)
{
	if (state != NULL) {
		return (state->p >= 0)
		    || ((state->y < (state->region.Y + state->region.Height))
		    && (state->x < (state->region.X + state->region.Width)));
	} else {
		return FALSE;
	}
}

unsigned int /* <-- can be an ARGB or a palette index */
gdip_pixel_stream_get_next (StreamingState *state)
{
	unsigned int ret;

	if (state == NULL) {
		int bright_pink;
		set_pixel_bgra(&bright_pink, 0,
			0xFF, 0x00, 0xFF, 0xFF); /* bright pink; hopefully this will get somebody's attention :-) */
		return bright_pink;
	}

	/* Note: This function does not check whether the end of the region has been hit. This function can
	 * potentially overrun memory buffers! gdip_pixel_stream_has_next () must be used in conjunction
	 * with this function.
	 */

	if (state->pixels_per_byte == 1) {
		/* A fast path for 8-bit indexed data: pixels are byte-aligned, so no special unpacking is required. */
		ret = *state->scan;

		state->scan++;
		state->x++;

		if (state->x >= (state->region.X + state->region.Width)) {
			state->x = state->region.X;
			state->y++;

			state->scan = (BYTE*)(state->data->scan0)
			            + state->y * state->data->stride
			            + state->x;
		}
	} else if (state->pixels_per_byte > 0) {
		/* We have an indexed format (the RGB formats can't fit a whole pixel into a single byte). */
		if (state->p < 0) {
			state->buffer = *state->scan;
			state->scan++;
			state->p = 0;

			if (state->x == state->region.X) {
				/* First pixel of the row; check whether it is aligned to the byte or not. */
				int index_into_byte = state->x & (state->pixels_per_byte - 1);

				if (index_into_byte != 0) {
					/* Not aligned; need to advance the buffer to the
					 * first pixel in the stream region.
					 */
					state->buffer <<= (index_into_byte * state->one_pixel_shift);
					state->p = index_into_byte;
				}
			}
		}

		state->buffer <<= state->one_pixel_shift;

		ret = (state->buffer >> 8) & state->one_pixel_mask;

		state->x++;
		state->p++;

		/* Have we hit the end of the buffer? */
		if (state->p >= state->pixels_per_byte)	
			state->p = -1;

		if (state->x >= (state->region.X + state->region.Width)) {
			state->x = state->region.X;
			state->y++;

			state->scan = (BYTE*)(state->data->scan0)
			            + state->y * state->data->stride
			            + state->x * gdip_get_pixel_format_bpp (state->data->pixel_format) / 8;

			state->p = -1;
		}
	} else {
		/* We have an RGB format. In the current implementation, these are always stored as
		 * CAIRO_FORMAT_ARGB. This makes this section very easy to implement. If native
		 * support for 15- and 16-bit pixel formats needs to be added in the future, though,
		 * then this is where it needs to be done.
		 *
		 * In order to simplify advancing the state->scan pointer, the state->pixels_per_byte
		 * member is set to the number of bytes per pixel, negated. That is, for 24-bit
		 * formats, it is set to -3, and for 32-bit formats, it is set to -4.
		 *
		 * Note that pixel streams do not support 48- and 64-bit data at this time.
		 */
		if (state->pixels_per_byte == -4) {
#if WORDS_BIGENDIAN
			ret = state->scan [0] | (state->scan [1] << 8) | (state->scan [2] << 16) | ((guint32)state->scan [3] << 24);
#else
			ret = *(unsigned int *)state->scan;
#endif
		} else {
			/* Special case: 24-bit data needs to have the cairo format alpha component forced
			 * to 0xFF, or many operations will do nothing (or do strange things if the alpha
			 * channel contains garbage).
			 */
			ret = state->scan [0] | (state->scan [1] << 8) | (state->scan [2] << 16) | 0xFF000000;
		}

		state->scan -= state->pixels_per_byte;
		state->x++;

		if (state->x >= (state->region.X + state->region.Width)) {
			state->x = state->region.X;
			state->y++;

			state->scan = (BYTE*)(state->data->scan0)
			            + state->y * state->data->stride
			            + state->x * -state->pixels_per_byte;
		}
	}

	return ret;
}

static void
gdip_pixel_stream_set_next (StreamingState *state, unsigned int pixel_value)
{
	if (state == NULL) {
		return;
	}

	/* Note: This function does not check whether the end of the region has been hit. This function can
	 * potentially overrun memory buffers! gdip_pixel_stream_has_next () must be used in conjunction
	 * with this function.
	 */

	if (state->pixels_per_byte == 1) {
		/* A fast path for 8-bit indexed data: pixels are byte-aligned, so no special packing is required. */
		*state->scan = pixel_value;

		state->scan++;
		state->x++;

		if (state->x >= (state->region.X + state->region.Width)) {
			state->x = state->region.X;
			state->y++;

			state->scan = (BYTE*)(state->data->scan0)
			            + state->y * state->data->stride
			            + state->x;
		}
	} else if (state->pixels_per_byte > 0) {
		/* We have an indexed format (the RGB formats can't fit a whole pixel into a single byte). */
		if (state->p < 0) {
			state->p = 0;

			if (state->x == state->region.X) {
				/* First pixel of the row; check whether it is aligned to the byte or not. */
				int index_into_byte = state->x & (state->pixels_per_byte - 1);

				if (index_into_byte == 0) {
					/* It is aligned; all we need to do is clear the buffer. */
					state->buffer = 0;
				}
				else {
					/* It is not aligned; the buffer needs to be pre-loaded with those
					 * pixels that are to the left of the first pixel to be set.
					 */
					state->buffer = (*state->scan << (index_into_byte * state->one_pixel_shift));
					state->p = index_into_byte;
				}
			}
		}

		state->buffer <<= state->one_pixel_shift;
		state->buffer |= ((pixel_value & state->one_pixel_mask) << 8);

		state->x++;
		state->p++;

		/* Have we hit the end of the buffer? */
		if (state->p >= state->pixels_per_byte)	{
			*state->scan = (state->buffer >> 8);
			state->scan++;
			state->p = -1;
		}

		if (state->x >= (state->region.X + state->region.Width)) {
			if (state->p >= 0) {
				int existing_mask = 0;

				while (state->p < state->pixels_per_byte) {
					existing_mask <<= state->one_pixel_shift;
					existing_mask |= state->one_pixel_mask;

					state->buffer <<= state->one_pixel_shift;
					state->p++;
				}

				*state->scan = (*state->scan & existing_mask) | (state->buffer >> 8);
			}

			state->x = state->region.X;
			state->y++;

			state->scan = (BYTE*)(state->data->scan0)
			            + state->y * state->data->stride
			            + state->x * gdip_get_pixel_format_bpp (state->data->pixel_format) / 8;

			state->p = -1;
		}
	} else {
		/* We have an RGB format. In the current implementation, these are always stored as
		 * CAIRO_FORMAT_ARGB. This makes this section very easy to implement. If native
		 * support for 15- and 16-bit pixel formats needs to be added in the future, though,
		 * then this is where it needs to be done.
		 *
		 * In order to simplify advancing the state->scan pointer, the state->pixels_per_byte
		 * member is set to the number of bytes per pixel, negated. That is, for 24-bit
		 * formats, it is set to -3, and for 32-bit formats, it is set to -4.
		 *
		 * Note that pixel streams do not support 48- and 64-bit data at this time.
		 */

		if (state->pixels_per_byte == -4) {
#if WORDS_BIGENDIAN
			state->scan [0] = (pixel_value >> 24);
			state->scan [1] = (pixel_value >> 16);
			state->scan [2] = (pixel_value >> 8);
			state->scan [3] = (state->data->pixel_format == PixelFormat32bppRGB) ? 0xFF : pixel_value;
#else
			if (state->data->pixel_format == PixelFormat32bppRGB)
				pixel_value |= 0xFF000000;
			*(unsigned int *)state->scan = pixel_value;
#endif
		} else {
			/* ensure we don't get one byte over our allocated buffer */
#if WORDS_BIGENDIAN
			state->scan [0] = (pixel_value >> 24);
			state->scan [1] = (pixel_value >> 16);
			state->scan [2] = (pixel_value >> 8);
#else
			state->scan [2] = (pixel_value >> 16);
			state->scan [1] = (pixel_value >> 8);
			state->scan [0] = pixel_value;
#endif
		}

		state->scan -= state->pixels_per_byte;
		state->x++;

		if (state->x >= (state->region.X + state->region.Width)) {
			state->x = state->region.X;
			state->y++;

			state->scan = (BYTE*)(state->data->scan0)
			            + state->y * state->data->stride
			            + state->x * -state->pixels_per_byte;
		}
	}
}

static BOOL /* <-- TRUE if optimisation was possible and copy done, else FALSE */
gdip_pixel_stream_copy_optimized (StreamingState *dst_state, StreamingState *src_state)
{
	if (src_state == NULL) return FALSE;
	if (dst_state == NULL) return FALSE;

	/* in a first shot we only support copy operations for cases where source and destination storage width is exactly the same */
	if (src_state->pixels_per_byte != dst_state->pixels_per_byte) return FALSE;

	//if (src_state->data->pixel_format == PixelFormat32bppRGB) return FALSE;

	/* if the target is in 32bpp format with only RGB but no alpha then we should forcefully set all alpha bits to 0xff -> optimize this later! */
	if (dst_state->pixels_per_byte == -4) {
		if (dst_state->data->pixel_format == PixelFormat32bppRGB) return FALSE;
	}

	/* optimisation code differs for different bytes per pixel (== pixels per byte) */
	if (src_state->pixels_per_byte == 1) {
		/* A fast path for 8-bit indexed data: pixels are byte-aligned, so no special unpacking is required. */
		return FALSE; /* optimize this later */
	} else if (src_state->pixels_per_byte > 0) {
		/* We have an indexed format (the RGB formats can't fit a whole pixel into a single byte). */
		return FALSE; /* optimize this later */
	} else if (src_state->pixels_per_byte == 0) {
		return FALSE; /* unknown format - dont know how to handle; this is a sanity check for crash prevention in the following code */
	} else {
		/* We have an RGB format. In the current implementation, these are always stored as
		 * CAIRO_FORMAT_ARGB. This makes this section very easy to implement. If native
		 * support for 15- and 16-bit pixel formats needs to be added in the future, though,
		 * then this is where it needs to be done.
		 *
		 * In order to simplify advancing the state->scan pointer, the state->pixels_per_byte
		 * member is set to the number of bytes per pixel, negated. That is, for 24-bit
		 * formats, it is set to -3, and for 32-bit formats, it is set to -4.
		 *
		 * Note that pixel streams do not support 48- and 64-bit data at this time.
		 */
#if WORDS_BIGENDIAN
		return FALSE; /* optimize this later */
#else
		int bytes_per_pixel = -src_state->pixels_per_byte;
		int bytes_per_line = bytes_per_pixel * src_state->region.Width;
		int bytes_per_region = bytes_per_line * src_state->region.Height;

		int src_stride = src_state->data->stride;
		int dst_stride = dst_state->data->stride;

		/* calculate region base addresses (this might resemble the current ..._state->scan value, but we simply dont care) */
		BYTE* src_region = (BYTE*)(src_state->data->scan0) + src_state->region.Y * src_stride + src_state->region.X * bytes_per_pixel;
		BYTE* dst_region = (BYTE*)(dst_state->data->scan0) + dst_state->region.Y * dst_stride + dst_state->region.X * bytes_per_pixel;

		/* check if thze data at both locations is in a perfectly consecutive arrangement */
		if ((src_state->region.Width * bytes_per_pixel == src_stride)
			&& (dst_state->region.Width * bytes_per_pixel == dst_stride)) {
			memcpy (dst_region, src_region, bytes_per_region);
		} else {
			BYTE *src = src_region;
			BYTE *dst = dst_region;
			int lines;

			for (lines = src_state->region.Height; lines; lines--) {
				memcpy (dst, src, bytes_per_line);
				src += src_stride;
				dst += dst_stride;
			}
		}
		/* all data got copied */

		/* adjust the position index and the scan value to the end of the region */
		src_state->x = src_state->region.X + src_state->region.Width;
		src_state->y = src_state->region.Y + src_state->region.Height;
		src_state->scan += src_state->region.Y * src_stride;

		dst_state->x = dst_state->region.X + dst_state->region.Width;
		dst_state->y = dst_state->region.Y + dst_state->region.Height;
		dst_state->scan += dst_state->region.Y * dst_stride;
#endif
	}

	return TRUE;
}

/**
 * srcData - input data
 * srcRect - rectangle of input data to place in destData
 * destData - where to place output; only the PixelFormat field is needed,
 *            which specifies the output type.
 * destRect - destination rectangle in output.
 *
 * assumes that:
 *	- non-null data
 *	- rectangles are valid
 *	- the pixel format conversion has already been validated.
 */
static GpStatus
gdip_bitmap_change_rect_pixel_format (ActiveBitmapData *srcData, const Rect *srcRect, ActiveBitmapData *destData, Rect *destRect)
{
	PixelFormat	srcFormat;
	PixelFormat	destFormat;
	StreamingState	srcStream;
	StreamingState	destStream;
	Rect		effectiveDestRect;
	GpStatus	status;

	srcFormat = srcData->pixel_format;
	destFormat = destData->pixel_format;

	if (!gdip_is_pixel_format_conversion_valid (srcFormat, destFormat))
		return InvalidParameter;

	if (!destData->scan0)
		return InvalidParameter;

	/* Check that the destRect lies fully within the destData buffer. */
	if ((destRect->X + destRect->Width > destData->width) || (destRect->Y + destRect->Height > destData->height))
		return InvalidParameter;

	effectiveDestRect = *destRect;

	if (effectiveDestRect.Width > srcRect->Width) {
		effectiveDestRect.Width = srcRect->Width;
	}

	if (effectiveDestRect.Height > srcRect->Height) {
		effectiveDestRect.Height = srcRect->Height;
	}

	/* Fire up the pixel streams. */
	status = gdip_init_pixel_stream (&srcStream, srcData, srcRect->X, srcRect->Y, srcRect->Width, srcRect->Height);

	if (status != Ok) {
		return status;
	}

	status = gdip_init_pixel_stream (&destStream, destData, effectiveDestRect.X, effectiveDestRect.Y, effectiveDestRect.Width, effectiveDestRect.Height);

	if (status != Ok) {
		return status;
	}

	/* Move the data; special path going from indexed to not-indexed */
	if ((srcFormat & PixelFormatIndexed) && !(destFormat & PixelFormatIndexed)) {
		int	pixel;

		while (gdip_pixel_stream_has_next (&srcStream)) {
			pixel = gdip_pixel_stream_get_next (&srcStream);

			/* Look up the pixel in the palette and get the ARGB value */
			pixel = srcData->palette->Entries[pixel];
#if G_BYTE_ORDER != G_LITTLE_ENDIAN
			pixel = GUINT32_FROM_LE (pixel);
#endif
			gdip_pixel_stream_set_next (&destStream, pixel);
		}
	} else {
		if (!gdip_pixel_stream_copy_optimized (&destStream, &srcStream)) {
			while (gdip_pixel_stream_has_next (&srcStream)) {
				gdip_pixel_stream_set_next (&destStream, gdip_pixel_stream_get_next (&srcStream));
			}
		}
	}

	return Ok;
}

GpStatus WINGDIPAPI
GdipBitmapLockBits (GpBitmap *bitmap, GDIPCONST GpRect *rect, UINT flags, PixelFormat format, BitmapData *lockedBitmapData)
{
	Rect 	src_rect;
	ActiveBitmapData	*src_data;
	ActiveBitmapData	*dest_data;
	GpStatus	status;

	if (!bitmap || !lockedBitmapData)
		return InvalidParameter;

	src_data = bitmap->active_bitmap;
	dest_data = (ActiveBitmapData *) lockedBitmapData;

	/* Is this bitmap already locked? */
	if (src_data->reserved & GBD_LOCKED)
		return WrongState;

	if (rect) {
		if ((rect->X < 0) || (rect->Y < 0) || (rect->Width <= 0) || (rect->Height <= 0))
			return InvalidParameter;

		if (((rect->X + rect->Width) > src_data->width) || ((rect->Y + rect->Height) > src_data->height))
			return InvalidParameter;

		src_rect = *rect;
	} else {
		// Lock the entire bitmap if no source rect is supplied.
		src_rect.X = 0;
		src_rect.Y = 0;
		src_rect.Width = src_data->width;
		src_rect.Height = src_data->height;
	}

	/* if the current and specified format are different, that the bitmap is indexed and that we ask for write then... */
	if ((src_data->pixel_format != format) && gdip_is_an_indexed_pixelformat (src_data->pixel_format) &&
		((flags & ImageLockModeWrite) != 0)) {
		return InvalidParameter;
	}

	if (!gdip_is_a_supported_pixelformat (format))
		return InvalidParameter;

	/* Common stuff */
	if ((flags & ImageLockModeWrite) != 0) {
		dest_data->reserved |= GBD_WRITE_OK;
		dest_data->image_flags &= ~ImageFlagsReadOnly;
	} else {
		dest_data->reserved &= ~GBD_WRITE_OK;
		dest_data->image_flags |= ImageFlagsReadOnly;
	}

	if ((format & PixelFormatAlpha) != 0) {
		dest_data->image_flags |= ImageFlagsHasAlpha;
	}

	src_data->reserved |= GBD_LOCKED;
	dest_data->reserved |= GBD_LOCKED;
	dest_data->x = src_rect.X;
	dest_data->y = src_rect.Y;
	dest_data->width = src_rect.Width;
	dest_data->height = src_rect.Height;
	dest_data->pixel_format = format;
	dest_data->palette = NULL;

	if (format != PixelFormat24bppRGB && format == src_data->pixel_format && (flags & ImageLockModeUserInputBuf) == 0) {
		// No conversion needed, just read the bits directly.
		dest_data->reserved &= ~GBD_OWN_SCAN0;
		dest_data->stride = src_data->stride;
		dest_data->scan0 = src_data->scan0;
	}
	else {
		int		dest_pixel_format_bpp;

		switch (format) {
		case PixelFormat24bppRGB:
			// Cairo uses 32bpp to represent 24bpp images so this is read as 32bpp.
			// https://github.com/mono/libgdiplus/issues/448.
			dest_pixel_format_bpp = 24;
			dest_data->reserved |= GBD_TRUE24BPP;
			break;
		default:
			dest_pixel_format_bpp = gdip_get_pixel_format_bpp (format);
			break;
		}

		dest_data->stride = (src_rect.Width * dest_pixel_format_bpp + 7) >> 3;
		gdip_align_stride (dest_data->stride);

		if ((flags & ImageLockModeUserInputBuf) == 0) {
			dest_data->reserved |= GBD_OWN_SCAN0;

			unsigned long long int size = (unsigned long long int)src_rect.Height * dest_data->stride;
			if (size > G_MAXINT32)
				return OutOfMemory;

			dest_data->scan0 = GdipAlloc (size);
			if (!dest_data->scan0)
				return OutOfMemory;
		} else {
			dest_data->reserved &= ~GBD_OWN_SCAN0;
			/* User is supposed to have provided the buffer */
			if (!dest_data->scan0)
				return InvalidParameter;
		}
	}

	gdip_bitmap_flush_surface (bitmap);

	/* If the user wants the original data to be readable, then convert the bits. */
	if ((flags & ImageLockModeRead) != 0) {
		Rect dest_rect = {0, 0, src_rect.Width, src_rect.Height};
		status = gdip_bitmap_change_rect_pixel_format (src_data, &src_rect, dest_data, &dest_rect);
		if (status != Ok) {
			if ((dest_data->reserved & GBD_OWN_SCAN0) != 0) {
				GdipFree (dest_data->scan0);
				dest_data->scan0 = NULL;
				dest_data->reserved &= ~GBD_OWN_SCAN0;
			}
				
			return status;
		}
	}

	return Ok;
}

GpStatus WINGDIPAPI
GdipBitmapUnlockBits (GpBitmap *bitmap, BitmapData *lockedBitmapData)
{
	GpStatus	status;
	ActiveBitmapData	*src_data;
	ActiveBitmapData	*dest_data;

	if (!bitmap || !lockedBitmapData)
		return InvalidParameter;

	src_data = (ActiveBitmapData *) lockedBitmapData;
	dest_data = bitmap->active_bitmap;

	/* It is not safe to assume that the correct ActiveBitmapData has been passed in.
	 * Sanity check: Make sure the locked data is in fact locked.*/
	if (!(dest_data->reserved & GBD_LOCKED) || !(dest_data->reserved & GBD_LOCKED)) {
		return Win32Error;
	}

	/* Sanity check: Make sure the locked data's size is consistent with having
	 * been returned from LockBits (). */
	if ((src_data->width > dest_data->width) || (src_data->height > dest_data->height)) {
		return InvalidParameter;
	}

	/* We need to copy the locked data back to the root data's Scan0 if the image was writeable */
	if ((src_data->reserved & GBD_WRITE_OK) != 0) {
		Rect src_rect = { 0, 0, src_data->width, src_data->height };
		Rect dest_rect = { src_data->x, src_data->y, src_data->width, src_data->height };

		status = gdip_bitmap_change_rect_pixel_format (src_data, &src_rect, dest_data, &dest_rect);
	} else {
		status = Ok;
	}

	if ((src_data->reserved & GBD_OWN_SCAN0) != 0) {
		GdipFree(src_data->scan0);
		src_data->scan0 = NULL;
		src_data->reserved &= ~GBD_OWN_SCAN0;
	}

	if (src_data->palette) {
		GdipFree(src_data->palette);
		src_data->palette = NULL;
	}

	if (bitmap->surface != NULL) {
		BYTE *surface_scan0 = cairo_image_surface_get_data (bitmap->surface);
		if (surface_scan0 != bitmap->active_bitmap->scan0) {
			gdip_bitmap_get_premultiplied_scan0_inplace (bitmap, surface_scan0);
		}
	}

	src_data->reserved &= ~GBD_LOCKED;
	dest_data->reserved &= ~GBD_LOCKED;

	return status;
}

GpStatus WINGDIPAPI
GdipBitmapSetPixel (GpBitmap *bitmap, INT x, INT y, ARGB color)
{
	BYTE *v;
	ActiveBitmapData *data;
	PixelFormat pixel_format;
	
	if (!bitmap || !bitmap->active_bitmap)
		return InvalidParameter;
	
	data = bitmap->active_bitmap;

	if (gdip_is_an_indexed_pixelformat (data->pixel_format))
		return InvalidParameter;
	if (data->reserved & GBD_LOCKED)
		return WrongState;
	if (x < 0 || x >= data->width || y < 0 || y >= data->height)
		return InvalidParameter;

	if (bitmap->surface != NULL && gdip_bitmap_format_needs_premultiplication(bitmap)) {
		v = (BYTE*)(cairo_image_surface_get_data (bitmap->surface)) + y * data->stride;
		pixel_format = PixelFormat32bppPARGB;
	} else {
		v = (BYTE*)(data->scan0) + y * data->stride;
		pixel_format = data->pixel_format;
	}

	switch (pixel_format) {
	case PixelFormat24bppRGB:
	case PixelFormat32bppRGB:
		color |= 0xFF000000; /* force the alpha for Cairo */
		/* fall through */
	case PixelFormat32bppARGB: {
		ARGB *scan = (ARGB *)v;
		scan[x] = color;
		break;
	}
	case PixelFormat32bppPARGB: {
		BYTE r, g, b, a;
		get_pixel_bgra (color, b, g, r, a);
		if (a < 0xff) {
			b = pre_multiplied_table [b][a];
			g = pre_multiplied_table [g][a];
			r = pre_multiplied_table [r][a];
			set_pixel_bgra(v, x * 4, b, g, r, a);
		} else {
			ARGB *scan = (ARGB *)v;
			scan[x] = color;
		}
		break;
	}
	case PixelFormat16bppGrayScale:
		return InvalidParameter;
	default:
		return NotImplemented;
	} 

	return Ok;		
}

GpStatus WINGDIPAPI
GdipBitmapGetPixel (GpBitmap *bitmap, INT x, INT y, ARGB *color)
{
	ActiveBitmapData	*data;

	if (!bitmap || !bitmap->active_bitmap || !color)
		return InvalidParameter;

	data = bitmap->active_bitmap;

	if (gdip_is_an_indexed_pixelformat (data->pixel_format)) {
		if (x < 0 || x >= data->width || y < 0 || y >= data->height)
			return InvalidParameter;

		StreamingState	pixel_stream;
		GpStatus	status;
		unsigned int	palette_index;

		if (!data->palette)
			return InvalidParameter;

		status = gdip_init_pixel_stream (&pixel_stream, data, x, y, 1, 1);
		if (status != Ok)
			return status;

		palette_index = gdip_pixel_stream_get_next (&pixel_stream);

		if (palette_index >= data->palette->Count) {
			*color = 0xFF000000;
		} else {
			*color = data->palette->Entries[palette_index];
		}
	} else {
		if (data->reserved & GBD_LOCKED)
			return WrongState;
		if (x < 0 || x >= data->width || y < 0 || y >= data->height)
			return InvalidParameter;

		BYTE *v;
		PixelFormat pixel_format;

		if (bitmap->surface != NULL && gdip_bitmap_format_needs_premultiplication(bitmap)) {
			v = (BYTE*)(cairo_image_surface_get_data (bitmap->surface)) + y * data->stride;
			pixel_format = PixelFormat32bppPARGB;
		} else {
			v = (BYTE*)(data->scan0) + y * data->stride;
			pixel_format = data->pixel_format;
		}

		switch (pixel_format) {
		case PixelFormat16bppGrayScale:
			return InvalidParameter;
		case PixelFormat32bppARGB: {
			ARGB *scan = (ARGB *)v;
			*color = scan[x];
			break;
		}
		case PixelFormat32bppPARGB: {
			ARGB *scan = (ARGB *)v;
			BYTE r, g, b, a;
			get_pixel_bgra (scan[x], b, g, r, a);
			if (a < 0xff) {
				b = pre_multiplied_table_reverse [b][a];
				g = pre_multiplied_table_reverse [g][a];
				r = pre_multiplied_table_reverse [r][a];
				set_pixel_bgra(color, 0, b, g, r, a);
			} else {
				*color = scan[x];
			}
			break;
		}
		case PixelFormat24bppRGB:
		case PixelFormat32bppRGB: {
			ARGB *scan = (ARGB *)v;
			*color = scan[x] | 0xFF000000;
			break;
		}
		case PixelFormat16bppARGB1555:
		case PixelFormat16bppRGB555:
			*color = gdip_getpixel_16bppRGB555 (v, x);
			break;
		case PixelFormat16bppRGB565:
			*color = gdip_getpixel_16bppRGB565 (v, x);
			break;
		default:
			return NotImplemented;
		}
	} 
	
	return Ok;
}

GpStatus WINGDIPAPI
GdipBitmapSetResolution (GpBitmap *bitmap, REAL xdpi, REAL ydpi)
{
	if (!bitmap || !bitmap->active_bitmap || isnan(xdpi) || isnan(xdpi) || (xdpi <= 0.0f) || (ydpi <= 0.0f))
		return InvalidParameter;

	bitmap->active_bitmap->dpi_horz = xdpi;
	bitmap->active_bitmap->dpi_vert = ydpi;

	bitmap->active_bitmap->image_flags |= ImageFlagsHasRealDPI;

	return Ok;
}

cairo_surface_t *
gdip_bitmap_ensure_surface (GpBitmap *bitmap)
{
	cairo_format_t format;
	ActiveBitmapData *data = bitmap->active_bitmap;

	if (bitmap->surface || !data || !data->scan0)
		return bitmap->surface;

	switch (data->pixel_format) {
	case PixelFormat24bppRGB:
		format = CAIRO_FORMAT_RGB24;
		break;

	case PixelFormat32bppARGB:	/* premultiplication is required */
	case PixelFormat32bppRGB:	/* no alpha */
	case PixelFormat32bppPARGB:	/* alpha already premultiplied */
		format = CAIRO_FORMAT_ARGB32;
		break;

	default:
		g_warning ("gdip_bitmap_ensure_surface: Unable to create a surface for raw bitmap data of format 0x%08x", data->pixel_format);
		return NULL;
	}

	if (gdip_bitmap_format_needs_premultiplication (bitmap)) {
		BYTE *premul = gdip_bitmap_get_premultiplied_scan0 (bitmap);
		if (!premul)
			return NULL;
		bitmap->surface = cairo_image_surface_create_for_data (premul, CAIRO_FORMAT_ARGB32,
			data->width, data->height, data->stride);
	} else {
		bitmap->surface = cairo_image_surface_create_for_data ((BYTE*)data->scan0, format, 
			data->width, data->height, data->stride);
	}

	return bitmap->surface;
}

void gdip_bitmap_flush_surface (GpBitmap *bitmap)
{
	if (bitmap->surface != NULL) {
		BYTE *surface_scan0 = cairo_image_surface_get_data (bitmap->surface);
		if (surface_scan0 != bitmap->active_bitmap->scan0) {
			// The surface had to be premultiplied, we need to reverse the transition
			gdip_bitmap_get_premultiplied_scan0_reverse (bitmap, surface_scan0);
		}
	}
}

void gdip_bitmap_invalidate_surface (GpBitmap *bitmap)
{
	if (bitmap->surface != NULL) {
		BYTE *surface_scan0 = cairo_image_surface_get_data (bitmap->surface);
		cairo_surface_destroy (bitmap->surface);
		bitmap->surface = NULL;
		if (surface_scan0 != bitmap->active_bitmap->scan0) {
			GdipFree (surface_scan0);
		}
	}
}

BOOL
gdip_bitmap_format_needs_premultiplication (GpBitmap *bitmap)
{
	return (bitmap->active_bitmap->pixel_format == PixelFormat32bppARGB);
}

static void
gdip_bitmap_get_premultiplied_scan0_internal (GpBitmap *bitmap, BYTE *src, BYTE *dest, const BYTE pre_multiplied_table[256][256])
{
	ActiveBitmapData *data = bitmap->active_bitmap;
	BYTE *source = src;
	BYTE *target = dest;
	int y, x;
	for (y = 0; y < data->height; y++) {
		ARGB *sp = (ARGB*) source;
		ARGB *tp = (ARGB*) target;
		for (x = 0; x < data->width; x++) {
			BYTE r, g, b, a;
			get_pixel_bgra (*sp, b, g, r, a);

			if (a < 0xff) {
				b = pre_multiplied_table [b][a];
				g = pre_multiplied_table [g][a];
				r = pre_multiplied_table [r][a];
				set_pixel_bgra (tp, 0, b, g, r, a);
			} else {
				*tp = *sp;
			}
			sp++;
			tp++;
		}
		source += data->stride;
		target += data->stride;
	}
}

BYTE*
gdip_bitmap_get_premultiplied_scan0 (GpBitmap *bitmap)
{
	ActiveBitmapData *data = bitmap->active_bitmap;
	unsigned long long int size = (unsigned long long int)data->height * data->stride;
	if (size > G_MAXINT32)
		return NULL;
	BYTE* premul = (BYTE*) GdipAlloc (size);
	if (!premul)
		return NULL;

	gdip_bitmap_get_premultiplied_scan0_internal (bitmap, (BYTE*)data->scan0, premul, pre_multiplied_table);
	return premul;
}

void
gdip_bitmap_get_premultiplied_scan0_inplace (GpBitmap *bitmap, BYTE *premul)
{
	gdip_bitmap_get_premultiplied_scan0_internal (bitmap, (BYTE*)bitmap->active_bitmap->scan0, premul, pre_multiplied_table);
}

void
gdip_bitmap_get_premultiplied_scan0_reverse (GpBitmap *bitmap, BYTE *premul)
{
	gdip_bitmap_get_premultiplied_scan0_internal (bitmap, premul, (BYTE*)bitmap->active_bitmap->scan0, pre_multiplied_table_reverse);
}

GpBitmap *
gdip_convert_indexed_to_rgb (GpBitmap *indexed_bmp)
{
	ActiveBitmapData	*data;
	ColorPalette	*palette;
	int		rgb_stride;
	int		rgb_bytes;
	int		force_alpha;
	int		one_pixel_mask;
	int		one_pixel_shift;
	int		pixels_per_byte;
	ARGB		*rgb_scan0;
	int		p;
	int		x;
	int		y;
	GpBitmap	*ret;
	GpStatus	status;
	BYTE		*indexed_scan;
	ARGB		*rgb_scan;
	int		pixels_this_byte;
	unsigned short	sample;
	int		index;
	int		format;

	data = indexed_bmp->active_bitmap;
	if (data == NULL) {
		return NULL;
	}

	palette = data->palette;

	if (palette == NULL || !gdip_is_an_indexed_pixelformat (data->pixel_format)) {
		return NULL;
	}

	switch (data->pixel_format) {
	case PixelFormat1bppIndexed:
		one_pixel_mask = 0x01;
		one_pixel_shift = 1;
		pixels_per_byte = 8;
		break;
	case PixelFormat4bppIndexed:
		one_pixel_mask = 0x0F;
		one_pixel_shift = 4;
		pixels_per_byte = 2;
		break;
	case PixelFormat8bppIndexed:
		one_pixel_mask = 0xFF;
		one_pixel_shift = 8;
		pixels_per_byte = 1;
		break;
	default: /* something is wrong!! */
		return NULL;
	}

	if ((palette->Flags & PaletteFlagsHasAlpha) == 0) {
		format = PixelFormat32bppRGB;
		set_pixel_bgra (&force_alpha, 0, 0, 0, 0, 0xFF); /* full alpha bits set */
	} else {
		format = PixelFormat32bppARGB;
		force_alpha = 0;
	}

	rgb_stride = data->width * sizeof (ARGB);

	/* ensure 32bits alignment */
	gdip_align_stride (rgb_stride);
	rgb_bytes = data->height * rgb_stride;

	/* allocate the RGB frame */
	rgb_scan0 = GdipAlloc (rgb_bytes);

	if (rgb_scan0 == NULL) { /* out of memory?? */
		return NULL;
	}

	/* convert the indexed pixels into RGB values and store them into the RGB frame */
	for (y=0; y < data->height; y++) {
		indexed_scan = (BYTE*)(data->scan0) + y * data->stride;
		rgb_scan = rgb_scan0 + (y * data->width);
		/* Speed up the 8bpp case */
		if (pixels_per_byte == 1) {
			for (x = 0; x < data->width; x++) {
				index = *indexed_scan++;
				rgb_scan [x] = palette->Entries [index] | force_alpha;
			}
			continue;
		}

		/* For 4bpp and 1bpp */
		for (x=0; x < data->width; x += pixels_per_byte) {
			pixels_this_byte = pixels_per_byte;
			sample = *indexed_scan;

			indexed_scan++;

			if (x + pixels_this_byte >= data->width) {
				pixels_this_byte = data->width - x;
			}

			for (p=0; p < pixels_this_byte; p++) {
				sample <<= one_pixel_shift;
				index = (sample >> 8) & one_pixel_mask;

				rgb_scan[x + p] = palette->Entries[index] | force_alpha;
			}
		}
	}

	/* try to get a GpBitmap out of it :-) */
	status = GdipCreateBitmapFromScan0 (data->width, data->height, rgb_stride, format, (BYTE*)rgb_scan0, &ret);
	if (status == Ok) {
		ret->active_bitmap->reserved = GBD_OWN_SCAN0;
		return ret;
	}

	if (ret != NULL) {
		gdip_bitmap_dispose(ret);
	}
	if (rgb_scan0 != NULL) {
		GdipFree (rgb_scan0);
	}
	return NULL;
}


ColorPalette*
gdip_create_greyscale_palette (int num_colors)
{
	ColorPalette *palette;
	int i;

	if ((num_colors < 0) || (num_colors > 256))
		return NULL;

	/* ColorPalette definition already include 1 ARGB member */
	palette = GdipAlloc (sizeof(ColorPalette) + (num_colors - 1) * sizeof(ARGB));
	if (!palette)
		return NULL;

	palette->Count = num_colors;
	palette->Flags = 0; /* not every codec sets the ImageFlagsColorSpaceGRAY flag*/

	/* always force alpha to opaque */
	if (num_colors == 256) {
		for (i = 0; i < 256; i++)
			set_pixel_bgra (&palette->Entries[i], 0, i, i, i, 0xFF); 
	} else {
		for (i = 0; i < num_colors; i++) {
			int intensity = i * 255 / (num_colors - 1);
			set_pixel_bgra (&palette->Entries[i], 0, intensity, intensity, intensity, 0xFF);
		}
	}
	return palette;
}
