/*===========================================
          DolphMorph（ドルフモーフ）

      モーフィング＆変形アニメ作成ソフト

  モーフィングアルゴリズム: EAST 1994
  インターフェース作成:     松内 良介 1994
===========================================*/
/*
	ＥＡＳＴ氏によるモーフィングアルゴリズム

	速度測定:	1994.9.6-00		1.15 clocks/loop
				1994.9.6-01		1.17 clocks/loop
				1994.9.6-02		1.07 clocks/loop
				1994.9.6-03		1.00 clocks/loop
				1994.9.6-04		0.91 clocks/loop
				1994.9.6-05		1.08 clocks/loop (座標バッファを動的確保)
					誰かもっと速くしてくれ〜(笑)

			void	morphInitialize(int width, int height, float limit,
									LIST *plMORPHPOINT, LIST *plMORPHLINE)
			void	morphTerminate()

	static	void	do_line3(int x1,int y1,int t1, int x2,int y2,int t2,
							 void func(int x,int y,int t)!)
	static	void	morphSetInit(void)

			int		morphExec(float time, int (*func)(MORPH_INFO *info),
							  int callCnt, int do_type)
			void	morphGetColor(int x, int y,  int *r, int *g, int *b)
			void	morphDumpOut(char *filename, int wid, int ht)
*/

#define	CALLOC	TL_calloc
#define	FREE	TL_free

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winb.h>
#include <te.h>
#include <fntb.h>
#include <gui.h>
#include <file_dlg.h>
#include <egb.h>
#include <ryosuke.h>
#include <usrlib.h>
#include <time.h>
#include <math.h>

#include "domorph.h"
#include "fifo.h"

#include "morphgo.h"

#define	_lim(a, min, max)	(_min(_max((min),(a)), (max)))

/*--------------------------------------------------------*/
/*                    モジュール内変数                    */
/*--------------------------------------------------------*/

	static LIST *plPOINT;
	static LIST *plLINE;
	// static MORPH_POINT *p;
	// static int morph_point_num = 0;
	// static MORPH_LINE *l;
	// static int morph_line_num = 0;

	static fxp  *from_x;
	static fxp  *from_y;
	static fxp  *to_x;
	static fxp  *to_y;
		/*  ↑from_x,from_y,to_x,to_y : 対応座標バッファ
			  横方向の要素数が morph_width+2, 縦方向の要素数が morph_height+2
			  の２次元配列のイメージ。
			  ただ実際には、morphInitialize 内で、１次元配列としてメモリ領域を
			  動的確保している。
			  中間画像上の点(x,y) について、
					(from_x[1+y][1+x], from_y[1+y][1+x])＝画像Ａに対応する座標
					(to_x[1+y][1+x], to_y[1+y][1+x])    ＝画像Ｂに対応する座標
			  を表す。 */
	static fxp  *tmp;
		/*  ↑対応座標の収束計算のための一時バッファ。*/
	static char	*mask;
		/*  ↑対応座標の移動不可を表すフラグ。
				mask[1+y][1+x] が 0 なら…中間画像内(x,y)の対応座標は移動可
				mask[1+y][1+x] が 1 なら…中間画像内(x,y)の対応座標は移動不可
			  これは、収束計算のときに境界点を移動させないために用いる。 */
	static int  BUFWID;
		/*  ↑from_x,from_y,to_x,to_y,mask,tmp の横要素数
		      これは morph_width+2 に設定される。*/
	static fxp morph_limit = 0x10000;
		/* ↑対応座標の収束計算時の限界移動量
		　　（この値よりも移動量が小さくなったら計算を終了する）
		　　 morphInitialize で設定。 */
	static int morph_width,morph_height;
		/*  ↑モーフィング画像の横・縦のピクセル数
		      morphInitialize で設定 */
	static fxp morph_t1 = UNIT;
	static fxp morph_t2 = 0;
		/*  ↑現在計算中の中間画像の、画像Ａ／Ｂ間での分率。
			  0x00000〜0x10000 の値をとる(16ビット固定小数点)
			  morphExec で設定 */
	static int morphType;
		/*  DO_MORPH , DO_TRANSFORM  */

	#define	INDEX(x,y)	((1+(x)) + BUFWID * (1+(y)))
	#define	IA(name,n)	*((int*)(name) + (n))
	#define	CA(name,n)	*((char*)(name) + (n))

