//----------------------------------------------------------------------------------
//		サンプルビデオフィルタ(フィルタプラグイン)  for AviUtl ver0.99e以降
//----------------------------------------------------------------------------------
#include <windows.h>

#include "filter.h"
#include "opencv2\opencv.hpp"

using namespace cv;



//---------------------------------------------------------------------
//		フィルタ構造体定義
//---------------------------------------------------------------------
#define	TRACK_N	7														//	トラックバーの数
TCHAR	*track_name[] = { "ThresU", "ThresL", "preBlur","FindAlg", "FillAlg", "SamPx", "HSV" };	//	トラックバーの名前
int		track_default[] = { 180, 40, 1, 3, 1, 10, 0 };	//	トラックバーの初期値
int		track_s[] = { 0, 0, 1, 1, 0, 2, 0 };	//	トラックバーの下限値
int		track_e[] = { 255, 255, 20, 4, 1, 50, 3 };	//	トラックバーの上限値


#define	CHECK_N	1														//	チェックボックスの数
TCHAR	*check_name[] = { "Show selection box"};				//	チェックボックスの名前
int		check_default[] = { 1 };				//	チェックボックスの初期値 (値は0か1)

FILTER_DLL filter = {
	FILTER_FLAG_EX_INFORMATION | FILTER_FLAG_MAIN_MESSAGE,	//	フィルタのフラグ
	//	FILTER_FLAG_ALWAYS_ACTIVE		: フィルタを常にアクティブにします
	//	FILTER_FLAG_CONFIG_POPUP		: 設定をポップアップメニューにします
	//	FILTER_FLAG_CONFIG_CHECK		: 設定をチェックボックスメニューにします
	//	FILTER_FLAG_CONFIG_RADIO		: 設定をラジオボタンメニューにします
	//	FILTER_FLAG_EX_DATA				: 拡張データを保存出来るようにします。
	//	FILTER_FLAG_PRIORITY_HIGHEST	: フィルタのプライオリティを常に最上位にします
	//	FILTER_FLAG_PRIORITY_LOWEST		: フィルタのプライオリティを常に最下位にします
	//	FILTER_FLAG_WINDOW_THICKFRAME	: サイズ変更可能なウィンドウを作ります
	//	FILTER_FLAG_WINDOW_SIZE			: 設定ウィンドウのサイズを指定出来るようにします
	//	FILTER_FLAG_DISP_FILTER			: 表示フィルタにします
	//	FILTER_FLAG_EX_INFORMATION		: フィルタの拡張情報を設定できるようにします
	//	FILTER_FLAG_NO_CONFIG			: 設定ウィンドウを表示しないようにします
	//	FILTER_FLAG_AUDIO_FILTER		: オーディオフィルタにします
	//	FILTER_FLAG_RADIO_BUTTON		: チェックボックスをラジオボタンにします
	//	FILTER_FLAG_WINDOW_HSCROLL		: 水平スクロールバーを持つウィンドウを作ります
	//	FILTER_FLAG_WINDOW_VSCROLL		: 垂直スクロールバーを持つウィンドウを作ります
	//	FILTER_FLAG_IMPORT				: インポートメニューを作ります
	//	FILTER_FLAG_EXPORT				: エクスポートメニューを作ります
	0, 0,						//	設定ウインドウのサイズ (FILTER_FLAG_WINDOW_SIZEが立っている時に有効)
	"mtDeSub",			//	フィルタの名前
	TRACK_N,					//	トラックバーの数 (0なら名前初期値等もNULLでよい)
	track_name,					//	トラックバーの名前郡へのポインタ
	track_default,				//	トラックバーの初期値郡へのポインタ
	track_s, track_e,			//	トラックバーの数値の下限上限 (NULLなら全て0〜256)
	CHECK_N,					//	チェックボックスの数 (0なら名前初期値等もNULLでよい)
	check_name,					//	チェックボックスの名前郡へのポインタ
	check_default,				//	チェックボックスの初期値郡へのポインタ
	func_proc,					//	フィルタ処理関数へのポインタ (NULLなら呼ばれません)
	func_init,						//	開始時に呼ばれる関数へのポインタ (NULLなら呼ばれません)
	func_exit,						//	終了時に呼ばれる関数へのポインタ (NULLなら呼ばれません)
	NULL,						//	設定が変更されたときに呼ばれる関数へのポインタ (NULLなら呼ばれません)
	func_WndProc,						//	設定ウィンドウにウィンドウメッセージが来た時に呼ばれる関数へのポインタ (NULLなら呼ばれません)
	NULL, NULL,					//	システムで使いますので使用しないでください
	NULL,						//  拡張データ領域へのポインタ (FILTER_FLAG_EX_DATAが立っている時に有効)
	NULL,						//  拡張データサイズ (FILTER_FLAG_EX_DATAが立っている時に有効)
	"mtDeSub ver0.1 by MaverickTse",
	//  フィルタ情報へのポインタ (FILTER_FLAG_EX_INFORMATIONが立っている時に有効)
	NULL,						//	セーブが開始される直前に呼ばれる関数へのポインタ (NULLなら呼ばれません)
	NULL,						//	セーブが終了した直前に呼ばれる関数へのポインタ (NULLなら呼ばれません)
};


