//**************************************************************************
//*                     This file is part of the                           *
//*                MMC - Mpxplay Multimedia Commander                      *
//*                   The source code of MMC is                            *
//*        (C) copyright 1998-2018 by PDSoft (Attila Padar)                *
//*                http://mpxplay.sourceforge.net                          *
//*                  email: mpxplay@freemail.hu                            *
//**************************************************************************
//*  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.                  *
//*  Please contact with the author (with me) if you want to use           *
//*  or modify this source.                                                *
//**************************************************************************
//function: video widget handling

//#define MPXPLAY_USE_DEBUGF 1
#define DISPQT_DEBUG_OUTPUT NULL //stdout
#define DISPQT_DEBUG_ERROR stderr
#define DISPQT_DEBUGOUT_FFMPEG stdout // NULL
#define DISPQT_DEBUGOUT_RENDER NULL // stdout
#define DISPQT_DEBUGOUT_SUBTITLE NULL // stdout
#define DISPQT_DEBUGOUT_CROP NULL // stdout
#define DISPQT_DEBUGOUT_ACROP NULL // stdout

#include <QtGui>
#include <QtWidgets>
#include <QtGui/QPixMap>
#include <QtPlatformHeaders/QWindowsWindowFunctions.h>
#include "moc_video_qt.h"
#include "moc_mainwindow.h"

#define DISPQT_VIDEO_AR_SCALE 1000
#define DISPQT_VIDEO_ROUND_VALUE 1
#define DISPQT_VIDEO_ROUND_MASK (~1)

#define DISPQT_VIDEO_AUTOCROP_COUNTER_OPEN     2  // at opening a new video
#define DISPQT_VIDEO_AUTOCROP_COUNTER_REFRESH  1  // at seeking or at window resize

#ifdef MPXPLAY_LINK_ORIGINAL_FFMPEG

#if (DISPQT_USE_OPENGL_WIDGET == 2)
FFMpegVideoWidget::FFMpegVideoWidget(MainWindow *main_window, QWidget *parent) : QOpenGLWidget(parent)
#else
FFMpegVideoWidget::FFMpegVideoWidget(MainWindow *main_window, QWidget *parent) : QWidget(parent)
#endif
{
	struct mmc_dispqt_config_s *gcfg = main_window->gui_config;
	mpxplay_debugf(DISPQT_DEBUGOUT_FFMPEG,"FFMpegVideoWidget INIT");
	this->main_window = main_window;
	this->parent_widget = parent;

	this->vid_src_ori_width = this->vid_src_ori_height = 0;
	this->vid_src_ori_pix_fmt = AV_PIX_FMT_NONE;

	this->sws_src_width = this->sws_src_height = 0;
	this->sws_srccrop_x = this->sws_srccrop_y = 0;
	this->sws_srccrop_width = this->sws_srccrop_height = 0;
	this->sws_crop_w = this->sws_crop_h = 0;
	this->sws_crop_x = this->sws_crop_y = 0;
	this->sws_src_pix_fmt = AV_PIX_FMT_NONE;
	this->videoout_surface_pix_fmt = DISPQT_VIDEO_QIMAGE_OUTPUT_AVFORMAT; // TODO: use separated video output config
	this->last_video_crop_type = gcfg->video_crop_type;
	this->last_video_zoom_min_val = this->last_video_zoom_ti_val = 0;
	this->last_video_render_type = gcfg->video_renderer_type;

	this->sws_dst_width = this->width();
	this->sws_dst_height = this->height();
	this->video_zoom_globalpos_x = this->sws_dst_width / 2;
	this->video_zoom_globalpos_y = this->sws_dst_height / 2;
	this->sws_dst_pos_x = this->sws_dst_pos_y = 0;
	this->sws_autoxcrop_framecounter = 0;

	//cleared in videowidget_ffmpeg_videoctx_clear (not needed here)
	this->ffmpeg_decoded_frame = NULL;
	this->ffmpeg_transformed_frame = NULL;
	this->ffmpeg_filtered_frame = NULL;
	this->ffmpeg_output_frame = NULL;

	// cleared in videowidget_ffmpeg_swsctx_clear too (not needed here)
	this->ffmpeg_swsframe_hasdata = this->videoout_surface_recalc = this->videoout_surface_clearpic = false;
	this->ffmpeg_swsframe_cropreset = DISPQT_VIDEO_AUTOCROP_COUNTER_OPEN;
	this->ffmpeg_sws_frame = NULL;
	this->sws_ctx = NULL;
	this->sws_out_pix_fmt = AV_PIX_FMT_NONE;

	this->sub_title_infos = NULL;

#if (DISPQT_USE_OPENGL_WIDGET == 1)
//	this->windowHandle()->setSurfaceType(QWindow::OpenGLSurface);
	this->video_opengl_context = new QOpenGLContext(this);
	this->video_opengl_context->create();
#elif (DISPQT_USE_OPENGL_WIDGET == 2)
	QWindowsWindowFunctions::setHasBorderInFullScreen(main_window->windowHandle(), true);
#endif

	this->videowidget_ffmpeg_videoctx_init();
	this->videowidget_ffmpeg_swsctx_clear();

	connect(this, SIGNAL(signal_ffmpeg_swctx_resize(int,int,bool)), this, SLOT(videowidget_ffmpeg_swctx_resize(int,int,bool)));
	connect(this, SIGNAL(signal_ffmpeg_swctx_move(int,int)), this, SLOT(videowidget_ffmpeg_swctx_move(int,int)));
	connect(this, SIGNAL(signal_ffmpeg_swctx_zoom(int,int,int)), this, SLOT(videowidget_ffmpeg_swctx_zoom(int,int,int)));
	connect(this, SIGNAL(signal_ffmpeg_filtered_trans_refresh()), this, SLOT(videowidget_ffmpeg_filtered_trans_refresh()));
	connect(this, SIGNAL(signal_ffmpeg_filtered_eq_refresh()), this, SLOT(videowidget_ffmpeg_filtered_eq_refresh()));

	//this->video_renderer_evr = new EVRCustomPresenter(NULL);
	//this->video_sample_scheduler_evr = new Scheduler(this->video_renderer_evr);
	this->video_decoder_worker = new FFMpegVideoDecoderWorker(main_window, this);
	QObject::connect(this->video_decoder_worker, SIGNAL(signal_ffmpeg_frame_display(int, unsigned int, AVFrame *, ffmpegvideo_subtitle_info_s *)), this, SLOT(videowidget_ffmpeg_frame_display(int, unsigned int, AVFrame *, ffmpegvideo_subtitle_info_s *)));
	QObject::connect(&this->video_decoder_timer, SIGNAL(timeout()), this->video_decoder_worker, SLOT(decoderworker_TimedEvent()));
	//QObject::connect(&this->video_decoder_thread, SIGNAL(finished()), this->video_decoder_worker, SLOT(deleteLater()));

	this->video_decoder_timer.start(DISPQT_FFMPEG_WORKERTHREAD_REFRESH_MS);
	this->video_decoder_timer.moveToThread(&this->video_decoder_thread);
	this->video_decoder_worker->moveToThread(&this->video_decoder_thread);
	this->video_decoder_thread.start();
}

FFMpegVideoWidget::~FFMpegVideoWidget()
{
	this->mutex_video.tryLock(DISPQT_VIDEO_MUTEX_TIMEOUT);
	this->video_decoder_timer.stop();
	if(this->video_decoder_worker){
		this->video_decoder_thread.quit();
		delete this->video_decoder_worker;
		this->video_decoder_worker = NULL;
	}
	videowidget_ffmpeg_subtitle_close();
	videowidget_ffmpeg_swsctx_close();
	videowidget_ffmpeg_videoctx_close();
	this->mutex_video.unlock();
}

// --------------------------------------------------------------------------------------------------------------------

int FFMpegVideoWidget::videowidget_ffmpeg_videoctx_init(void)
{
	int retcode = MPXPLAY_ERROR_OK;
	this->videowidget_ffmpeg_videoctx_clear();
	this->ffmpeg_filtered_frame = av_frame_alloc();
	this->ffmpeg_filtered_frame->format = this->videoout_surface_pix_fmt;
	return retcode;
}

void FFMpegVideoWidget::videowidget_ffmpeg_videoctx_close(void)
{
	this->videowidget_ffmpeg_videoctx_clear();
	av_frame_free(&this->ffmpeg_transformed_frame);
	av_frame_free(&this->ffmpeg_filtered_frame);
}

void FFMpegVideoWidget::videowidget_ffmpeg_videoctx_clear(void)
{
	this->video_aspect_ratio.num = 1;
	this->video_aspect_ratio.den = 0;
	av_frame_free(&this->ffmpeg_decoded_frame);
	this->ffmpeg_output_frame = NULL;
}
// --------------------------------------------------------------------------------------------------------------------

void FFMpegVideoWidget::videowidget_ffmpeg_swctx_resize(int window_width, int window_height, bool do_repaint)
{
	bool mutex_locked = this->mutex_video.tryLock(DISPQT_VIDEO_MUTEX_TIMEOUT);
	if(window_width < 0)
		window_width = this->width();
	if(window_height < 0)
		window_height = this->height();
	this->videowidget_ffmpeg_filtered_crop_update(false);
	this->videowidget_ffmpeg_swsctx_calculate(window_width, window_height);
	if(do_repaint)
		this->videowidget_ffmpeg_filtered_surface_update(mutex_locked);
	else if(mutex_locked)
		this->mutex_video.unlock();
}

