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


		#include <cstring>
		#include "picture/pict.h"
		#include "color/color.h"
		#include "log/log.h"

		using namespace GS;


//------------------------------------------------------------------------------
bool				Picture::Reframe(int offset_sx, int offset_sy, int offset_ex, int offset_ey, const Color *fill)
{
	if	(!GetData() || (GetPixelFormat().GetBpp() != 32))
		return false;

	int _width = GetWidth() - offset_sx + offset_ex,
		_height = GetHeight() - offset_sy + offset_ey;

	uint *new_data = (uint *)AllocMemory(sizeof(uint) * _width * _height), *_d = new_data;

	if	(!new_data)
		__ERR__(__LOG_E__ << "Failed to allocate destination buffer.\n", false)

	// Fill color.
	uint _fill = fill ? GetPixelFormat().Format(fill->x, fill->y, fill->z, fill->w) : GetPixelFormat().Format(0, 0, 0);

	// Top framing.
	for (int y = offset_sy; y < 0; ++y)
		for (int x = 0; x < _width; ++x)
			*_d++ = _fill;

	// Blit + left/right framing.
	int blit_ex = offset_ex > 0 ? GetWidth() : GetWidth() + offset_ex,
		blit_ey = offset_ey > 0 ? GetHeight() : GetHeight() + offset_ey;

	uint *s = (uint *)GetData();
	if	(offset_sy > 0)
		s += GetWidth() * offset_sy;

	for (int y = offset_sy > 0 ? offset_sy : 0; y < blit_ey; ++y)
	{
		uint *_s = s;

		for (int x = offset_sx; x < 0; ++x)
			*_d++ = _fill;		// Left framing
		for (int x = offset_sx > 0 ? offset_sx : 0; x < blit_ex; ++x)
			*_d++ = _s[x];		// Blit
		for (int x = 0; x < offset_ex; ++x)
			*_d++ = _fill;		// Right framing

		s += GetWidth();
	}

	// Bottom framing.
	for (int y = 0; y < offset_ey; ++y)
		for (int x = 0; x < _width; ++x)
			*_d++ = _fill;

	SetData(new_data, _width, _height, GetPixelFormat().GetDesc(), true);
	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Picture::Flip(bool flip_h, bool flip_v)
{
	if	(!GetData() || (GetPixelFormat().GetBpp() != 32))
		return false;

	uint	*s = (uint *)GetData(),
			*d = s, t;

	if	(flip_h)
	{
		if	(flip_v)
		{
			d += GetWidth() * GetHeight() - 1;

			while (d > s)
			{
				t = *s;
				*s++ = *d;
				*d-- = t;
			}
		}
		else
		{
			for (uint n = 0; n < GetHeight(); ++n)
			{
				uint *_s = s, *_d = d + GetWidth() - 1;

				while (_d > _s)
				{
					t = *_s;
					*_s++ = *_d;
					*_d-- = t;
				}
				s += GetWidth();
				d += GetWidth();
			}
		}
	}
	else
		if	(flip_v)
		{
			d += GetWidth() * (GetHeight() - 1);
			while (d > s)
			{
				for (uint n = 0; n < GetWidth(); ++n)
				{
					t = s[n];
					s[n] = d[n];
					d[n] = t;
				}
				s += GetWidth();
				d -= GetWidth();
			}
		}

	return true;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
static	void		WindowClip(const Rect <int> *a, Rect <int> *fa, const Rect <int> *b, Rect <int> *fb)
{
	Rect <int> _b(*b);
	_b.ex = _b.sx + a->GetWidth();
	_b.ey = _b.sy + a->GetHeight();

	// Clip source rectangle and correct destination rectangle.
	*fa = fa->Intersection(*a);
	_b.sx += fa->sx - a->sx;
	_b.sy += fa->sy - a->sy;
	_b.ex += fa->ex - a->ex;
	_b.ey += fa->ey - a->ey;

	// Clip destination rectangle and correct source rectangle.
	*fb = fb->Intersection(_b);
	fa->sx += fb->sx - _b.sx;
	fa->sy += fb->sy - _b.sy;
	fa->ex += fb->ex - _b.ex;
	fa->ey += fb->ey - _b.ey;
}
static	void		WindowStretch(Rect <float> *a, Rect <float> *fa, Rect <float> *b, Rect <float> *fb)
{
	float ku = (float)b->GetWidth() / (float)a->GetWidth(),
		  kv = (float)b->GetHeight() / (float)a->GetHeight();

	// Clip source rectangle and correct destination rectangle.
	fa[0] = fa->Intersection(a[0]);
	b->sx += (fa->sx - a->sx) * ku;
	b->sy += (fa->sy - a->sy) * kv;
	b->ex += (fa->ex - a->ex) * ku;
	b->ey += (fa->ey - a->ey) * kv;

	// Clip destination rectangle and correct source rectangle.
	fb[0] = fb->Intersection(b[0]);
	ku = 1 / ku;
	kv = 1 / kv;
	fa->sx += (fb->sx - b->sx) * ku;
	fa->sy += (fb->sy - b->sy) * kv;
	fa->ex += (fb->ex - b->ex) * ku;
	fa->ey += (fb->ey - b->ey) * kv;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Picture::BlitMask(const Picture &src, Picture &dst, Picture &msk, const Rect <int> *src_rect, const Rect <int> *dst_rect)
{
	if	(!src.GetData() || !dst.GetData())
		return false;

	// Default blit rectangles.
	Rect <int>	default_a(src.GetRect()), default_b(dst.GetRect());
	if	(!src_rect)
		src_rect = &default_a;
	if	(!dst_rect)
		dst_rect = &default_b;

	// Compute blit window.
	Rect <int>	frame_a(src.GetRect()), frame_b(dst.GetRect());
	WindowClip(src_rect, &frame_a, dst_rect, &frame_b);

	// Limit to mask dimensions.
	if	(frame_b.GetWidth() > (int)msk.GetWidth())
		frame_b.SetWidth(msk.GetWidth());
	if	(frame_b.GetHeight() > (int)msk.GetHeight())
		frame_b.SetHeight(msk.GetHeight());

	if	((frame_b.GetWidth() <= 0) || (frame_b.GetHeight() <= 0))
		return false;

	PixelFormatDescription initial = dst.GetPixelFormat().GetDesc();
	dst.Convert(src.GetPixelFormat().GetDesc());

	// Perform blit.
	uchar *psrc = src.GetDataOffset(frame_a.sx, frame_a.sy),
		  *pdst = dst.GetDataOffset(frame_b.sx, frame_b.sy),
		  *pmsk = msk.GetData();

	for	(int n = 0; n < frame_b.GetHeight(); n++)
	{
		uchar *_pdst = pdst, *_psrc = psrc, *_pmsk = pmsk;

		for (int x = frame_b.GetWidth(); x--; )
		{
			uchar alpha = _pmsk[3];

			uchar a_blend = AlphaCompositeAlpha(_pdst[3], alpha);
			_pdst[0] = AlphaCompositeColor(_pdst[0], _psrc[0], _pdst[3], alpha, a_blend);
			_pdst[1] = AlphaCompositeColor(_pdst[1], _psrc[1], _pdst[3], alpha, a_blend);
			_pdst[2] = AlphaCompositeColor(_pdst[2], _psrc[2], _pdst[3], alpha, a_blend);
			_pdst[3] = a_blend;

			_psrc += 4;
			_pdst += 4;
			_pmsk += 4;
		}

		psrc += src.GetPitch();
		pdst += dst.GetPitch();
		pmsk += msk.GetPitch();
	}
	dst.Convert(initial);
	return true;
}
bool				Picture::Blit(const Picture &src, Picture &dst, const Rect <int> *src_rect, const Rect <int> *dst_rect, BlendMode mode)
{
	if	(!src.GetData() || !dst.GetData())
		return false;

	// Default blit rectangles.
	Rect <int> default_a(src.GetRect()), default_b(dst.GetRect());
	if	(!src_rect)
		src_rect = &default_a;
	if	(!dst_rect)
		dst_rect = &default_b;

	// Compute blit window.
	Rect <int> frame_a(src.GetRect()), frame_b(dst.GetRect());
	WindowClip(src_rect, &frame_a, dst_rect, &frame_b);

	if	((frame_b.GetWidth() <= 0) || (frame_b.GetHeight() <= 0))
		return false;

	PixelFormatDescription	initial = dst.GetPixelFormat().GetDesc();
	dst.Convert(src.GetPixelFormat().GetDesc());

	// Perform blit.
	uchar *psrc = src.GetDataOffset(frame_a.sx, frame_a.sy),
		  *pdst = dst.GetDataOffset(frame_b.sx, frame_b.sy);

	for	(int n = 0; n < frame_b.GetHeight(); n++)
	{
		uchar *_pdst = pdst, *_psrc = psrc;

		switch (mode)
		{
			case RgbToAlpha:
				for (int x = frame_b.GetWidth(); x--; )
				{
					_pdst[3] = (_psrc[0] + _psrc[1] + _psrc[2]) / 3;
					_psrc += 4;
					_pdst += 4;
				}
				break;

			case BlendReplace:
				memmove(pdst, psrc, frame_b.GetWidth() * dst.GetBpp() / 8);
				break;

			case BlendComposeFast:
				for (int x = frame_b.GetWidth(); x--; )
				{
					int	a = _psrc[3], ia = 255 - a;
					_pdst[0] = uchar((_psrc[0] * a + _pdst[0] * ia) >> 8);
					_pdst[1] = uchar((_psrc[1] * a + _pdst[1] * ia) >> 8);
					_pdst[2] = uchar((_psrc[2] * a + _pdst[2] * ia) >> 8);
					_pdst[3] = uchar(Types::Max <int> (_pdst[3], a));

					_psrc += 4;
					_pdst += 4;
				}
				break;

			case BlendCompose:
				for (int x = frame_b.GetWidth(); x--; )
				{
					uchar a_blend = AlphaCompositeAlpha(_pdst[3], _psrc[3]);
					_pdst[0] = AlphaCompositeColor(_pdst[0], _psrc[0], _pdst[3], _psrc[3], a_blend);
					_pdst[1] = AlphaCompositeColor(_pdst[1], _psrc[1], _pdst[3], _psrc[3], a_blend);
					_pdst[2] = AlphaCompositeColor(_pdst[2], _psrc[2], _pdst[3], _psrc[3], a_blend);
					_pdst[3] = a_blend;

					_psrc += 4;
					_pdst += 4;
				}
				break;

			default: break;
		}
		psrc += src.GetPitch();
		pdst += dst.GetPitch();
	}
	dst.Convert(initial);
	return true;
}
bool				Picture::ScaleBlit(Picture &src, Picture &dst, Rect <float> *src_rect, Rect <float> *dst_rect)
{
	if	(!src.GetData() || !dst.GetData())
		return false;

	// Default blitting rectangles.
	Rect <float>	default_a(src.GetRect().AsFloat()), default_b(dst.GetRect().AsFloat());
	if	(!src_rect)
		src_rect = &default_a;
	if	(!dst_rect)
		dst_rect = &default_b;

	Rect <float> frame_a(src.GetRect().AsFloat()), frame_b(dst.GetRect().AsFloat());
	WindowStretch(src_rect, &frame_a, dst_rect, &frame_b);

	if	((frame_b.GetWidth() <= 0) || (frame_b.GetHeight() <= 0))
		return false;

	// Apply sub-pixel/sub-texel correction.
	float	ku = frame_a.GetWidth() / frame_b.GetWidth(),
			kv = frame_a.GetHeight() / frame_b.GetHeight();

	float	fb_isx = Math::Floor(frame_b.sx),
			fb_isy = Math::Floor(frame_b.sy),
			fb_iex = Math::Floor(frame_b.ex),
			fb_iey = Math::Floor(frame_b.ey);

	float	dt_su = (frame_b.sx - fb_isx),
			dt_sv = (frame_b.sy - fb_isy),
			dt_eu = (frame_b.ex - fb_iex),
			dt_ev = (frame_b.ey - fb_iey);

	frame_a.sx -= dt_su * ku;
	frame_a.sy -= dt_sv * kv;
	frame_a.ex -= dt_eu * ku;
	frame_a.ey -= dt_ev * kv;

	frame_b.sx = fb_isx;
	frame_b.sy = fb_isy;
	frame_b.ex = fb_iex;
	frame_b.ey = fb_iey;

	// Convert sub pixel deltas to blending coefficients.
	dt_su = 1 - dt_su;
	dt_sv = 1 - dt_sv;

	uint *pdst = (uint *)dst.GetDataOffset((uint)frame_b.sx, (uint)frame_b.sy);

	int	src_width = (int)frame_b.GetWidth(),
		src_height = (int)frame_b.GetHeight();

	float v = frame_a.sy;
	for	(int y = 0; y < src_height; y++)
	{
		float u = frame_a.sx;

		// Blended or opaque scanline.
		if	((!y) || (y == (src_height - 1)))
		{
			float kfrst, kscan, klast;

			// Set blend coefficients.
			if	(!y)
			{
				kfrst = dt_su * dt_sv;			// top-left
				kscan = dt_sv;					// top
				klast = dt_eu * dt_sv;			// top-right
			}
			else
			{
				kfrst = dt_su * dt_ev;			// bottom-left
				kscan = dt_ev;					// bottom
				klast = dt_eu * dt_ev;			// bottom-right
			}

			// 1st pixel, scanline, last pixel.
			pdst[0] = ColorBlend(pdst[0], src.SampleInteger(u, v), kfrst);
			u += ku;

            int x;
			for	(x = 1; x < (src_width - 1); x++)
			{
				pdst[x] = ColorBlend(pdst[x], src.SampleInteger(u, v), kscan);
				u += ku;
			}
			pdst[x] = ColorBlend(pdst[x], src.SampleInteger(u, v), klast);
		}
		else
		{
			pdst[0] = ColorBlend(pdst[0], src.SampleInteger(u, v), dt_su);
			u += ku;

            int x;
			for	(x = 1; x < (src_width - 1); x++)
			{
				pdst[x] = src.SampleInteger(u, v);
				u += ku;
			}
			pdst[x] = ColorBlend(pdst[x], src.SampleInteger(u, v), dt_eu);
		}

		pdst += dst.GetWidth();
		v += kv;
	}
	return true;
}
//------------------------------------------------------------------------------
