/* scale0tilt.c
 * Copyright (C) 2007 Richard Spindler (richard.spindler@gmail.com)
 * This file is a Frei0r plugin.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <math.h>
#include "frei0r.h"
#include <gavl/gavl.h>
#include <stdlib.h>
#include <string.h>

#define EPSILON 1e-6

typedef struct scale0tilt_instance {
	double cl, ct, cr, cb;
	double sx, sy;
	double tx, ty;
	int w, h;
	gavl_video_scaler_t* video_scaler;
	gavl_video_frame_t* frame_src;
	gavl_video_frame_t* frame_dst;
	int do_scale;
	gavl_video_format_t format_src;
	gavl_video_frame_t* padded;
} scale0tilt_instance_t;

void update_scaler( scale0tilt_instance_t* inst )
{
	float dst_x, dst_y, dst_w, dst_h;
	float src_x, src_y, src_w, src_h;
        
	inst->do_scale = 1;
	src_x = inst->w * inst->cl;
	src_y = inst->h * inst->ct;
	src_w = inst->w * (1.0 - inst->cl - inst->cr );
	src_h = inst->h * (1.0 - inst->ct - inst->cb );

	dst_x = inst->w * inst->cl * inst->sx + inst->tx * inst->w;
	dst_y = inst->h * inst->ct * inst->sy + inst->ty * inst->h;
	dst_w = inst->w * (1.0 - inst->cl - inst->cr) * inst->sx;
	dst_h = inst->h * (1.0 - inst->ct - inst->cb) * inst->sy;

	if((dst_w < EPSILON) || (dst_h < EPSILON) || 
	   (src_w < EPSILON) || (src_h < EPSILON)) {
		inst->do_scale = 0;
		return;
	}

	if ( dst_x + dst_w > inst->w ) {
		src_w = src_w * ( (inst->w-dst_x) / dst_w );
		dst_w = inst->w - dst_x;
	}
	if ( dst_y + dst_h > inst->h ) {
		src_h = src_h * ( (inst->h-dst_y) / dst_h );
		dst_h = inst->h - dst_y;
	}
	if ( dst_x < 0 ) {
		src_x = src_x - dst_x * ( src_w / dst_w );
		src_w = src_w * ( (dst_w+dst_x) / dst_w );
		dst_w = dst_w + dst_x;
		dst_x = 0;
	}
	if ( dst_y < 0 ) {
		src_y = src_y - dst_y * ( src_h / dst_h );
		src_h = src_h * ( (dst_h+dst_y) / dst_h );
		dst_h = dst_h + dst_y;
		dst_y = 0;
	}

	if((dst_w < EPSILON) || (dst_h < EPSILON) ||
	   (src_w < EPSILON) || (src_h < EPSILON)) {
		inst->do_scale = 0;
		return;
	}

	gavl_video_options_t* options = gavl_video_scaler_get_options( inst->video_scaler );

	gavl_video_format_t format_dst;

	memset(&inst->format_src, 0, sizeof(inst->format_src));
	memset(&format_dst, 0, sizeof(format_dst));

	format_dst.frame_width  = inst->w;
	format_dst.frame_height = inst->h;
	format_dst.image_width  = inst->w;
	format_dst.image_height = inst->h;
	format_dst.pixel_width = 1;
	format_dst.pixel_height = 1;
	format_dst.pixelformat = GAVL_RGBA_32;
	
	inst->format_src.frame_width  = inst->w;
	inst->format_src.frame_height = inst->h;
	inst->format_src.image_width  = inst->w;
	inst->format_src.image_height = inst->h;
	inst->format_src.pixel_width = 1;
	inst->format_src.pixel_height = 1;
	inst->format_src.pixelformat = GAVL_RGBA_32;

	gavl_rectangle_f_t src_rect;
	gavl_rectangle_i_t dst_rect;

	src_rect.x = src_x;
	src_rect.y = src_y;
	src_rect.w = src_w;
	src_rect.h = src_h;

	dst_rect.x = lroundf(dst_x);
	dst_rect.y = lroundf(dst_y);
	dst_rect.w = lroundf(dst_w);
	dst_rect.h = lroundf(dst_h);
	
	gavl_video_options_set_rectangles( options, &src_rect, &dst_rect );
	gavl_video_scaler_init( inst->video_scaler, &inst->format_src, &format_dst );
}

int f0r_init()
{
  return 1;
}
void f0r_deinit()
{ /* empty */ }