void FFMpegVideoWidget::videowidget_ffmpeg_swctx_move(int move_x, int move_y)
{
	struct mmc_dispqt_config_s *gcfg;
	int max_crop_x, max_crop_y, prev_crop_x, prev_crop_y;

	bool mutex_locked = this->mutex_video.tryLock(DISPQT_VIDEO_MUTEX_TIMEOUT);

	gcfg = this->main_window->gui_config;
	prev_crop_x = this->sws_crop_x;
	prev_crop_y = this->sws_crop_y;

	move_x = move_x * DISPQT_CONFIG_VIDEOZOOM_SCALE / gcfg->video_zoom_value;  // scale moving to zoom rate
	max_crop_x = this->sws_srccrop_width - this->sws_crop_w;
	if(max_crop_x > 0)
	{
		this->sws_crop_x += move_x;
		if(this->sws_crop_x < this->sws_srccrop_x)
			this->sws_crop_x = this->sws_srccrop_x;
		else if(this->sws_crop_x > (this->sws_srccrop_x + max_crop_x))
			this->sws_crop_x = this->sws_srccrop_x + max_crop_x;
	}

	move_y = move_y * DISPQT_CONFIG_VIDEOZOOM_SCALE / gcfg->video_zoom_value;
	max_crop_y = this->sws_srccrop_height - this->sws_crop_h;
	if(max_crop_y > 0)
	{
		this->sws_crop_y += move_y;
		if(this->sws_crop_y < this->sws_srccrop_y)
			this->sws_crop_y = this->sws_srccrop_y;
		else if(this->sws_crop_y > (this->sws_srccrop_y + max_crop_y))
			this->sws_crop_y = this->sws_srccrop_y + max_crop_y;
	}

	// reconfig zoom center after move
	this->video_zoom_globalpos_x = this->sws_dst_width / 2;
	this->video_zoom_globalpos_y = this->sws_dst_height / 2;
	if(!this->isFullScreen()){
		this->video_zoom_globalpos_x += QWidget::mapToGlobal(this->pos()).x();
		this->video_zoom_globalpos_y += QWidget::mapToGlobal(this->pos()).y();
	}

	if(mutex_locked)
		this->mutex_video.unlock();

	if((prev_crop_x != this->sws_crop_x) || (prev_crop_y = this->sws_crop_y)){
		mpxplay_debugf(DISPQT_DEBUGOUT_FFMPEG, "MOVE cx:%d cy:%d", this->sws_crop_x, this->sws_crop_y);
		// TODO: sw crop (not QPainter based) for EVR
		this->update();
	}
}

void FFMpegVideoWidget::videowidget_ffmpeg_swctx_zoom(int globalpos_x, int globalpos_y, int direction)
{
	struct mmc_dispqt_config_s *gcfg = this->main_window->gui_config;
	int i, zoom_val, center_values[2];

	if((this->last_video_zoom_min_val <= 0) || (this->last_video_zoom_ti_val <= 0) || (this->sws_srccrop_width <= 0) || (this->sws_srccrop_height <= 0) || (gcfg->video_zoom_value <= 0))
		return;

	zoom_val = gcfg->video_zoom_value + direction * DISPQT_CONFIG_VIDEOZOOM_STEP * gcfg->video_zoom_value / DISPQT_CONFIG_VIDEOZOOM_SCALE;

	center_values[0] = DISPQT_CONFIG_VIDEOZOOM_CENTER;
	center_values[1] = this->last_video_zoom_ti_val;
	mpxplay_debugf(DISPQT_DEBUGOUT_CROP, "swctx_zoom cv:%d zv:%d mv:%d", gcfg->video_zoom_value, zoom_val, this->last_video_zoom_min_val);

	if(zoom_val < this->last_video_zoom_min_val)
		zoom_val = this->last_video_zoom_min_val;
	else if(zoom_val > DISPQT_CONFIG_VIDEOZOOM_MAX)
		zoom_val = DISPQT_CONFIG_VIDEOZOOM_MAX;

	for(i = 0; i < (sizeof(center_values) / sizeof(center_values[0])); i++)
	{
		const int center = center_values[i];
		//mpxplay_debugf(DISPQT_DEBUGOUT_CROP, "swctx_zoom i:%d c:%d", i, center);
		if(((gcfg->video_zoom_value > center) && (zoom_val <= (center + center / 10))) || ((gcfg->video_zoom_value < center) && (zoom_val >= (center - center / 10)))){
			zoom_val = center;
			mpxplay_debugf(DISPQT_DEBUGOUT_CROP, "swctx_zoom i:%d c:%d", i, center);
			break;
		}
	}

	mpxplay_debugf(DISPQT_DEBUGOUT_CROP, "swctx_zoom zt:%d zv:%d mv:%d", gcfg->video_zoom_type, zoom_val, this->last_video_zoom_min_val);

	if(gcfg->video_zoom_value != zoom_val)
	{
		gcfg->video_zoom_type = DISPQT_VIDEOFULLSCREENZOOMTYPE_MANUAL;
		gcfg->video_zoom_value = zoom_val;
		if(globalpos_x >= 0)
			this->video_zoom_globalpos_x = globalpos_x;
		if(globalpos_y >= 0)
			this->video_zoom_globalpos_y = globalpos_y;
		this->videowidget_ffmpeg_swctx_resize(-1,-1,true);
	}
	this->main_window->qt_video_player->video_mouse_cursor_show(true);
}

void FFMpegVideoWidget::resizeEvent(QResizeEvent *e)
{
	this->videowidget_ffmpeg_swctx_resize(e->size().width(), e->size().height(), false);
#if (DISPQT_USE_OPENGL_WIDGET == 2)
	QOpenGLWidget::resizeEvent(e);
#else
	QWidget::resizeEvent(e);
#endif
}

void FFMpegVideoWidget::videowidget_ffmpeg_subtitle_render(QPainter *painter, ffmpegvideo_subtitle_info_s *subtitle_infos)
{
	struct mmc_dispqt_config_s *gcfg = this->main_window->gui_config;
	int i, scale, charpixelsize, x, y, cy, cx;
	unsigned int flags;
#ifdef DISPQT_FFMPEG_TEST_SPEED
	mpxp_int64_t render_time = pds_gettimem();
#endif

	if(!painter || !subtitle_infos)
		return;

	if(subtitle_infos->last_size_x && subtitle_infos->last_size_y)
	{
		// clear black subtitle areas
		if(subtitle_infos->last_render_x < this->sws_dst_pos_x)
			painter->fillRect(0, 0, this->sws_dst_pos_x, this->height(), Qt::black); // left side
		cx = this->sws_dst_pos_x + this->sws_dst_width;
		if((this->width() > cx) && ((subtitle_infos->last_render_x + subtitle_infos->last_size_x) > cx))
			painter->fillRect(cx, 0, this->width() - cx, this->height(), Qt::black);  // right side
		if(subtitle_infos->last_render_y < this->sws_dst_pos_y)
			painter->fillRect(0, 0, this->width(), this->sws_dst_pos_y, Qt::black);   // top side
		cy = this->sws_dst_pos_y + this->sws_dst_height;
		if((this->height() > cy) && ((subtitle_infos->last_render_y + subtitle_infos->last_size_y) > cy))
			painter->fillRect(0, cy, this->width(), this->height() - cy, Qt::black);  // bottom side
	}

	subtitle_infos->last_render_x = subtitle_infos->last_render_y = subtitle_infos->last_size_x = subtitle_infos->last_size_y = 0;

	if(!subtitle_infos->nb_lines)
		return;

	scale = 100 * this->height() / QApplication::desktop()->screenGeometry().height();
	if(scale < 5)
		scale = 5;
	else if(scale > 100)
		scale = 100;

	if(gcfg->font_text_name[DISPQT_FONTTEXTID_SUBTITLE])
		this->font_subtitle.fromString(QString::fromUtf8(gcfg->font_text_name[DISPQT_FONTTEXTID_SUBTITLE])); // TODO: move out from here
	charpixelsize = this->font_subtitle.pointSize();
	charpixelsize = charpixelsize * scale / 100;
	this->font_subtitle.setPointSize(charpixelsize);
	painter->setFont(this->font_subtitle);

	y = this->height() - subtitle_infos->nb_lines * charpixelsize * 3 / 2 - charpixelsize * 2;
	if(y < 0)
		y = 0;
	subtitle_infos->last_render_y = y;
	flags = 0;

	for(i = 0; i < subtitle_infos->nb_lines; i++)
	{
		QFontMetrics fm(this->font_subtitle);
		int textpixellen = fm.width(QString::fromUtf8(subtitle_infos->line_texts[i]));
		if(textpixellen <= 0)
			continue;
		x = this->width() / 2 - textpixellen / 2;
		if(x < 0)
			x = 0;
		if(flags != subtitle_infos->line_flags[i]){
			flags = subtitle_infos->line_flags[i];
			this->font_subtitle.setItalic((flags & INFFMPEG_ASS_DECODED_FLAG_ITALIC)? true : false);
			painter->setFont(this->font_subtitle);
		}
		painter->setPen(Qt::black);
		painter->drawText(x + 2, y + 2, QString::fromUtf8(subtitle_infos->line_texts[i])); // shadow

		painter->setPen(Qt::white);
		painter->drawText(x, y, QString::fromUtf8(subtitle_infos->line_texts[i])); // white text

		//mpxplay_debugf(DISPQT_DEBUGOUT_SUBTITLE, "SUBTITLE drawText: w:%d cs:%d x:%d y:%d l:%d cy:%d %s", this->width(), charpixelsize, x, y, i, cy, subtitle_infos->line_texts[i]);
		y += charpixelsize * 3 / 2;
		subtitle_infos->last_render_x = (i == 0)? x : min(x, subtitle_infos->last_render_x);
		subtitle_infos->last_size_x = max(subtitle_infos->last_size_x, textpixellen);
	}
	subtitle_infos->last_size_y = y - subtitle_infos->last_render_y;
#ifdef DISPQT_FFMPEG_TEST_SPEED
	render_time = pds_gettimem() - render_time;
	if(render_time > 0){
		//mpxplay_debugf(DISPQT_DEBUGOUT_RENDER, "videowidget_ffmpeg_subtitle_render TIME %llu", render_time);
	}
#endif
}

