/*
	colchg.c
*/

#include <stdlib.h>
#include <stdlib.h>
#include <math.h>

#include "ge.h"
#include "imageman.h"
#include "dispman.h"
#include "mainmenu.h"


static int cmat[3][4];


static int colchg_init(int c1,int c2,int mode)
{
#define	F(a)	(int)((a)*65536.0)
	int r1,g1,b1,r2,g2,b2;
	double n1,n2,n3,l;
	double cos_t,sin_t,l1,l2;
	// ２色をRGB要素に分解
	r1=getR(c1),g1=getG(c1),b1=getB(c1);
	r2=getR(c2),g2=getG(c2),b2=getB(c2);
	l1 = sqrt((double)(r1*r1+g1*g1+b1*b1));
	l2 = sqrt((double)(r2*r2+g2*g2+b2*b2));
	// C1からC2へ回転するときの軸を算出 (C1,C2の外積) → (n1,n2,n3)
	// 回転する角度の余弦・正弦を算出 → cos_t, sin_t
	n1=g1*b2-b1*g2, n2=b1*r2-r1*b2, n3=r1*g2-g1*r2;
	l=sqrt(n1*n1+n2*n2+n3*n3);
	if (l == 0)
	{
		n1 = 1.0, n2 = n3 = 0.0;
		cos_t=1.0, sin_t=0.0;
	}
	else
	{
		n1=n1/l, n2=n2/l, n3=n3/l;
		if (l1 == 0 || l2 == 0)
			return -1;
		cos_t = (double)(r1*r2+g1*g2+b1*b2) / (l1 * l2);
		if (cos_t <= 0.0)
			cos_t = 0.0;
		else if (cos_t > 1.0)
			cos_t = 1.0;
		sin_t = sqrt(1-cos_t*cos_t);
	}
	if (mode == 0)
	{	// ★直交変換のみによる変換
		// スケーリング値を算出
		double k;
		if (l1 == 0.0)
			k = (l2 == 0.0 ? 1.0 : 50.0);	// RGB各５ビットなので50で十分
		else
			k = l2 / l1;
		// 変換行列を作成
		double p = 1-cos_t;
		cmat[0][0] = F(k * (p*n1*n1+cos_t));
		cmat[0][1] = F(k * (p*n1*n2-n3*sin_t));
		cmat[0][2] = F(k * (p*n1*n3+n2*sin_t));
		cmat[0][3] = 0;
		cmat[1][0] = F(k * (p*n1*n2+n3*sin_t));
		cmat[1][1] = F(k * (p*n2*n2+cos_t));
		cmat[1][2] = F(k * (p*n2*n3-n1*sin_t));
		cmat[1][3] = 0;
		cmat[2][0] = F(k * (p*n1*n3-n2*sin_t));
		cmat[2][1] = F(k * (p*n2*n3+n1*sin_t));
		cmat[2][2] = F(k * (p*n3*n3+cos_t));
		cmat[2][3] = 0;
	}
	else if (mode == 1)
	{	// ★直交変換＋平行移動による変換(２色の明るさがほぼ同じ場合によい)
		if (l2 == 0.0)
			goto mode2;
		// 回転後に平行移動する変位を算出
		double m1,m2,m3;
		m1=r2/l2,m2=g2/l2,m3=b2/l2;
		m1 *= (l2-l1), m2 *= (l2-l1), m3 *= (l2-l1);
		// 変換行列を作成
		double p = 1-cos_t;
		cmat[0][0] = F(p*n1*n1+cos_t);
		cmat[0][1] = F(p*n1*n2-n3*sin_t);
		cmat[0][2] = F(p*n1*n3+n2*sin_t);
		cmat[0][3] = F(m1);
		cmat[1][0] = F(p*n1*n2+n3*sin_t);
		cmat[1][1] = F(p*n2*n2+cos_t);
		cmat[1][2] = F(p*n2*n3-n1*sin_t);
		cmat[1][3] = F(m2);
		cmat[2][0] = F(p*n1*n3-n2*sin_t);
		cmat[2][1] = F(p*n2*n3+n1*sin_t);
		cmat[2][2] = F(p*n3*n3+cos_t);
		cmat[2][3] = F(m3);
	}
	else if (mode == 2)
	{	// ★平行移動のみによる変換(２色がよく似た色の場合によい)
mode2:
		cmat[0][0] = F(1);
		cmat[0][1] = 0;
		cmat[0][2] = 0;
		cmat[0][3] = F((double)(r2-r1));
		cmat[1][0] = 0;
		cmat[1][1] = F(1);
		cmat[1][2] = 0;
		cmat[1][3] = F((double)(g2-g1));
		cmat[2][0] = 0;
		cmat[2][1] = 0;
		cmat[2][2] = F(1);
		cmat[2][3] = F((double)(b2-b1));
	}
	else if (mode == 3)
	{
		// ★RGB各要素に定数を掛ける変換(特定要素を強調したい/弱めたいときによい)
		cmat[0][0] = (r1>0 ? F((double)r2/(double)r1) : r2 > 0 ? F(50) : 0);
		cmat[0][1] = 0;
		cmat[0][2] = 0;
		cmat[0][3] = 0;
		cmat[1][0] = 0;
		cmat[1][1] = (g1>0 ? F((double)g2/(double)g1) : g2 > 0 ? F(50) : 0);
		cmat[1][2] = 0;
		cmat[1][3] = 0;
		cmat[2][0] = 0;
		cmat[2][1] = 0;
		cmat[2][2] = (b1>0 ? F((double)b2/(double)b1) : b2 > 0 ? F(50) : 0);
		cmat[2][3] = 0;
	}
	return 0;
#undef	F
}