//---------------------------------------------------------------------
//		Global Variables
//---------------------------------------------------------------------

//FILTER_PROC_INFO fpi; //a global store for fpip for use by all functions. Do not use if possible.

typedef struct {
	short	x, y;
} SHORTPOINT;
static SHORTPOINT mousePos1; //previous mouse position on main window(on_mouse_down)
static SHORTPOINT mousePos2; //position for current position (on_mouse_move/up)

static Mat OCVRGB; //IMPORTANT: Current image store in OCV's format
static Mat img_grey;

static int ref_frame = 0; //ref frame #
static BOOL isMouseDown = FALSE; //For tracking mouse drag
static BOOL isSelectionDefined = FALSE; //track if user has selected an area

//---------------------------------------------------------------------
//		Function Forward Declerations (every function carries FILTER pointer for convenient)
//---------------------------------------------------------------------
static void Aviutl2OCV(FILTER *fp, FILTER_PROC_INFO *src, Mat &dest); //Convert Aviutl's image to OCV's BGR. Crop away canvas.
static void Aviutl2OCV(FILTER *src, Mat &dest, void *editp); //Convert Aviutl's image to OCV's BGR. Overloaded function that takes FILTER instead of FILTER_PROC_INFO
static void OCV2AviUtl(FILTER *fp, Mat &src, FILTER_PROC_INFO *dest); //Display OCV's image on AviUtl main window. Output must be FILTER_PROC_INFO pointer. Note: OCV image has no canvas area.

static void DrawSelection(FILTER *fp, FILTER_PROC_INFO *dest); //Draw selection and display it
static void fillEdgeImage(Mat edgesIn, Mat& filledEdgesOut);
static void delogo(FILTER *fp); //the actual delogo function

//---------------------------------------------------------------------
//		フィルタ構造体のポインタを渡す関数
//---------------------------------------------------------------------
EXTERN_C FILTER_DLL __declspec(dllexport) * __stdcall GetFilterTable(void)
{
	return &filter;
}
//	下記のようにすると1つのaufファイルで複数のフィルタ構造体を渡すことが出来ます
/*
FILTER_DLL *filter_list[] = {&filter,&filter2,NULL};
EXTERN_C FILTER_DLL __declspec(dllexport) ** __stdcall GetFilterTableList( void )
{
return (FILTER_DLL **)&filter_list;
}
*/

//---------------------------------------------------------------------
//		Initialization
//---------------------------------------------------------------------
BOOL func_init(FILTER *fp)
{
	mousePos1.x = 0;
	mousePos1.y = 0;
	mousePos2.x = 0;
	mousePos2.y = 0;
	return TRUE;
}

//---------------------------------------------------------------------
//		Before ending
//---------------------------------------------------------------------
BOOL func_exit(FILTER *fp)
{
	//mem_free();
	return TRUE;
}




