/***************************************************************************

  cpaint.c

  (c) 2000-2017 Benoît Minisini <benoit.minisini@gambas-basic.org>

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2, or (at your option)
  any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  MA 02110-1301, USA.

***************************************************************************/

#define __CPAINT_C

#include "gb.image.h"
#include "main.h"
#include "cpaint.h"

static GB_PAINT *_current = NULL;
#define THIS _current
#define PAINT _current->desc

#define XTHIS ((PAINT_EXTENTS *)_object)

#define MTHIS ((PAINT_MATRIX *)_object)
#define MPAINT (_matrix_desc)

#define BTHIS ((PAINT_BRUSH *)_object)
#define BPAINT (BTHIS->desc)

#define CHECK_DEVICE() if (check_device()) return
#define CHECK_PATH() if (check_path()) return

#define RAD(_deg) ((_deg) * M_PI / 180)

static GB_PAINT_MATRIX_DESC *_matrix_desc = NULL;

static void load_matrix_interface()
{
	if (!_matrix_desc)
	{
		_matrix_desc = (GB_PAINT_MATRIX_DESC *)GB.GetClassInterface(GB.FindClass("Image"), "PaintMatrix");
		if (!_matrix_desc)
		{
			fprintf(stderr, "gb.draw: error: unable to find PaintMatrix interface\n");
			abort();
		}
	}
}

static bool check_device()
{
	if (!_current || !_current->extra)
	{
		GB.Error("No current device");
		return TRUE;
	}
	else
		return FALSE;
}

static bool check_path()
{
	if (THIS->has_path)
	{
		GB.Error("Pending path");
		return TRUE;
	}
	else
		return FALSE;
}

GB_PAINT *PAINT_get_current()
{
	if (check_device())
		return NULL;
	else
		return _current;
}

GB_PAINT *PAINT_from_device(void *device)
{
	GB_PAINT *d;

	for (d = _current; d; d = d->previous)
	{
		if (d->device == device && d->opened)
			break;
	}

	return d;
}

bool PAINT_is_painted(void *device)
{
	return PAINT_from_device(device) != NULL;
}

void PAINT_set_background(GB_COLOR color)
{
	if (!THIS)
		return;
	
	PAINT->Background(THIS, TRUE, &color);
	GB.StoreObject(NULL, POINTER(&THIS->brush));
}

bool PAINT_open(GB_PAINT *paint)
{
	if (paint->opened)
		return FALSE;
	
	//fprintf(stderr, "PAINT_open: %p\n", paint);
	GB.Alloc(POINTER(&paint->extra), paint->desc->size);
	memset(paint->extra, 0, paint->desc->size);
	paint->opened = !paint->desc->Begin(paint);

	if (!paint->opened)
		GB.Free(POINTER(&paint->extra));
	
	return !paint->opened;
}

void PAINT_close(GB_PAINT *paint)
{
	if (!paint->opened)
		return;

	if (!paint->other)
	{
		//fprintf(stderr, "PAINT_close: %p\n", paint);
		paint->desc->End(paint);
		GB.Free(POINTER(&paint->extra));
	}
	else
		PAINT->Restore(THIS);

	paint->opened = FALSE;
}

bool PAINT_begin(void *device)
{
	GB_PAINT_DESC *desc;
	GB_PAINT *paint, *other;
	GB_CLASS klass;
	
	klass = GB.GetClass(device);
	desc = (GB_PAINT_DESC *)GB.GetClassInterface(klass, "Paint");
	load_matrix_interface();

	if (!desc)
	{
		GB.Error("Not a paintable device");
		return TRUE;
	}

	GB.Alloc(POINTER(&paint), sizeof(GB_PAINT));
	
	other = PAINT_from_device(device);
	
	paint->desc = desc;
	GB.Ref(device);
	paint->device = device;
	paint->brush = NULL;
	paint->opened = FALSE;
	paint->other = FALSE;
	paint->has_path = FALSE;
	paint->tag = NULL;
	paint->area.x = paint->area.y = 0;
	paint->fontScale = 1;
	
	paint->previous = _current;
	_current = paint;
	
	//paint->draw = DRAW_from_device(device);
	//if (paint->draw)
	//	DRAW_close(paint->draw);
	
	if (other)
	{
		paint->extra = other->extra;
		paint->opened = TRUE;
		paint->other = TRUE;
		paint->area = other->area;
		paint->resolutionX = other->resolutionX;
		paint->resolutionY = other->resolutionY;
		paint->brush = other->brush;
		if (paint->brush)
			GB.Ref(paint->brush);
		paint->fontScale = other->fontScale;
		PAINT->Save(THIS);
	}
	else
	{
		if (PAINT_open(paint))
			return TRUE;
	}
	
	return FALSE;
}

void PAINT_end()
{
	GB_PAINT *paint;

	if (!_current)
		return;
		
	paint = _current;
	_current = _current->previous;
	
	PAINT_close(paint);

	//if (paint->draw)
	//	DRAW_open(paint->draw);
	
	if (paint->brush)
		GB.Unref(POINTER(&paint->brush));
	GB.Unref(POINTER(&paint->device));
	GB.StoreObject(NULL, POINTER(&(paint->tag)));
	GB.Free(POINTER(&paint));
}

void PAINT_translate(float tx, float ty)
{
	GB_TRANSFORM transform;

	if (tx == 0.0 && ty == 0.0)
		return;

	MPAINT->Create(&transform);
	PAINT->Matrix(THIS, FALSE, transform);
	MPAINT->Translate(transform, tx, ty);
	PAINT->Matrix(THIS, TRUE, transform);
	MPAINT->Delete(&transform);
}

void PAINT_scale(float sx, float sy)
{
	GB_TRANSFORM transform;
	
	if (sx == 1.0 && sy == 1.0)
		return;
	
	MPAINT->Create(&transform);
	PAINT->Matrix(THIS, FALSE, transform);
	MPAINT->Scale(transform, sx, sy);
	PAINT->Matrix(THIS, TRUE, transform);
	MPAINT->Delete(&transform);
}

void PAINT_set_area(GEOM_RECTF *area)
{
	THIS->area.x += area->x;
	THIS->area.y += area->y;

	THIS->area.width = area->w; //MIN(area->w, THIS->area.width - THIS->area.x);
	THIS->area.height = area->h; //MIN(area->h, THIS->area.height - THIS->area.y);

	if (THIS->area.width <= 0 || THIS->area.height <= 0)
		THIS->area.width = THIS->area.height = 0;

	PAINT_translate(area->x, area->y);
}

//---- PaintExtents ---------------------------------------------------------

#define IMPLEMENT_EXTENTS_PROPERTY(_method, _field) \
BEGIN_PROPERTY(_method) \
	GB.ReturnFloat(XTHIS->ext._field); \
END_PROPERTY

IMPLEMENT_EXTENTS_PROPERTY(PaintExtents_X, x1)
IMPLEMENT_EXTENTS_PROPERTY(PaintExtents_Y, y1)
IMPLEMENT_EXTENTS_PROPERTY(PaintExtents_X2, x2)
IMPLEMENT_EXTENTS_PROPERTY(PaintExtents_Y2, y2)

BEGIN_PROPERTY(PaintExtents_Width)

	GB.ReturnFloat(XTHIS->ext.x2 - XTHIS->ext.x1);

END_PROPERTY

BEGIN_PROPERTY(PaintExtents_Height)

	GB.ReturnFloat(XTHIS->ext.y2 - XTHIS->ext.y1);

END_PROPERTY

BEGIN_METHOD(PaintExtents_Merge, GB_OBJECT extents)

	PAINT_EXTENTS *extents = VARG(extents);
	
	if (GB.CheckObject(extents))
		return;
		
	if (extents->ext.x1 < XTHIS->ext.x1) XTHIS->ext.x1 = extents->ext.x1;
	if (extents->ext.y1 < XTHIS->ext.y1) XTHIS->ext.y1 = extents->ext.y1;
	if (extents->ext.x2 > XTHIS->ext.x2) XTHIS->ext.x2 = extents->ext.x2;
	if (extents->ext.y2 > XTHIS->ext.y2) XTHIS->ext.y2 = extents->ext.y2;

	GB.ReturnObject(XTHIS);

END_METHOD

GB_DESC PaintExtentsDesc[] = 
{
	GB_DECLARE("PaintExtents", sizeof(PAINT_EXTENTS)),
	
	GB_PROPERTY_READ("X", "f", PaintExtents_X),
	GB_PROPERTY_READ("Y", "f", PaintExtents_Y),
	GB_PROPERTY_READ("X2", "f", PaintExtents_X2),
	GB_PROPERTY_READ("Y2", "f", PaintExtents_Y2),
	GB_PROPERTY_READ("W", "f", PaintExtents_Width),
	GB_PROPERTY_READ("H", "f", PaintExtents_Height),
	GB_PROPERTY_READ("Width", "f", PaintExtents_Width),
	GB_PROPERTY_READ("Height", "f", PaintExtents_Height),
	
	GB_METHOD("Merge", "PaintExtents", PaintExtents_Merge, "(Extents)PaintExtents;"),
	
	GB_END_DECLARE
};


