/*
* astyle --force-indent=tab=4 --brackets=linux --indent-switches
*		  --pad=oper --one-line=keep-blocks  --unpad=paren
*
* Miranda IM: the free IM client for Microsoft* Windows*
*
* Copyright 2000-2010 Miranda ICQ/IM project,
* all portions of this codebase are copyrighted to the people
* listed in contributors.txt.
*
* 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
* of the License, 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*
* part of clist_ng plugin for Miranda.
*
* (C) 2005-2010 by silvercircle _at_ gmail _dot_ com and contributors
*
* $Id: gfx.cpp 134 2010-10-01 10:23:10Z silvercircle $
*
* clist_ng low level graphics code
*/

#include <commonheaders.h>
#include <math.h>

#ifdef _USE_D2D
	ID2D1Factory* 					Gfx::pD2DFactory = 0;
#endif
COLORREF						Gfx::txtColor = 0;
//Gdiplus::GdiplusStartupInput 	Gfx::gdiPlusStartupInput;
//ULONG_PTR						Gfx::gdiPlusToken;

/**
 * Create a 32bit RGBA bitmap, compatible for rendering with alpha channel.
 * Required by anything which would render on a transparent aero surface.
 * the image is a "bottom up" bitmap, as it has a negative
 * height. This is a requirement for some UxTheme APIs (e.g.
 * DrawThemeTextEx).
 *
 * @param rc     RECT &: the rectangle describing the target area.
 * @param dc     The device context for which the bitmap should be created.
 *
 * @return HBITMAP: handle to the bitmap created.
 */

BYTE*	Gfx::m_p = nullptr;
size_t	Gfx::m_sAllocated = 0;

HBITMAP Gfx::createRGBABitmap(const LONG cx, const LONG cy)
{
	BITMAPINFO dib = {0};

	dib.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    dib.bmiHeader.biWidth = cx;
    dib.bmiHeader.biHeight = -cy;			// use a "topdown" bitmap (0,0 = left, top corner)
    dib.bmiHeader.biPlanes = 1;
    dib.bmiHeader.biBitCount = 32;
    dib.bmiHeader.biCompression = BI_RGB;
    return(CreateDIBSection(0, &dib, DIB_RGB_COLORS, NULL, NULL, 0 ));
}

/**
 * initialize Direct2D, create the factory
 */
void Gfx::D2D_Init()
{
#ifdef _USE_D2D
	HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &pD2DFactory);
#endif
}

void Gfx::D2D_Release()
{
#ifdef _USE_D2D
	pD2DFactory->Release();
#endif
}

void Gfx::deSaturate(HBITMAP hBitmap, bool fReduceContrast)
{
	BITMAP 	bmp;
	DWORD 	dwLen;
	BYTE	bMin = 255, bMax = 0, bRamp = 0, bMaxAdjust, bMinAdjust;

	int 	x, y;

	GetObject(hBitmap, sizeof(bmp), &bmp);

	if (bmp.bmBitsPixel != 32)
		return;

	dwLen = bmp.bmWidth * bmp.bmHeight * (bmp.bmBitsPixel / 8);
	if (dwLen > m_sAllocated) {
		m_p = (BYTE *)realloc(m_p, dwLen);
		m_sAllocated = dwLen;
	}
	memset(m_p, 0, dwLen);

	GetBitmapBits(hBitmap, dwLen, m_p);

	if(fReduceContrast) {
		for (y = 0; y < bmp.bmHeight; ++y) {
			BYTE *px = m_p + bmp.bmWidth * 4 * y;

			for (x = 0; x < bmp.bmWidth; ++x) {
				BYTE avg = (px[0] + px[1] + px[2]) / 3;
				bMin = min(avg, bMin);
				bMax = max(avg, bMax);
				px += 4;
			}
		}
		bRamp = (bMax + bMin) / 2;
		bMaxAdjust = (bMax - bRamp) / 2;
		bMinAdjust = (bRamp - bMin) / 2;
	}
	for (y = 0; y < bmp.bmHeight; ++y) {
		BYTE *px = m_p + bmp.bmWidth * 4 * y;

		for (x = 0; x < bmp.bmWidth; ++x) {
			BYTE avg = (px[0] + px[1] + px[2]) / 3;
			//if(fReduceContrast)
			//	avg = (avg < bRamp ? avg + bMinAdjust : avg - bMaxAdjust);
			px[0] = px[1] = px[2] = avg;
			px += 4;
		}
	}
	SetBitmapBits(hBitmap, bmp.bmWidth * bmp.bmHeight * 4, m_p);
}