void FFMpegVideoWidget::videowidget_ffmpeg_subtitle_close(void)
{
	if(this->sub_title_infos)
	{
		video_ffmpeg_subtitleinfo_clear(this->sub_title_infos);
		av_freep(&this->sub_title_infos);
	}
}

//-------------------------------------------------------------------------------------------------------------------------------------
#ifdef MPXPLAY_USE_DEBUGF
static unsigned int dispqt_render_counter = 0;
#endif

void FFMpegVideoWidget::videowidget_ffmpeg_filtered_crop_update(bool check_autocrop)
{
	struct mmc_dispqt_config_s *gcfg = this->main_window->gui_config;
	if(this->last_video_crop_type != gcfg->video_crop_type)
	{
		this->last_video_crop_type = gcfg->video_crop_type;
		if(!this->ffmpeg_swsframe_cropreset)
			this->ffmpeg_swsframe_cropreset = DISPQT_VIDEO_AUTOCROP_COUNTER_REFRESH;
		this->sws_autoxcrop_framecounter = 0;
		check_autocrop = true;
	}
	if((gcfg->video_crop_type == DISPQT_VIDEOSCRCROPTYPE_AUTO) && check_autocrop)
	{
		AVFrame *autocrop_input_frame = ((this->ffmpeg_swsframe_hasdata)? this->ffmpeg_sws_frame : ((this->ffmpeg_transformed_frame)? this->ffmpeg_transformed_frame : this->ffmpeg_decoded_frame)); // if the video is RGB, we use the original frame
		if(this->videowidget_ffmpeg_swsctx_crop_auto_detect(autocrop_input_frame))
		{
			this->videoout_surface_recalc = true;
		}
	}
}

void FFMpegVideoWidget::videowidget_ffmpeg_filtered_trans_refresh(void)
{
	if(this->mutex_video.tryLock(DISPQT_VIDEO_MUTEX_TIMEOUT))
	{
		this->videoout_surface_recalc = true;
		this->videowidget_ffmpeg_filtered_trans_update(true);
	}
}

void FFMpegVideoWidget::videowidget_ffmpeg_filtered_trans_update(bool mutex_locked)
{
	struct mmc_dispqt_config_s *gcfg = this->main_window->gui_config;
	if(!mutex_locked)
		mutex_locked = this->mutex_video.tryLock(DISPQT_VIDEO_MUTEX_TIMEOUT);
	av_frame_free(&this->ffmpeg_transformed_frame);
	if(this->ffmpeg_decoded_frame)
	{
		DispQtVideoFrameFilter *vfilter_process_trans = this->main_window->qt_video_player->video_frame_filter_transform_process;
		if(vfilter_process_trans->video_filter_check(this->ffmpeg_decoded_frame, AV_PIX_FMT_NONE) != AV_PIX_FMT_NONE)
		{
			DispQtVideoFrameFilter *vfilter_process_eq = this->main_window->qt_video_player->video_frame_filter_eq_process;
			int frameout_format = (vfilter_process_eq->video_filter_check(this->ffmpeg_decoded_frame, AV_PIX_FMT_NONE) == AV_PIX_FMT_NONE)? this->videoout_surface_pix_fmt : DISPQT_VIDEO_FILTERPROCESS_AVFORMAT;
			int retVal = vfilter_process_trans->video_filter_apply(this->ffmpeg_decoded_frame, &this->ffmpeg_transformed_frame, frameout_format);
			if(this->ffmpeg_transformed_frame && ((this->sws_src_width != this->ffmpeg_transformed_frame->width) || (this->sws_src_height != this->ffmpeg_transformed_frame->height)))
			{
				this->sws_src_width = this->ffmpeg_transformed_frame->width;
				this->sws_src_height = this->ffmpeg_transformed_frame->height;
				this->videoout_surface_recalc = true;
			}
		}
		else if((this->sws_src_width != this->ffmpeg_decoded_frame->width) || (this->sws_src_height != this->ffmpeg_decoded_frame->height))
		{
			this->sws_src_width = this->ffmpeg_decoded_frame->width;
			this->sws_src_height = this->ffmpeg_decoded_frame->height;
			this->videoout_surface_recalc = true;
		}
		this->ffmpeg_swsframe_hasdata = videowidget_ffmpeg_swsctx_conv_frame((this->ffmpeg_transformed_frame)? this->ffmpeg_transformed_frame : this->ffmpeg_decoded_frame);
		this->videowidget_ffmpeg_filtered_crop_update(true);
	}
	if(this->videoout_surface_recalc)
	{
		this->videowidget_ffmpeg_swsctx_calculate(this->width(), this->height());
	}
	this->videowidget_ffmpeg_filtered_eq_update(mutex_locked);
}

void FFMpegVideoWidget::videowidget_ffmpeg_filtered_eq_refresh(void)
{
	if(this->mutex_video.tryLock(DISPQT_VIDEO_MUTEX_TIMEOUT))
		videowidget_ffmpeg_filtered_eq_update(true);
}

void FFMpegVideoWidget::videowidget_ffmpeg_filtered_eq_update(bool mutex_locked)
{
	AVFrame *filter_in_frame;
	if(!mutex_locked)
		mutex_locked = this->mutex_video.tryLock(DISPQT_VIDEO_MUTEX_TIMEOUT);
	this->ffmpeg_output_frame = NULL;
	av_frame_unref(this->ffmpeg_filtered_frame);
	filter_in_frame = (this->ffmpeg_transformed_frame)? this->ffmpeg_transformed_frame : this->ffmpeg_decoded_frame;
	if(filter_in_frame)
	{
		DispQtVideoFrameFilter *vfilter_process_eq = this->main_window->qt_video_player->video_frame_filter_eq_process;
		bool swsframe_for_outsurface = videowidget_ffmpeg_swsctx_check_swsframe_for_outsurface();
		if((!swsframe_for_outsurface && (filter_in_frame->format != this->videoout_surface_pix_fmt)) || (vfilter_process_eq->video_filter_check(filter_in_frame, AV_PIX_FMT_NONE) != AV_PIX_FMT_NONE))
		{
			int retVal = vfilter_process_eq->video_filter_apply(filter_in_frame, &this->ffmpeg_filtered_frame, this->videoout_surface_pix_fmt);
			if(retVal & DISPQT_VIDEOFILTER_APPLYRETBIT_OUTPUTFRAME_PRODUCED)
				this->ffmpeg_output_frame = this->ffmpeg_filtered_frame;
			mpxplay_debugf(DISPQT_DEBUGOUT_RENDER, "paintEvent    c:%4d filter r:%d sw:%d sh:%d dw:%d dh:%d", dispqt_render_counter, retVal, ffmpeg_filter_input_frame->width, ffmpeg_filter_input_frame->height, this->ffmpeg_output_frame->width, this->ffmpeg_output_frame->height);
		}
		else
		{
			this->ffmpeg_output_frame = (swsframe_for_outsurface)? this->ffmpeg_sws_frame : filter_in_frame;
		}
	}
	videowidget_ffmpeg_filtered_surface_update(mutex_locked);
}

void FFMpegVideoWidget::videowidget_ffmpeg_filtered_surface_update(bool mutex_locked)
{
	if(mutex_locked)
		this->mutex_video.unlock();
	this->repaint();
}

