/* rgbparade.c
 * Copyright (C) 2008 Albert Frisch (albert.frisch@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 <stdlib.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include "frei0r.h"

#include <gavl/gavl.h>

#include "rgbparade_image.h"

#define OFFSET_R        0
#define OFFSET_G        8
#define OFFSET_B        16
#define OFFSET_A        24

#define PARADE_HEIGHT	256
#define PARADE_STEP	5

typedef struct {
	double red, green, blue;
} rgb_t;

typedef struct rgbparade {
	int w, h;
	unsigned char* scala;
	gavl_video_scaler_t* parade_scaler;
	gavl_video_frame_t* parade_frame_src;
	gavl_video_frame_t* parade_frame_dst;
	double mix;
	double overlay_sides;
} rgbparade_t;

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

void f0r_get_plugin_info( f0r_plugin_info_t* info )
{
	info->name = "RGB-Parade";
	info->author = "Albert Frisch";
	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 = 2; 
	info->num_params =  2; 
	info->explanation = "Displays a histogram of R, G and B of the video-data";
}

void f0r_get_param_info( f0r_param_info_t* info, int param_index )
{
	switch(param_index)
	{
	case 0:
		info->name = "mix";
		info->type = F0R_PARAM_DOUBLE;
		info->explanation = "The amount of source image mixed into background of display";
		break;
	case 1:
		info->name = "overlay sides";
		info->type = F0R_PARAM_BOOL;
		info->explanation = "If false, the sides of image are shown without overlay";
		break;
	} 
}

f0r_instance_t f0r_construct(unsigned int width, unsigned int height)
{
	rgbparade_t* inst = (rgbparade_t*)calloc(1, sizeof(*inst));
	inst->w = width;
	inst->h = height;
	
	inst->mix = 0.0;
	inst->overlay_sides = 1.0;
	
	inst->scala = (unsigned char*)malloc( width * height * 4 );
	
	gavl_video_scaler_t* video_scaler;
	gavl_video_frame_t* frame_src;
	gavl_video_frame_t* frame_dst;
	
	video_scaler = gavl_video_scaler_create();
	frame_src = gavl_video_frame_create( 0 );
	frame_dst = gavl_video_frame_create( 0 );
	frame_dst->strides[0] = width * 4;
	frame_src->strides[0] = rgbparade_image.width * 4;
	
	gavl_video_options_t* options = gavl_video_scaler_get_options( video_scaler );
	gavl_video_format_t format_src;
	gavl_video_format_t format_dst;
	
	memset(&format_src, 0, sizeof(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;
	format_dst.interlace_mode = GAVL_INTERLACE_NONE;
	
	format_src.frame_width  = rgbparade_image.width;
	format_src.frame_height = rgbparade_image.height;
	format_src.image_width  = rgbparade_image.width;
	format_src.image_height = rgbparade_image.height;
	format_src.pixel_width = 1;
	format_src.pixel_height = 1;
	format_src.pixelformat = GAVL_RGBA_32;
	format_src.interlace_mode = GAVL_INTERLACE_NONE;
	
	gavl_rectangle_f_t src_rect;
	gavl_rectangle_i_t dst_rect;
	
	src_rect.x = 0;
	src_rect.y = 0;
	src_rect.w = rgbparade_image.width;
	src_rect.h = rgbparade_image.height;
	dst_rect.x = 0;
	dst_rect.y = 0;
	dst_rect.w = width;
	dst_rect.h = height * 0.995;
	
	gavl_video_options_set_rectangles( options, &src_rect, &dst_rect );
	gavl_video_scaler_init( video_scaler, &format_src, &format_dst );
	
	frame_src->planes[0] = (uint8_t *)rgbparade_image.pixel_data;
	frame_dst->planes[0] = (uint8_t *)inst->scala;
	
	/* Pad the source image to make the stride a multiple of 16. */
	gavl_video_frame_t* padded = gavl_video_frame_create( &format_src );
	gavl_video_frame_copy( &format_src, padded, frame_src );
	
	float transparent[4] = { 0.0, 0.0, 0.0, 0.0 };
	gavl_video_frame_fill( frame_dst, &format_dst, transparent );
	
	gavl_video_scaler_scale( video_scaler, padded, frame_dst );
	
	gavl_video_scaler_destroy(video_scaler);
	gavl_video_frame_null( frame_src );
	gavl_video_frame_destroy( frame_src );
	gavl_video_frame_null( frame_dst );
	gavl_video_frame_destroy( frame_dst );
	gavl_video_frame_destroy( padded );
	
	options = gavl_video_scaler_get_options( inst->parade_scaler );
	
	inst->parade_scaler = gavl_video_scaler_create();
	inst->parade_frame_src = gavl_video_frame_create(0);
	inst->parade_frame_dst = gavl_video_frame_create(0);
	inst->parade_frame_src->strides[0] = width * 4;
	inst->parade_frame_dst->strides[0] = width * 4;
	options = gavl_video_scaler_get_options( inst->parade_scaler );
	
	format_src.frame_width  = width;
	format_src.frame_height = PARADE_HEIGHT;
	format_src.image_width  = width;
	format_src.image_height = PARADE_HEIGHT;
	format_src.pixel_width = 1;
	format_src.pixel_height = 1;
	format_src.pixelformat = GAVL_RGBA_32;
	format_dst.frame_width  = width;
	format_dst.frame_height = height;
	format_dst.image_width  = width;
	format_dst.image_height = height;
	format_dst.pixel_width = 1;
	format_dst.pixel_height = 1;
	format_dst.pixelformat = GAVL_RGBA_32;
	
	gavl_rectangle_f_set_all( &src_rect, &format_src );
	dst_rect.x = width * 0.05;
	dst_rect.y = height * 0.011;
	dst_rect.w = width * 0.9;
	dst_rect.h = height * 0.978;
	
	gavl_video_options_set_rectangles( options, &src_rect, &dst_rect );
	gavl_video_scaler_init( inst->parade_scaler, &format_src, &format_dst );
	
	return (f0r_instance_t)inst;
}

