/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include "picture/pict_gradient.h"
		#include "picture/pict.h"
		#include "metafile/nml.h"
		#include "log/log.h"

		using namespace GS;


//------------------------------------------------------------------------------
static uchar		ClampChannel(int v)
{
	if	(v < 0)
		return 0;
	if	(v > 255)
		return 255;
	return (uchar)v;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Picture::DrawGradient(const Gradient &gradient, const Rect <int> *clip_rect)
{
	if	(!gradient.GetControlPointCount())
		return;

	Rect <int>	rect = GetRect();
	if	(!clip_rect)
		clip_rect = &rect;

	uint current_control_point = 0,
		 next_control_point = 1;
	if	(next_control_point == gradient.GetControlPointCount())
		next_control_point = current_control_point;
	uchar *pdata = GetDataOffset(clip_rect->sx, clip_rect->sy);

	for (int y = 0; y < clip_rect->GetHeight(); ++y)
	{
		float c_k = (float)y / clip_rect->GetHeight();

		// Check gradient interval change.
		if	((c_k > gradient.k[next_control_point]) && (current_control_point + 1 < gradient.GetControlPointCount()))
		{
			current_control_point++;
			next_control_point++;
			if	(next_control_point == gradient.GetControlPointCount())
				next_control_point = current_control_point;
		}

		// Blend.
		Color blend_color = gradient.color[current_control_point];

		if	(current_control_point != next_control_point)
			blend_color =	(gradient.color[next_control_point] - gradient.color[current_control_point]) *
							(c_k - gradient.k[current_control_point]) / (gradient.k[next_control_point] - gradient.k[current_control_point]) +
							gradient.color[current_control_point];

		uchar *pscan = pdata,
			  r = (uchar)(blend_color.x * 255),
			  g = (uchar)(blend_color.y * 255),
			  b = (uchar)(blend_color.z * 255),
			  a = (uchar)(blend_color.w * 255);

		switch (gradient.GetOperator())
		{
			case Picture::BlendReplace:
				for	(int x = 0; x < rect.GetWidth(); ++x)
				{
					pscan[0] = r;
					pscan[1] = g;
					pscan[2] = b;
					pscan[3] = a;
					pscan += 4;
				}
				break;

			case Picture::BlendCompose:
				for	(int x = 0; x < rect.GetWidth(); ++x)
				{
					uchar a_blend = AlphaCompositeAlpha(pscan[3], a);
					pscan[0] = AlphaCompositeColor(pscan[0], r, pscan[3], a, a_blend);
					pscan[1] = AlphaCompositeColor(pscan[1], g, pscan[3], a, a_blend);
					pscan[2] = AlphaCompositeColor(pscan[2], b, pscan[3], a, a_blend);
					pscan[3] = a_blend;
					pscan += 4;
				}
				break;

			case Picture::BlendMultiply:
				for	(int x = 0; x < rect.GetWidth(); ++x)
				{
					pscan[0] = (pscan[0] * r) >> 8;
					pscan[1] = (pscan[1] * g) >> 8;
					pscan[2] = (pscan[2] * b) >> 8;
					pscan[3] = (pscan[3] * a) >> 8;
					pscan += 4;
				}
				break;

			case Picture::BlendMultiply2x:
				for	(int x = 0; x < rect.GetWidth(); ++x)
				{
					pscan[0] = ClampChannel((pscan[0] * r) >> 7);
					pscan[1] = ClampChannel((pscan[1] * g) >> 7);
					pscan[2] = ClampChannel((pscan[2] * b) >> 7);
					pscan[3] = (pscan[3] * a) >> 8;
					pscan += 4;
				}
				break;

			case Picture::BlendAdd:
				for	(int x = 0; x < rect.GetWidth(); ++x)
				{
					pscan[0] = ClampChannel(pscan[0] + r);
					pscan[1] = ClampChannel(pscan[1] + g);
					pscan[2] = ClampChannel(pscan[2] + b);
					pscan[3] = (pscan[3] * a) >> 8;
					pscan += 4;
				}
				break;

			default: break;
		}
		pdata += GetPitch();
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Gradient::FromMetaTag(NML::Tag *tag)
{
	control_point_count = 0;
	op = Picture::BlendMultiply;

	NMLTagForeach(ctag, *tag)
	{
		if	(ctag->name == "Operator")
		{
			String op_string(ctag->GetString());

					if	(op_string == "Replace")
						op = Picture::BlendReplace;
			else	if	(op_string == "Multiply")
						op = Picture::BlendMultiply;
			else	if	(op_string == "Multiply2x")
						op = Picture::BlendMultiply2x;
			else	if	(op_string == "Add")
						op = Picture::BlendAdd;
			else	if	(op_string == "Compose")
						op = Picture::BlendCompose;
			else	__LOG_W__ << "Unknown gradient operator.\n";
		}
		else	if	(ctag->name == "Control")
		{
			// Control point count safety.
			if	(control_point_count == 8)
				__ERR__(__LOG_E__ << "Too many control points in gradient definition.\n", false);

			// Coordinate.
			NML::Tag *attr_tag = ctag->GetTypedTag("K", Variant::VariantFloat);
			if	(!attr_tag)
				__ERR__(__LOG_E__ << "Missing tag (control point coordinate) in gradient definition.\n", false);
			k[control_point_count] = attr_tag->GetReal();

			// Color.
			attr_tag = ctag->GetTypedTag("Color", Variant::VariantNone);
			if	(!attr_tag)
				__ERR__(__LOG_E__ << "Missing tag (control point color) in gradient definition.\n", false);
			color[control_point_count].Set();
			if	(!color[control_point_count].FromMetaTag(*attr_tag))
				__ERR__(__LOG_E__ << "Erroneous control point color in gradient definition.\n", false);

			control_point_count++;
		}
		else	__ERR__(__LOG_E__ << "Unexpected tag in gradient definition.\n", false);
	}
	return true;
}
//------------------------------------------------------------------------------