//---------------------------------------------------------------------
//		フィルタ処理関数
//---------------------------------------------------------------------
BOOL func_proc(FILTER *fp, FILTER_PROC_INFO *fpip)
{
	//
	//	fp->track[n]			: トラックバーの数値
	//	fp->check[n]			: チェックボックスの数値
	//	fpip->w 				: 実際の画像の横幅
	//	fpip->h 				: 実際の画像の縦幅
	//	fpip->max_w				: 画像領域の横幅
	//	fpip->max_h				: 画像領域の縦幅
	//	fpip->ycp_edit			: 画像領域へのポインタ
	//	fpip->ycp_temp			: テンポラリ領域へのポインタ
	//	fpip->ycp_edit[n].y		: 画素(輝度    )データ (     0 〜 4096 )
	//	fpip->ycp_edit[n].cb	: 画素(色差(青))データ ( -2048 〜 2048 )
	//	fpip->ycp_edit[n].cr	: 画素(色差(赤))データ ( -2048 〜 2048 )
	//
	//  画素データは範囲外に出ていることがあります。
	//  また範囲内に収めなくてもかまいません。
	//
	//	画像サイズを変えたいときは fpip->w や fpip->h を変えます。
	//
	//	テンポラリ領域に処理した画像を格納したいときは
	//	fpip->ycp_edit と fpip->ycp_temp を入れ替えます。
	//
	if (fp->exfunc->is_filter_active(fp))
	{
		Aviutl2OCV(fp, fpip, OCVRGB);
		if (fp->check[0])
		{
			DrawSelection(fp, fpip);
		}
		
		if (isSelectionDefined)
		{
			delogo(fp);
		}
		
		OCV2AviUtl(fp, OCVRGB, fpip);
		//imshow("Test Image", OCVRGB);

	}
	return TRUE;
}


//---------------------------------------------------------------------
//		WndProc (lparam is actually the mouse coordinate)
//---------------------------------------------------------------------
BOOL func_WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam, void *editp, FILTER *fp)
{
	//TODO
	switch (message)
	{
	case WM_FILTER_INIT:
		
		SetWindowText(fp->hwnd, "mtDeSub");
		return TRUE;
	case WM_FILTER_MAIN_MOUSE_DOWN:
		if (fp->exfunc->get_frame_n(editp)>1)
		{
			isMouseDown = TRUE;
			isSelectionDefined = FALSE;
			SetWindowText(fp->hwnd, "Selecting...");
			SHORTPOINT* msp = (SHORTPOINT*)&lparam;
			mousePos1.x = msp->x;
			mousePos1.y = msp->y;
			if (mousePos1.x<0) mousePos1.x = 0;
			if (mousePos1.y<0) mousePos1.y = 0;

			int fw, fh;
			fp->exfunc->get_frame_size(editp, &fw, &fh);

			if (mousePos1.x >= fw) mousePos1.x = fw - 1;
			if (mousePos1.y >= fh) mousePos1.y = fh - 1;
			return FALSE;
		}
		break;
	case WM_FILTER_MAIN_MOUSE_MOVE:
		if (fp->exfunc->get_frame_n(editp)>1 && isMouseDown)
		{
			SHORTPOINT* msp = (SHORTPOINT*)&lparam;
			mousePos2.x = msp->x;
			mousePos2.y = msp->y;
			if (mousePos2.x<0) mousePos2.x = 0;
			if (mousePos2.y<0) mousePos2.y = 0;

			int fw, fh;
			fp->exfunc->get_frame_size(editp, &fw, &fh);

			if (mousePos2.x >= fw) mousePos2.x = fw - 1;
			if (mousePos2.y >= fh) mousePos2.y = fh - 1;
			return TRUE;
		}
		break;
	case WM_FILTER_MAIN_MOUSE_UP:
		if (fp->exfunc->get_frame_n(editp)>1)
		{
			isMouseDown = FALSE;
			isSelectionDefined = TRUE;
			SetWindowText(fp->hwnd, "Selected");
			SHORTPOINT* msp = (SHORTPOINT*)&lparam;
			mousePos2.x = msp->x;
			mousePos2.y = msp->y;
			if (mousePos2.x<0) mousePos2.x = 0;
			if (mousePos2.y<0) mousePos2.y = 0;

			int fw, fh;
			fp->exfunc->get_frame_size(editp, &fw, &fh);

			if (mousePos2.x >= fw) mousePos2.x = fw - 1;
			if (mousePos2.y >= fh) mousePos2.y = fh - 1;
			return TRUE;
		}
		//break;
/*	case WM_COMMAND:
		switch (LOWORD(wparam))
		{
		
		default:
			return FALSE;
		}*/
	}
	return FALSE;
}


//########################################################################
//     Function Implementations
//########################################################################