static int colchg_trans(int c)
{
	int r0,g0,b0,r,g,b;
	r0=getR(c),g0=getG(c),b0=getB(c);
	r = (cmat[0][0]*r0 + cmat[0][1]*g0 + cmat[0][2]*b0
		 + cmat[0][3] + 0x8000) >> 16;
	g = (cmat[1][0]*r0 + cmat[1][1]*g0 + cmat[1][2]*b0
		 + cmat[1][3] + 0x8000) >> 16;
	b = (cmat[2][0]*r0 + cmat[2][1]*g0 + cmat[2][2]*b0
		 + cmat[2][3] + 0x8000) >> 16;
	r = _lim(r,0,31);
	g = _lim(g,0,31);
	b = _lim(b,0,31);
	return GRB(g,r,b);
}


static void colchg_hline(int x1,int x2,int y)
{
	char *dp0 = EIMadrs(x1,y), *sp0 = EIMadrs_back(x1,y);
	void hline(int _x1, int _x2, int _y)
	{
		short *dp = (short*)dp0 + _x1 - x1, *sp = (short*)sp0 + _x1 - x1;
		for (int i=_x1; i<=_x2; i++,dp++,sp++)
			*dp = colchg_trans(*sp);
	}
	hline_func(x1,x2,y,hline);
	DMimage_hline_map(x1,x2,y,dp0);
}

static void mono_hline(int x1,int x2,int y)
{
	char *dp0 = EIMadrs(x1,y), *sp0 = EIMadrs_back(x1,y);
	void hline(int _x1, int _x2, int _y)
	{
		short *dp = (short*)dp0 + _x1 - x1, *sp = (short*)sp0 + _x1 - x1;
		for (int i=_x1; i<=_x2; i++,dp++,sp++)
		{
			int sc = *sp;
			int r=getR(sc),g=getG(sc),b=getB(sc);
			int dg = (306*r+601*g+117*b+512)>>10;
			*dp = GRB(dg,dg,dg);
		}
	}
	hline_func(x1,x2,y,hline);
	DMimage_hline_map(x1,x2,y,dp0);
}