//---- PaintMatrix ----------------------------------------------------------

static bool _do_not_init = FALSE;

static PAINT_MATRIX *create_matrix(GB_TRANSFORM transform)
{
	PAINT_MATRIX *matrix;
	_do_not_init = TRUE;
	matrix = GB.New(GB.FindClass("PaintMatrix"), NULL, NULL);
	_do_not_init = FALSE;
	matrix->transform = transform;
	return matrix;
}

BEGIN_METHOD(PaintMatrix_new, GB_FLOAT xx; GB_FLOAT xy; GB_FLOAT yx; GB_FLOAT yy; GB_FLOAT x0; GB_FLOAT y0)

	load_matrix_interface();

	if (_do_not_init)
		return;
	
	MPAINT->Create(&MTHIS->transform);
	MPAINT->Init(MTHIS->transform, VARGOPT(xx, 1.0), VARGOPT(xy, 0.0), VARGOPT(yx, 0.0), VARGOPT(yy, 1.0), VARGOPT(x0, 0.0), VARGOPT(y0, 0.0));

END_METHOD

BEGIN_METHOD(PaintMatrix_call, GB_FLOAT xx; GB_FLOAT xy; GB_FLOAT yx; GB_FLOAT yy; GB_FLOAT x0; GB_FLOAT y0)

	GB_TRANSFORM transform;
	
	load_matrix_interface();
	MPAINT->Create(&transform);
	MPAINT->Init(transform, VARGOPT(xx, 1.0), VARGOPT(xy, 0.0), VARGOPT(yx, 0.0), VARGOPT(yy, 1.0), VARGOPT(x0, 0.0), VARGOPT(y0, 0.0));
	GB.ReturnObject(create_matrix(transform));

END_METHOD

BEGIN_METHOD_VOID(PaintMatrix_free)

	if (MTHIS->transform)
		MPAINT->Delete(&MTHIS->transform);

END_METHOD

BEGIN_METHOD_VOID(PaintMatrix_Copy)

	GB_TRANSFORM transform;

	MPAINT->Copy(&transform, MTHIS->transform);
	GB.ReturnObject(create_matrix(transform));

END_METHOD