void f0r_get_plugin_info( f0r_plugin_info_t* info )
{
	info->name = "Scale0Tilt";
	info->author = "Richard Spindler";
	info->plugin_type = F0R_PLUGIN_TYPE_FILTER;
	info->color_model = F0R_COLOR_MODEL_RGBA8888;
	info->frei0r_version = FREI0R_MAJOR_VERSION;
	info->major_version = 0; 
	info->minor_version = 1; 
	info->num_params =  8; 
	info->explanation = "Scales, Tilts and Crops an Image";

}
void f0r_get_param_info( f0r_param_info_t* info, int param_index )
{
	switch ( param_index ) {
		case 0:
			info->name = "Clip left";
			info->type = F0R_PARAM_DOUBLE;
			info->explanation = "";
			break;
		case 1:
			info->name = "Clip right";
			info->type = F0R_PARAM_DOUBLE;
			info->explanation = "";
			break;
		case 2:
			info->name = "Clip top";
			info->type = F0R_PARAM_DOUBLE;
			info->explanation = "";
			break;
		case 3:
			info->name = "Clip bottom";
			info->type = F0R_PARAM_DOUBLE;
			info->explanation = "";
			break;
		case 4:
			info->name = "Scale X";
			info->type = F0R_PARAM_DOUBLE;
			info->explanation = "";
			break;
		case 5:
			info->name = "Scale Y";
			info->type = F0R_PARAM_DOUBLE;
			info->explanation = "";
			break;
		case 6:
			info->name = "Tilt X";
			info->type = F0R_PARAM_DOUBLE;
			info->explanation = "";
			break;
		case 7:
			info->name = "Tilt Y";
			info->type = F0R_PARAM_DOUBLE;
			info->explanation = "";
			break;
	}
}

f0r_instance_t f0r_construct(unsigned int width, unsigned int height)
{
	scale0tilt_instance_t* inst = (scale0tilt_instance_t*)calloc(1, sizeof(*inst));
	inst->w = width;
	inst->h = height;
	inst->sx = 1.0;
	inst->sy = 1.0;
	inst->video_scaler = gavl_video_scaler_create();
	inst->frame_src = gavl_video_frame_create( 0 );
	inst->frame_dst = gavl_video_frame_create( 0 );
	inst->frame_src->strides[0] = width * 4;
    inst->frame_dst->strides[0] = width * 4;
	update_scaler(inst);
	if ( inst->frame_src->strides[0] % 16 )
		inst->padded = gavl_video_frame_create( &inst->format_src );
	return (f0r_instance_t)inst;
}
void f0r_destruct(f0r_instance_t instance)
{
	scale0tilt_instance_t* inst = (scale0tilt_instance_t*)instance;
	gavl_video_scaler_destroy(inst->video_scaler);
	gavl_video_frame_null( inst->frame_src );
	gavl_video_frame_destroy( inst->frame_src );
	gavl_video_frame_null( inst->frame_dst );
	gavl_video_frame_destroy( inst->frame_dst );
	if ( inst->padded )
		gavl_video_frame_destroy( inst->padded );
	free(instance);
}
void f0r_set_param_value(f0r_instance_t instance, 
                         f0r_param_t param, int param_index)
{
	scale0tilt_instance_t* inst = (scale0tilt_instance_t*)instance;
	switch ( param_index ) {
		case 0:
			inst->cl = *((double*)param);
			break;
		case 1:
			inst->cr = *((double*)param);
			break;
		case 2:
			inst->ct = *((double*)param);
			break;
		case 3:
			inst->cb = *((double*)param);
			break;
		case 4:
			inst->sx = *((double*)param) * 2.0;
			break;
		case 5:
			inst->sy = *((double*)param) * 2.0;
			break;
		case 6:
			inst->tx = *((double*)param) * 2.0 - 1.0;
			break;
		case 7:
			inst->ty = *((double*)param) * 2.0 - 1.0;
			break;
	}
	update_scaler( inst );
}
void f0r_get_param_value(f0r_instance_t instance,
                         f0r_param_t param, int param_index)
{
	scale0tilt_instance_t* inst = (scale0tilt_instance_t*)instance;
	switch ( param_index ) {
		case 0:
			*((double*)param) = inst->cl;
			break;
		case 1:
			*((double*)param) = inst->cr;
			break;
		case 2:
			*((double*)param) = inst->ct;
			break;
		case 3:
			*((double*)param) = inst->cb;
			break;
		case 4:
			*((double*)param) = inst->sx / 2.0;
			break;
		case 5:
			*((double*)param) = inst->sy / 2.0;
			break;
		case 6:
			*((double*)param) = (inst->tx + 1.0) / 2.0;
			break;
		case 7:
			*((double*)param) = (inst->ty + 1.0) / 2.0;
			break;
	}
}
void f0r_update(f0r_instance_t instance, double time,
                const uint32_t* inframe, uint32_t* outframe)
{
	scale0tilt_instance_t* inst = (scale0tilt_instance_t*)instance;
	gavl_video_frame_t* frame_src = inst->frame_src;
	inst->frame_src->planes[0] = (uint8_t *)inframe;
	inst->frame_dst->planes[0] = (uint8_t *)outframe;
	int len = inst->w * inst->h;
	int i;
	for ( i = 0; i < len; i++ ) {
		outframe[i] = 0;
	}
	if ( inst->do_scale ) {
		if ( inst->padded ) {
			gavl_video_frame_copy( &inst->format_src, inst->padded, frame_src );
			frame_src = inst->padded;
		}
		gavl_video_scaler_scale( inst->video_scaler, frame_src, inst->frame_dst );
	}
}