void Gfx::setBitmapAlpha(HBITMAP hBitmap, BYTE bAlpha)
{
	BITMAP bmp;
	DWORD dwLen;
	int x, y;

	GetObject(hBitmap, sizeof(bmp), &bmp);

	if (bmp.bmBitsPixel != 32)
		return;

	dwLen = bmp.bmWidth * bmp.bmHeight * (bmp.bmBitsPixel / 8);
	if (dwLen > m_sAllocated) {
		m_p = (BYTE *)realloc(m_p, dwLen);
		m_sAllocated = dwLen;
	}
	memset(m_p, 0, dwLen);

	GetBitmapBits(hBitmap, dwLen, m_p);

	for (y = 0; y < bmp.bmHeight; ++y) {
		BYTE *px = m_p + bmp.bmWidth * 4 * y;

		for (x = 0; x < bmp.bmWidth; ++x) {
			px[3] = bAlpha;
			px += 4;
		}
	}
	SetBitmapBits(hBitmap, bmp.bmWidth * bmp.bmHeight * 4, m_p);
}

/**
 * render text using UxTheme (vista+) DrawTextEx() API
 * @param hdc				device context
 * @param hTheme			a valid theme handle
 * @param szText			text to draw
 * @param rc				rectangle where to draw
 * @param dtFlags			flags (same as DrawText())
 * @param iGlowSize			glow size if we want glow, if 0 use solid color
 * @param clr				color to use
 * @param fForceAero		force using composited text with glow
 * @return					return value of DrawText()
 */
int Gfx::renderText(HDC hdc, HANDLE hTheme, const TCHAR *szText, RECT *rc, DWORD dtFlags, const int iGlowSize, int length, bool fForceAero)
{
	if(hTheme || fForceAero) {
		DTTOPTS dto = {0};
		dto.dwSize = sizeof(dto);
		if(iGlowSize && (cfg::isAero || fForceAero)) {
			dto.iGlowSize = iGlowSize;
			dto.dwFlags = DTT_COMPOSITED|DTT_GLOWSIZE;
		}
		else {
			dto.dwFlags = DTT_TEXTCOLOR|DTT_COMPOSITED;//|DTT_SHADOWTYPE|DTT_SHADOWOFFSET|DTT_SHADOWCOLOR|DTT_BORDERSIZE|DTT_BORDERCOLOR;
			dto.crText = txtColor;
		}
		dto.iBorderSize = 10;
		return(Api::pfnDrawThemeTextEx(hTheme, hdc, BP_PUSHBUTTON, PBS_NORMAL, szText, length, dtFlags, rc, &dto));
	}
	else {
		::SetTextColor(hdc, txtColor);
		return(::DrawText(hdc, szText, -1, rc, dtFlags));
	}
}

void Gfx::preMultiply(HBITMAP hBitmap, int mode)
{
	DWORD dwLen;
    int width, height, x, y;
    BITMAP bmp;
    BYTE alpha;

	GetObject(hBitmap, sizeof(bmp), &bmp);
    width = bmp.bmWidth;
	height = bmp.bmHeight;
	dwLen = width * height * 4;
	if (dwLen > m_sAllocated) {
		m_p = (BYTE *)realloc(m_p, dwLen);
		m_sAllocated = dwLen;
	}
    if(m_p) {
        GetBitmapBits(hBitmap, dwLen, m_p);
        for (y = 0; y < height; ++y) {
            BYTE *px = m_p + width * 4 * y;

            for (x = 0; x < width; ++x) {
                if(mode) {
                    alpha = px[3];
                    px[0] = px[0] * alpha/255;
                    px[1] = px[1] * alpha/255;
                    px[2] = px[2] * alpha/255;
                }
                else
                    px[3] = 255;
                px += 4;
            }
        }
        dwLen = SetBitmapBits(hBitmap, dwLen, m_p);
    }
}

/**
 * render a image item on the given target surface.
 *
 * @param hdc		target HDC
 * @param item		image item
 * @param rc		target rectangle (client coordinates)
 */