void FFMpegVideoWidget::paintEvent(QPaintEvent *event)
{
#if (DISPQT_USE_OPENGL_WIDGET == 1)
	this->video_opengl_context->makeCurrent(this->video_opengl_context->surface());
	QOpenGLPaintDevice device(size() * devicePixelRatio());
	QPainter p(&device);
//#elif (DISPQT_USE_OPENGL_WIDGET == 2)
//    QOpenGLPaintDevice device(size() * devicePixelRatio());
//    QPainter p(&device);
#else
	QPainter p;
#endif
	if(!this->mutex_video.tryLock(0))
	{
		return;
	}
	p.begin(this);
	if(this->videoout_surface_clearpic || !this->ffmpeg_output_frame)
	{
		p.fillRect(0,0,this->width(),this->height(),Qt::black);
		this->videoout_surface_clearpic = false;
		//mpxplay_debugf(DISPQT_DEBUGOUT_RENDER, "paintEvent c:%4d CLEARPIC", dispqt_render_counter);
	}
	if(this->ffmpeg_output_frame)
	{
		p.setRenderHint(QPainter::SmoothPixmapTransform, true);
		QImage image(this->ffmpeg_output_frame->data[0], this->ffmpeg_output_frame->width, this->ffmpeg_output_frame->height, DISPQT_VIDEO_QIMAGE_OUTPUT_QFORMAT);
		p.drawImage(QRectF(this->sws_dst_pos_x, this->sws_dst_pos_y, this->sws_dst_width, this->sws_dst_height), image,
				QRectF(this->sws_crop_x, this->sws_crop_y, this->sws_crop_w, this->sws_crop_h), Qt::NoFormatConversion);
		//p.drawImage(QRect(this->sws_dst_pos_x, this->sws_dst_pos_y, this->sws_dst_width, this->sws_dst_height), image);
		this->videowidget_ffmpeg_subtitle_render(&p, this->sub_title_infos);
	}
	p.end();
#if (DISPQT_USE_OPENGL_WIDGET == 1)
	this->video_opengl_context->swapBuffers(this->video_opengl_context->surface());
//#elif (DISPQT_USE_OPENGL_WIDGET == 2)
//	QOpenGLWidget::paintEvent(event);
#endif
#ifdef MPXPLAY_USE_DEBUGF
	dispqt_render_counter++;
#endif
	this->mutex_video.unlock();
}

//-------------------------------------------------------------------------------------------------------------------------------------
static int videowidget_ffmpeg_swsctx_get_custom_ar(char *custom_str, int scale_value, int default_value)
{
	int ar_value = default_value;
	char *ar_separator = pds_strchr(custom_str, DISPQT_CONFIG_VIDEOAR_DIVISORCHAR);
	if(ar_separator){
		int ar_custom_x = pds_atol(custom_str);
		int ar_custom_y = pds_atol(ar_separator + 1);
		if((ar_custom_x > 0) && (ar_custom_y > 0)){
			int ar = scale_value * ar_custom_x / ar_custom_y;
			if((ar >= (scale_value / 100)) && (ar <= (scale_value * 100))) // FIXME: / 100
				ar_value = ar;
		}
	}
	return ar_value;
}

#if DISPQT_VIDEO_AUTOCROP_USE_RGB32
#define DISPQT_VIDEO_AUTOCROP_RGB32_LIMITMASK 0x00e0e0e0
#else
#define DISPQT_VIDEO_AUTOCROP_YUV8_LIMITMASK 0xc0  // 8 bit planar Y values
#endif

bool FFMpegVideoWidget::videowidget_ffmpeg_swsctx_crop_auto_detect(AVFrame *input_video_frame)
{
	if(!input_video_frame)
		return false;
#if DISPQT_VIDEO_AUTOCROP_USE_RGB32
	const uint32_t limit_mask = DISPQT_VIDEO_AUTOCROP_RGB32_LIMITMASK;
#else
	const uint32_t limit_mask = DISPQT_VIDEO_AUTOCROP_YUV8_LIMITMASK;
#endif
	const int skip_border = DISPQT_CONFIG_VIDEO_AUTOCROP_SKIP_BORDER;
	const int check_range = 100 / DISPQT_CONFIG_VIDEO_AUTOCROP_CHECK_RANGE;
	uint8_t *video_data = input_video_frame->data[0];
	const int width = input_video_frame->width, height = input_video_frame->height;
	int x, y, check_len, counter, crop_x = 0, crop_y = 0, crop_width = 0, crop_height = 0;
#if DISPQT_VIDEO_AUTOCROP_USE_RGB32
	uint32_t *data32_begin = (uint32_t *)video_data, *data32;
#else
	uint8_t *data32_begin = (uint8_t *)video_data, *data32;
#endif
	bool retval = false;

	if(this->ffmpeg_swsframe_cropreset == DISPQT_VIDEO_AUTOCROP_COUNTER_OPEN){
		this->sws_srccrop_x = 0;
		this->sws_srccrop_y = 0;
		this->sws_srccrop_width = width;
		this->sws_srccrop_height = height;
		this->ffmpeg_swsframe_cropreset --;
		this->sws_autoxcrop_framecounter = 0;
		this->videoout_surface_clearpic = true;
		retval = true;
		mpxplay_debugf(DISPQT_DEBUGOUT_ACROP, "AUTOCROP0 x:%d y:%d w:%d h:%d sw:%d sh:%d", crop_x, crop_y, crop_width, crop_height, this->sws_src_width, this->sws_src_height);
	}
	if(!data32_begin || (width < 32) || (height < 32) || (input_video_frame->format != DISPQT_VIDEO_AUTOCROP_AVFORMAT))
		return false;
	if(this->sws_autoxcrop_framecounter > 0){
		this->sws_autoxcrop_framecounter--;
		return false;
	}

	// check top side
	x = skip_border;
	y = skip_border;
	data32 = data32_begin + y * width + x;
	check_len = height / check_range;
	do{
		if(*data32 & limit_mask){
			//mpxplay_debugf(DISPQT_DEBUGOUT_CROP, "auto1 data:%8.8X m:%8.8X x:%d y:%d", *data32, limit_mask, x,y);
			break;
		}
		data32++;
		if(++x >= (width - skip_border)){
			x = skip_border; y++;
			data32 = data32_begin + y * width + x;
		}
	}while(y < check_len);

	if(y >= check_len)  // probably full (or partially) black picture
		goto err_out_autocrop_black;
	if(y > skip_border)
		crop_y = y;

	// check bottom side
	x = skip_border;
	y = skip_border;
	data32 = data32_begin + (height - y) * width + x;
	check_len = height / check_range;
	do{
		if(*data32 & limit_mask){
			//mpxplay_debugf(DISPQT_DEBUGOUT_CROP, "auto1 data:%8.8X m:%8.8X c:%d", *data32, limit_mask, counter);
			break;
		}
		data32++;
		if(++x >= (width - skip_border)){
			x = skip_border; y++;
			data32 = data32_begin + (height - y) * width + x;
		}
	}while(y < check_len);

	if(y >= check_len)  // partially black picture
		goto err_out_autocrop_black;

	if(this->ffmpeg_swsframe_cropreset){ // top and bottom side crop shall be the same initially
		if(y > crop_y)
			y = crop_y;
		else if(crop_y > y)
			crop_y = y;
	}

	crop_height = height - crop_y;
	if((y > skip_border) && (y < check_len))
		crop_height -= y;

	//check left side
	x = skip_border;
	y = skip_border;
	data32 = data32_begin + y * width + x;
	check_len = width / check_range;
	do{
		if(*data32 & limit_mask){
			//mpxplay_debugf(DISPQT_DEBUGOUT_CROP, "auto3 data:%8.8X m:%8.8X x:%d", *data32, limit_mask, x);
			break;
		}
		data32 += width;
		if(++y >= (height - skip_border)){
			x++; y = skip_border;
			data32 = data32_begin + y * width + x;
		}
	}while(x < check_len);

	if(x >= check_len) // partially black picture
		goto err_out_autocrop_black;
	if((x > skip_border) && (x < check_len))
		crop_x = x - 1;

	//check right side
	x = skip_border;
	y = skip_border;
	data32 = data32_begin + y * width + width - x;
	check_len = width / check_range;
	do{
		if(*data32 & limit_mask)
			break;
		data32 += width;
		if(++y >= (height - skip_border)){
			x++; y = skip_border;
			data32 = data32_begin + y * width + width - x;
		}
	}while(x < check_len);

	if(x >= check_len) // partially black picture
		goto err_out_autocrop_black;

	if(this->ffmpeg_swsframe_cropreset){ // left and right side crop shall be the same initially
		if(x > crop_x)
			x = crop_x;
		else if(crop_x > x)
			crop_x = x;
	}

	crop_width = width - crop_x;
	if((x > skip_border) && (x < check_len))
		crop_width -= x - 1;

	if(this->ffmpeg_swsframe_cropreset){
		this->sws_srccrop_x = crop_x;
		this->sws_srccrop_y = crop_y;
		this->sws_srccrop_width = crop_width;
		this->sws_srccrop_height = crop_height;
		this->ffmpeg_swsframe_cropreset --;
		this->videoout_surface_clearpic = true;
		mpxplay_debugf(DISPQT_DEBUGOUT_ACROP, "AUTOCROP1 x:%d y:%d w:%d h:%d sw:%d sh:%d", crop_x, crop_y, crop_width, crop_height, this->sws_src_width, this->sws_src_height);
		retval = true;
	}else{
		const int diff_change = DISPQT_CONFIG_VIDEO_AUTOCROP_SENSITIVITY;
		const int change_step_x = DISPQT_CONFIG_VIDEO_AUTOCROP_STEP, change_step_y = DISPQT_CONFIG_VIDEO_AUTOCROP_STEP;
		int change_step_w, change_step_h;

		change_step_w = change_step_x * 2;
		change_step_h = change_step_y * 2;

		if((crop_x + diff_change ) < this->sws_srccrop_x){
			this->sws_srccrop_x -= change_step_x;  retval = true;
		}else if(crop_x > (this->sws_srccrop_x + diff_change)){
			this->sws_srccrop_x += change_step_x;  retval = true;
		}else{
			change_step_w = change_step_x;
		}
		if((crop_y + diff_change ) < this->sws_srccrop_y){
			this->sws_srccrop_y -= change_step_y;  retval = true;
		}else if(crop_y > (this->sws_srccrop_y + diff_change)){
			this->sws_srccrop_y += change_step_y;  retval = true;
		}else{
			change_step_h = change_step_y;
		}
		if((crop_width + diff_change ) < this->sws_srccrop_width){
			this->sws_srccrop_width -= change_step_w;  retval = true;
		}else if(crop_width > (this->sws_srccrop_width + diff_change)){
			this->sws_srccrop_width += change_step_w;  retval = true;
		}
		if((crop_height + diff_change ) < this->sws_srccrop_height){
			this->sws_srccrop_height -= change_step_h;  retval = true;
		}else if(crop_height > (this->sws_srccrop_height + diff_change)){
			this->sws_srccrop_height += change_step_h;  retval = true;
		}

		mpxplay_debugf(DISPQT_DEBUGOUT_ACROP, "AUTOCROP2 r:%d CR:%d x:%d y:%d w:%d h:%d sw:%d sw:%d cx:%d cy:%d cw:%d ch:%d sx:%d sy:%d",
				(int)retval, this->ffmpeg_swsframe_cropreset, crop_x, crop_y, crop_width, crop_height, this->sws_src_width, this->sws_src_height,
				this->sws_srccrop_x, this->sws_srccrop_y, this->sws_srccrop_width, this->sws_srccrop_height, change_step_x, change_step_y);
		this->sws_autoxcrop_framecounter = (retval)? DISPQT_CONFIG_VIDEO_AUTOCROP_SHORTDELAY : DISPQT_CONFIG_VIDEO_AUTOCROP_LONGDELAY;
	}

	return retval;

err_out_autocrop_black:
	this->sws_autoxcrop_framecounter = DISPQT_CONFIG_VIDEO_AUTOCROP_SKIPDELAY;
	return retval;
}