static void colchg(int col1,int col2,int method,int a)
// a:0=色彩変換  1=モノクロ化
{
	int ax1,ay1,ax2,ay2;
	area_getboundxy(&ax1,&ay1,&ax2,&ay2);
	int x,y;
	if (a == 0)
	{
		if (colchg_init(col1,col2,method) != 0)
			return;
	}
	for (y=ay1; y<=ay2; y++)
	{
		x = ax1 + area_chkxylen(ax1,ax2,y,YES);
		while (x<=ax2)
		{
			int l;
			if ((l = area_chkxylen(x,ax2,y,NO)) == 0)
				break;
			if (a == 0)
				colchg_hline(x,x+l-1,y);
			else
				mono_hline(x,x+l-1,y);
			x += l + area_chkxylen(x+l,ax2,y,YES);
		}
	}
}


static void do_colchg(int col1, int col2, int method)
{
	bool first = YES;
	for (;;)
	{
		if (area_input(AREA_POLYGON) != 0)
			break;
		if (first)
			{ EIMbackup();  first = NO; }
		colchg(col1,col2,method,0);
	}
}


static void do_mono()
{
	for (;;)
	{
		if (area_input(AREA_POLYGON) != 0)
			break;
		EIMbackup();
		colchg(0,0,0,1);
	}
}

/*--------------------------------------------------------*/
/*                 色彩変換メニューの定義                 */
/*--------------------------------------------------------*/

static int col1,col2;
static int method = 0;

#define	MYLEN	256

/*
#define	itemExec	1
#define	itemCancel	2
#define	itemCol1	4
#define	itemCol2	6
#define	itemColList	8
#define	itemRgbBar	9
#define	itemMethod	11
#define	itemHelp	12
#define	itemCmdMono	13
*/

static void disp_colchgmenu(), erase_colchgmenu();

#include "colchg.md"

static char *help[] = {
"［標準変換］\n\
RGB 空間での回転＋スカラー倍による変換。たいていはこれでこと足りる。",
"［グラデ保存変換］\n\
RGB 空間での回転＋平行移動による変換。\
微妙なグラデーションを壊したくないときによい。",
"［ちょびっと変換］\n\
RGB 空間での平行移動のみによる変換。わずかな色彩変更のときによい。",
"［RGB 掛け算変換］\n\
RGB 各要素についての色1,色2 の比を全色に掛ける。\
特定の色要素を強(弱)めたいときによい。"
};

static void disp_methodhelp(int m)
{
	int ix,iy,ixlen,iylen;
	menu_getbuttonxy(&colchgmenu, itemHelp, &ix, &iy, &ixlen, &iylen);
	grboxfill(ix,iy,ixlen,iylen,DMgetmenuplt(White),DrawNORMAL);
	ix+=4,ixlen-=16;
	iy+=4,iylen-=8;
	putmsg_width(ix,iy,ixlen,help[m],DMgetmenuplt(Black));
}

static void draw_csrs(),erase_csrs();

static void disp_colchgmenu()
{
	int ix,iy;
	menu_getbuttonxy(&colchgmenu, itemColList, &ix, &iy, NULL, NULL);
	drawPltList(ix,iy);
	drawPltCsr(pltnum);
	cols_init(&col1,&col2, &colchgmenu, itemCol1, itemCol2);
	drawCols();
	drawColCsr();
	draw_csrs();
	disp_methodhelp(menu_selector_getvar(menu_selector(&colchgmenu,selMethod)));
}

static void erase_colchgmenu()
{
	eraseCols();
	erasePltList();
}

static void draw_csrs_sub(int col)
{
#if 0
	// 変換方法カーソルの描画
	int ix,iy;
	menu_getbuttonxy(&colchgmenu, itemMethod, &ix, &iy, NULL, NULL);
	grboxline(ix+24*method-4,iy-2,24,16+4,DMgetmenuplt(col),DrawNORMAL);
#endif
}