void __fastcall Gfx::renderImageItem(HDC hdc, TImageItem *item, RECT *rc)
{
    BYTE l = item->bLeft, r = item->bRight, t = item->bTop, b = item->bBottom;
    LONG width = rc->right - rc->left;
    LONG height = rc->bottom - rc->top;
    BOOL isGlyph = (item->dwFlags & IMAGE_GLYPH) && Skin::glyphItem;
    HDC hdcSrc = isGlyph ? Skin::glyphItem->hdc : item->hdc;
    LONG srcOrigX = isGlyph ? item->glyphMetrics[0] : 0;
    LONG srcOrigY = isGlyph ? item->glyphMetrics[1] : 0;

    if(item->dwFlags & IMAGE_FLAG_DIVIDED) {
        // top 3 items

    	Api::pfnAlphaBlend(hdc, rc->left, rc->top, l, t, hdcSrc, srcOrigX, srcOrigY, l, t, item->bf);
    	Api::pfnAlphaBlend(hdc, rc->left + l, rc->top, width - l - r, t, hdcSrc, srcOrigX + l, srcOrigY, item->inner_width, t, item->bf);
    	Api::pfnAlphaBlend(hdc, rc->right - r, rc->top, r, t, hdcSrc, srcOrigX + (item->width - r), srcOrigY, r, t, item->bf);
        // middle 3 items

    	Api::pfnAlphaBlend(hdc, rc->left, rc->top + t, l, height - t - b, hdcSrc, srcOrigX, srcOrigY + t, l, item->inner_height, item->bf);

        if(item->dwFlags & IMAGE_FILLSOLID && item->fillBrush) {
            RECT rcFill;
            rcFill.left = rc->left + l; rcFill.top = rc->top +t;
            rcFill.right = rc->right - r; rcFill.bottom = rc->bottom - b;
            FillRect(hdc, &rcFill, item->fillBrush);
        }
        else
        	Api::pfnAlphaBlend(hdc, rc->left + l, rc->top + t, width - l - r, height - t - b, hdcSrc, srcOrigX + l, srcOrigY + t, item->inner_width, item->inner_height, item->bf);

        Api::pfnAlphaBlend(hdc, rc->right - r, rc->top + t, r, height - t - b, hdcSrc, srcOrigX + (item->width - r), srcOrigY + t, r, item->inner_height, item->bf);

        // bottom 3 items

        Api::pfnAlphaBlend(hdc, rc->left, rc->bottom - b, l, b, hdcSrc, srcOrigX, srcOrigY + (item->height - b), l, b, item->bf);
        Api::pfnAlphaBlend(hdc, rc->left + l, rc->bottom - b, width - l - r, b, hdcSrc, srcOrigX + l, srcOrigY + (item->height - b), item->inner_width, b, item->bf);
        Api::pfnAlphaBlend(hdc, rc->right - r, rc->bottom - b, r, b, hdcSrc, srcOrigX + (item->width - r), srcOrigY + (item->height - b), r, b, item->bf);
    }
    else {
        switch(item->bStretch) {
            case IMAGE_STRETCH_H:
                // tile image vertically, stretch to width
            {
                LONG top = rc->top;

                do {
                    if(top + item->height <= rc->bottom) {
                    	Api::pfnAlphaBlend(hdc, rc->left, top, width, item->height, hdcSrc, srcOrigX, srcOrigY, item->width, item->height, item->bf);
                        top += item->height;
                    }
                    else {
                    	Api::pfnAlphaBlend(hdc, rc->left, top, width, rc->bottom - top, hdcSrc, srcOrigX, srcOrigY, item->width, rc->bottom - top, item->bf);
                        break;
                    }
                } while (TRUE);
                break;
            }
            case IMAGE_STRETCH_V:
                // tile horizontally, stretch to height
            {
                LONG left = rc->left;

                do {
                    if(left + item->width <= rc->right) {
                    	Api::pfnAlphaBlend(hdc, left, rc->top, item->width, height, hdcSrc, srcOrigX, srcOrigY, item->width, item->height, item->bf);
                        left += item->width;
                    }
                    else {
                    	Api::pfnAlphaBlend(hdc, left, rc->top, rc->right - left, height, hdcSrc, srcOrigX, srcOrigY, rc->right - left, item->height, item->bf);
                        break;
                    }
                } while (TRUE);
                break;
            }
            case IMAGE_STRETCH_B:
                // stretch the image in both directions...
            	Api::pfnAlphaBlend(hdc, rc->left, rc->top, width, height, hdcSrc, srcOrigX, srcOrigY, item->width, item->height, item->bf);
                break;
            default:
                break;
        }
    }
}