bool FFMpegVideoWidget::videowidget_ffmpeg_swsctx_calculate(int window_width, int window_height)
{
	struct mmc_dispqt_config_s *gcfg;
	int video_ar, video_crop_ar = 0, window_ar, widget_global_pos_x, widget_global_pos_y;
	unsigned long ar_type;

	if((this->sws_src_width <= 0) || (this->sws_src_height <= 0))
		return false;

	this->sws_dst_width = window_width & DISPQT_VIDEO_ROUND_MASK;
	this->sws_dst_height = window_height & DISPQT_VIDEO_ROUND_MASK;

	if((window_width <= 0) || (window_height <= 0))
		return false;

	gcfg = this->main_window->gui_config;

	video_ar = DISPQT_VIDEO_AR_SCALE;
	video_ar *= this->sws_src_width * ((this->video_aspect_ratio.num > 0)? this->video_aspect_ratio.num : 1);
	video_ar /= this->sws_src_height * ((this->video_aspect_ratio.den > 0)? this->video_aspect_ratio.den : 1);

	// --- crop ratio calculate (manual, auto) -------------------------------------------------------------------------------------

	if((gcfg->video_crop_type != DISPQT_VIDEOSCRCROPTYPE_AUTO) || !this->sws_srccrop_width || !this->sws_srccrop_height
	 || (this->sws_srccrop_width > this->sws_src_width) || (this->sws_srccrop_height > this->sws_src_height)
	){
		this->sws_srccrop_x = this->sws_srccrop_y = 0;
		this->sws_srccrop_width = this->sws_src_width;
		this->sws_srccrop_height = this->sws_src_height;
	}

	switch(gcfg->video_crop_type)
	{
		case DISPQT_VIDEOSCRCROPTYPE_AUTO:  video_ar = video_ar * this->sws_srccrop_width / this->sws_src_width * this->sws_src_height / this->sws_srccrop_height; break;
		case DISPQT_VIDEOSCRCROPTYPE_4_3:   video_crop_ar = DISPQT_VIDEO_AR_SCALE * 4 / 3; break;
		case DISPQT_VIDEOSCRCROPTYPE_16_9:  video_crop_ar = DISPQT_VIDEO_AR_SCALE * 16 / 9; break;
		case DISPQT_VIDEOSCRCROPTYPE_235_1: video_crop_ar = DISPQT_VIDEO_AR_SCALE * 235 / 100; break;
		case DISPQT_VIDEOSCRCROPTYPE_CUSTOM:video_crop_ar = videowidget_ffmpeg_swsctx_get_custom_ar(gcfg->video_crop_custom, DISPQT_VIDEO_AR_SCALE, 0); break;
	}

	if((video_crop_ar > 0) && (video_crop_ar != video_ar))
	{
		if(video_ar > video_crop_ar)
			this->sws_srccrop_width = this->sws_src_width * video_crop_ar / video_ar;
		else
			this->sws_srccrop_height = this->sws_src_height * video_ar / video_crop_ar;
		this->sws_srccrop_x = (this->sws_src_width - this->sws_srccrop_width) / 2;
		this->sws_srccrop_y = (this->sws_src_height - this->sws_srccrop_height) / 2;
		mpxplay_debugf(DISPQT_DEBUGOUT_CROP, "CROP var:%d car:%d sw:%d sh:%d cw:%d ch:%d", video_ar, video_crop_ar, this->sws_src_width, this->sws_src_height, this->sws_srccrop_width, this->sws_srccrop_height);
		video_ar = video_crop_ar;
	}

	// --- aspect ratio calculate -------------------------------------------------------------------------------------

	if(this->ffmpeg_decoded_frame && (this->ffmpeg_decoded_frame->flags & DISPQT_AVFRAME_FLAG_AR_WINDOW)) // window (fill) aspect ratio by decoder (eg. visualization output)
	{
		ar_type = DISPQT_VIDEOFULLSCREENARTYPE_FILL;
	}
	else
	{
		ar_type = (this->isFullScreen() || (gcfg->video_windowed_ar_type == DISPQT_VIDEOWINDOWEDARTYPE_EQFULLSCREEN))? gcfg->video_fullscreen_ar_type : gcfg->video_windowed_ar_type;
	}

	window_ar = DISPQT_VIDEO_AR_SCALE * window_width / window_height;

	switch(ar_type){
		case DISPQT_VIDEOFULLSCREENARTYPE_FILL:  video_ar = window_ar; break;
		case DISPQT_VIDEOFULLSCREENARTYPE_1_1:   video_ar = DISPQT_VIDEO_AR_SCALE; break;
		case DISPQT_VIDEOFULLSCREENARTYPE_4_3:   video_ar = DISPQT_VIDEO_AR_SCALE * 4 / 3; break;
		case DISPQT_VIDEOFULLSCREENARTYPE_16_9:  video_ar = DISPQT_VIDEO_AR_SCALE * 16 / 9; break;
		case DISPQT_VIDEOFULLSCREENARTYPE_235_1: video_ar = DISPQT_VIDEO_AR_SCALE * 235 / 100; break;
		case DISPQT_VIDEOFULLSCREENARTYPE_CUSTOM:video_ar = videowidget_ffmpeg_swsctx_get_custom_ar(gcfg->video_ar_custom, DISPQT_VIDEO_AR_SCALE, DISPQT_VIDEO_AR_SCALE); break;
		case DISPQT_VIDEOFULLSCREENARTYPE_PIXEL: video_ar = DISPQT_VIDEO_AR_SCALE * this->sws_src_width / this->sws_src_height; break;
	}

	// --- zoom value calculate -----------------------------------------------------------------------------------------

	const int prev_crop_w = this->sws_crop_w;
	const int prev_crop_h = this->sws_crop_h;
	const int pixel_ar = DISPQT_VIDEO_AR_SCALE * this->sws_srccrop_width / this->sws_srccrop_height;

	window_width = this->sws_srccrop_width;
	window_height = this->sws_srccrop_height;

	if(video_ar > pixel_ar)
		window_height = window_height * pixel_ar / video_ar;
	else if((video_ar > 0) && (video_ar < pixel_ar))
		window_width = window_width * video_ar / pixel_ar;

	if((window_width <= 0) || (window_height <= 0))
		return false;

	const int zoom_val_ti_w = (DISPQT_CONFIG_VIDEOZOOM_SCALE * this->sws_dst_width + (window_width >> 1)) / window_width;
	const int zoom_val_ti_h = (DISPQT_CONFIG_VIDEOZOOM_SCALE * this->sws_dst_height + (window_height >> 1)) / window_height;
	const int zoom_val_to = max(zoom_val_ti_w , zoom_val_ti_h);
	const int zoom_val_ti = (ar_type == DISPQT_VIDEOFULLSCREENARTYPE_FILL)? zoom_val_to : min(zoom_val_ti_w , zoom_val_ti_h);
	int zoom_value = DISPQT_CONFIG_VIDEOZOOM_CENTER;

	this->last_video_zoom_ti_val = zoom_val_ti;
//	this->last_video_zoom_min_val = ((ar_type == DISPQT_VIDEOFULLSCREENARTYPE_FILL) || (zoom_val_ti < DISPQT_CONFIG_VIDEOZOOM_MIN))? zoom_val_ti : DISPQT_CONFIG_VIDEOZOOM_MIN; // FIXME: if video resolution is larger than window, zoom is incorrect in fill mode below zoom_val_ti
	this->last_video_zoom_min_val = (zoom_val_ti < DISPQT_CONFIG_VIDEOZOOM_MIN)? zoom_val_ti : DISPQT_CONFIG_VIDEOZOOM_MIN;

	switch(gcfg->video_zoom_type)
	{
		case DISPQT_VIDEOFULLSCREENZOOMTYPE_TOUCHINSIDE: zoom_value = zoom_val_ti; break;
		case DISPQT_VIDEOFULLSCREENZOOMTYPE_TOUCHOUTSIDE: zoom_value = zoom_val_to; break;
		case DISPQT_VIDEOFULLSCREENZOOMTYPE_2_1: zoom_value = 2 * DISPQT_CONFIG_VIDEOZOOM_SCALE; break;
		case DISPQT_VIDEOFULLSCREENZOOMTYPE_4_1: zoom_value = 4 * DISPQT_CONFIG_VIDEOZOOM_SCALE; break;
		case DISPQT_VIDEOFULLSCREENZOOMTYPE_CUSTOM: zoom_value = videowidget_ffmpeg_swsctx_get_custom_ar(gcfg->video_zoom_custom, DISPQT_CONFIG_VIDEOZOOM_SCALE, DISPQT_CONFIG_VIDEOZOOM_SCALE); break;
		case DISPQT_VIDEOFULLSCREENZOOMTYPE_MANUAL: zoom_value = gcfg->video_zoom_value; break;
	}

	if(zoom_value < this->last_video_zoom_min_val)
	{
		zoom_value = this->last_video_zoom_min_val;
	}
	else if(zoom_value > DISPQT_CONFIG_VIDEOZOOM_MAX)
	{
		zoom_value = DISPQT_CONFIG_VIDEOZOOM_MAX;
	}

	if(zoom_value != DISPQT_CONFIG_VIDEOZOOM_CENTER){
		window_width = window_width * zoom_value / DISPQT_CONFIG_VIDEOZOOM_SCALE;
		window_height = window_height * zoom_value / DISPQT_CONFIG_VIDEOZOOM_SCALE;
	}

	//window_width = (window_width + DISPQT_VIDEO_ROUND_VALUE) & DISPQT_VIDEO_ROUND_MASK;
	//window_height = (window_height + DISPQT_VIDEO_ROUND_VALUE) & DISPQT_VIDEO_ROUND_MASK;

	this->sws_crop_w = this->sws_srccrop_width;
	this->sws_crop_h = this->sws_srccrop_height;

	if(window_width > this->sws_dst_width){
		this->sws_crop_w = this->sws_srccrop_width * this->sws_dst_width / window_width;
		window_width = this->sws_dst_width;
	}
	if(window_height > this->sws_dst_height){
		this->sws_crop_h = this->sws_srccrop_height * this->sws_dst_height / window_height;
		window_height = this->sws_dst_height;
	}

	//this->sws_crop_w = (this->sws_crop_w + DISPQT_VIDEO_ROUND_VALUE) & DISPQT_VIDEO_ROUND_MASK;
	//this->sws_crop_h = (this->sws_crop_h + DISPQT_VIDEO_ROUND_VALUE) & DISPQT_VIDEO_ROUND_MASK;

	gcfg->video_zoom_value = zoom_value;

	// --- zoom center (source view window) calculate -----------------------------------------------------------------------------------------

	if(this->isFullScreen()){
		widget_global_pos_x = widget_global_pos_y = 0;
	}else{
		widget_global_pos_x = QWidget::mapToGlobal(this->pos()).x();
		widget_global_pos_y = QWidget::mapToGlobal(this->pos()).y();
	}

	int max_crop_x = this->sws_srccrop_width - this->sws_crop_w;
	int max_crop_y = this->sws_srccrop_height - this->sws_crop_h;

	if( (gcfg->video_zoom_type == DISPQT_VIDEOFULLSCREENZOOMTYPE_TOUCHINSIDE) || (gcfg->video_zoom_type == DISPQT_VIDEOFULLSCREENZOOMTYPE_TOUCHOUTSIDE)){
		this->video_zoom_globalpos_x = widget_global_pos_x + this->sws_dst_width / 2;
		this->video_zoom_globalpos_y = widget_global_pos_y + this->sws_dst_height / 2;
		this->sws_crop_x = this->sws_srccrop_x + max_crop_x / 2;
		this->sws_crop_y = this->sws_srccrop_y + max_crop_y / 2;
	}
	else // 1:1, 2:1, 4:1, custom, manual
	{
		int zoom_widget_pos_x = this->video_zoom_globalpos_x - widget_global_pos_x;

		if((zoom_widget_pos_x >= 0) && (zoom_widget_pos_x < this->sws_dst_width))
		{
			if(prev_crop_w > 0){
				int sws_prev_pos_x = this->sws_crop_x + prev_crop_w * zoom_widget_pos_x / this->sws_dst_width;
				mpxplay_debugf(DISPQT_DEBUGOUT_FFMPEG, "ZOOMX px:%d", sws_prev_pos_x);
				this->sws_crop_x = sws_prev_pos_x - this->sws_crop_w * zoom_widget_pos_x / this->sws_dst_width;
			}else{
				this->sws_crop_x = this->sws_srccrop_x + max_crop_x * zoom_widget_pos_x / this->sws_dst_width;
			}
			if(this->sws_crop_x < this->sws_srccrop_x)
				this->sws_crop_x = this->sws_srccrop_x;
			else if(this->sws_crop_x > (this->sws_srccrop_x + max_crop_x))
				this->sws_crop_x = this->sws_srccrop_x + max_crop_x;
		}else{
			this->sws_crop_x = this->sws_srccrop_x + max_crop_x / 2;
		}

		int zoom_widget_pos_y = this->video_zoom_globalpos_y - widget_global_pos_y;

		if((zoom_widget_pos_y >= 0) && (zoom_widget_pos_y < this->sws_dst_height))
		{
			if(prev_crop_h > 0){
				int sws_prev_pos_y = this->sws_crop_y + prev_crop_h * zoom_widget_pos_y / this->sws_dst_height;
				mpxplay_debugf(DISPQT_DEBUGOUT_FFMPEG, "ZOOMX px:%d", sws_prev_pos_y);
				this->sws_crop_y = sws_prev_pos_y - this->sws_crop_h * zoom_widget_pos_y / this->sws_dst_height;
			}else{
				this->sws_crop_y = this->sws_srccrop_y + max_crop_y * zoom_widget_pos_y / this->sws_dst_height;
			}
			if(this->sws_crop_y < this->sws_srccrop_y)
				this->sws_crop_y = this->sws_srccrop_y;
			else if(this->sws_crop_y > (this->sws_srccrop_y + max_crop_y))
				this->sws_crop_y = this->sws_srccrop_y + max_crop_y;
		}else{
			this->sws_crop_y = this->sws_srccrop_y + max_crop_y / 2;
		}

		if(zoom_value == zoom_val_ti)
			gcfg->video_zoom_type = DISPQT_VIDEOFULLSCREENZOOMTYPE_TOUCHINSIDE;
		else if(zoom_value == zoom_val_to)
			gcfg->video_zoom_type = DISPQT_VIDEOFULLSCREENZOOMTYPE_TOUCHOUTSIDE;
		else if(zoom_value == DISPQT_CONFIG_VIDEOZOOM_CENTER)
			gcfg->video_zoom_type = DISPQT_VIDEOFULLSCREENZOOMTYPE_1_1;

		mpxplay_debugf(DISPQT_DEBUGOUT_FFMPEG, "ZOOM cx:%d cy:%d", this->sws_crop_x, this->sws_crop_y);
	}

	mpxplay_debugf(DISPQT_DEBUGOUT_CROP, "sws_calculate war:%d var:%d n:%d d:%d sw:%d sh:%d ww:%d wh:%d z:%d zw:%d zh:%d cx:%d cy:%d cw:%d ch:%d",
			window_ar, video_ar, this->video_aspect_ratio.num, this->video_aspect_ratio.den, this->sws_src_width, this->sws_src_height,
			this->sws_dst_width, this->sws_dst_height, zoom_value, zoom_val_ti_w, zoom_val_ti_h, this->sws_crop_x, this->sws_crop_y, this->sws_crop_w, this->sws_crop_h);

	// --- target window calculate -----------------------------------------------------------------------------------------

	if(window_width < this->sws_dst_width)
	{
		this->sws_dst_pos_x = (this->sws_dst_width - window_width) / 2;
		this->sws_dst_width = window_width;
		this->videoout_surface_clearpic = true;
	}else{
		this->sws_dst_pos_x = 0;
	}
	if(window_height < this->sws_dst_height)
	{
		this->sws_dst_pos_y = (this->sws_dst_height - window_height) / 2;
		this->sws_dst_height = window_height;
		this->videoout_surface_clearpic = true;
	}else{
		this->sws_dst_pos_y = 0;
	}

	this->videoout_surface_recalc = false;

	return true;
}
//-------------------------------------------------------------------------------------------------------------------------------------
#if 1
static int videowidget_ffmpeg_color_range_correction(int pix_fmt, int *pixfmt_corr)
{
	int color_range = AVCOL_RANGE_JPEG;
	int pixfmt_new = pix_fmt;
	switch (pix_fmt)
	{
		case AV_PIX_FMT_YUVJ420P: pixfmt_new = AV_PIX_FMT_YUV420P; break;
		case AV_PIX_FMT_YUVJ422P: pixfmt_new = AV_PIX_FMT_YUV422P; break;
		case AV_PIX_FMT_YUVJ444P: pixfmt_new = AV_PIX_FMT_YUV444P; break;
		case AV_PIX_FMT_GRAY8:
		case AV_PIX_FMT_GRAY16LE:
		case AV_PIX_FMT_RGB555LE:
		case AV_PIX_FMT_BGR24:
		case AV_PIX_FMT_BGRA:
		case AV_PIX_FMT_BGR48LE:
		case AV_PIX_FMT_RGB48BE:
		case AV_PIX_FMT_BGRA64LE:
		case AV_PIX_FMT_RGBA64BE:
			break;
		default: color_range = AVCOL_RANGE_MPEG; break;
	}
	if(pixfmt_corr)
		*pixfmt_corr = pixfmt_new;
	return color_range;
}