/* ユーザー定義された色読み込み関数の外部定義 */
	extern int morphGetFromPixel(fxp x,fxp y,int *r,int *g,int *b);
	extern int morphGetToPixel(fxp x,fxp y,int *r,int *g,int *b);

/*--------------------------------------------------------*/
/*             モーフィング処理の初期化／終了             */
/*--------------------------------------------------------*/

	void morphInitialize(int width, int height, float limit,
						 LIST *plMORPHPOINT, LIST *plMORPHLINE)
	{
		morph_width = width;
		morph_height = height;
		morph_limit = FP2FX(limit);
		plPOINT = plMORPHPOINT;
		plLINE  = plMORPHLINE;
		// morph_point_num = point_num;
		// p = point;
		// morph_line_num = line_num;
		// l = line;
		from_x = (fxp*)CALLOC(sizeof(fxp), (width+2)*(height+2));
		from_y = (fxp*)CALLOC(sizeof(fxp), (width+2)*(height+2));
		to_x   = (fxp*)CALLOC(sizeof(fxp), (width+2)*(height+2));
		to_y   = (fxp*)CALLOC(sizeof(fxp), (width+2)*(height+2));
		tmp    = (fxp*)CALLOC(sizeof(fxp), (width+2)*(height+2));
		mask   = (char*)CALLOC(sizeof(char), (width+2)*(height+2));
		BUFWID = width + 2;
	}

	void morphTerminate()
	{
		FREE(from_x);
		FREE(from_y);
		FREE(to_x);
		FREE(to_y);
		FREE(tmp);
		FREE(mask);
	}

/*--------------------------------------------------------*/
/*                    線分アルゴリズム                    */
/*--------------------------------------------------------*/

	static void do_line3(int x1,int y1,int t1, int x2,int y2,int t2,
						 void func(int x,int y,int t)!)
	/* (x1,y1)〜(x2,y2) の直線上の各点を求める。その際、始点 で t1、
	    終点で t2 となるような値 t も同時に求め、各点について関数
	    func (x,y,t) を呼び出す。*/
	{
		int xr,yr,tr,dx,dy,dt,x,y,t;
		xr=abs(x2-x1),yr=abs(y2-y1),tr=abs(t2-t1);
		func(x1,y1,t1);	// 最初の点
		if (xr==0 && yr==0)
			;
		else if (xr >= yr)
		{
			dy = (yr<<16) / xr;   if(y1>y2)  dy = -dy;
			dt = (tr<<16) / xr;   if(t1>t2)  dt = -dt;
			if (x1 < x2)
				for (x=x1+1, y=(y1<<16)+0x8000+dy, t=(t1<<16)+0x8000+dt;
					 x <= x2;  x++, y+=dy, t+=dt)
					func(x,y>>16,t>>16);
			else
				for (x=x1-1, y=(y1<<16)+0x8000+dy, t=(t1<<16)+0x8000+dt;
					 x >= x2;  x--, y+=dy, t+=dt)
					func(x,y>>16,t>>16);
		}
		else
		{
			dx = (xr<<16) / yr;   if(x1>x2)  dx = -dx;
			dt = (tr<<16) / yr;   if(t1>t2)  dt = -dt;
			if (y1 < y2)
				for (y=y1+1, x=(x1<<16)+0x8000+dx, t=(t1<<16)+0x8000+dt;
					 y <= y2;  y++, x+=dx, t+=dt)
					func(x>>16,y,t>>16);
			else
				for (y=y1-1, x=(x1<<16)+0x8000+dx, t=(t1<<16)+0x8000+dt;
					 y >= y2;  y--, x+=dx, t+=dt)
					func(x>>16,y,t>>16);
		}
	}