//Convert  Au image struct to OCV Mat using fp and fpip
void Aviutl2OCV(FILTER *fp, FILTER_PROC_INFO *src, Mat &dest)
{
	//Test:OK
	if (src) //if fpip is not a null pointer, do the main work
	{
		int fw = src->w;
		int fh = src->h;
		int aubufsize = fw*fh;
		PIXEL* rgb = new PIXEL[aubufsize];
		PIXEL_YC* yc = new PIXEL_YC[aubufsize];
		PIXEL_YC* yc_base = yc;
		PIXEL_YC* yctemp_base = src->ycp_edit;
		Mat mBuffer(fh, fw, CV_8UC3);

		int canvasOffset = (src->max_w) - (src->w);

		for (int row = 0; row< src->h; ++row)
		{
			for (int col = 0; col< src->w; ++col)
			{
				yc->y = yctemp_base->y;
				yc->cb = yctemp_base->cb;
				yc->cr = yctemp_base->cr;
				++yc;
				++yctemp_base;
			}
			yctemp_base += canvasOffset; //skip canvas
		}
		//restore pointer
		yc = yc_base;


		BOOL rgbOK = fp->exfunc->yc2rgb(rgb, yc, aubufsize); //without canvas
		if (rgbOK)
		{

			PIXEL* rgbDataCopy = rgb; // going to work on a copy of the pointer
			for (int row = 0; row< src->h; ++row) //loop through each row
			{
				for (int col = 0; col< src->w; ++col) //and each column
				{
					mBuffer.at<Vec3b>(row, col)[0] = rgb->b; //Copy Blue value
					mBuffer.at<Vec3b>(row, col)[1] = rgb->g; //Copy Green value
					mBuffer.at<Vec3b>(row, col)[2] = rgb->r; //Copy Red value
					++rgb; //move to next pixel
				}

			}
			rgb = rgbDataCopy;
		}
		else
		{
			SetWindowText(fp->hwnd, "YC2RGB failed!");
		}
		delete[] rgb;
		delete[] yc;
		mBuffer.copyTo(dest);
	}
	else //or throw error
	{
		SetWindowText(fp->hwnd, "fpip is NULL!");
	}

}

//Convert  Au image struct to OCV Mat using fp and editp
void Aviutl2OCV(FILTER *src, Mat &dest, void *editp)
{
	
	int srcw, srch;
	src->exfunc->get_frame_size(editp, &srcw, &srch);

	
	if (src) //if fp is not a null pointer, do the main work
	{
		PIXEL* rgb = new PIXEL[srcw*srch];
		BOOL gotRGB = src->exfunc->get_pixel_filtered(editp, src->exfunc->get_frame(editp), rgb, &srcw, &srch);
		if (gotRGB)
		{
			Mat Buffer(srch, srcw, CV_8UC3);
			dest.create(srch, srcw, CV_8UC3); //allocate space for OCV image
			PIXEL* rgbDataCopy = rgb; // going to work on a copy of the pointer
			for (int row = 0; row< srch; ++row) //loop through each row
			{
				for (int col = 0; col< srcw; ++col) //and each column
				{
					Buffer.at<Vec3b>(row, col)[0] = rgbDataCopy->b; //Copy Blue value
					Buffer.at<Vec3b>(row, col)[1] = rgbDataCopy->g; //Copy Green value
					Buffer.at<Vec3b>(row, col)[2] = rgbDataCopy->r; //Copy Red value
					++rgbDataCopy; //move to next pixel
				}
			}
			flip(Buffer, dest, 0);
		}
		else
		{
			SetWindowText(src->hwnd, "Failed to get RGB image");
		}

	}
}

//Display an OCV image on Aviutl main window
void OCV2AviUtl(FILTER *fp, Mat &src, FILTER_PROC_INFO *dest)
{
	//TEST OK
	if (dest && !src.empty()) //if fpip is not null and src has image
	{
		PIXEL *RGBTEMP = new PIXEL[dest->max_w*dest->max_h]; //allocate rgb buffer
		PIXEL *rgbPointerBase = RGBTEMP;
		for (int row = 0; row< dest->h; ++row)
		{
			for (int col = 0; col< dest->w; ++col)
			{
				RGBTEMP->r = src.at<Vec3b>(row, col)[2]; //Red
				RGBTEMP->g = src.at<Vec3b>(row, col)[1]; //Green
				RGBTEMP->b = src.at<Vec3b>(row, col)[0]; //Blue
				++RGBTEMP; //to next pixel
			}
			RGBTEMP += dest->max_w - dest->w; //add canvas area
		}
		RGBTEMP = rgbPointerBase; //restore pointer base
		BOOL ycOK = fp->exfunc->rgb2yc(dest->ycp_temp, RGBTEMP, dest->max_w*dest->max_h); //rgb to YC
		if (ycOK)
		{
			PIXEL_YC *YCTEMP = dest->ycp_edit; //Swap display and buffer when done.
			dest->ycp_edit = dest->ycp_temp;
			dest->ycp_temp = YCTEMP;
		}
		else
		{
			SetWindowText(fp->hwnd, "RGB2YC failed!");
		}
		delete[] RGBTEMP;
	}
	else
	{
		SetWindowText(fp->hwnd, "dest is null or src is empty");
	}
}