static void draw_csrs()
{
	draw_csrs_sub(Black);
}

static void erase_csrs()
{
	draw_csrs_sub(COL_menu);
}

void commandColchg()
{
	static bool first = YES;
	if (first)
		col1 = forecol, col2 = backcol, method = 0;
	first = NO;
	menu_disp(&colchgmenu);
	for (;;)
	{
		DMdispcsr(ms.x,ms.y);
		for (;;)
		{
			ms_get(&ms);
			if (ms.dx!=0||ms.dy!=0||ms.btn1!=OFF||ms.btn2!=OFF||key_chk()!=0)
				break;
		}
		DMerasecsr();
		scrollForCsr(1,1);
		if (ms.btn1 == OFFON)
		{
			int a; int ax,ay;
			a = menu_where(ms.x,ms.y,&colchgmenu, &ax,&ay,NULL);
			switch (a)
			{
			case itemExec:
				menu_erase();
				do_colchg(col1,col2,menu_selector_getvar(menu_selector(&colchgmenu,selMethod)));
				goto end;
			case itemCancel:
				goto exitloop;
			case itemCol1:
			case itemCol2:
				drawColCsr();
				cols_setcsrpos((a==itemCol1 ? 0 : 1));
				drawColCsr();
				break;
			case itemColList:
				drawPltCsr(pltnum);	// erase
				pltnum = ax/16 + (ay/16)*4;
				makeupRgbBar();
				drawPltCsr(pltnum);	// draw
				if (cols_getcsrpos() == 0)
					col1 = plt_getcode(pltnum);
				else
					col2 = plt_getcode(pltnum);
				makeupCols();
				break;
			case itemRgbBar:
				touchRgbBar(ax,ay,ms.btn1);
				if (cols_getcsrpos() == 0)
					col1 = plt_getcode(pltnum);
				else
					col2 = plt_getcode(pltnum);
				makeupCols();
				break;
			case itemMethod:
				erase_csrs();
				method = ax / 24;
				// disp_methodhelp(0);
				draw_csrs();
				break;
			case itemCmdMono:
				menu_erase();
				do_mono();
				goto end;
			case itemMoveMenu:
				menu_move();
				break;
			case itemSelector:
				menu_touchselector(&colchgmenu);
				disp_methodhelp(menu_selector_getvar(menu_selector(&colchgmenu,selMethod)));
				break;
			}
		}
		else if (ms.btn1 == ON)
		{
			int a; int ax,ay;
			a = menu_where(ms.x,ms.y,&colchgmenu, &ax,&ay,NULL);
			switch (a)
			{
			case itemRgbBar:
				touchRgbBar(ax,ay,ms.btn1);
				if (cols_getcsrpos() == 0)
					col1 = plt_getcode(pltnum);
				else
					col2 = plt_getcode(pltnum);
				makeupCols();
			}
		}
		if (ms.btn2 == OFFON)
		{
			int a;
			a = menu_where(ms.x,ms.y,&colchgmenu, NULL,NULL,NULL);
			if (a == OutOfMenu)
			{
				int px,py;
				px = DMimage_getx(ms.x),  py = DMimage_gety(ms.y);
				drawPltCsr(pltnum);	// erase
				if (mode == MODE32K)
					plt_setcode(pltnum, EIMpoint(px,py));
				else
					pltnum = EIMpoint(px,py);
				makeupPltList();
				makeupRgbBar();
				drawPltCsr(pltnum);	// draw
				drawColCsr();  // erase
				if (cols_getcsrpos() == 0)
					col1 = plt_getcode(pltnum);
				else
					col2 = plt_getcode(pltnum);
				makeupCols();
				drawColCsr();	// draw
			}
			else
				break;
		}
	}
exitloop:
	forecol = (cols_getcsrpos() == 0 ? col1 : col2);
	cols_setcsrpos(0);
	menu_erase();
end:
	return;
}