/*--------------------------------------------------------*/
/*                     境界条件の設定                     */
/*--------------------------------------------------------*/

	static void morphSetInit(void)
	{
		// printf("morphSetInit begin\n");
		int x,y,idx;
	  /* 対応座標配列を初期化 */
		for (y=-1; y<=morph_height; y++)
			for (x=-1,idx=INDEX(x,y); x<=morph_width; x++,idx++)
			{
				mask  [idx] = (0 <= x && x < morph_width &&
							   0 <= y && y < morph_height ? 0 : 1);
				from_x[idx] = I2FX(x);
				from_y[idx] = I2FX(y);
				to_x  [idx] = I2FX(x);
				to_y  [idx] = I2FX(y);
			}
	  /* 点タイプの境界条件設定 */
		if (plPOINT == NULL)
			goto NOPOINT;
		for (list_top(plPOINT); !list_isOut(plPOINT); list_next(plPOINT))
		{
			MORPH_POINT mp;
			list_getData(plPOINT,&mp);
		  /* 中間画像上での境界点の座標を計算 */
			x = FX2I(mp.from.x * morph_t1 + mp.to.x * morph_t2);
			y = FX2I(mp.from.y * morph_t1 + mp.to.y * morph_t2);
			x = _lim(x,0,morph_width-1);
			y = _lim(y,0,morph_height-1);
		  /* 境界点について、画像Ａ／Ｂへの対応座標を設定 */
			// printf("  morphSetInit (point %d,%d  from=%d,%d  to=%d,%d)\n",
			// 		x,y, mp.from.x, mp.from.y, mp.to.x, mp.to.y);
			idx = INDEX(x,y);
			from_x[idx] = I2FX(mp.from.x);
			from_y[idx] = I2FX(mp.from.y);
			to_x  [idx] = I2FX(mp.to.x);
			to_y  [idx] = I2FX(mp.to.y);
			mask  [idx] = 1;	/* 境界点では対応座標は移動させない */
		}
	   NOPOINT:;
	  /* 線指定境界条件の設定 */
		if (plLINE == NULL)
			goto NOLINE;
		for (list_top(plLINE); !list_isOut(plLINE); list_next(plLINE))
		{
			// printf("  morphSetInit (line)\n");
			MORPH_LINE ml;
			list_getData(plLINE, &ml);
			int	tx1,ty1,tx2,ty2;
			#define	L	ml
		  /* 中間画像上での境界線分の始点・終点座標を計算 */
			tx1 = FX2I(L.from1.x * morph_t1 + L.to1.x * morph_t2);
			ty1 = FX2I(L.from1.y * morph_t1 + L.to1.y * morph_t2);
			tx2 = FX2I(L.from2.x * morph_t1 + L.to2.x * morph_t2);
			ty2 = FX2I(L.from2.y * morph_t1 + L.to2.y * morph_t2);
		  /* 境界線分上の点ついて、画像Ａ／Ｂへの対応座標を設定する関数 */
			void pset(int x,int y,int s)
				/* (x,y):境界線分上の点の座標
				   s:0x0000(始点)〜0x1000(終点) 線分のなかのどの位置か
												(12ビット固定少数点) 	*/
			{
				int r;
				r = 0x1000 - s;
				idx = INDEX(x,y);
				  /* r,s は12bit固定小数点だが、求めるべき対応座標は16bit
					 固定小数点なので、４ビット上位へシフトしてから代入 */
				from_x[idx] = (L.from1.x * r + L.from2.x * s) << 4;
				from_y[idx] = (L.from1.y * r + L.from2.y * s) << 4;
				to_x  [idx] = (L.to1.x   * r + L.to2.x   * s) << 4;
				to_y  [idx] = (L.to1.y   * r + L.to2.y   * s) << 4;
				mask  [idx] = 1;
			}
		  /* 境界線分上の各点について、対応座標を設定 */
			do_line3(tx1,ty1,0x0000,  tx2,ty2,0x1000,  pset);
			#undef L
		}
	   NOLINE:;
	   END:;
		// printf("morphSetInit end\n");
		return;
	}

