/* === S Y N F I G ========================================================= */
/*! \file state_brush.cpp
** \brief Template File
**
** $Id$
**
** \legal
** ......... ... 2014 Ivan Mahonin
** ......... ... 2014 Jerome Blanchi
**
** 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 <synfig/general.h>
#include <gtkmm/dialog.h>
#include <gtkmm/entry.h>
#include <gtkmm/grid.h>
#include <gtkmm/toggletoolbutton.h>
#include <glibmm/timeval.h>
#include <giomm.h>
#include <synfig/canvasfilenaming.h>
#include <synfig/layers/layer_switch.h>
#include "state_brush.h"
#include "state_normal.h"
#include "canvasview.h"
#include "workarea.h"
#include "app.h"
#include <ETL/hermite>
#include <ETL/calculus>
#include <utility>
#include "event_mouse.h"
#include "event_layerclick.h"
#include "docks/dock_toolbox.h"
#include <synfigapp/blineconvert.h>
#include <synfigapp/wplistconverter.h>
#include <synfigapp/main.h>
#include <synfigapp/actions/layerpaint.h>
#include <ETL/gaussian>
#include "docks/dialog_tooloptions.h"
#include "ducktransform_matrix.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 ========================================================= */
#ifndef BRUSH_ICON_SIZE
# define BRUSH_ICON_SIZE 48
#endif
/* === G L O B A L S ======================================================= */
StateBrush studio::state_brush;
/* === C L A S S E S & S T R U C T S ======================================= */
class studio::StateBrush_Context : public sigc::trackable
{
public:
class BrushConfig {
public:
struct MapEntry {
float x, y;
MapEntry(): x(0.f), y(0.f) { }
};
struct InputEntry {
int input;
std::vector<MapEntry> mapping;
InputEntry(): input(0) { }
};
struct Entry {
float base;
std::vector<InputEntry> inputs;
Entry(): base(0.f) { }
};
String filename;
Entry settings[BRUSH_SETTINGS_COUNT];
void clear();
void load(const String &filename);
void apply(brushlib::Brush &brush);
private:
static const char * setting_names[BRUSH_SETTINGS_COUNT];
static const char * input_names[INPUT_COUNT];
bool read_row(const char **pos);
bool read_space(const char **pos);
bool read_to_line_end(const char **pos);
bool read_key(const char **pos, const char *key);
bool read_word(const char **pos, String &out_value);
bool read_float(const char **pos, float &out_value);
bool read_input_entry(const char **pos, InputEntry &out_value);
bool read_map_entry(const char **pos, MapEntry &out_value);
};
private:
etl::handle<CanvasView> canvas_view_;
CanvasView::IsWorking is_working;
WorkArea::PushState push_state;
Gtk::Menu menu;
Glib::TimeVal time;
etl::handle<synfigapp::Action::LayerPaint> action;
TransformStack transform_stack;
BrushConfig selected_brush_config;
Gtk::ToggleToolButton *selected_brush_button;
std::map<String, Gtk::ToggleToolButton*> brush_buttons;
bool scan_directory(const String &path, int scan_sub_levels, std::set<String> &out_files);
void select_brush(Gtk::ToggleToolButton *button, String filename);
void refresh_ducks();
synfigapp::Settings &settings;
Gtk::CheckButton eraser_checkbox;
void draw_to(Vector pos, Real pressure);
public:
void load_settings();
void save_settings();
Smach::event_result event_stop_handler(const Smach::event& x);
Smach::event_result event_refresh_handler(const Smach::event& x);
Smach::event_result event_mouse_down_handler(const Smach::event& x);
Smach::event_result event_mouse_up_handler(const Smach::event& x);
Smach::event_result event_mouse_draw_handler(const Smach::event& x);
Smach::event_result event_refresh_tool_options(const Smach::event& x);
static bool build_transform_stack(
Canvas::Handle canvas,
Layer::Handle layer,
CanvasView::Handle canvas_view,
TransformStack& transform_stack );
void refresh_tool_options();
StateBrush_Context(CanvasView* canvas_view);
~StateBrush_Context();
const etl::handle<CanvasView>& get_canvas_view()const{return canvas_view_;}
etl::handle<synfigapp::CanvasInterface> get_canvas_interface()const{return canvas_view_->canvas_interface();}
synfig::Time get_time()const { return get_canvas_interface()->get_time(); }
synfig::Canvas::Handle get_canvas()const{return canvas_view_->get_canvas();}
WorkArea * get_work_area()const{return canvas_view_->get_work_area();}
}; // END of class StateBrush_Context
/* === M E T H O D S ======================================================= */
const char * StateBrush_Context::BrushConfig::setting_names[] = {
"opaque", // BRUSH_OPAQUE 0
"opaque_multiply", // BRUSH_OPAQUE_MULTIPLY 1
"opaque_linearize", // BRUSH_OPAQUE_LINEARIZE 2
"radius_logarithmic", // BRUSH_RADIUS_LOGARITHMIC 3
"hardness", // BRUSH_HARDNESS 4
"anti_aliasing", // BRUSH_ANTI_ALIASING 5
"dabs_per_basic_radius", // BRUSH_DABS_PER_BASIC_RADIUS 6
"dabs_per_actual_radius", // BRUSH_DABS_PER_ACTUAL_RADIUS 7
"dabs_per_second", // BRUSH_DABS_PER_SECOND 8
"radius_by_random", // BRUSH_RADIUS_BY_RANDOM 9
"speed1_slowness", // BRUSH_SPEED1_SLOWNESS 10
"speed2_slowness", // BRUSH_SPEED2_SLOWNESS 11
"speed1_gamma", // BRUSH_SPEED1_GAMMA 12
"speed2_gamma", // BRUSH_SPEED2_GAMMA 13
"offset_by_random", // BRUSH_OFFSET_BY_RANDOM 14
"offset_by_speed", // BRUSH_OFFSET_BY_SPEED 15
"offset_by_speed_slowness", // BRUSH_OFFSET_BY_SPEED_SLOWNESS 16
"slow_tracking", // BRUSH_SLOW_TRACKING 17
"slow_tracking_per_dab", // BRUSH_SLOW_TRACKING_PER_DAB 18
"tracking_noise", // BRUSH_TRACKING_NOISE 19
"color_h", // BRUSH_COLOR_H 20
"color_s", // BRUSH_COLOR_S 21
"color_v", // BRUSH_COLOR_V 22
"restore_color", // BRUSH_RESTORE_COLOR 23
"change_color_h", // BRUSH_CHANGE_COLOR_H 24
"change_color_l", // BRUSH_CHANGE_COLOR_L 25
"change_color_hsl_s", // BRUSH_CHANGE_COLOR_HSL_S 26
"change_color_v", // BRUSH_CHANGE_COLOR_V 27
"change_color_hsv_s", // BRUSH_CHANGE_COLOR_HSV_S 28
"smudge", // BRUSH_SMUDGE 29
"smudge_length", // BRUSH_SMUDGE_LENGTH 30
"smudge_radius_log", // BRUSH_SMUDGE_RADIUS_LOG 31
"eraser", // BRUSH_ERASER 32
"stroke_treshold", // BRUSH_STROKE_THRESHOLD 33
"stroke_duration_logarithmic", // BRUSH_STROKE_DURATION_LOGARITHMIC 34
"stroke_holdtime", // BRUSH_STROKE_HOLDTIME 35
"custom_input", // BRUSH_CUSTOM_INPUT 36
"custom_input_slowness", // BRUSH_CUSTOM_INPUT_SLOWNESS 37
"elliptical_dab_ratio", // BRUSH_ELLIPTICAL_DAB_RATIO 38
"elliptical_dab_angle", // BRUSH_ELLIPTICAL_DAB_ANGLE 39
"direction_filter", // BRUSH_DIRECTION_FILTER 40
"lock_alpha" // BRUSH_LOCK_ALPHA 41
};
const char * StateBrush_Context::BrushConfig::input_names[] = {
"pressure", // INPUT_PRESSURE 0
"speed1", // INPUT_SPEED1 1
"speed2", // INPUT_SPEED2 2
"random", // INPUT_RANDOM 3
"stroke", // INPUT_STROKE 4
"direction", // INPUT_DIRECTION 5
"tilt_declination", // INPUT_TILT_DECLINATION 6
"tilt_ascension", // INPUT_TILT_ASCENSION 7
"custom", // INPUT_CUSTOM 8
};
StateBrush::StateBrush():
Smach::state<StateBrush_Context>("brush")
{
insert(event_def(EVENT_STOP,&StateBrush_Context::event_stop_handler));
insert(event_def(EVENT_REFRESH,&StateBrush_Context::event_refresh_handler));
insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_DOWN,&StateBrush_Context::event_mouse_down_handler));
insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_UP,&StateBrush_Context::event_mouse_up_handler));
insert(event_def(EVENT_WORKAREA_MOUSE_BUTTON_DRAG,&StateBrush_Context::event_mouse_draw_handler));
insert(event_def(EVENT_REFRESH_TOOL_OPTIONS,&StateBrush_Context::event_refresh_tool_options));
}
StateBrush::~StateBrush()
{
}
void
StateBrush_Context::BrushConfig::clear()
{
filename.clear();
for(int i = 0; i < BRUSH_SETTINGS_COUNT; ++i)
{
settings[i].base = 0;
settings[i].inputs.clear();
}
}
bool StateBrush_Context::BrushConfig::read_space(const char **pos)
{
while (**pos > 0 && **pos <= ' ') ++(*pos);
return true;
}
bool StateBrush_Context::BrushConfig::read_to_line_end(const char **pos)
{
while (**pos != 0 && **pos != '\n' && **pos != '\r') ++(*pos);
if (**pos == 0) return false;
if (**pos == '\n' && *(++(*pos)) == '\r') ++(*pos);
if (**pos == '\r' && *(++(*pos)) == '\n') ++(*pos);
return true;
}
bool StateBrush_Context::BrushConfig::read_key(const char **pos, const char *key)
{
size_t l = strlen(key);
if (strncmp(*pos, key, l) == 0)
{ *pos += l; return true; }
return false;
}
bool StateBrush_Context::BrushConfig::read_word(const char **pos, String &out_value)
{
out_value.clear();
const char *p = *pos;
while ((*p >= 'a' && *p <= 'z') || *p == '_') ++p;
if (p > *pos) { out_value.assign(*pos, p); *pos = p; return true; }
return false;
}
bool StateBrush_Context::BrushConfig::read_float(const char **pos, float &out_value)
{
out_value = 0.f;
const char *p = *pos;
bool negative = *p == '-';
if (negative) ++p;
const char *num_start = p;
while(*p >= '0' && *p <= '9')
out_value = 10.f*out_value + (float)(*(p++) - '0');
if (p <= num_start) return false;
if (*p == '.')
{
++p;
float amplifier = 1.f;
while(*p >= '0' && *p <= '9')
out_value += (amplifier *= 0.1f)*(float)(*(p++) - '0');
}
*pos = p;
return true;
}
bool StateBrush_Context::BrushConfig::read_map_entry(const char **pos, MapEntry &out_value)
{
out_value.x = 0.f;
out_value.y = 0.f;
const char *p = *pos;
bool success = read_key(&p, "(")
&& read_space(&p)
&& read_float(&p, out_value.x)
&& read_space(&p)
&& read_float(&p, out_value.y)
&& read_space(&p)
&& read_key(&p, ")");
if (success) { *pos = p; return true; }
out_value.x = 0.f;
out_value.y = 0.f;
return false;
}
bool StateBrush_Context::BrushConfig::read_input_entry(const char **pos, InputEntry &out_value)
{
out_value.input = 0;
out_value.mapping.clear();
const char *p = *pos;
String word;
if (read_space(&p) && read_word(&p, word))
{
for(int i = 0; i < INPUT_COUNT; ++i)
{
if (word == input_names[i])
{
MapEntry entry;
const char *pp = p;
while(read_space(&pp) && (out_value.mapping.empty() || (read_key(&pp, ",") && read_space(&pp))) && read_map_entry(&pp, entry))
{ out_value.mapping.push_back(entry); p = pp; }
if (out_value.mapping.size() > 1)
{
out_value.input = i;
*pos = p;
return true;
}
out_value.mapping.clear();
break;
}
}
}
return false;
}
bool
StateBrush_Context::BrushConfig::read_row(const char **pos)
{
const char *p = *pos;
String word;
if (read_space(&p) && read_word(&p, word))
{
for(int i = 0; i < BRUSH_SETTINGS_COUNT; ++i)
{
if (word == setting_names[i])
{
if (read_space(&p) && read_float(&p, settings[i].base))
{
InputEntry entry;
const char *pp = p;
while(read_space(&pp) && read_key(&pp, "|") && read_space(&pp) && read_input_entry(&pp, entry))
{ settings[i].inputs.push_back(entry); p = pp; }
*pos = p;
}
break;
}
}
}
return read_to_line_end(pos);
}
void
StateBrush_Context::BrushConfig::load(const String &filename)
{
clear();
char *buffer = NULL;
{
Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(filename);
goffset s = file->query_info()->get_size();
if (s < 0) return;
size_t size = s > INT_MAX-1 ? INT_MAX-1 : (size_t)s;
buffer = new char[size+1];
memset(buffer, 0, size+1);
Glib::RefPtr<Gio::FileInputStream> stream = file->read();
stream->read(buffer, size);
stream->close();
}
const char *pos = buffer;
if (pos != NULL) while(read_row(&pos)) { }
if (buffer) delete[] buffer;
this->filename = filename;
}
void
StateBrush_Context::BrushConfig::apply(brushlib::Brush &brush)
{
for(int i = 0; i < BRUSH_SETTINGS_COUNT; ++i)
{
brush.set_base_value(i, settings[i].base);
for(int j = 0; j < INPUT_COUNT; ++j)
brush.set_mapping_n(i, j, 0);
for(std::vector<InputEntry>::const_iterator j = settings[i].inputs.begin(); j != settings[i].inputs.end(); ++j)
{
brush.set_mapping_n(i, j->input, (int)j->mapping.size());
for(std::vector<MapEntry>::const_iterator k = j->mapping.begin(); k != j->mapping.end(); ++k)
brush.set_mapping_point(i, j->input, (int)(k - j->mapping.begin()), k->x, k->y);
}
}
}
void
StateBrush_Context::load_settings()
{
try
{
synfig::ChangeLocale change_locale(LC_NUMERIC, "C");
String value;
bool bvalue(settings.get_value("brush.path_count",value));
int count = atoi(value.c_str());
if(bvalue && count>0)
{
App::brushes_path.clear();
int count = atoi(value.c_str());
for(int i = 0; i < count; ++i)
if(settings.get_value(strprintf("brush.path_%d", i),value))
App::brushes_path.insert(value);
}
else
{
if (App::brushes_path.empty())
App::brushes_path.insert(App::get_base_path()+ETL_DIRECTORY_SEPARATOR+"share"+ETL_DIRECTORY_SEPARATOR+"synfig"+ETL_DIRECTORY_SEPARATOR+"brushes");
else
App::brushes_path.insert(*(App::brushes_path.begin()));
}
refresh_tool_options();
if (settings.get_value("brush.selected_brush_filename",value))
if (brush_buttons.count(value))
brush_buttons[value]->set_active(true);
if (settings.get_value("brush.eraser",value))
eraser_checkbox.set_active(value == "true");
}
catch(...)
{
synfig::warning("State Brush: Caught exception when attempting to load settings.");
}
}
void
StateBrush_Context::save_settings()
{
try
{
synfig::ChangeLocale change_locale(LC_NUMERIC, "C");
settings.set_value("brush.path_count", strprintf("%d", (int)App::brushes_path.size()));
int j = 0;
for(std::set<String>::const_iterator i = App::brushes_path.begin(); i != App::brushes_path.end(); ++i)
settings.set_value(strprintf("brush.path_%d", j++), *i);
settings.set_value("brush.selected_brush_filename", selected_brush_config.filename);
settings.set_value("brush.eraser", eraser_checkbox.get_active() ? "true" : "false");
}
catch(...)
{
synfig::warning("State Brush: Caught exception when attempting to save settings.");
}
}
StateBrush_Context::StateBrush_Context(CanvasView* canvas_view):
canvas_view_(canvas_view),
is_working(*canvas_view),
push_state(*get_work_area()),
selected_brush_button(NULL),
settings(synfigapp::Main::get_selected_input_device()->settings()),
eraser_checkbox(_("Eraser"))
{
load_settings();
//refresh_tool_options();
App::dialog_tool_options->present();
// Hide all tangent and width ducks
get_work_area()->set_type_mask(get_work_area()->get_type_mask()-Duck::TYPE_TANGENT-Duck::TYPE_WIDTH);
get_canvas_view()->toggle_duck_mask(Duck::TYPE_NONE);
// Turn off layer clicking
get_work_area()->set_allow_layer_clicks(false);
// Turn off duck clicking
get_work_area()->set_allow_duck_clicks(false);
get_work_area()->set_cursor(Gdk::PENCIL);
App::dock_toolbox->refresh();
refresh_ducks();
}
StateBrush_Context::~StateBrush_Context()
{
if (action)
{
get_canvas_interface()->get_instance()->perform_action(action);
action = NULL;
transform_stack.clear();
}
save_settings();
brush_buttons.clear();
selected_brush_button = NULL;
App::dialog_tool_options->clear();
get_work_area()->reset_cursor();
// Refresh the work area
get_work_area()->queue_draw();
App::dock_toolbox->refresh();
}
bool
StateBrush_Context::scan_directory(const String &path, int scan_sub_levels, std::set<String> &out_files)
{
if (scan_sub_levels < 0) return false;
Glib::RefPtr<Gio::File> directory = Gio::File::create_for_path(path);
Glib::RefPtr<Gio::FileEnumerator> e;
try
{
e = directory->enumerate_children();
}
catch(Gio::Error&) { return false; }
catch(Glib::FileError&) { return false; }
Glib::RefPtr<Gio::FileInfo> info;
while((bool)(info = e->next_file()))
{
String filepath = FileSystem::fix_slashes(directory->get_child(info->get_name())->get_path());
if (!scan_directory(filepath, scan_sub_levels-1, out_files))
out_files.insert(filepath);
}
return true;
}
void
StateBrush_Context::refresh_tool_options()
{
brush_buttons.clear();
App::dialog_tool_options->clear();
App::dialog_tool_options->set_local_name(_("Brush Tool"));
App::dialog_tool_options->set_name("brush");
// create the brush options container
Gtk::Grid *brush_option_grid= Gtk::manage(new Gtk::Grid());
brush_option_grid->set_orientation(Gtk::ORIENTATION_VERTICAL);
// add options
brush_option_grid->add(eraser_checkbox);
// create brushes scrollable palette
Gtk::ToolItemGroup *tool_item_group = manage(new class Gtk::ToolItemGroup());
gtk_tool_item_group_set_label(tool_item_group->gobj(), NULL);
Gtk::ToolPalette *palette = manage(new Gtk::ToolPalette());
palette->add(*tool_item_group);
palette->set_expand(*tool_item_group);
palette->set_exclusive(*tool_item_group, true);
palette->set_icon_size(Gtk::IconSize(BRUSH_ICON_SIZE));
// let the palette propagate the scroll events
palette->add_events(Gdk::SCROLL_MASK);
Gtk::ScrolledWindow *brushes_scroll = manage(new Gtk::ScrolledWindow());
brushes_scroll->set_hexpand(true);
brushes_scroll->set_vexpand(true);
brushes_scroll->add(*palette);
// load brushes files definition
// scan directories
std::set<String> files;
for(std::set<String>::const_iterator i = App::brushes_path.begin(); i != App::brushes_path.end(); ++i)
scan_directory(*i, 1, files);
// run through brush definition and assign a button
Gtk::ToggleToolButton *first_button = NULL;
for(std::set<String>::const_iterator i = files.begin(); i != files.end(); ++i)
{
if (!brush_buttons.count(*i) && filename_extension(*i) == ".myb")
{
const String &brush_file = *i;
const String icon_file = filename_sans_extension(brush_file) + "_prev.png";
if (files.count(icon_file))
{
// create a single brush button
Gtk::ToggleToolButton *brush_button = brush_buttons[*i] = (new class Gtk::ToggleToolButton());
Glib::RefPtr<Gdk::Pixbuf> pixbuf, pixbuf_scaled;
pixbuf = Gdk::Pixbuf::create_from_file(icon_file);
pixbuf_scaled = pixbuf->scale_simple(BRUSH_ICON_SIZE, BRUSH_ICON_SIZE, Gdk::INTERP_BILINEAR);
brush_button->set_icon_widget(*Gtk::manage(new Gtk::Image(pixbuf_scaled)));
brush_button->set_halign(Gtk::ALIGN_CENTER);
// connect the button click event and brush file definition
brush_button->signal_clicked().connect(
sigc::bind(sigc::mem_fun(*this, &StateBrush_Context::select_brush), brush_button, brush_file) );
// add the button to the palette
tool_item_group->insert(*brush_button);
// keep the first brush
if (first_button == NULL) first_button = brush_button;
}
}
}
brush_option_grid->add(*brushes_scroll);
brush_option_grid->show_all();
App::dialog_tool_options->add(*brush_option_grid);
// select first brush
if (first_button != NULL)
{
first_button->set_active(true);
selected_brush_button = first_button;
}
}
void
StateBrush_Context::select_brush(Gtk::ToggleToolButton *button, String filename)
{
if (button != NULL && button->get_active())
{
if (selected_brush_button != NULL) selected_brush_button->set_active(false);
selected_brush_config.load(filename);
eraser_checkbox.set_active(selected_brush_config.settings[BRUSH_ERASER].base > 0.0);
selected_brush_button = button;
}
}
Smach::event_result
StateBrush_Context::event_refresh_tool_options(const Smach::event& /*x*/)
{
refresh_tool_options();
return Smach::RESULT_ACCEPT;
}
Smach::event_result
StateBrush_Context::event_stop_handler(const Smach::event& /*x*/)
{
if (action)
{
get_canvas_interface()->get_instance()->perform_action(action);
action = NULL;
}
throw &state_normal;
return Smach::RESULT_OK;
}
Smach::event_result
StateBrush_Context::event_refresh_handler(const Smach::event& /*x*/)
{
refresh_ducks();
return Smach::RESULT_ACCEPT;
}
bool
StateBrush_Context::build_transform_stack(
Canvas::Handle canvas,
Layer::Handle layer,
CanvasView::Handle canvas_view,
TransformStack& transform_stack )
{
int count = 0;
for(Canvas::iterator i = canvas->begin(); i != canvas->end() ;++i)
{
if(*i == layer) return true;
if((*i)->active())
{
Transform::Handle trans((*i)->get_transform());
if(trans) { transform_stack.push(trans); count++; }
}
// If this is a paste canvas layer, then we need to
// descend into it
if(etl::handle<Layer_PasteCanvas> layer_pastecanvas = etl::handle<Layer_PasteCanvas>::cast_dynamic(*i))
{
transform_stack.push_back(
new Transform_Matrix(
layer_pastecanvas->get_guid(),
layer_pastecanvas->get_summary_transformation().get_matrix()
)
);
if (build_transform_stack(layer_pastecanvas->get_sub_canvas(), layer, canvas_view, transform_stack))
return true;
transform_stack.pop();
}
}
while(count-- > 0) transform_stack.pop();
return false;
}
Smach::event_result
StateBrush_Context::event_mouse_down_handler(const Smach::event& x)
{
const EventMouse& event(*reinterpret_cast<const EventMouse*>(&x));
switch(event.button)
{
case BUTTON_LEFT:
{
// Enter the stroke state to get the stroke
Layer::Handle selected_layer = canvas_view_->get_selection_manager()->get_selected_layer();
etl::handle<Layer_Bitmap> layer = etl::handle<Layer_Bitmap>::cast_dynamic(selected_layer);
if (!layer)
{
etl::handle<Layer_Switch> layer_switch = etl::handle<Layer_Switch>::cast_dynamic(selected_layer);
if (layer_switch) layer = etl::handle<Layer_Bitmap>::cast_dynamic(layer_switch->get_current_layer());
}
// No image found to draw in, add it.
if(!layer)
{
canvas_view_->add_layer("import");
selected_layer = canvas_view_->get_selection_manager()->get_selected_layer();
layer = etl::handle<Layer_Bitmap>::cast_dynamic(selected_layer);
// Set temporary description to generate the name
String temp_description(_("brush image"));
layer->set_description(temp_description);
if (selected_layer->get_param_list().count("filename") != 0)
{
// generate name based on description
String description, filename, filename_param;
get_canvas_interface()
->get_instance()
->generate_new_name(
layer,
description,
filename,
filename_param );
// create and save surface
get_canvas_interface()
->get_instance()
->save_surface(layer->rendering_surface, filename);
selected_layer->set_param("filename", filename_param);
selected_layer->set_description(description);
}
}
if (layer)
{
transform_stack.clear();
if (build_transform_stack(get_canvas(), layer, get_canvas_view(), transform_stack))
{
etl::handle<synfigapp::Action::LayerPaint> action = new synfigapp::Action::LayerPaint();
action->set_param("canvas",get_canvas());
action->set_param("canvas_interface",get_canvas_interface());
action->stroke.set_layer(layer);
selected_brush_config.apply( action->stroke.brush() );
Color color = synfigapp::Main::get_outline_color();
Real epsilon = 0.00000001;
Real r(color.get_r()), g(color.get_g()), b(color.get_b());
Real max_rgb = max(r, max(g, b));
Real min_rgb = min(r, min(g, b));
Real diff = max_rgb-min_rgb;
Real val = max_rgb;
Real sat = fabs(max_rgb) > epsilon ? 1.0 - (min_rgb / max_rgb) : 0;
Real hue = fabs(diff) <= epsilon ?
0 : max_rgb == r ?
60.0 * fmod ((g - b)/(diff), 6.0) : max_rgb == g ?
60.0 * (((b - r)/(diff))+2.0) : 60.0 * (((r - g)/(diff))+4.0);
Real opaque = color.get_a();
Real radius = synfigapp::Main::get_bline_width();
Real eraser = eraser_checkbox.get_active() ? 1.0 : 0.0;
action->stroke.brush().set_base_value(BRUSH_COLOR_H, hue/360.0);
action->stroke.brush().set_base_value(BRUSH_COLOR_S, sat);
action->stroke.brush().set_base_value(BRUSH_COLOR_V, val);
action->stroke.brush().set_base_value(BRUSH_OPAQUE, opaque);
action->stroke.brush().set_base_value(BRUSH_RADIUS_LOGARITHMIC, log(radius));
action->stroke.brush().set_base_value(BRUSH_ERASER, eraser);
action->stroke.prepare();
time.assign_current_time();
this->action = action;
draw_to(event.pos, 0);
return Smach::RESULT_ACCEPT;
}
}
break;
}
default:
break;
}
return Smach::RESULT_OK;
}
Smach::event_result
StateBrush_Context::event_mouse_up_handler(const Smach::event& x)
{
const EventMouse& event(*reinterpret_cast<const EventMouse*>(&x));
switch(event.button)
{
case BUTTON_LEFT:
{
if (action)
{
get_canvas_interface()->get_instance()->perform_action(action);
action = NULL;
transform_stack.clear();
return Smach::RESULT_ACCEPT;
}
break;
}
default:
break;
}
return Smach::RESULT_OK;
}
void
StateBrush_Context::draw_to(Vector pos, Real pressure)
{
Glib::TimeVal prev_time = time;
time.assign_current_time();
double delta_time = (time - prev_time).as_double();
if (delta_time < 0.00001) delta_time = 0.00001;
Point p = transform_stack.unperform( pos );
Point tl = action->stroke.get_layer()->get_param("tl").get(Point());
Point br = action->stroke.get_layer()->get_param("br").get(Point());
int w = action->stroke.get_layer()->rendering_surface ? action->stroke.get_layer()->rendering_surface->get_width() : 0;
int h = action->stroke.get_layer()->rendering_surface ? action->stroke.get_layer()->rendering_surface->get_height() : 0;
action->stroke.add_point_and_apply(
synfigapp::Action::LayerPaint::PaintPoint(
(float)((p[0] - tl[0])/(br[0] - tl[0])*w),
(float)((p[1] - tl[1])/(br[1] - tl[1])*h),
(float)pressure,
delta_time ));
}
Smach::event_result
StateBrush_Context::event_mouse_draw_handler(const Smach::event& x)
{
const EventMouse& event(*reinterpret_cast<const EventMouse*>(&x));
switch(event.button)
{
case BUTTON_LEFT:
{
if (action)
{
draw_to(event.pos, event.pressure);
return Smach::RESULT_ACCEPT;
}
break;
}
default:
break;
}
return Smach::RESULT_OK;
}
void
StateBrush_Context::refresh_ducks()
{
get_canvas_view()->queue_rebuild_ducks();
}