Blob Blame Raw
/* === S Y N F I G ========================================================= */
/*!	\file gtkmm/render.cpp
**	\brief Template File
**
**	$Id$
**
**	\legal
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2007, 2008 Chris Moore
**
**	This package 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 package 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.
**	\endlegal
*/
/* ========================================================================= */

/* === H E A D E R S ======================================================= */

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include <cerrno>
#include <map>
#include <glibmm.h>

#include <gtkmm/frame.h>
#include <gtkmm/alignment.h>

#include <ETL/stringf>

#include <synfig/general.h>
#include <synfig/target_scanline.h>
#include <synfig/canvas.h>
#include <synfig/soundprocessor.h>

#include "app.h"
#include "asyncrenderer.h"
#include "docks/dockmanager.h"
#include "docks/dock_info.h"
#include "dialogs/dialog_ffmpegparam.h"
#include "dialogs/dialog_spritesheetparam.h"

#include "render.h"

#include <gui/localization.h>

#endif

/* === U S I N G =========================================================== */

using namespace std;
using namespace etl;
using namespace synfig;
using namespace studio;

/* === M A C R O S ========================================================= */

/* === G L O B A L S ======================================================= */

/* === P R O C E D U R E S ================================================= */

/* === M E T H O D S ======================================================= */

RenderSettings::RenderSettings(Gtk::Window& parent, etl::handle<synfigapp::CanvasInterface> canvas_interface):
	Gtk::Dialog(_("Render Settings"),parent),
	canvas_interface_(canvas_interface),
	adjustment_quality(Gtk::Adjustment::create(3,0,9)),
	entry_quality(adjustment_quality,1,0),
	adjustment_antialias(Gtk::Adjustment::create(1,1,31)),
	entry_antialias(adjustment_antialias,1,0),
	toggle_single_frame(_("Render _current frame only"), true),
	toggle_extract_alpha(_("Extract alpha"), true),
	tparam("mpeg4",6000)
{
	tparam.sequence_separator=App::sequence_separator;
	widget_rend_desc.show();
	widget_rend_desc.signal_changed().connect(sigc::mem_fun(*this,&studio::RenderSettings::on_rend_desc_changed));
	widget_rend_desc.set_rend_desc(canvas_interface_->get_canvas()->rend_desc());

	canvas_interface->signal_rend_desc_changed().connect(sigc::mem_fun(*this,&RenderSettings::on_rend_desc_changed));

	comboboxtext_target.append(_("Auto"));
	target_names.push_back(String());
	synfig::Target::Book::iterator iter;
	synfig::Target::Book book(synfig::Target::book());
	for(iter=book.begin();iter!=book.end();iter++)
	{
		comboboxtext_target.append(iter->first);
		target_names.push_back(iter->first);
	}
	comboboxtext_target.set_active(0);
	comboboxtext_target.signal_changed().connect(sigc::mem_fun(this, &RenderSettings::on_comboboxtext_target_changed));

	Gtk::Alignment *dialogPadding = manage(new Gtk::Alignment(0, 0, 1, 1));
	dialogPadding->set_padding(12, 12, 12, 12);
	get_vbox()->pack_start(*dialogPadding, false, false, 0);

	Gtk::VBox *dialogBox = manage(new Gtk::VBox(false, 12));
	dialogPadding->add(*dialogBox);

	Gtk::Button *choose_button(manage(new class Gtk::Button(Gtk::StockID(_("Choose...")))));
	choose_button->show();
	choose_button->signal_clicked().connect(sigc::mem_fun(*this, &studio::RenderSettings::on_choose_pressed));

	tparam_button=manage(new class Gtk::Button(Gtk::StockID(_("Parameters..."))));
	tparam_button->show();
	tparam_button->set_sensitive(false);
	tparam_button->signal_clicked().connect(sigc::mem_fun(*this, &studio::RenderSettings::on_targetparam_pressed));

	Gtk::Frame *target_frame=manage(new Gtk::Frame(_("Target")));
	target_frame->set_shadow_type(Gtk::SHADOW_NONE);
	((Gtk::Label *) target_frame->get_label_widget())->set_markup(_("<b>Target</b>"));
	dialogBox->pack_start(*target_frame);
	Gtk::Alignment *targetPadding = manage(new Gtk::Alignment(0, 0, 1, 1));
	targetPadding->set_padding(6, 0, 24, 0);
	target_frame->add(*targetPadding);

	Gtk::Table *target_table = manage(new Gtk::Table(2, 3, false));
	target_table->set_row_spacings(6);
	target_table->set_col_spacings(12);
	targetPadding->add(*target_table);

	Gtk::Label *filenameLabel = manage(new Gtk::Label(_("_Filename"), true));
	filenameLabel->set_alignment(0, 0.5);
	filenameLabel->set_mnemonic_widget(entry_filename);
	target_table->attach(*filenameLabel, 0, 1, 0, 1, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
	target_table->attach(entry_filename, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
	target_table->attach(*choose_button, 2, 3, 0, 1, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);

	Gtk::Label *targetLabel = manage(new Gtk::Label(_("_Target"), true));
	targetLabel->set_alignment(0, 0.5);
	targetLabel->set_mnemonic_widget(comboboxtext_target);
	target_table->attach(*targetLabel, 0, 1, 1, 2, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
	target_table->attach(comboboxtext_target, 1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
	target_table->attach(*tparam_button, 2, 3, 1, 2, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);

	toggle_single_frame.signal_toggled().connect(sigc::mem_fun(*this, &studio::RenderSettings::on_single_frame_toggle));

	Gtk::Frame *settings_frame=manage(new Gtk::Frame(_("Settings")));
	settings_frame->set_shadow_type(Gtk::SHADOW_NONE);
	((Gtk::Label *) settings_frame->get_label_widget())->set_markup(_("<b>Settings</b>"));
	dialogBox->pack_start(*settings_frame);

	Gtk::Alignment *settingsPadding = manage(new Gtk::Alignment(0, 0, 1, 1));
	settingsPadding->set_padding(6, 0, 24, 0);
	settings_frame->add(*settingsPadding);

	Gtk::Table *settings_table=manage(new Gtk::Table(3,2,false));
	settings_table->set_row_spacings(6);
	settings_table->set_col_spacings(12);
	settingsPadding->add(*settings_table);

	Gtk::Label *qualityLabel = manage(new Gtk::Label(_("_Quality"), true));
	qualityLabel->set_alignment(0, 0.5);
	qualityLabel->set_mnemonic_widget(entry_quality);
	settings_table->attach(*qualityLabel, 0, 1, 0, 1, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
	settings_table->attach(entry_quality, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);

	Gtk::Label *antiAliasLabel = manage(new Gtk::Label(_("_Anti-Aliasing"), true));
	antiAliasLabel->set_alignment(0, 0.5);
	antiAliasLabel->set_mnemonic_widget(entry_antialias);
	settings_table->attach(*antiAliasLabel, 0, 1, 1, 2, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
	settings_table->attach(entry_antialias, 1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);

	toggle_single_frame.set_alignment(0, 0.5);
	settings_table->attach(toggle_single_frame, 2, 3, 0, 1, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
	toggle_single_frame.set_active(false);

	toggle_extract_alpha.set_alignment(0, 0.5);
	settings_table->attach(toggle_extract_alpha, 2, 3, 1, 2, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
	toggle_extract_alpha.set_active(false);

	dialogBox->pack_start(widget_rend_desc);


	Gtk::Button *render_button(manage(new class Gtk::Button(Gtk::StockID(_("Render")))));
	render_button->show();
	add_action_widget(*render_button,1);
	render_button->signal_clicked().connect(sigc::mem_fun(*this, &studio::RenderSettings::on_render_pressed));

	Gtk::Button *cancel_button(manage(new class Gtk::Button(Gtk::StockID("gtk-cancel"))));
	cancel_button->show();
	add_action_widget(*cancel_button,0);
	cancel_button->signal_clicked().connect(sigc::mem_fun(*this, &studio::RenderSettings::on_cancel_pressed));

	//set_default_response(1);

	set_title(_("Render Settings")+String(" - ")+canvas_interface_->get_canvas()->get_name());

	widget_rend_desc.enable_time_section();

	set_entry_filename();

	get_vbox()->show_all();
}

RenderSettings::~RenderSettings()
{
}

void
RenderSettings::set_entry_filename()
{
	String filename(filename_sans_extension(canvas_interface_->get_canvas()->get_file_name()));

	// if this isn't the root canvas, append (<canvasname>) to the filename
	etl::handle<synfig::Canvas> canvas = canvas_interface_->get_canvas();
	if (!canvas->is_root())
	{
		if(canvas->get_name().empty())
			filename+=" ("+canvas->get_id()+')';
		else
			filename+=" ("+canvas->get_name()+')';
	}
	
	try
	{
		if(!comboboxtext_target.get_active_row_number())
			entry_filename.set_text((filename +".avi"));
		// in case the file was saved and loaded again then .ext should be according to target
		else on_comboboxtext_target_changed();
	}
	catch(...)
	{
		synfig::warning("Averted crash!");
		entry_filename.set_text("output.avi");
	}
}

void
RenderSettings::on_comboboxtext_target_changed()
{
	std::map<std::string,std::string> ext = {{"bmp",".bmp"}, {"cairo_png",".png"},{"dv",".dv"},
					{"ffmpeg",".avi"},{"gif",".gif"},{"imagemagick",".png"}, {"jpeg",".jpg"},
					{"magick++",".gif"},{"mng",".mng"},{"openexr",".exr"},{"png",".png"},
					{"png-spritesheet",".png"},{"ppm",".ppm"}, {"yuv420p",".yuv"}, {"libav",".avi"}};
	int i = comboboxtext_target.get_active_row_number();
	if (i < 0 || i >= (int)target_names.size()) return;
	if (target_name == target_names[i]) return;
	auto itr = ext.find(target_names[i]); 
    // check if target_name is there in map
    if(itr != ext.end())
	{
		String filename = entry_filename.get_text();
		String newfilename = filename.substr(0,filename.find_last_of('.'))+itr->second;
		entry_filename.set_text(newfilename);
	}
	set_target(target_names[i]);
}

void
RenderSettings::on_rend_desc_changed()
{
	widget_rend_desc.set_rend_desc(canvas_interface_->get_canvas()->rend_desc());
}

void
RenderSettings::set_target(synfig::String name)
{
	target_name=name;
	//TODO: Replace this condition
	tparam_button->set_sensitive(!(target_name.compare("ffmpeg") && target_name.compare("png-spritesheet")));
}

void
RenderSettings::on_choose_pressed()
{
	String filename=entry_filename.get_text();
	if(App::dialog_save_file_render("Save Render As", filename, RENDER_DIR_PREFERENCE))
		entry_filename.set_text(filename);
}

void
RenderSettings::on_targetparam_pressed()
{
	Dialog_TargetParam * dialogtp;
	//TODO: Replace this conditions too
	if (!target_name.compare("ffmpeg"))
		dialogtp = new Dialog_FFmpegParam (*this);
	else if (!target_name.compare("png-spritesheet"))
		dialogtp = new Dialog_SpriteSheetParam (*this);
	else
		return;

	RendDesc rend_desc(widget_rend_desc.get_rend_desc());
	dialogtp->set_desc(rend_desc);
	dialogtp->set_tparam(tparam);
	if(dialogtp->run() == Gtk::RESPONSE_OK)
		tparam = dialogtp->get_tparam();
	delete dialogtp;
}

void
RenderSettings::on_render_pressed()
{
	String filename=entry_filename.get_text();
	calculated_target_name=target_name;

	if(filename.empty())
	{
		canvas_interface_->get_ui_interface()->error(_("You must supply a filename!"));
		return;
	}

	// If the target type is not yet defined,
	// try to figure it out from the outfile.
	if(calculated_target_name.empty())
	{
		try
		{
			String ext(filename_extension(filename));
			if (ext.size()) ext=ext.substr(1); // skip initial '.'
			synfig::info("render target filename: '%s'; extension: '%s'", filename.c_str(), ext.c_str());
			if(Target::ext_book().count(ext))
			{
				calculated_target_name=Target::ext_book()[ext];
				synfig::info("'%s' is a known extension - using target '%s'", ext.c_str(), calculated_target_name.c_str());
			}
			else
			{
				calculated_target_name=ext;
				synfig::info("unknown extension");
			}
		}
		catch(std::runtime_error& x)
		{
			canvas_interface_->get_ui_interface()->error(_("Unable to determine proper target from filename."));
			return;
		}
	}

	if(filename.empty() && calculated_target_name!="null")
	{
		canvas_interface_->get_ui_interface()->error(_("A filename is required for this target"));
		return;
	}

	hide();

	render_passes.clear();
	if (toggle_extract_alpha.get_active())
	{
		String filename_alpha(filename_sans_extension(filename)+"-alpha"+filename_extension(filename));

		render_passes.push_back(make_pair(TARGET_ALPHA_MODE_EXTRACT, filename_alpha));
		render_passes.push_back(make_pair(TARGET_ALPHA_MODE_REDUCE, filename));

	} else {
		render_passes.push_back(make_pair(TARGET_ALPHA_MODE_KEEP, filename));
	}
	
	App::dock_info_->set_n_passes_requested(render_passes.size());
	App::dock_info_->set_n_passes_pending(render_passes.size());
	App::dock_info_->set_render_progress(0.0);
	App::dock_manager->find_dockable("info").present(); //Bring Dock_Info to front
	
	submit_next_render_pass();

	return;
}

void
RenderSettings::submit_next_render_pass()
{
	if (render_passes.size()>0) {
		pair<TargetAlphaMode,String> pass_info = render_passes.back();
		render_passes.pop_back();

		App::dock_info_->set_n_passes_pending(render_passes.size()); //! Decrease until 0
		App::dock_info_->set_render_progress(0.0); //For this pass
		
		TargetAlphaMode pass_alpha_mode = pass_info.first;
#ifdef _WIN32
		String pass_filename = Glib::locale_from_utf8(pass_info.second);
#else
		String pass_filename = pass_info.second;
#endif

		Target::Handle target=Target::create(calculated_target_name,pass_filename, tparam);
		if(!target)
		{
			canvas_interface_->get_ui_interface()->error(_("Unable to create target for ")+pass_filename);
			return;
		}
		// Test whether the output file is writable (path exists or has write permit)
		if (access(dirname(pass_filename).c_str(),W_OK) == -1)
		{
			canvas_interface_->get_ui_interface()->error(_("Unable to create file for ")+pass_filename+": "+strerror( errno ));
			return;
		}

		target->set_canvas(canvas_interface_->get_canvas());
		RendDesc rend_desc(widget_rend_desc.get_rend_desc());
		rend_desc.set_antialias((int)adjustment_antialias->get_value());
		rend_desc.set_render_excluded_contexts(false);

		// If we are to only render the current frame
		if(toggle_single_frame.get_active())
			rend_desc.set_time(canvas_interface_->get_time());

		target->set_rend_desc(&rend_desc);
		target->set_quality((int)adjustment_quality->get_value());
		if( !target->init(canvas_interface_->get_ui_interface().get()) ){
			canvas_interface_->get_ui_interface()->error(_("Target initialization failure"));
			return;
		}
		if(pass_alpha_mode!=TARGET_ALPHA_MODE_KEEP)
			target->set_alpha_mode(pass_alpha_mode);

		canvas_interface_->get_ui_interface()->task(_("Rendering ")+pass_filename);

		/*
		if(async_renderer)
		{
			async_renderer->stop();
			async_renderer.detach();
		}
		*/
		async_renderer=new AsyncRenderer(target);
		async_renderer->signal_finished().connect( sigc::mem_fun(*this,&RenderSettings::on_finished));
		async_renderer->start();
		/*
		if(!target->render(canvas_interface_->get_ui_interface().get()))
		{
			canvas_interface_->get_ui_interface()->error(_("Render Failure"));
			canvas_interface_->get_ui_interface()->amount_complete(0,10000);
			return;
		}

		// Success!
		canvas_interface_->get_ui_interface()->task(pass_filename+_(" rendered successfully"));
		canvas_interface_->get_ui_interface()->amount_complete(0,10000);
		*/
	}
	return;
}

void
RenderSettings::on_finished()
{
	String text(_("File rendered successfully"));
	Real execution_time = async_renderer ? async_renderer->get_execution_time() : 0.0;
	if (execution_time > 0) text += strprintf(" (%f %s)", execution_time, _("sec"));

	canvas_interface_->get_ui_interface()->task(text);
	canvas_interface_->get_ui_interface()->amount_complete(0,10000);

	bool really_finished = (render_passes.size() == 0); //Must be checked BEFORE submit_next_render_pass();
	
	submit_next_render_pass();

	if (really_finished) { // Because of multi-pass render
		if (App::use_render_done_sound && App::sound_render_done) {
			App::sound_render_done->set_position(Time());
			App::sound_render_done->set_playing(true);
		}
		App::dock_info_->set_render_progress(1.0);
	}
}

void
RenderSettings::on_cancel_pressed()
{
	hide();
}

void
RenderSettings::on_single_frame_toggle()
{
	if(toggle_single_frame.get_active())
		widget_rend_desc.disable_time_section();
	else
		widget_rend_desc.enable_time_section();
}