/*--------------------------------------------------------*/
/*                   初期対応座標の設定                   */
/*--------------------------------------------------------*/

	static void morphSetFromTo(void)
	{
	  /* x 方向 */
		int x,y;
		for (y=0; y<morph_height; y++)
		{
			int idx0;
			int left;
			idx0 = INDEX(0,y);
			left = -1;
			for (x=-1; x<=morph_width; )
			{
				if (mask[idx0+x] == 0)
					x++;
				else
				{
				  // それまでの非マスク領域について、マスク座標を内分
					if (x > left)
					{
						int fx1,fx2,tx1,tx2;
						fx1 = (from_x[idx0+left] + 128) >> 8; // 8bit fixed.p.
						fx2 = (from_x[idx0+   x] + 128) >> 8;
						tx1 = (to_x  [idx0+left] + 128) >> 8;
						tx2 = (to_x  [idx0+   x] + 128) >> 8;
						int k,ip,ipwid,h;
						ipwid = x-left;
						h = ipwid / 2;
						for (k=left+1,ip=1; k<=x-1; k++,ip++)
						{
						  from_x[idx0+k]=((fx1*(ipwid-ip)+fx2*ip+h)/ipwid)<<8;
						  to_x  [idx0+k]=((tx1*(ipwid-ip)+tx2*ip+h)/ipwid)<<8;
						}
					}
					while (mask[idx0+x] == 1 && x <= morph_width)
						x++;
					left = x-1;
				}
			}
		}
	  /* y 方向 */
		for (x=0; x<morph_width; x++)
		{
			int idx0;
			int top;
			idx0 = INDEX(x,0);
			top = 0;
			for (y=-1; y<=morph_height; )
			{
				if (mask[idx0+BUFWID*y] == 0)
					y++;
				else
				{
				  // それまでの非マスク領域について、マスク座標を内分
					if (y > top)
					{
						int fy1,fy2,ty1,ty2;
						fy1 = (from_y[idx0+BUFWID * top] + 128) >> 8;
						fy2 = (from_y[idx0+BUFWID *   y] + 128) >> 8;
						ty1 = (to_y  [idx0+BUFWID * top] + 128) >> 8;
						ty2 = (to_y  [idx0+BUFWID *   y] + 128) >> 8;
						int k,ip,ipwid,h;
						ipwid = y-top;
						h = ipwid / 2;
						for (k=top+1,ip=1; k<=y-1; k++,ip++)
						{
						  from_y[idx0+BUFWID*k] =
							((fy1*(ipwid-ip)+fy2*ip+h)/ipwid) << 8;
						  to_y  [idx0+BUFWID*k] =
							((ty1*(ipwid-ip)+ty2*ip+h)/ipwid) << 8;
						}
					}
					while (mask[idx0+BUFWID*y] == 1 && y <= morph_height)
						y++;
					top = y - 1;
				}
			} // for y
		} // for x
	}