void f0r_destruct(f0r_instance_t instance)
{
	rgbparade_t* inst = (rgbparade_t*)instance;
	gavl_video_scaler_destroy( inst->parade_scaler );
	gavl_video_frame_null( inst->parade_frame_src );
	gavl_video_frame_destroy( inst->parade_frame_src );
	gavl_video_frame_null( inst->parade_frame_dst );
	gavl_video_frame_destroy( inst->parade_frame_dst );
	free(inst);
}

void f0r_get_param_value(f0r_instance_t instance, f0r_param_t param, int param_index)
{  
	assert(instance);
	rgbparade_t* inst = (rgbparade_t*)instance;
	
	switch(param_index)
	{
	case 0:
		*((double *)param) = inst->mix;
		break;
	case 1:
		*((double *)param) = inst->overlay_sides;
		break;
	}
}

void f0r_set_param_value(f0r_instance_t instance, f0r_param_t param, int param_index)
{ 
	assert(instance);
	rgbparade_t* inst = (rgbparade_t*)instance;
	
	switch(param_index)
	{
	case 0:
		inst->mix = *((double *)param);
		break;
	case 1:
		inst->overlay_sides = *((double *)param);
		break;
	}
}

void draw_grid(unsigned char* scope, double width, double height)
{
	double i, j;
	long offset;
	
	for(j=0;j<6;j++)
	{
		for(i=0;i<width;++i)
		{
			offset = j*(height-1)*width/5+i;
			scope[offset] = 255;
		}
	}
	for(j=0;j<2;j++)
	{
		for(i=0;i<height;i++)
		{
			offset = i*width+j*(width-1);
			scope[offset] = 255;
		}
	}
}