BEGIN_METHOD_VOID(PaintMatrix_Reset)

	MPAINT->Init(MTHIS->transform, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
	RETURN_SELF();

END_METHOD

BEGIN_METHOD(PaintMatrix_Translate, GB_FLOAT tx; GB_FLOAT ty)

	MPAINT->Translate(MTHIS->transform, (float)VARG(tx), (float)VARG(ty));
	RETURN_SELF();

END_METHOD

BEGIN_METHOD(PaintMatrix_Scale, GB_FLOAT sx; GB_FLOAT sy)

	MPAINT->Scale(MTHIS->transform, (float)VARG(sx), (float)VARG(sy));
	RETURN_SELF();

END_METHOD

BEGIN_METHOD(PaintMatrix_Shear, GB_FLOAT sh; GB_FLOAT sv)

	MPAINT->Shear(MTHIS->transform, (float)VARG(sh), (float)VARG(sv));
	RETURN_SELF();

END_METHOD

BEGIN_METHOD(PaintMatrix_Rotate, GB_FLOAT angle)

	MPAINT->Rotate(MTHIS->transform, (float)VARG(angle));
	RETURN_SELF();

END_METHOD

BEGIN_METHOD_VOID(PaintMatrix_Invert)

	if (MPAINT->Invert(MTHIS->transform))
		GB.ReturnNull();
	else
		RETURN_SELF();

END_METHOD

BEGIN_METHOD(PaintMatrix_Multiply, GB_OBJECT matrix)

	PAINT_MATRIX *matrix = (PAINT_MATRIX *)VARG(matrix);
	
	if (GB.CheckObject(matrix))
		return;

	MPAINT->Multiply(MTHIS->transform, matrix->transform);
	RETURN_SELF();

END_METHOD

BEGIN_METHOD(PaintMatrix_Map, GB_OBJECT point)

	GEOM_POINTF *point = VARG(point);
	double x, y;

	if (GB.CheckObject(point))
		return;

	x = point->x;
	y = point->y;

	MPAINT->Map(MTHIS->transform, &x, &y);

	GB.ReturnObject(GEOM.CreatePointF(x, y));

END_METHOD

static bool PaintMatrix_convert(PAINT_MATRIX *_object, GB_TYPE type, GB_VALUE *conv)
{
	static bool _init = FALSE;
	static GB_CLASS CLASS_FloatArray;
	static GB_CLASS CLASS_SingleArray;
	
	if (!_init)
	{
		load_matrix_interface();
		CLASS_FloatArray = GB.FindClass("Float[]");
		CLASS_SingleArray = GB.FindClass("Single[]");
		_init = TRUE;
	}
	
	if (MTHIS)
	{
		/*if (type == GB_T_STRING || type == GB_T_CSTRING)
		{
			conv->_string.value.addr = _to_string(THIS, type == GB_T_CSTRING);
			conv->_string.value.start = 0;
			conv->_string.value.len = GB.StringLength(conv->_string.value.addr);
			return FALSE;
		}
		else*/ if (type == CLASS_FloatArray || type == CLASS_SingleArray)
		{
			GB_ARRAY array;
			float values[6];
			int i;
			
			MPAINT->Get(MTHIS->transform, values);
			
			if (type == CLASS_FloatArray)
			{
				GB.Array.New(&array, GB_T_FLOAT, 6);
				double *p = (double *)GB.Array.Get(array, 0);
				for (i = 0; i < 5; i++)
					p[i] = values[i];
			}
			else
			{
				GB.Array.New(&array, GB_T_SINGLE, 6);
				float *p = (float *)GB.Array.Get(array, 0);
				for (i = 0; i < 5; i++)
					p[i] = values[i];
			}
			
			conv->_object.value = array;
			return FALSE;
		}
		else
			return TRUE;
	}
	else
	{
		if (type == CLASS_FloatArray || type == CLASS_SingleArray)
		{
			GB_ARRAY array = (GB_ARRAY)conv->_object.value;
			GB_TRANSFORM transform;
			
			if (GB.Array.Count(array) != 6)
				return TRUE;
			
			MPAINT->Create(&transform);
			
			if (type == CLASS_FloatArray)
			{
				double *values = (double *)GB.Array.Get(array, 0);
				MPAINT->Init(transform, values[0], values[1], values[2], values[3], values[4], values[5]);
			}
			else
			{
				float *values = (float *)GB.Array.Get(array, 0);
				MPAINT->Init(transform, values[0], values[1], values[2], values[3], values[4], values[5]);
			}
			
			conv->_object.value = create_matrix(transform);
			return FALSE;
		}
	}
	
	return TRUE;
}

GB_DESC PaintMatrixDesc[] = 
{
	GB_DECLARE("PaintMatrix", sizeof(PAINT_MATRIX)),
	
	GB_METHOD("_new", NULL, PaintMatrix_new, "[(XX)f(XY)f(YX)f(YY)f(X0)f(Y0)f]"),
	GB_STATIC_METHOD("_call", "PaintMatrix", PaintMatrix_call, "[(XX)f(XY)f(YX)f(YY)f(X0)f(Y0)f]"),
	GB_METHOD("_free", NULL, PaintMatrix_free, NULL),

	GB_METHOD("Copy", "PaintMatrix", PaintMatrix_Copy, NULL),
	GB_METHOD("Reset", "PaintMatrix", PaintMatrix_Reset, NULL),
	GB_METHOD("Translate", "PaintMatrix", PaintMatrix_Translate, "(TX)f(TY)f"),
	GB_METHOD("Scale", "PaintMatrix", PaintMatrix_Scale, "(SX)f(SY)f"),
	GB_METHOD("Shear", "PaintMatrix", PaintMatrix_Shear, "(SH)f(SV)f"),
	GB_METHOD("Rotate", "PaintMatrix", PaintMatrix_Rotate, "(Angle)f"),
	GB_METHOD("Invert", "PaintMatrix", PaintMatrix_Invert, NULL),
	GB_METHOD("Multiply", "PaintMatrix", PaintMatrix_Multiply, "(Matrix)PaintMatrix;"),	
	GB_METHOD("Map", "PointF", PaintMatrix_Map, "(Point)PointF;"),

	GB_INTERFACE("_convert", &PaintMatrix_convert),
	
	GB_END_DECLARE	
};


//---- PaintBrush -----------------------------------------------------------

BEGIN_METHOD_VOID(PaintBrush_free)

	BPAINT->Brush.Free(BTHIS->brush);

END_METHOD

BEGIN_PROPERTY(PaintBrush_Matrix)

	GB_TRANSFORM transform;
	PAINT_MATRIX *matrix;
	
	if (READ_PROPERTY)
	{
		MPAINT->Create(&transform);
		BPAINT->Brush.Matrix(BTHIS->brush, FALSE, transform);
		GB.ReturnObject(create_matrix(transform));
	}
	else
	{
		matrix = (PAINT_MATRIX *)VPROP(GB_OBJECT);
		if (!matrix)
			BPAINT->Brush.Matrix(BTHIS->brush, TRUE, NULL);
		else
			BPAINT->Brush.Matrix(BTHIS->brush, TRUE, matrix->transform);
	}

END_PROPERTY

BEGIN_METHOD_VOID(PaintBrush_Reset)

	BPAINT->Brush.Matrix(BTHIS->brush, TRUE, NULL);

END_METHOD

BEGIN_METHOD(PaintBrush_Translate, GB_FLOAT tx; GB_FLOAT ty)

	GB_TRANSFORM transform;

	MPAINT->Create(&transform);
	BPAINT->Brush.Matrix(BTHIS->brush, FALSE, transform);
	MPAINT->Translate(transform, (float)VARG(tx), (float)VARG(ty));
	BPAINT->Brush.Matrix(BTHIS->brush, TRUE, transform);
	MPAINT->Delete(&transform);

END_METHOD

BEGIN_METHOD(PaintBrush_Scale, GB_FLOAT sx; GB_FLOAT sy)

	GB_TRANSFORM transform;

	MPAINT->Create(&transform);
	BPAINT->Brush.Matrix(BTHIS->brush, FALSE, transform);
	MPAINT->Scale(transform, (float)VARG(sx), (float)VARG(sy));
	BPAINT->Brush.Matrix(BTHIS->brush, TRUE, transform);
	MPAINT->Delete(&transform);

END_METHOD

BEGIN_METHOD(PaintBrush_Rotate, GB_FLOAT angle)

	GB_TRANSFORM transform;

	MPAINT->Create(&transform);
	BPAINT->Brush.Matrix(BTHIS->brush, FALSE, transform);
	MPAINT->Rotate(transform, (float)VARG(angle));
	BPAINT->Brush.Matrix(BTHIS->brush, TRUE, transform);
	MPAINT->Delete(&transform);

END_METHOD


GB_DESC PaintBrushDesc[] = 
{
	GB_DECLARE("PaintBrush", sizeof(PAINT_BRUSH)), GB_NOT_CREATABLE(),

	GB_METHOD("_free", NULL, PaintBrush_free, NULL),
	
	GB_PROPERTY("Matrix", "PaintMatrix", PaintBrush_Matrix),
	GB_METHOD("Reset", NULL, PaintBrush_Reset, NULL),
	GB_METHOD("Translate", NULL, PaintBrush_Translate, "(TX)f(TY)f"),
	GB_METHOD("Scale", NULL, PaintBrush_Scale, "(SX)f(SY)f"),
	GB_METHOD("Rotate", NULL, PaintBrush_Rotate, "(Angle)f"),

	GB_END_DECLARE
};


//---- Paint ----------------------------------------------------------------


BEGIN_METHOD(Paint_Begin, GB_OBJECT device; GB_OBJECT area)

	void *device = VARG(device);
	GEOM_RECTF *area = VARGOPT(area, NULL);

	if (GB.CheckObject(device))
		return;

	PAINT_begin(device);
	if (area)
		PAINT_set_area(area);

END_METHOD


BEGIN_METHOD_VOID(Paint_End)

	PAINT_end();
	
END_METHOD


BEGIN_METHOD_VOID(Paint_exit)

	while (_current)
		PAINT_end();

END_METHOD


BEGIN_PROPERTY(Paint_Device)
	
	if (THIS)
		GB.ReturnObject(THIS->device);
	else
		GB.ReturnNull();

END_PROPERTY


BEGIN_PROPERTY(Paint_Width)

	CHECK_DEVICE();
	GB.ReturnFloat(THIS->area.width);

END_PROPERTY


BEGIN_PROPERTY(Paint_Height)

	CHECK_DEVICE();
	GB.ReturnFloat(THIS->area.height);

END_PROPERTY


BEGIN_PROPERTY(Paint_ResolutionX)

	CHECK_DEVICE();
	GB.ReturnInteger(THIS->resolutionX);

END_PROPERTY


BEGIN_PROPERTY(Paint_ResolutionY)

	CHECK_DEVICE();
	GB.ReturnInteger(THIS->resolutionY);

END_PROPERTY

#define IMPLEMENT_METHOD(_method, _api) \
BEGIN_METHOD_VOID(_method) \
	CHECK_DEVICE(); \
	PAINT->_api(THIS); \
END_METHOD


#define IMPLEMENT_METHOD_PRESERVE(_method, _api) \
BEGIN_METHOD(_method, GB_BOOLEAN preserve) \
  bool preserve = VARGOPT(preserve, FALSE); \
	CHECK_DEVICE(); \
	PAINT->_api(THIS, preserve); \
	if (!preserve) \
		THIS->has_path = FALSE; \
END_METHOD

#define IMPLEMENT_PROPERTY_EXTENTS(_property, _api) \
BEGIN_PROPERTY(_property) \
	PAINT_EXTENTS *extents; \
	CHECK_DEVICE(); \
	extents = GB.New(GB.FindClass("PaintExtents"), NULL, NULL); \
	PAINT->_api(THIS, &extents->ext); \
	GB.ReturnObject(extents); \
END_METHOD

#define IMPLEMENT_PROPERTY(_property, _api, _type, _gtype, _return) \
BEGIN_PROPERTY(_property) \
	_type value; \
	CHECK_DEVICE(); \
	if (READ_PROPERTY) \
	{ \
		PAINT->_api(THIS, FALSE, &value); \
		_return(value); \
	} \
	else \
	{ \
		value = (_type)VPROP(_gtype); \
		PAINT->_api(THIS, TRUE, &value); \
	} \
END_METHOD

#define IMPLEMENT_PROPERTY_BOOLEAN(_property, _api) \
	IMPLEMENT_PROPERTY(_property, _api, int, GB_BOOLEAN, GB.ReturnBoolean)

#define IMPLEMENT_PROPERTY_INTEGER(_property, _api) \
	IMPLEMENT_PROPERTY(_property, _api, int, GB_INTEGER, GB.ReturnInteger)

#define IMPLEMENT_PROPERTY_UNSIGNED_INTEGER(_property, _api) \
	IMPLEMENT_PROPERTY(_property, _api, uint, GB_INTEGER, GB.ReturnInteger)

#define IMPLEMENT_PROPERTY_FLOAT(_property, _api) \
	IMPLEMENT_PROPERTY(_property, _api, float, GB_FLOAT, GB.ReturnFloat)

IMPLEMENT_PROPERTY_BOOLEAN(Paint_Invert, Invert)
IMPLEMENT_PROPERTY_INTEGER(Paint_FillStyle, FillStyle)

BEGIN_PROPERTY(Paint_Background)

	uint value;

	CHECK_DEVICE();

	if (READ_PROPERTY)
	{
		PAINT->Background(THIS, FALSE, &value);
		GB.ReturnInteger(value);
	}
	else
	{
		PAINT_set_background(VPROP(GB_INTEGER));
	}

END_METHOD

IMPLEMENT_PROPERTY_BOOLEAN(Paint_Antialias, Antialias)
IMPLEMENT_METHOD(Paint_Save, Save)
IMPLEMENT_METHOD(Paint_Restore, Restore)
IMPLEMENT_METHOD_PRESERVE(Paint_Clip, Clip)
IMPLEMENT_METHOD(Paint_ResetClip, ResetClip)
IMPLEMENT_PROPERTY_EXTENTS(Paint_ClipExtents, ClipExtents)
IMPLEMENT_METHOD_PRESERVE(Paint_Fill, Fill)
IMPLEMENT_METHOD_PRESERVE(Paint_Stroke, Stroke)
IMPLEMENT_PROPERTY_EXTENTS(Paint_PathExtents, PathExtents)


static GB_ARRAY _outline;
static GB_ARRAY _outline_p;

static void make_path_outline(int cmd, float x, float y)
{
	if (!_outline)
		GB.Array.New(&_outline, GB.FindClass("PointF[]"), 0);

	if (cmd == GB_PAINT_PATH_MOVE)
	{
		GB.Array.New(&_outline_p, GB.FindClass("PointF"), 0);
		GB.Ref(_outline_p);
		*((void **)GB.Array.Add(_outline)) = _outline_p;
	}

	GEOM_POINTF *p = GEOM.CreatePointF(x, y);
	*((void **)GB.Array.Add(_outline_p)) = p;
	GB.Ref(p);
}

BEGIN_PROPERTY(Paint_PathOutline)

	CHECK_DEVICE();

	_outline = NULL;
	PAINT->PathOutline(THIS, make_path_outline);
	GB.ReturnObject(_outline);

END_PROPERTY


BEGIN_PROPERTY(Paint_ClipRect)

	GB_EXTENTS ext;
	GEOM_RECT *rect;
	int w, h;
	
	CHECK_DEVICE();
	
	if (READ_PROPERTY)
	{
		(*PAINT->ClipExtents)(THIS, &ext);
		
		w = floorf(ext.x2) - ceilf(ext.x1);
		h = floorf(ext.y2) - ceilf(ext.y1);
		
		if (w <= 0 || h <= 0)
		{
			GB.ReturnNull();
			return;
		}
		
		rect = GEOM.CreateRect();
		rect->x = ceilf(ext.x1);
		rect->y = ceilf(ext.y1);
		rect->w = w;
		rect->h = h;
		
		GB.ReturnObject(rect);
	}
	else
	{
		rect = (GEOM_RECT *)VPROP(GB_OBJECT);
		if (rect)
			PAINT->ClipRect(THIS, rect->x, rect->y, rect->w, rect->h);
		else
			PAINT->ResetClip(THIS);
	}

END_PROPERTY


BEGIN_METHOD(Paint_PathContains, GB_FLOAT x; GB_FLOAT y)

	CHECK_DEVICE();
	GB.ReturnBoolean(PAINT->PathContains(THIS, (float)VARG(x), (float)VARG(y)));

END_METHOD

IMPLEMENT_PROPERTY_INTEGER(Paint_FillRule, FillRule)
IMPLEMENT_PROPERTY_INTEGER(Paint_LineCap, LineCap)
IMPLEMENT_PROPERTY_INTEGER(Paint_LineJoin, LineJoin)
IMPLEMENT_PROPERTY_INTEGER(Paint_Operator, Operator)
IMPLEMENT_PROPERTY_FLOAT(Paint_LineWidth, LineWidth)
IMPLEMENT_PROPERTY_FLOAT(Paint_MiterLimit, MiterLimit)

BEGIN_PROPERTY(Paint_Brush)

	CHECK_DEVICE();
	
	if (READ_PROPERTY)
		GB.ReturnObject(THIS->brush);
	else
	{
		PAINT_BRUSH *old_brush = THIS->brush;
		PAINT_BRUSH *new_brush = (PAINT_BRUSH *)VPROP(GB_OBJECT);
		if (new_brush)
		{
			GB.Ref(new_brush);
			PAINT->SetBrush(THIS, new_brush->brush);
		}
		GB.Unref(POINTER(&old_brush));
		THIS->brush = new_brush;
	}

END_PROPERTY


BEGIN_PROPERTY(Paint_BrushOrigin)

	float x, y;
	
	CHECK_DEVICE();

	if (READ_PROPERTY)
	{
		PAINT->BrushOrigin(THIS, FALSE, &x, &y);
		GB.ReturnObject(GEOM.CreatePointF(x, y));
	}
	else
	{
		GEOM_POINT *p = VPROP(GB_OBJECT);
		if (!p)
			x = y = 0.0;
		else
		{
			x = p->x;
			y = p->y;
		}
			
		PAINT->BrushOrigin(THIS, TRUE, &x, &y);
	}

END_PROPERTY


BEGIN_PROPERTY(Paint_Dash)

	GB_ARRAY array;
	float *dashes;
	int count, i;
	
	CHECK_DEVICE();
	
	if (READ_PROPERTY)
	{
		PAINT->Dash(THIS, FALSE, &dashes, &count);
		if (!count)
			GB.ReturnNull();
		else
		{
			GB.Array.New(POINTER(&array), GB_T_FLOAT, count);
			for (i = 0; i < count; i++)
				*((double *)GB.Array.Get(array, i)) = (double)dashes[i];
			GB.ReturnObject(array);
		}
		GB.Free(POINTER(&dashes));
	}
	else
	{
		array = (GB_ARRAY)VPROP(GB_OBJECT);
		if (!array)
			count = 0;
		else
			count = GB.Array.Count(array);
		
		if (!count)
		{
			PAINT->Dash(THIS, TRUE, NULL, &count);
			//THIS->lineStyle = GB_PAINT_LINE_STYLE_SOLID;
		}
		else
		{
			GB.Alloc(POINTER(&dashes), sizeof(float) * count);
			for (i = 0; i < count; i++)
				dashes[i] = (float)*((double *)GB.Array.Get(array, i));
			PAINT->Dash(THIS, TRUE, &dashes, &count);
			GB.Free(POINTER(&dashes));
			//THIS->lineStyle = GB_PAINT_LINE_STYLE_CUSTOM;
		}
	}

END_PROPERTY


IMPLEMENT_PROPERTY_FLOAT(Paint_DashOffset, DashOffset)

BEGIN_METHOD_VOID(Paint_NewPath)

	CHECK_DEVICE();

	PAINT->NewPath(THIS);
	THIS->has_path = FALSE;

END_METHOD

IMPLEMENT_METHOD(Paint_ClosePath, ClosePath)


BEGIN_PROPERTY(Paint_X)

	float x, y;
	CHECK_DEVICE();
	PAINT->GetCurrentPoint(THIS, &x, &y);
	GB.ReturnFloat((double)x);

END_PROPERTY


BEGIN_PROPERTY(Paint_Y)

	float x, y;
	CHECK_DEVICE();
	PAINT->GetCurrentPoint(THIS, &x, &y);
	GB.ReturnFloat((double)y);

END_PROPERTY


BEGIN_METHOD(Paint_Arc, GB_FLOAT xc; GB_FLOAT yc; GB_FLOAT radius; GB_FLOAT angle; GB_FLOAT length; GB_BOOLEAN pie)

	CHECK_DEVICE();
	
	bool pie = VARGOPT(pie, FALSE);
	float angle = VARGOPT(angle, 0.0);
	float length = VARGOPT(length, MISSING(angle) ? M_PI * 2 : 0.0);
	
	if (MISSING(length) || length == 0.0)
		pie = FALSE;
	
	PAINT->Arc(THIS, VARG(xc), VARG(yc), VARG(radius), angle, length, pie);
	THIS->has_path = TRUE;

END_METHOD


BEGIN_METHOD(Paint_Ellipse, GB_FLOAT x; GB_FLOAT y; GB_FLOAT width; GB_FLOAT height; GB_FLOAT angle; GB_FLOAT length; GB_BOOLEAN pie)

	CHECK_DEVICE();

	bool pie = VARGOPT(pie, FALSE);
	float angle = VARGOPT(angle, 0.0);
	float length = VARGOPT(length, MISSING(angle) ? M_PI * 2 : 0.0);
	
	if (MISSING(length) || length == 0.0)
		pie = FALSE;
	
	PAINT->Ellipse(THIS, VARG(x), VARG(y), VARG(width), VARG(height), angle, length, pie);
	THIS->has_path = TRUE;

END_METHOD


BEGIN_METHOD(Paint_Polygon, GB_OBJECT points)

	GB_ARRAY points = VARG(points);
	int i, n;
	double *p;
	
	CHECK_DEVICE();
	
	if (!points)
		return;
	
	n = GB.Array.Count(points);
	if (n < 4) 
		return;

	CHECK_DEVICE();
	
	p = (double *)GB.Array.Get(points, 0);
	
	PAINT->MoveTo(THIS, p[0], p[1]);
	for (i = 2; i < n; i+= 2)
		PAINT->LineTo(THIS, p[i], p[i + 1]);
	PAINT->LineTo(THIS, p[0], p[1]);
	THIS->has_path = TRUE;

END_METHOD


BEGIN_METHOD(Paint_CurveTo, GB_FLOAT x1; GB_FLOAT y1; GB_FLOAT x2; GB_FLOAT y2; GB_FLOAT x3; GB_FLOAT y3)

	CHECK_DEVICE();
	PAINT->CurveTo(THIS, VARG(x1), VARG(y1), VARG(x2), VARG(y2), VARG(x3), VARG(y3));
	THIS->has_path = TRUE;

END_METHOD


BEGIN_METHOD(Paint_RelCurveTo, GB_FLOAT x1; GB_FLOAT y1; GB_FLOAT x2; GB_FLOAT y2; GB_FLOAT x3; GB_FLOAT y3)

	float x, y;
	CHECK_DEVICE();
	PAINT->GetCurrentPoint(THIS, &x, &y);
	PAINT->CurveTo(THIS, x + VARG(x1), y + VARG(y1), x + VARG(x2), y + VARG(y2), x + VARG(x3), y + VARG(y3));
	THIS->has_path = TRUE;

END_METHOD


BEGIN_METHOD(Paint_LineTo, GB_FLOAT x; GB_FLOAT y)

	CHECK_DEVICE();
	PAINT->LineTo(THIS, VARG(x), VARG(y));
	THIS->has_path = TRUE;

END_METHOD


BEGIN_METHOD(Paint_RelLineTo, GB_FLOAT x; GB_FLOAT y)

	float fx, fy;
	CHECK_DEVICE();
	PAINT->GetCurrentPoint(THIS, &fx, &fy);
	PAINT->LineTo(THIS, fx + VARG(x), fy + VARG(y));
	THIS->has_path = TRUE;

END_METHOD


BEGIN_METHOD(Paint_MoveTo, GB_FLOAT x; GB_FLOAT y)

	CHECK_DEVICE();
	PAINT->MoveTo(THIS, VARG(x), VARG(y));

END_METHOD


BEGIN_METHOD(Paint_RelMoveTo, GB_FLOAT x; GB_FLOAT y)

	float fx, fy;
	CHECK_DEVICE();
	PAINT->GetCurrentPoint(THIS, &fx, &fy);
	PAINT->MoveTo(THIS, fx + VARG(x), fy + VARG(y));

END_METHOD


BEGIN_METHOD(Paint_Rectangle, GB_FLOAT x; GB_FLOAT y; GB_FLOAT w; GB_FLOAT h; GB_FLOAT radius)

	CHECK_DEVICE();
	
	float x = VARG(x);
	float y = VARG(y);
	float w = VARG(w);
	float h = VARG(h);
	float r = VARGOPT(radius, 0.0);
	
	if (w == 0.0 || h == 0.0)
		return;

	float sw = w > 0.0 ? 1 : -1;
	float sh = h > 0.0 ? 1 : -1;

	if (r <= 0.0)
	{
		PAINT->Rectangle(THIS, x, y, w, h);
	}
	else
	{
		r = Min(r, Min(fabsf(w), fabsf(h)) / 2);
		float r2 = r * (1 - 0.55228475);

		PAINT->MoveTo(THIS, x + r * sw, y);
		PAINT->LineTo(THIS, x + w - r * sw, y);
		PAINT->CurveTo(THIS, x + w - r2 * sw, y, x + w, y + r2 * sh, x + w, y + r * sh);
		PAINT->LineTo(THIS, x + w, y + h - r * sh);
		PAINT->CurveTo(THIS, x + w, y + h - r2 * sh, x + w - r2 * sw, y + h, x + w - r * sw, y + h);
		PAINT->LineTo(THIS, x + r * sw, y + h);
		PAINT->CurveTo(THIS, x + r2 * sw, y + h, x, y + h - r2 * sh, x, y + h - r * sh);
		PAINT->LineTo(THIS, x, y + r * sh);
		PAINT->CurveTo(THIS, x, y + r2 * sh, x + r2 * sw, y, x + r * sw, y);
	}

	THIS->has_path = TRUE;

END_METHOD


IMPLEMENT_PROPERTY(Paint_Font, Font, GB_FONT, GB_OBJECT, GB.ReturnObject)

BEGIN_PROPERTY(Paint_FontScale)

	if (READ_PROPERTY)
		GB.ReturnFloat(THIS->fontScale);
	else
	{
		THIS->fontScale = VPROP(GB_FLOAT);
		if (THIS->fontScale == 0)
			THIS->fontScale = 1;
	}

END_PROPERTY


BEGIN_METHOD(Paint_Text, GB_STRING text; GB_FLOAT x; GB_FLOAT y; GB_FLOAT w; GB_FLOAT h; GB_INTEGER align)

	CHECK_DEVICE();
	
	if (!MISSING(x) && !MISSING(y))
		PAINT->MoveTo(THIS, (float)VARG(x), (float)VARG(y));
	
	PAINT->Text(THIS, STRING(text), LENGTH(text), VARGOPT(w, -1), VARGOPT(h, -1), VARGOPT(align, GB_DRAW_ALIGN_DEFAULT), FALSE);
	THIS->has_path = TRUE;

END_METHOD


BEGIN_METHOD(Paint_RichText, GB_STRING text; GB_FLOAT x; GB_FLOAT y; GB_FLOAT w; GB_FLOAT h; GB_INTEGER align)

	CHECK_DEVICE();
	
	if (!MISSING(x) && !MISSING(y))
		PAINT->MoveTo(THIS, (float)VARG(x), (float)VARG(y));
	
	PAINT->RichText(THIS, STRING(text), LENGTH(text), VARGOPT(w, -1), VARGOPT(h, -1), VARGOPT(align, GB_DRAW_ALIGN_DEFAULT), FALSE);
	THIS->has_path = TRUE;

END_METHOD


BEGIN_METHOD(Paint_DrawText, GB_STRING text; GB_FLOAT x; GB_FLOAT y; GB_FLOAT w; GB_FLOAT h; GB_INTEGER align)

	CHECK_DEVICE();
	CHECK_PATH();

	if (!MISSING(x) && !MISSING(y))
		PAINT->MoveTo(THIS, (float)VARG(x), (float)VARG(y));
	
	PAINT->Text(THIS, STRING(text), LENGTH(text), VARGOPT(w, -1), VARGOPT(h, -1), VARGOPT(align, GB_DRAW_ALIGN_DEFAULT), TRUE);
	
END_METHOD


BEGIN_METHOD(Paint_DrawRichText, GB_STRING text; GB_FLOAT x; GB_FLOAT y; GB_FLOAT w; GB_FLOAT h; GB_INTEGER align)

	CHECK_DEVICE();
	CHECK_PATH();

	if (!MISSING(x) && !MISSING(y))
		PAINT->MoveTo(THIS, (float)VARG(x), (float)VARG(y));
	
	PAINT->RichText(THIS, STRING(text), LENGTH(text), VARGOPT(w, -1), VARGOPT(h, -1), VARGOPT(align, GB_DRAW_ALIGN_DEFAULT), TRUE);
	
END_METHOD


BEGIN_METHOD(Paint_TextExtents, GB_STRING text)

	PAINT_EXTENTS *extents;
	
	CHECK_DEVICE();

	extents = GB.New(GB.FindClass("PaintExtents"), NULL, NULL);
	
	if (!LENGTH(text))
	{
		PAINT->GetCurrentPoint(THIS, &extents->ext.x1, &extents->ext.y1);
		extents->ext.x2 = extents->ext.x1;
		extents->ext.y2 = extents->ext.y1;
	}
	else
		PAINT->TextExtents(THIS, STRING(text), LENGTH(text), &extents->ext);
	
	GB.ReturnObject(extents);

END_METHOD


BEGIN_METHOD(Paint_RichTextExtents, GB_STRING text; GB_FLOAT width)

	PAINT_EXTENTS *extents;
	
	CHECK_DEVICE();

	extents = GB.New(GB.FindClass("PaintExtents"), NULL, NULL);
	PAINT->RichTextExtents(THIS, STRING(text), LENGTH(text), &extents->ext, VARGOPT(width, -1));
	
	GB.ReturnObject(extents);

END_METHOD


BEGIN_METHOD(Paint_TextSize, GB_STRING text)

	float w, h;
	GEOM_RECTF *size;

	CHECK_DEVICE();
	PAINT->TextSize(THIS, STRING(text), LENGTH(text), &w, &h);
	size = GEOM.CreateRectF();
	size->w = w;
	size->h = h;
	GB.ReturnObject(size);

END_METHOD


BEGIN_PROPERTY(Paint_TextHeight)

	float h;

	CHECK_DEVICE();
	PAINT->TextSize(THIS, " ", 1, NULL, &h);
	GB.ReturnFloat(h);

END_PROPERTY


BEGIN_METHOD(Paint_RichTextSize, GB_STRING text; GB_FLOAT width)

	float w, h;
	GEOM_RECTF *size;
	
	w = VARGOPT(width, -1);
	
	CHECK_DEVICE();
	PAINT->RichTextSize(THIS, STRING(text), LENGTH(text), w, &w, &h);
	size = GEOM.CreateRectF();
	size->w = w;
	size->h = h;
	GB.ReturnObject(size);

END_METHOD


static PAINT_BRUSH *make_brush(GB_PAINT *d, GB_BRUSH brush)
{
	PAINT_BRUSH *that;
	that = GB.New(GB.FindClass("PaintBrush"), NULL, NULL);
	that->desc = d->desc;
	that->brush = brush;
	GB.ReturnObject(that);
	return that;
}


BEGIN_METHOD(Paint_Color, GB_INTEGER color)

	GB_BRUSH brush;

	CHECK_DEVICE();
	
	PAINT->Brush.Color(&brush, VARG(color));
	make_brush(THIS, brush);

END_METHOD


BEGIN_METHOD(Paint_Image, GB_OBJECT image; GB_FLOAT x; GB_FLOAT y)

	GB_BRUSH brush;

	CHECK_DEVICE();
	
	if (GB.CheckObject(VARG(image)))
		return;
	
	PAINT->Brush.Image(&brush, (GB_IMAGE)VARG(image));
	make_brush(THIS, brush);
	
	if (!MISSING(x) || !MISSING(y))
	{
		GB_TRANSFORM transform;
		MPAINT->Create(&transform);
		MPAINT->Translate(transform, VARGOPT(x, 0.0), VARGOPT(y, 0.0));
		PAINT->Brush.Matrix(brush, TRUE, transform);
		MPAINT->Delete(&transform);
	}

END_METHOD


BEGIN_METHOD(Paint_LinearGradient, GB_FLOAT x0; GB_FLOAT y0; GB_FLOAT x1; GB_FLOAT y1; GB_OBJECT colors; GB_OBJECT positions; GB_INTEGER extend)

	GB_BRUSH brush;
	GB_ARRAY positions, colors;
	int nstop;
	
	CHECK_DEVICE();

	positions = (GB_ARRAY)VARG(positions);
	if (GB.CheckObject(positions))
		return;
	colors = (GB_ARRAY)VARG(colors);
	if (GB.CheckObject(colors))
		return;

	nstop = Min(GB.Array.Count(positions), GB.Array.Count(colors));
	
	PAINT->Brush.LinearGradient(&brush, (float)VARG(x0), (float)VARG(y0), (float)VARG(x1), (float)VARG(y1),
		nstop, (double *)GB.Array.Get(positions, 0), (GB_COLOR *)GB.Array.Get(colors, 0), VARGOPT(extend, GB_PAINT_EXTEND_PAD));

	make_brush(THIS, brush);

END_METHOD


BEGIN_METHOD(Paint_RadialGradient, GB_FLOAT cx; GB_FLOAT cy; GB_FLOAT radius; GB_FLOAT fx; GB_FLOAT fy; GB_OBJECT colors; GB_OBJECT positions; GB_INTEGER extend)

	GB_BRUSH brush;
	GB_ARRAY positions, colors;
	int nstop;
	
	CHECK_DEVICE();

	positions = (GB_ARRAY)VARG(positions);
	if (GB.CheckObject(positions))
		return;
	colors = (GB_ARRAY)VARG(colors);
	if (GB.CheckObject(colors))
		return;
	
	nstop = Min(GB.Array.Count(positions), GB.Array.Count(colors));
	
	PAINT->Brush.RadialGradient(&brush, (float)VARG(cx), (float)VARG(cy), (float)VARG(radius), (float)VARG(fx), (float)VARG(fy),
		nstop, (double *)GB.Array.Get(positions, 0), (GB_COLOR *)GB.Array.Get(colors, 0), VARGOPT(extend, GB_PAINT_EXTEND_PAD));
	
	make_brush(THIS, brush);

END_METHOD


BEGIN_PROPERTY(Paint_Matrix)

	GB_TRANSFORM transform;
	PAINT_MATRIX *matrix;
	
	CHECK_DEVICE();
	
	if (READ_PROPERTY)
	{
		MPAINT->Create(&transform);
		PAINT->Matrix(THIS, FALSE, transform);
		GB.ReturnObject(create_matrix(transform));
	}
	else
	{
		matrix = (PAINT_MATRIX *)VPROP(GB_OBJECT);
		if (!matrix)
			PAINT->Matrix(THIS, TRUE, NULL);
		else
			PAINT->Matrix(THIS, TRUE, matrix->transform);
	}

END_PROPERTY


BEGIN_METHOD_VOID(Paint_Reset)

	CHECK_DEVICE();
	PAINT->Matrix(THIS, TRUE, NULL);
	PAINT_translate(THIS->area.x, THIS->area.y);

END_METHOD


BEGIN_METHOD(Paint_Translate, GB_FLOAT tx; GB_FLOAT ty)

	CHECK_DEVICE();
	PAINT_translate(VARG(tx), VARG(ty));

END_METHOD


BEGIN_METHOD(Paint_Scale, GB_FLOAT sx; GB_FLOAT sy)

	CHECK_DEVICE();
	double sx = VARG(sx);
	double sy = VARGOPT(sy, sx);

	PAINT_scale(sx, sy);

END_METHOD


BEGIN_METHOD(Paint_Rotate, GB_FLOAT angle)

	GB_TRANSFORM transform;

	CHECK_DEVICE();
	MPAINT->Create(&transform);
	PAINT->Matrix(THIS, FALSE, transform);
	MPAINT->Rotate(transform, (float)VARG(angle));
	PAINT->Matrix(THIS, TRUE, transform);
	MPAINT->Delete(&transform);

END_METHOD


BEGIN_METHOD(Paint_DrawImage, GB_OBJECT image; GB_FLOAT x; GB_FLOAT y; GB_FLOAT width; GB_FLOAT height; GB_FLOAT opacity; GB_OBJECT source)

	GB_IMG *image;
	float x, y, w, h;
	float opacity = VARGOPT(opacity, 1.0);
	GEOM_RECT *source = (GEOM_RECT *)VARGOPT(source, NULL);

	CHECK_DEVICE();
	CHECK_PATH();

	image = (GB_IMG *)VARG(image);
	
	if (GB.CheckObject(image))
		return;
	
	x = VARG(x);
	y = VARG(y);
	w = VARGOPT(width, -1);
	h = VARGOPT(height, -1);
	
	if (GB.CheckObject(VARG(image)))
		return;
	
	if (w < 0) w = image->width;
	if (h < 0) h = image->height;
	
	if (w <= 0.0 || h <= 0.0)
		return;
	
	if (image->width <= 0 || image->height <= 0)
		return;
	
	PAINT->DrawImage(THIS, VARG(image), x, y, w, h, opacity, source ? (GB_RECT *)&source->x : NULL);

END_METHOD


BEGIN_METHOD(Paint_DrawPicture, GB_OBJECT picture; GB_FLOAT x; GB_FLOAT y; GB_FLOAT w; GB_FLOAT h; GB_OBJECT source)

	GB_PICTURE picture = VARG(picture);
	GB_PICTURE_INFO info;
	GEOM_RECT *source = (GEOM_RECT *)VARGOPT(source, NULL);
	float x, y, w, h;

	CHECK_DEVICE();
	CHECK_PATH();

	if (GB.CheckObject(picture))
		return;

	PAINT->GetPictureInfo(THIS, picture, &info);

	x = VARG(x);
	y = VARG(y);
	w = VARGOPT(w, -1);
	h = VARGOPT(h, -1);

	if (w < 0) w = info.width;
	if (h < 0) h = info.height;
	
	if (info.width <= 0 || info.height <= 0)
		return;
	
	PAINT->DrawPicture(THIS, picture, x, y, w, h, source ? (GB_RECT *)&source->x : NULL);

END_METHOD


BEGIN_METHOD(Paint_ZoomImage, GB_OBJECT image; GB_INTEGER zoom; GB_INTEGER x; GB_INTEGER y; GB_INTEGER grid; GB_OBJECT source)

	GB_IMAGE image = VARG(image);
	GB_IMG *info = (GB_IMG *)image;
	GEOM_RECT *source = (GEOM_RECT *)VARGOPT(source, NULL);
	int zoom;
	int x, y, sx, sy, sw, sh;
	int i, j, xr, yr;
	bool border;
	GB_COLOR borderColor;
	int antialias = FALSE;
	GB_RECT rect;
	float opacity = 1.0; //VARGOPT(opacity, 1.0);

	CHECK_DEVICE();
	CHECK_PATH();

	if (GB.CheckObject(image))
		return;

	zoom = VARG(zoom);
	if (zoom < 1)
	{
		GB.Error("Bad zoom factor");
		return;
	}
	
	x = VARGOPT(x, 0);
	y = VARGOPT(y, 0);

	if (source)
	{
		sx = source->x;
		sy = source->y;
		sw = source->w;
		sh = source->h;
	}
	else
	{
		sx = sy = 0;
		sw = info->width;
		sh = info->height;
	}

	DRAW_NORMALIZE(x, y, sw, sh, sx, sy, sw, sh, info->width, info->height);

	//DRAW->Fill.GetOrigin(THIS, &ox, &oy);
	
	PAINT->Save(THIS);
	PAINT->Antialias(THIS, TRUE, &antialias);
	
	borderColor = VARGOPT(grid, GB_COLOR_DEFAULT);
	border = borderColor != GB_COLOR_DEFAULT;

	rect.x = sx;
	rect.y = sy;
	rect.w = sw;
	rect.h = sh;
	
	PAINT->DrawImage(THIS, image, x, y, sw * zoom, sh * zoom, opacity, &rect);
	
	if (border && zoom >= 3)
	{
		float dashes[2] = { 1.0, 1.0 };
		int count;
		float *pdashes = dashes;
	
		for (i = sx + 1, xr = x + zoom; i < (sx + sw); i++, xr += zoom)
		{
			//PAINT->FillRect(THIS, xr, y, 1, sh * zoom, borderColor);
			PAINT->MoveTo(THIS, xr, y);
			PAINT->LineTo(THIS, xr, y + sh * zoom);
		}
		
		for (j = sy + 1, yr = y + zoom; j < (sy + sh); j++, yr += zoom)
		{
			//PAINT->FillRect(THIS, x, yr, sw * zoom, 1, borderColor);
			PAINT->MoveTo(THIS, x, yr);
			PAINT->LineTo(THIS, x + sw * zoom, yr);
		}
		
		count = 0;
		PAINT->Dash(THIS, TRUE, NULL, &count);
		PAINT->Background(THIS, TRUE, &borderColor);
		PAINT->Stroke(THIS, TRUE);
		
		borderColor ^= 0xFFFFFF;
		count = 2;
		PAINT->Dash(THIS, TRUE, &pdashes, &count);
		PAINT->Background(THIS, TRUE, &borderColor);
		PAINT->Stroke(THIS, FALSE);
		
		THIS->has_path = FALSE;
	}
	
	PAINT->Restore(THIS);

END_METHOD


#if 0
BEGIN_PROPERTY(Paint_LineStyle)

	int v;
	int count;
	float dashes[6];

	CHECK_DEVICE();

	if (READ_PROPERTY)
	{
		GB.ReturnInteger(THIS->lineStyle);
		return;
	}
	
	v = VPROP(GB_INTEGER);
	
	switch (v)
	{
		case GB_PAINT_LINE_STYLE_NONE:
			break;
			
		case GB_PAINT_LINE_STYLE_SOLID:
			PAINT->Dash(THIS, TRUE, NULL, &count);
			break;
			
		case GB_PAINT_LINE_STYLE_DASH:
			dashes[0] = 3; dashes[1] = 3; count = 2;
			PAINT->Dash(THIS, TRUE, dashes, &count);
			break;
			
		case GB_PAINT_LINE_STYLE_DOT:
			dashes[0] = 1; dashes[1] = 3; count = 2;
			PAINT->Dash(THIS, TRUE, dashes, &count);
			break;
			
		case GB_PAINT_LINE_STYLE_DASH_DOT:
			dashes[0] = 3; dashes[1] = 3; dashes[2] = 3; dashes[3] = 1; dacount = 4;
			PAINT->Dash(THIS, TRUE, dashes, &count);
			break;
			
		case GB_PAINT_LINE_STYLE_DASH_DOT_DOT:
			dashes[0] = 3; dashes[1] = 3; dashes[2] = 3; dashes[3] = 1; dashes[4] = 3; dashes[5] = 1; dacount = 6;
			PAINT->Dash(THIS, TRUE, dashes, &count);
			break;
			
		default:
			return;
	}

	THIS->lineStyle = v;

END_PROPERTY
#endif

BEGIN_METHOD(Paint_FillRect, GB_FLOAT x; GB_FLOAT y; GB_FLOAT w; GB_FLOAT h; GB_INTEGER color)

	CHECK_DEVICE();
	CHECK_PATH();
	PAINT->FillRect(THIS, VARG(x), VARG(y), VARG(w), VARG(h), VARG(color));
	
END_METHOD


BEGIN_PROPERTY(Paint_Tag)

	CHECK_DEVICE();
	
	if (READ_PROPERTY)
		GB.ReturnObject(THIS->tag);
	else
		GB.StoreObject(PROP(GB_OBJECT), POINTER(&(THIS->tag)));

END_PROPERTY


IMPLEMENT_PROPERTY_FLOAT(Paint_Opacity, Opacity)

#if 0
Static Public Sub DrawEllipsizedText(Text As String, X As Integer, Y As Integer, W As Integer, H As Integer, Optional Alignment As Integer = Align.TopLeft)

  Dim aElt As String[] = Split(Text, " \n")
  Dim sElt As String
  Dim I As Integer
  Dim HL As Integer = Paint.Font.Height
  Dim YT, WT As Integer
  Dim sText As String

  YT = 0

  For I = 0 To aElt.Max
    sElt = aElt[I]

    If (YT + HL * 2) > H Then
      WT = Paint.Font.TextWidth(LTrim(sText & " " & sElt & "…"))
      If WT > W Then
        Paint.DrawText(sText & "…", X, Y + YT, W, H, Align.TopLeft)
        Return
      Endif
    Else
      WT = Paint.Font.TextWidth(LTrim(sText & " " & sElt))
      If WT > W Then
        Paint.DrawText(sText, X, Y + YT, W, H, Align.TopLeft)
        sText = ""
        YT += HL
      Endif
    Endif

    sText = RTrim(sText & " " & sElt)

  Next

  Paint.DrawText(sText, X, Y + YT, W, H, Align.TopLeft)

End
#endif

//-- Style API --------------------------------------------------------------

#define GET_COORD() \
	int x, y, w, h; \
\
	CHECK_DEVICE(); \
\
	x = VARG(x); \
	y = VARG(y); \
	w = VARG(w); \
	h = VARG(h); \
\
	if (w < 1 || h < 1) \
		return;


GB_DESC PaintDesc[] =
{
	GB_DECLARE("Paint", 0), GB_VIRTUAL_CLASS(),

	GB_STATIC_METHOD("_exit", NULL, Paint_exit, NULL),

	GB_CONSTANT("ExtendPad", "i",        GB_PAINT_EXTEND_PAD),
	GB_CONSTANT("ExtendRepeat", "i",     GB_PAINT_EXTEND_REPEAT),
	GB_CONSTANT("ExtendReflect", "i",    GB_PAINT_EXTEND_REFLECT),

	GB_CONSTANT("FillRuleWinding", "i",  GB_PAINT_FILL_RULE_WINDING),
	GB_CONSTANT("FillRuleEvenOdd", "i",  GB_PAINT_FILL_RULE_EVEN_ODD),

	GB_CONSTANT("LineCapButt", "i",      GB_PAINT_LINE_CAP_BUTT),
	GB_CONSTANT("LineCapRound", "i",     GB_PAINT_LINE_CAP_ROUND),
	GB_CONSTANT("LineCapSquare", "i",    GB_PAINT_LINE_CAP_SQUARE),
	
	GB_CONSTANT("LineJoinMiter", "i",    GB_PAINT_LINE_JOIN_MITER),
	GB_CONSTANT("LineJoinRound", "i",    GB_PAINT_LINE_JOIN_ROUND),
	GB_CONSTANT("LineJoinBevel", "i",    GB_PAINT_LINE_JOIN_BEVEL),
	
	GB_CONSTANT("OperatorClear", "i",    GB_PAINT_OPERATOR_CLEAR),
  GB_CONSTANT("OperatorSource", "i",   GB_PAINT_OPERATOR_SOURCE),
	GB_CONSTANT("OperatorOver", "i",     GB_PAINT_OPERATOR_OVER),
	GB_CONSTANT("OperatorIn", "i",       GB_PAINT_OPERATOR_IN),
	GB_CONSTANT("OperatorOut", "i",      GB_PAINT_OPERATOR_OUT),
	GB_CONSTANT("OperatorATop", "i",     GB_PAINT_OPERATOR_ATOP),
	GB_CONSTANT("OperatorDest", "i",     GB_PAINT_OPERATOR_DEST),
	GB_CONSTANT("OperatorDestOver", "i", GB_PAINT_OPERATOR_DEST_OVER),
	GB_CONSTANT("OperatorDestIn", "i",   GB_PAINT_OPERATOR_DEST_IN),
	GB_CONSTANT("OperatorDestOut", "i",  GB_PAINT_OPERATOR_DEST_OUT),
	GB_CONSTANT("OperatorDestATop", "i", GB_PAINT_OPERATOR_DEST_ATOP),
	GB_CONSTANT("OperatorXor", "i",      GB_PAINT_OPERATOR_XOR),
	GB_CONSTANT("OperatorAdd", "i",      GB_PAINT_OPERATOR_ADD),
	GB_CONSTANT("OperatorSaturate", "i", GB_PAINT_OPERATOR_SATURATE),
	
	GB_CONSTANT("OperatorMultiply", "i", GB_PAINT_OPERATOR_MULTIPLY),
	GB_CONSTANT("OperatorScreen", "i", GB_PAINT_OPERATOR_SCREEN),
	GB_CONSTANT("OperatorOverlay", "i", GB_PAINT_OPERATOR_OVERLAY),
	GB_CONSTANT("OperatorDarken", "i", GB_PAINT_OPERATOR_DARKEN),
	GB_CONSTANT("OperatorLighten", "i", GB_PAINT_OPERATOR_LIGHTEN),
	GB_CONSTANT("OperatorColorDodge", "i", GB_PAINT_OPERATOR_COLOR_DODGE),
	GB_CONSTANT("OperatorColorBurn", "i", GB_PAINT_OPERATOR_COLOR_BURN),
	GB_CONSTANT("OperatorHardLight", "i", GB_PAINT_OPERATOR_HARD_LIGHT),
	GB_CONSTANT("OperatorSoftLight", "i", GB_PAINT_OPERATOR_SOFT_LIGHT),
	GB_CONSTANT("OperatorDifference", "i", GB_PAINT_OPERATOR_DIFFERENCE),
	GB_CONSTANT("OperatorExclusion", "i", GB_PAINT_OPERATOR_EXCLUSION),

	GB_STATIC_METHOD("Begin", NULL, Paint_Begin, "(Device)o[(Area)RectF;]"),
	GB_STATIC_METHOD("End", NULL, Paint_End, NULL),
	
	GB_STATIC_PROPERTY_READ("Device", "o", Paint_Device),
	GB_STATIC_PROPERTY_READ("W", "f", Paint_Width),
	GB_STATIC_PROPERTY_READ("H", "f", Paint_Height),
	GB_STATIC_PROPERTY_READ("Width", "f", Paint_Width),
	GB_STATIC_PROPERTY_READ("Height", "f", Paint_Height),
	GB_STATIC_PROPERTY_READ("ResolutionX", "i", Paint_ResolutionX),
	GB_STATIC_PROPERTY_READ("ResolutionY", "i", Paint_ResolutionY),
	GB_STATIC_PROPERTY("AntiAlias", "b", Paint_Antialias),
	
	GB_STATIC_PROPERTY("_Invert", "b", Paint_Invert),
	GB_STATIC_PROPERTY("_FillStyle", "i", Paint_FillStyle),
	GB_STATIC_PROPERTY("Background", "i", Paint_Background),
	GB_STATIC_PROPERTY("_Tag", "o", Paint_Tag),

	GB_STATIC_METHOD("Save", NULL, Paint_Save, NULL),
	GB_STATIC_METHOD("Restore", NULL, Paint_Restore, NULL),
	
	GB_STATIC_METHOD("Clip", NULL, Paint_Clip, "[(Preserve)b]"),
	GB_STATIC_METHOD("ResetClip", NULL, Paint_ResetClip, NULL),
	GB_STATIC_PROPERTY_READ("ClipExtents", "PaintExtents", Paint_ClipExtents),
	GB_STATIC_PROPERTY("ClipRect", "Rect", Paint_ClipRect),
	
	GB_STATIC_METHOD("Fill", NULL, Paint_Fill, "[(Preserve)b]"),
	//GB_STATIC_PROPERTY_READ("FillExtents", "PaintExtents", Paint_FillExtents),
	//GB_STATIC_METHOD("InFill", "b", Paint_InFill, "(X)f(Y)f"),
	
	//GB_STATIC_METHOD("Mask", NULL, CAIRO_mask, "(Pattern)CairoPattern;"),
	
	//GB_STATIC_METHOD("Paint", NULL, CAIRO_paint, "[(Alpha)f]"),

	GB_STATIC_METHOD("Stroke", NULL, Paint_Stroke, "[(Preserve)b]"),
	//GB_STATIC_PROPERTY_READ("StrokeExtents", "PaintExtents", Paint_StrokeExtents),
	//GB_STATIC_METHOD("InStroke", "b", Paint_InStroke, "(X)f(Y)f"),

	GB_STATIC_PROPERTY_READ("PathExtents", "PaintExtents", Paint_PathExtents),
	GB_STATIC_METHOD("PathContains", "b", Paint_PathContains, "(X)f(Y)f"),
	GB_STATIC_PROPERTY_READ("PathOutline", "PointF[][]", Paint_PathOutline),

	GB_STATIC_PROPERTY("Brush", "PaintBrush", Paint_Brush),
	GB_STATIC_PROPERTY("BrushOrigin", "PointF", Paint_BrushOrigin),
	GB_STATIC_PROPERTY("Dash", "Float[]", Paint_Dash),
	GB_STATIC_PROPERTY("LineDash", "Float[]", Paint_Dash),
	GB_STATIC_PROPERTY("DashOffset", "f", Paint_DashOffset),
	GB_STATIC_PROPERTY("LineDashOffset", "f", Paint_DashOffset),
	GB_STATIC_PROPERTY("FillRule", "i", Paint_FillRule),
	GB_STATIC_PROPERTY("LineCap", "i", Paint_LineCap),
	GB_STATIC_PROPERTY("LineJoin", "i", Paint_LineJoin),
	GB_STATIC_PROPERTY("LineWidth", "f", Paint_LineWidth),
	//GB_STATIC_PROPERTY("LineStyle", "i", Paint_LineStyle),
	GB_STATIC_PROPERTY("MiterLimit", "f", Paint_MiterLimit),
	GB_STATIC_PROPERTY("Operator", "i", Paint_Operator),
	//GB_STATIC_PROPERTY("Tolerance", "f", CAIRO_tolerance),
	
	GB_STATIC_METHOD("NewPath", NULL, Paint_NewPath, NULL),
	GB_STATIC_METHOD("ClosePath", NULL, Paint_ClosePath, NULL),
	
	GB_STATIC_PROPERTY_READ("X", "f", Paint_X),
	GB_STATIC_PROPERTY_READ("Y", "f", Paint_Y),

	GB_STATIC_METHOD("Rectangle", NULL, Paint_Rectangle, "(X)f(Y)f(Width)f(Height)f[(Radius)f]"),
	GB_STATIC_METHOD("FillRect", NULL, Paint_FillRect, "(X)f(Y)f(Width)f(Height)f(Color)i"),
	GB_STATIC_METHOD("Arc", NULL, Paint_Arc, "(XC)f(YC)f(Radius)f[(Angle)f(Length)f(Pie)b]"),
	GB_STATIC_METHOD("Ellipse", NULL, Paint_Ellipse, "(X)f(Y)f(Width)f(Height)f[(Angle)f(Length)f(Pie)b]"),
	GB_STATIC_METHOD("Polygon", NULL, Paint_Polygon, "(Points)Float[];"),

	GB_STATIC_METHOD("CurveTo", NULL, Paint_CurveTo, "(X1)f(Y1)f(X2)f(Y2)f(X3)f(Y3)f"),
	GB_STATIC_METHOD("RelCurveTo", NULL, Paint_RelCurveTo, "(X1)f(Y1)f(X2)f(Y2)f(X3)f(Y3)f"),
	GB_STATIC_METHOD("LineTo", NULL, Paint_LineTo, "(X)f(Y)f"),
	GB_STATIC_METHOD("RelLineTo", NULL, Paint_RelLineTo, "(X)f(Y)f"),
	GB_STATIC_METHOD("MoveTo", NULL, Paint_MoveTo, "(X)f(Y)f"),
	GB_STATIC_METHOD("RelMoveTo", NULL, Paint_RelMoveTo, "(X)f(Y)f"),
	GB_STATIC_PROPERTY("Font", "Font", Paint_Font),
	GB_STATIC_PROPERTY("FontScale", "f", Paint_FontScale),
	GB_STATIC_METHOD("Text", NULL, Paint_Text, "(Text)s[(X)f(Y)f(Width)f(Height)f(Alignment)i]"),
	GB_STATIC_METHOD("TextSize", "RectF", Paint_TextSize, "(Text)s"),
	GB_STATIC_PROPERTY_READ("TextHeight", "f", Paint_TextHeight),
	GB_STATIC_METHOD("RichText", NULL, Paint_RichText, "(Text)s[(X)f(Y)f(Width)f(Height)f(Alignment)i]"),
	GB_STATIC_METHOD("RichTextSize", "RectF", Paint_RichTextSize, "(Text)s[(Width)f]"),
	GB_STATIC_METHOD("DrawText", NULL, Paint_DrawText, "(Text)s[(X)f(Y)f(Width)f(Height)f(Alignment)i]"),
	GB_STATIC_METHOD("DrawRichText", NULL, Paint_DrawRichText, "(Text)s[(X)f(Y)f(Width)f(Height)f(Alignment)i]"),
	GB_STATIC_METHOD("TextExtents", "PaintExtents", Paint_TextExtents, "(Text)s"),
	GB_STATIC_METHOD("RichTextExtents", "PaintExtents", Paint_RichTextExtents, "(Text)s[(Width)f]"),
	
	GB_STATIC_METHOD("Color", "PaintBrush", Paint_Color, "(Color)i"),
	GB_STATIC_METHOD("Image", "PaintBrush", Paint_Image, "(Image)Image;[(X)f(Y)f]"),
	GB_STATIC_METHOD("LinearGradient", "PaintBrush", Paint_LinearGradient, "(X0)f(Y0)f(X1)f(Y1)f(Colors)Integer[];(Positions)Float[];[(Extend)i]"),
	GB_STATIC_METHOD("RadialGradient", "PaintBrush", Paint_RadialGradient, "(CX)f(CY)f(Radius)f(FX)f(FY)f(Colors)Integer[];(Positions)Float[];[(Extend)i]"),

	GB_STATIC_PROPERTY("Matrix", "PaintMatrix", Paint_Matrix),

	GB_STATIC_METHOD("Reset", NULL, Paint_Reset, NULL),
	GB_STATIC_METHOD("Translate", NULL, Paint_Translate, "(TX)f(TY)f"),
	GB_STATIC_METHOD("Scale", NULL, Paint_Scale, "(SX)f[(SY)f]"),
	GB_STATIC_METHOD("Rotate", NULL, Paint_Rotate, "(Angle)f"),
	
	//GB_STATIC_METHOD("Clear", NULL, Paint_Clear, NULL),
	
	GB_STATIC_METHOD("DrawImage", NULL, Paint_DrawImage, "(Image)Image;(X)f(Y)f[(Width)f(Height)f(Opacity)f(Source)Rect;]"),
	GB_STATIC_METHOD("DrawPicture", NULL, Paint_DrawPicture, "(Picture)Picture;(X)f(Y)f[(Width)f(Height)f(Source)Rect;]"),
	GB_STATIC_METHOD("ZoomImage", NULL, Paint_ZoomImage, "(Image)Image;(Zoom)i(X)i(Y)i[(Grid)i(Source)Rect;]"),
	
	GB_STATIC_PROPERTY("Opacity", "f", Paint_Opacity),

	GB_END_DECLARE
};