/*--------------------------------------------------------*/
/*                   対応座標の収束計算                   */
/*--------------------------------------------------------*/

	int morphExec(float time, int (*func)(MORPH_INFO *info), int callCnt,
				   int do_type)
	/* morphInitialize 呼び出しの後にこの関数を呼び出すことで、
	   対応座標(from_x,from_y,to_x,to_y)の収束計算が行われる。 */
	/* 引数 time には、画像Ａ／Ｂ間の分率を 0.0 〜 1.0 の値で設定する。
	   つまり、0.0 を設定すると画像Ａそのものが、1.0 を設定すると画像Ｂ
	   そのものが得られる */
	/* do_type : DO_MORPH / DO_TRANSFORM */
	/* この関数を実行後、morphGetColor により、中間画像の各ピクセルを
	   得ることができる。*/
	/* 収束計算の途中で、callCnt 回のループごとに関数 func を呼び出す
	   関数 func が０以外の値を返せば、計算を中断 */
	/* 返値  0=通常終了  -1=中断された */

	{
		int	vv;
		morphType = (do_type == DO_TRANSFORM ? DO_TRANSFORM : DO_MORPH);
	  /* 中間画像の画像Ａ／Ｂ間での分率を morph_t1, morph_t2 に設定
	     (12ビット固定小数点) */
		morph_t2 = FP2FX(time);
		morph_t2 = _lim(morph_t2, 0, UNIT);
		morph_t1 = UNIT - morph_t2;
	  /* 境界条件の指定 */
		morphSetInit();
	  /* 初期対応座標の設定 */
		morphSetFromTo();
	  /* 収束計算 (vv ごとに、from_x, from_y, to_x, to_y の順) */
		for (vv=0; vv<(morphType == DO_TRANSFORM ? 2 : 4); vv++)
		{
			int	i;
			int	flag;
			int *var;
		  /* 無視して良い計算は無視。 */
			if (morphType == DO_MORPH)
			{
				if (morph_t1 == 0 && (vv==0||vv==1))
					continue;
				if (morph_t2 == 0 && (vv==2||vv==3))
					continue;
			}
			var = (vv==0 ? from_x : vv==1 ? from_y :
				   vv==2 ? to_x : to_y);
			unsigned int clk1,clk2;
			clk1 = clock();	// 実行時間計測開始
			int callCheck;
			callCheck = 0;
			for (i=0;i<=10000;i++)
			{
				int x,y,idx;
			  /* 中間画像内の全ての点について、その点の対応座標を再計算
			     (その点の上下左右の点に設定されている対応座標の平均を、
			      新たな対応座標として計算→いったん tmp に一時登録する) */
				for (y=0; y<morph_height; y++)
				{
				  /* まずは横16ピクセルずつまとめて処理 */
					for (x=0,idx=INDEX(x,y); x+16<=morph_width; x+=16)
					{
						int *p;
						p = var + idx;
						#define DO 											\
						  tmp[idx] = (p[-1]+p[1]+p[-BUFWID]+p[BUFWID])>>2;	\
						  p++,idx++;
						DO DO DO DO  DO DO DO DO
						DO DO DO DO  DO DO DO DO
						#undef DO
					}
				  /*  残りの 0 〜 15 ピクセルを処理  */
					for (; x<morph_width; x++,idx++)
						tmp[idx] = ( var[idx-1] + var[idx+1] +
									 var[idx-BUFWID] + var[idx+BUFWID] ) >> 2;
				}
			  /* 対応座標の移動可・不可フラグに従い、実際に対応座標を更新 */
			  /* 同時に、対応座標の最大移動量 (flag) も求める */
				flag = 0;
				for (y=0; y<morph_height; y++)
				{
				#if 1
				  /* まずは横８ピクセルずつまとめて処理 */
					for(x=0,idx=INDEX(x,y); x+8<=morph_width; x+=8)
					{
						#define DO									\
							if(mask[idx] == 0)						\
							{										\
								int f;								\
								f = abs(tmp[idx] - var[idx]);		\
								if (f > flag)  flag = f;			\
								var[idx] = tmp[idx];				\
							}										\
							idx++;
						DO DO DO DO DO DO DO DO
						#undef DO
					}
				  /* 残りの０〜７ピクセルを処理 */
					for (; x<morph_width; x++,idx++)
					{
						if(mask[idx] == 0)
						{
							int f = abs(tmp[idx] - var[idx]);
							if (f > flag)  flag = f;
							var[idx] = tmp[idx];
						}
					}
				#else
				  /* まずは横８ピクセルずつまとめて処理 */
					for(x=0,idx=INDEX(x,y); x+8<=morph_width; )
					{
						#define DO									\
							if(mask[idx] == 0)						\
							{										\
								int f;								\
								f = abs(tmp[idx] - var[idx]);		\
								if (f > flag)  flag = f;			\
								var[idx] = tmp[idx];				\
							}										\
							else if (i==0||i==1||i==2)					\
								; /* printf("morphExec: point(%d,%d,%3.1f,%3.1f)\n",x,y,FX2FP(var[idx-1]),FX2FP(var[idx+1])); */ \
							idx++,x++;
						DO DO DO DO DO DO DO DO
						#undef DO
					}
				  /* 残りの０〜７ピクセルを処理 */
					for (; x<morph_width; x++,idx++)
					{
						if(mask[idx] == 0)
						{
							int f = abs(tmp[idx] - var[idx]);
							if (f > flag)  flag = f;
							var[idx] = tmp[idx];
						}
						else if (i==0||i==1||i==2)
							; /* printf("morphExec: point(%d,%d)\n",x,y); */
					}
				#endif
				}
			  /* 対応座標の移動量が小さいなら、収束したということなので終了 */
				if (flag < morph_limit) break;
				if (func != 0)
				{
					if (callCheck <= 0)
					{
						MORPH_INFO info;
						info.varType = vv;
						info.loopCnt = i;
						info.moveMax = flag;
						info.moveLimit = morph_limit;
						info.morphType = morphType;
						if ((*func)(&info) != 0)
							goto BREAK;
						callCheck = callCnt;
					}
					else
						callCheck--;
				}
			}
			// printf("morphExec:\n");
			clk2 = clock();	// 実行時間計測終了
			// if (i >= 200)
			//     printf("loop rate: %lf clocks/loop\n",
			//            (float)(clk2-clk1)/(float)i);
			// printf("%d i=%ld, %lf\n",vv,i,FX2FP(flag));
			// char msg[200];
			// sprintf(msg,"%d i=%ld, %lf\n",vv,i,FX2FP(flag));
			// morphgo_msg(msg);
		}
	   END:;
		return 0;
	   BREAK:;
		return -1;
	}