void f0r_update(f0r_instance_t instance, double time, const uint32_t* inframe, uint32_t* outframe)
{
	assert(instance);
	rgbparade_t* inst = (rgbparade_t*)instance;
	
	int width = inst->w;
	int height = inst->h;
	double mix = inst->mix;
	int len = inst->w * inst->h;
	int parade_len = width * PARADE_HEIGHT;
	
	uint32_t* dst = outframe;
	uint32_t* dst_end;
	const uint32_t* src = inframe;
	const uint32_t* src_end;
	uint32_t* parade = (uint32_t*)malloc( parade_len * 4 );
	uint32_t* parade_end;
	
	long src_x, src_y;
	long x, y;
	rgb_t rgb;
	uint8_t* pixel;
	
	dst_end = dst + len;
	src_end = src + len;
	parade_end = parade + parade_len;
	
	if ( inst->overlay_sides > 0.5) {
		while ( dst < dst_end ) {
			*(dst++) = 0xFF000000;
		}
	} else {
		while ( dst < dst_end ) {
			*(dst++) = *(src++);
		}
		src -= len;
	}
	
	dst = outframe;
	while ( parade < parade_end ) {
		*(parade++) = 0xFF000000;
	}
	parade -= parade_len;
	
	for ( src_y = 0; src_y < height; src_y++ ) {
		for ( src_x = 0; src_x < width; src_x++ ) {
			rgb.red = (((*src) & 0x000000FF) >> OFFSET_R);
			rgb.green = (((*src) & 0x0000FF00) >> OFFSET_G);
			rgb.blue = (((*src) & 0x00FF0000) >> OFFSET_B);
			src++;		
			x = src_x / 3;
			y = PARADE_HEIGHT - rgb.red - 1;
			if ( x >= 0 && x < width && y >= 0 && y < PARADE_HEIGHT ) {
				pixel = (uint8_t*)&parade[x+width*y];
				if ( pixel[0] < (255-PARADE_STEP) ) pixel[0] += PARADE_STEP;
			}
			x += width / 3;
			y = PARADE_HEIGHT - rgb.green - 1;
			if ( x >= 0 && x < width && y >= 0 && y < PARADE_HEIGHT ) {
				pixel = (uint8_t*)&parade[x+width*y];
				if ( pixel[1] < (255-PARADE_STEP) ) pixel[1] += PARADE_STEP;
			}
			x += width / 3;
			y = PARADE_HEIGHT - rgb.blue - 1;
			if ( x >= 0 && x < width && y >= 0 && y < PARADE_HEIGHT ) {
				pixel = (uint8_t*)&parade[x+width*y];
				if ( pixel[2] < (255-PARADE_STEP) ) pixel[2] += PARADE_STEP;
			}
		}
	}
	
	inst->parade_frame_src->planes[0] = (uint8_t *)parade;
	inst->parade_frame_dst->planes[0] = (uint8_t *)dst;
	
	gavl_video_scaler_scale( inst->parade_scaler, inst->parade_frame_src, inst->parade_frame_dst );
	
	unsigned char *scala8, *dst8, *dst8_end, *src8;
	
	scala8 = inst->scala;
	src8 = (unsigned char*)inframe;
	dst8 = (unsigned char*)outframe;
	dst8_end = dst8 + ( len * 4 );
	if (mix > 0.001 ) { // to not lose performance for non-mixing users
		while ( dst8 < dst8_end ) {
			dst8[0] = ( ( ( scala8[0] - dst8[0] ) * 255 * scala8[3] ) >> 16 ) + dst8[0];
			dst8[1] = ( ( ( scala8[1] - dst8[1] ) * 255 * scala8[3] ) >> 16 ) + dst8[1];
			dst8[2] = ( ( ( scala8[2] - dst8[2] ) * 255 * scala8[3] ) >> 16 ) + dst8[2];
			if (dst8[0] == 0 && dst8[1] == 0 && dst8[2] == 0){
				dst8[0] = src8[0] * mix;
				dst8[1] = src8[1] * mix;
				dst8[2] = src8[2] * mix;
			}
			scala8 += 4;
			dst8 += 4;
			src8 += 4;
		}
	} else {
		while ( dst8 < dst8_end ) {
			dst8[0] = ( ( ( scala8[0] - dst8[0] ) * 255 * scala8[3] ) >> 16 ) + dst8[0];
			dst8[1] = ( ( ( scala8[1] - dst8[1] ) * 255 * scala8[3] ) >> 16 ) + dst8[1];
			dst8[2] = ( ( ( scala8[2] - dst8[2] ) * 255 * scala8[3] ) >> 16 ) + dst8[2];
			scala8 += 4;
			dst8 += 4;
		}
	}
	free(parade);
}