int FFMpegVideoWidget::videowidget_ffmpeg_swsctx_init(AVFrame *input_video_frame, int output_width, int output_height, enum AVPixelFormat output_pixelformat)
{
	int src_width, src_height, src_range, src_pix_fmt, dst_range, dst_pix_fmt, retcode;
	int output_flags = SWS_POINT;// | SWS_FULL_CHR_H_INP | SWS_ACCURATE_RND;
	const int *coefficients_input = NULL, *coefficients_output = NULL;
	enum AVColorSpace color_space_input;
	struct SwsContext *sws;

	if(!input_video_frame || (input_video_frame->width <= 0) || (input_video_frame->height <= 0) || (input_video_frame->format <= AV_PIX_FMT_NONE)
	  || (output_width <= 0) || (output_height <= 0) || ((int)output_pixelformat <= AV_PIX_FMT_NONE)
	){
		goto err_out_init;
	}

	//mpxplay_debugf(DISPQT_DEBUGOUT_FFMPEG, "videowidget_ffmpeg_swsctx_init BEGIN (close)");
	this->videowidget_ffmpeg_swsctx_close();

	src_width = input_video_frame->width;
	src_height = input_video_frame->height;
	src_pix_fmt = (enum AVPixelFormat)input_video_frame->format;
	src_range = (int)input_video_frame->color_range;
	color_space_input = input_video_frame->colorspace;

	if(src_range <= AVCOL_RANGE_UNSPECIFIED)
		src_range = videowidget_ffmpeg_color_range_correction(src_pix_fmt, &src_pix_fmt);

	dst_range = videowidget_ffmpeg_color_range_correction(output_pixelformat, &dst_pix_fmt);
	/* if context is YUV full range */
	if((dst_range < src_range) && (videowidget_ffmpeg_color_range_correction(src_pix_fmt, NULL) == videowidget_ffmpeg_color_range_correction(output_pixelformat, NULL)))
		dst_range = src_range;

	/* SWS_FULL_CHR_H_INT is correctly supported only for RGB formats */
	//if ((dst_pix_fmt == AV_PIX_FMT_BGR24) || (dst_pix_fmt == AV_PIX_FMT_BGRA))
	//    output_flags |= SWS_FULL_CHR_H_INT;

	sws = sws_alloc_context();
	if (!sws)
		goto err_out_init;
	this->sws_ctx = sws;

	av_opt_set_int(sws, "sws_flags",  output_flags, 0);

	av_opt_set_int(sws, "srcw",       src_width,   0);
	av_opt_set_int(sws, "srch",       src_height,  0);
	av_opt_set_int(sws, "src_format", src_pix_fmt, 0);
	av_opt_set_int(sws, "src_range",  ((src_range == AVCOL_RANGE_JPEG)? 1 : 0),   0);

	av_opt_set_int(sws, "dstw",       output_width,   0);
	av_opt_set_int(sws, "dsth",       output_height,  0);
	av_opt_set_int(sws, "dst_format", dst_pix_fmt, 0);
	av_opt_set_int(sws, "dst_range",  ((dst_range == AVCOL_RANGE_JPEG)? 1 : 0),   0);

	coefficients_input = sws_getCoefficients(color_space_input);
	coefficients_output = sws_getCoefficients(AVCOL_SPC_UNSPECIFIED);

	sws_setColorspaceDetails(sws,
							 coefficients_input, ((src_range == AVCOL_RANGE_JPEG)? 1 : 0),
							 coefficients_output, ((dst_range == AVCOL_RANGE_JPEG)? 1 : 0),
							 0, 1<<16, 1<<16);

	retcode = sws_init_context(sws, NULL, NULL);
	if(retcode < 0)
		goto err_out_init;

	this->ffmpeg_sws_frame = av_frame_alloc();
	if(!this->ffmpeg_sws_frame)
		goto err_out_init;
	this->ffmpeg_sws_frame->width  = output_width;
	this->ffmpeg_sws_frame->height = output_height;
	this->ffmpeg_sws_frame->format = dst_pix_fmt;
	this->sws_src_pix_fmt = (AVPixelFormat)src_pix_fmt;
	this->sws_out_pix_fmt = (AVPixelFormat)dst_pix_fmt;

	if(av_image_alloc(&this->ffmpeg_sws_frame->data[0], &this->ffmpeg_sws_frame->linesize[0], output_width, output_height, output_pixelformat, DISPQT_VIDEO_PROCESS_BUFFERALIGNMENT) <= 0)
		goto err_out_init;

	return MPXPLAY_ERROR_OK;

err_out_init:
	this->videowidget_ffmpeg_swsctx_close();
	return -1;
}