/*--------------------------------------------------------*/
/*               中間画像の各ピクセルの取得               */
/*--------------------------------------------------------*/

	// ★このへんのモジュール分割はむちゃくちゃ。要再考。

	void morphGetColor(int x,int y,  int *r,int *g,int *b)
	{
		int idx;
		int r1,g1,b1,r2,g2,b2;
		idx = INDEX(x,y);
		if (morphType == DO_MORPH)
		{
			if (morphgo_getBackFlag())
			{
				int c;
				c = morphgo_getBackImagePixel(x,y);
				if (morphGetFromPixel(from_x[idx],from_y[idx],&r1,&g1,&b1)!=0)
				{
					g1 = (c >> 10) & 0x1f;
					r1 = (c >>  5) & 0x1f;
					b1 = (c      ) & 0x1f;
				}
				if (morphGetToPixel(to_x[idx], to_y[idx], &r2,&g2,&b2) != 0)
				{
					g2 = (c >> 10) & 0x1f;
					r2 = (c >>  5) & 0x1f;
					b2 = (c      ) & 0x1f;
				}
			}
			else
			{
				morphGetFromPixel(from_x[idx],from_y[idx],&r1,&g1,&b1);
				morphGetToPixel(to_x[idx], to_y[idx], &r2,&g2,&b2);
			}
			*r = FX2I(r1 * morph_t1 + r2 * morph_t2);
			*g = FX2I(g1 * morph_t1 + g2 * morph_t2);
			*b = FX2I(b1 * morph_t1 + b2 * morph_t2);
		}
		else if (morphType == DO_TRANSFORM)
		{
			if (morphgo_getBackFlag())
			{
				int c;
				c = morphgo_getBackImagePixel(x,y);
				if (morphGetFromPixel(from_x[idx], from_y[idx],  r,g,b) != 0)
				{
					*g = (c >> 10) & 0x1f;
					*r = (c >>  5) & 0x1f;
					*b = (c      ) & 0x1f;
				}
			}
			else
				morphGetFromPixel(from_x[idx], from_y[idx],  r,g,b);
		}
		return;
	}

/*--------------------------------------------------------*/
/*                 ダンプ出力(デバッグ用)                 */
/*--------------------------------------------------------*/

	void morphDumpOut(char *filename,int wid,int ht)
	{
		int *var;
		int x,y,idx;
		var = from_x;
		FILE *fp;
		if ((fp = fopen(filename, "w")) == NULL)
			return;
		for (y=0; y<ht; y++)
		{
			for (x=0,idx=INDEX(x,y); x<wid; x++,idx++)
			{
				if (mask[idx] == 0)
					fprintf(fp,"%5.1f ",FX2FP(var[idx]));
				else
					fprintf(fp,"%3d * ",FX2I(var[idx]));
			}
			fprintf(fp,"\n");
		}
		fclose(fp);
	}

/* [end] */