void DrawSelection(FILTER *fp, FILTER_PROC_INFO *dest)
{
	//TEST OK
	if (!OCVRGB.empty())
	{
		rectangle(OCVRGB, Point(mousePos1.x, mousePos1.y), Point(mousePos2.x, mousePos2.y), CV_RGB(200, 200, 200), 1);
	}
	else
	{
		SetWindowText(fp->hwnd, "No image");
	}
}

void fillEdgeImage(Mat edgesIn, Mat& filledEdgesOut)
{
	Mat edgesNeg = edgesIn.clone();
	
	//Floodfill along width
	for (int i = 0; i < edgesIn.cols; i++)
	{
		floodFill(edgesNeg, Point(i, 0), CV_RGB(255, 255, 255));
		floodFill(edgesNeg, Point(i, edgesIn.rows-1), CV_RGB(255, 255, 255));
	}

	//Floodfill along height
	for (int i = 0; i < edgesIn.rows; i++)
	{
		floodFill(edgesNeg, Point(0, i), CV_RGB(255, 255, 255));
		floodFill(edgesNeg, Point(edgesIn.cols-1, i), CV_RGB(255, 255, 255));
	}

	//Mat element = getStructuringElement(MORPH_RECT,
	//	Size(2, 2),
	//	Point(1, 1));
	//dilate(edgesNeg, edgesNeg, element);
	bitwise_not(edgesNeg, edgesNeg);
	filledEdgesOut = (edgesNeg | edgesIn);

	return;
}

void delogo(FILTER *fp)
{
	Rect roi(Point(mousePos1.x, mousePos1.y), Point(mousePos2.x, mousePos2.y));
	//Determine on how to make the greyscale image
	if (fp->track[6] == 0)
	{
		cvtColor(OCVRGB, img_grey, CV_BGR2GRAY);
	}
	else
	{
		Mat hsv(OCVRGB.size(), CV_8UC3);
		vector<Mat> channels;
		cvtColor(OCVRGB, hsv, CV_BGR2HSV_FULL);
		split(hsv, channels);
		if (fp->track[6] == 1)
		{
			img_grey = channels[0];
		}
		else if (fp->track[6] == 2)
		{
			img_grey = channels[1];
		}
		else
		{
			img_grey = channels[2];
		}
	}
	//crop the image to selected area
	Mat roiImage(img_grey, roi);

	vector<vector<Point> > contours;
	vector<Vec4i> hierarchy;
	blur(roiImage, roiImage, Size(fp->track[2], fp->track[2])); //pre-blur to reduce noise
	Canny(roiImage, roiImage, fp->track[0], fp->track[1]);
	//dialate image
	Mat element = getStructuringElement(MORPH_RECT,
		Size(fp->track[2] + 2, fp->track[2] + 2),
		Point(fp->track[2], fp->track[2]));
	dilate(roiImage, roiImage, element);
	
	//track counter
	findContours(roiImage, contours, hierarchy, CV_RETR_EXTERNAL, fp->track[3]);
	Mat filled_mask = Mat::zeros(roiImage.size(), CV_8UC1);
	//vector<Point> approxShape;
	for (UINT i = 0; i < contours.size(); i++)
	{
		//approxPolyDP(contours[i], approxShape, arcLength(Mat(contours[i]), true)*0.04, true);
		drawContours(filled_mask, contours, i, 255, 2, 8, hierarchy, 0, Point());
	}
	dilate(filled_mask, filled_mask, element);
	fillEdgeImage(filled_mask, filled_mask); //fill holes
	//fillEdgeImage(roiImage, roiImage); //fill holes
	Mat final_mask = Mat::zeros(img_grey.size(), CV_8UC1);
	filled_mask.copyTo(final_mask(roi));
	//roiImage.copyTo(final_mask(roi));
	inpaint(OCVRGB, final_mask, OCVRGB, fp->track[5], fp->track[4]);
}