#else
int FFMpegVideoWidget::videowidget_ffmpeg_swsctx_init(AVFrame *input_video_frame, int output_width, int output_height, enum AVPixelFormat output_pixelformat)
{
	const unsigned int output_flags = SWS_POINT;

	if(!input_video_frame || (input_video_frame->width <= 0) || (input_video_frame->height <= 0) || (input_video_frame->format <= AV_PIX_FMT_NONE)
	  || (output_width <= 0) || (output_height <= 0) || ((int)output_pixelformat <= AV_PIX_FMT_NONE)
	){
		goto err_out_init;
	}
	//output_width = (output_width + (DISPQT_VIDEO_PROCESS_BUFFERALIGNMENT - 1)) & ~(DISPQT_VIDEO_PROCESS_BUFFERALIGNMENT - 1);

	//mpxplay_debugf(DISPQT_DEBUGOUT_FFMPEG, "videowidget_ffmpeg_swsctx_init BEGIN (close)");
	this->videowidget_ffmpeg_swsctx_close();

	this->ffmpeg_sws_frame = av_frame_alloc();
	if(!this->ffmpeg_sws_frame)
		goto err_out_init;
	this->ffmpeg_sws_frame->width  = output_width;
	this->ffmpeg_sws_frame->height = output_height;
	this->ffmpeg_sws_frame->format = output_pixelformat;
	this->sws_src_pix_fmt = (AVPixelFormat)input_video_frame->format;
	this->sws_out_pix_fmt = output_pixelformat;

	if(av_image_alloc(&this->ffmpeg_sws_frame->data[0], &this->ffmpeg_sws_frame->linesize[0], output_width, output_height, output_pixelformat, DISPQT_VIDEO_PROCESS_BUFFERALIGNMENT) <= 0)
		goto err_out_init;

	this->sws_ctx = sws_getContext(input_video_frame->width, input_video_frame->height, (enum AVPixelFormat)input_video_frame->format, output_width, output_height, output_pixelformat, output_flags, NULL, NULL, NULL);
//	mpxplay_debugf(DISPQT_DEBUGOUT_FFMPEG, "videowidget_ffmpeg_swsctx_init sws_getContext END sw:%d sh:%d sp:%d ww:%d wh:%d op:%d ctx:%8.8X", input_video_frame->width, input_video_frame->height,
//	(int)input_video_frame->format, output_width, output_height, (int)output_pixelformat, (int)this->sws_ctx);
	if(!this->sws_ctx)
		goto err_out_init;

	return MPXPLAY_ERROR_OK;

err_out_init:
	this->videowidget_ffmpeg_swsctx_close();
	return -1;
}
#endif

int FFMpegVideoWidget::videowidget_ffmpeg_swsctx_get_sws_outformat(AVFrame *input_video_frame)
{
	struct mmc_dispqt_config_s *gcfg = this->main_window->gui_config;
	DispQtVideoPlayer *qtvp = this->main_window->qt_video_player;
	enum AVPixelFormat sws_out_pixelformat = AV_PIX_FMT_NONE;

	if(gcfg->video_crop_type == DISPQT_VIDEOSCRCROPTYPE_AUTO)
	{
		if(input_video_frame->format != DISPQT_VIDEO_AUTOCROP_AVFORMAT)
		{
			sws_out_pixelformat = DISPQT_VIDEO_AUTOCROP_AVFORMAT;
		}
	}
	else if((qtvp->video_frame_filter_eq_process->video_filter_check(this->ffmpeg_decoded_frame, AV_PIX_FMT_NONE) == AV_PIX_FMT_NONE)
	     && (qtvp->video_frame_filter_transform_process->video_filter_check(this->ffmpeg_decoded_frame, AV_PIX_FMT_NONE) == AV_PIX_FMT_NONE)
	){
		if(input_video_frame->format != this->videoout_surface_pix_fmt)
		{
			sws_out_pixelformat = this->videoout_surface_pix_fmt;
		}
	}

	return sws_out_pixelformat; // returns AV_PIX_FMT_NONE if swsctx conversion is not needed
}