/**
 * colorize an image item (both standalone items with their own bitmap and glyph items).
 *
 * @param item			image item to colorize
 * @param clr			color to use (note: BGRA format required, although, alpha is ignored)
 * @param hue			hue adjustment (in degrees, -180 .. +180
 * @param saturation	scalar value (0.0 ... 1.0)
 * @param value			scalar value (0.0 ... 1.0)
 *
 * note: this isn't performance critical as it only runs at skin loading time or when
 * the user changes colorization options, never during rendering.
 *
 * if clr == 0, hsv transformation will be applied, otherwise it's rgb colorization.
 */
void Gfx::colorizeGlyph(TImageItem *item, const COLORREF clr, float hue, float saturation, float value)
{
	LONG	stride = 0, line, pixel;
	HBITMAP hBitmap = 0;
	LONG	x, y, x1, y1;
	BITMAP 	bmp = {0};
	DWORD	dwLen;
	BYTE*	pOrig, *pLine, alpha;
	float	v_s_u = 0, v_s_w = 0, r = 0, g = 0, b = 0;

    if(0 == clr) {			// do hsv transformation
    	v_s_u = value * saturation * cos(hue * M_PI/180);
    	v_s_w = value * saturation * sin(hue * M_PI/180);
    }
    else {					// rgb colorization
    	BYTE	rValue = GetRValue(clr);
    	BYTE	gValue = GetGValue(clr);
    	BYTE	bValue = GetBValue(clr);

    	r = (float)rValue / 2.55;
    	g = (float)gValue / 2.55;
    	b = (float)bValue / 2.55;
    }
	if(item) {
		/*
		 * colorize a rectangular glyph
		 */
		if(item->dwFlags & IMAGE_GLYPH) {
			hBitmap = Skin::glyphItem->hbm;
			x = item->glyphMetrics[0];
			y = item->glyphMetrics[1];
			x1 = x + item->glyphMetrics[2] - 1;
			y1 = y + item->glyphMetrics[3] - 1;

			GetObject(hBitmap, sizeof(bmp), &bmp);

			if (bmp.bmBitsPixel != 32)
				return;

			dwLen = bmp.bmWidth * bmp.bmHeight * 4;
			if (dwLen > m_sAllocated) {
				m_p = (BYTE *)realloc(m_p, dwLen);
				dwLen = (DWORD)m_sAllocated;
			}
			memset(m_p, 0, dwLen);
			pOrig = m_p;
			GetBitmapBits(hBitmap, dwLen, m_p);

			stride = bmp.bmWidthBytes;

			m_p += ((y * stride) + (4 * x));

			for(line = y; line <= y1; line++) {
				pLine = m_p;
				for(pixel = x; pixel <= x1; pixel++) {
                    alpha = m_p[3];
                    if(alpha > 0) {
                    	if(0 == clr)
                    		hsvTransformPixel(m_p, value, v_s_u, v_s_w, alpha);
                    	else
                    		rgbTransformPixel(m_p, r, g, b, alpha);
                    }
					m_p += 4;
				}
				m_p = pLine + stride;
			}
			SetBitmapBits(hBitmap, dwLen, pOrig);
		}
		else if (item->hbm) {
			GetObject(item->hbm, sizeof(bmp), &bmp);
			if (bmp.bmBitsPixel != 32)
				return;

			dwLen = bmp.bmWidth * bmp.bmHeight * 4;
			if (dwLen > m_sAllocated) {
				m_p = (BYTE *)realloc(m_p, dwLen);
				m_sAllocated = dwLen;
			}
			memset(m_p, 0, dwLen);
			pOrig = m_p;
			GetBitmapBits(item->hbm, dwLen, m_p);

			for(pixel = 0; pixel < (bmp.bmWidth * bmp.bmHeight); pixel++) {
                alpha = m_p[3];
                if(alpha > 0) {
                	if(0 == clr)
                		hsvTransformPixel(m_p, value, v_s_u, v_s_w, alpha);
                	else
                		rgbTransformPixel(m_p, r, g, b, alpha);
                }
				m_p += 4;
			}
			SetBitmapBits(item->hbm, dwLen, pOrig);
		}
	}
}