bool FFMpegVideoWidget::videowidget_ffmpeg_swsctx_conv_frame(AVFrame *input_video_frame)
{
	AVFrame *fsf = this->ffmpeg_sws_frame, *ivf = input_video_frame;
	enum AVPixelFormat output_pixelformat = (AVPixelFormat)videowidget_ffmpeg_swsctx_get_sws_outformat(input_video_frame);

	if(output_pixelformat <= AV_PIX_FMT_NONE)
	{
		if(this->sws_ctx)
		{
			this->videowidget_ffmpeg_swsctx_close();
		}
		return false;
	}

	if(!ivf || (ivf->width <= 0) || (ivf->height <= 0) || (ivf->format <= AV_PIX_FMT_NONE))
	{
		return false;
	}

	if(!this->sws_ctx || !fsf || (fsf->width != ivf->width) || (fsf->height != ivf->height) || (this->sws_src_pix_fmt != ivf->format) || (fsf->format != output_pixelformat))
	{
		if(videowidget_ffmpeg_swsctx_init(input_video_frame, ivf->width, ivf->height, output_pixelformat) != MPXPLAY_ERROR_OK)
		{
			return false;
		}
	}

	if(this->sws_ctx && this->ffmpeg_sws_frame && input_video_frame)
	{
		if(sws_scale(this->sws_ctx, (uint8_t const * const *)&input_video_frame->data[0], input_video_frame->linesize, 0, input_video_frame->height,
					&this->ffmpeg_sws_frame->data[0], &this->ffmpeg_sws_frame->linesize[0]) == this->ffmpeg_sws_frame->height
		){
			if(this->ffmpeg_sws_frame->data[0] && this->ffmpeg_sws_frame->linesize[0])
			{
				return true;
			}
		}
	}
	return false;
}

bool FFMpegVideoWidget::videowidget_ffmpeg_swsctx_check_swsframe_for_outsurface(void)
{   // returns true if swsctx has a valid output frame and its pixelformat matches with the video output surface (sws frame is used if all filters are disabled)
	return (this->ffmpeg_swsframe_hasdata && this->ffmpeg_sws_frame && (this->ffmpeg_sws_frame->format == this->videoout_surface_pix_fmt))? true : false;
}

void FFMpegVideoWidget::videowidget_ffmpeg_swsctx_close(void)
{
	if(this->ffmpeg_sws_frame)
	{
		av_freep(&this->ffmpeg_sws_frame->data[0]);
		av_frame_free(&this->ffmpeg_sws_frame);
	}
	if(this->sws_ctx)
	{
		sws_freeContext(this->sws_ctx);
	}
	this->videowidget_ffmpeg_swsctx_clear();
}

void FFMpegVideoWidget::videowidget_ffmpeg_swsctx_clear(void)
{
	this->sws_src_pix_fmt = AV_PIX_FMT_NONE;
	this->sws_ctx = NULL;
	this->sws_out_pix_fmt = AV_PIX_FMT_NONE;
	this->ffmpeg_sws_frame = NULL;
	this->ffmpeg_swsframe_hasdata = false;
	this->videoout_surface_clearpic = true;
	this->ffmpeg_swsframe_cropreset = DISPQT_VIDEO_AUTOCROP_COUNTER_OPEN;
	this->sws_autoxcrop_framecounter = 0;
}

//------------------------------------------------------------------------------------------------------------------------

bool FFMpegVideoWidget::videowidget_ffmpeg_config_callback(unsigned int cfgcmd, mpxplay_infile_callback_t *cbk_func, void *passdata)
{
	bool is_video_open = false;
	//mpxplay_debugf(DISPQT_DEBUGOUT_FFMPEG, "videowidget_ffmpeg_config_callback %8.8X %8.8X", (int)cbk_func, (int)passdata);
	this->mutex_video.tryLock(DISPQT_VIDEO_MUTEX_TIMEOUT);
	if(this->video_decoder_worker)
		is_video_open = this->video_decoder_worker->videothread_ffmpeg_config_callback(cfgcmd, cbk_func, passdata);
	this->mutex_video.unlock();
	if(this->video_decoder_worker && (is_video_open == false))
		this->videowidget_ffmpeg_frame_display(-1, 0, NULL, NULL);
	return is_video_open;
}

int FFMpegVideoWidget::videowidget_ffmpeg_callback_call(int globalpos_x, int globalpos_y, unsigned long command, void *arg1, int *arg2)
{
	int retval = -1;
	this->mutex_video.tryLock(DISPQT_VIDEO_MUTEX_TIMEOUT);
	if(this->video_decoder_worker)
		retval = this->video_decoder_worker->videothread_ffmpeg_callback_call(globalpos_x, globalpos_y, command, arg1, arg2);
	this->mutex_video.unlock();
	return retval;
}

void FFMpegVideoWidget::videowidget_ffmpeg_frame_display(int video_index, unsigned int flags, AVFrame *videoout_frame, ffmpegvideo_subtitle_info_s *subtitle_infos)
{
	struct mmc_dispqt_config_s *gcfg = this->main_window->gui_config;
	bool mutex_locked, doRepaint = false;

	mutex_locked = this->mutex_video.tryLock(DISPQT_VIDEO_MUTEX_TIMEOUT);
//	mpxplay_debugf(DISPQT_DEBUGOUT_RENDER, "frame_display c:%4d w:%d h:%d", dispqt_render_counter, ((videoout_frame)? videoout_frame->width : -1), ((videoout_frame)? videoout_frame->height : -1));

	if(video_index < 0)// means close rendering
	{
		if(this->ffmpeg_decoded_frame)
		{
			av_frame_free(&this->ffmpeg_decoded_frame);
			doRepaint = true;
		}
		this->videowidget_ffmpeg_swsctx_close();
		this->videowidget_ffmpeg_subtitle_close();
		mpxplay_debugf(DISPQT_DEBUGOUT_FFMPEG, "videowidget_ffmpeg_packet_store CLOSE");
	}
	else if(flags & DISPQT_FFMPGVIDEOCALLBACKFLAG_FIRSTFRAME) // means new file
	{
		this->vid_src_ori_width = 0;
		this->vid_src_ori_height = 0;
		this->ffmpeg_swsframe_cropreset = DISPQT_VIDEO_AUTOCROP_COUNTER_OPEN;
		this->sws_autoxcrop_framecounter = 0;
		mpxplay_debugf(DISPQT_DEBUGOUT_ACROP, "videowidget_ffmpeg_frame_display RESET2");
	}
	else if(!videoout_frame) // means seeking
	{
		if(!this->ffmpeg_swsframe_cropreset){
			this->ffmpeg_swsframe_cropreset = DISPQT_VIDEO_AUTOCROP_COUNTER_REFRESH;
			mpxplay_debugf(DISPQT_DEBUGOUT_ACROP, "videowidget_ffmpeg_frame_display RESET1");
		}
		this->sws_autoxcrop_framecounter = 0;
	}

	if(videoout_frame) // there is a new decoded video frame
	{
		if(this->sub_title_infos)
		{
			video_ffmpeg_subtitleinfo_clear(this->sub_title_infos);
		}
		if(subtitle_infos)
		{
			if(!this->sub_title_infos)
				this->sub_title_infos = (ffmpegvideo_subtitle_info_s *)av_mallocz(sizeof(ffmpegvideo_subtitle_info_s));
			video_ffmpeg_subtitleinfo_copy(this->sub_title_infos, subtitle_infos);
		}
		if(!(flags & DISPQT_FFMPGVIDEOCALLBACKFLAG_STILLPICT) || (this->vid_src_ori_width < videoout_frame->width) || (this->vid_src_ori_height < videoout_frame->height)) // display the largest frame at still pictures (eg. progressive jpeg)
		{  // replace the stored decoded video frame with the new one
			if( (this->vid_src_ori_width != videoout_frame->width) || (this->vid_src_ori_height != videoout_frame->height)
			 || (this->vid_src_ori_pix_fmt != (AVPixelFormat)videoout_frame->format)
			 || (this->video_aspect_ratio.num != videoout_frame->sample_aspect_ratio.num) || (this->video_aspect_ratio.den != videoout_frame->sample_aspect_ratio.den)) // TODO: ???
			{
				this->vid_src_ori_width = videoout_frame->width;
				this->vid_src_ori_height = videoout_frame->height;
				this->vid_src_ori_pix_fmt = (AVPixelFormat)videoout_frame->format;
				this->video_aspect_ratio.num = videoout_frame->sample_aspect_ratio.num;
				this->video_aspect_ratio.den = videoout_frame->sample_aspect_ratio.den;
				this->videoout_surface_recalc = true;
			}

			this->sws_src_width = videoout_frame->width;
			this->sws_src_height = videoout_frame->height;
			av_frame_free(&this->ffmpeg_decoded_frame);
			this->ffmpeg_decoded_frame = videoout_frame;
			videoout_frame = NULL;
			doRepaint = true;
		}
	}

	if(videoout_frame) // unused video frame
	{
		av_frame_free(&videoout_frame);
	}

	if(doRepaint)
	{
		if(this->main_window->gui_config->video_renderer_type == DISPQT_VIDEORENDERTYPE_WIDGET)
		{
			this->videowidget_ffmpeg_filtered_trans_update(mutex_locked);
		}
		else // TODO
		{
//			av_frame_free(&videoout_frame);
//			this->mutex_video.unlock();
		}
	}
	else
	{
		this->mutex_video.unlock();
	}
}

#endif // MPXPLAY_LINK_ORIGINAL_FFMPEG
