/* === 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 #endif #include #include #include #include #include #include #include #include "state_brush.h" #include "state_normal.h" #include "canvasview.h" #include "workarea.h" #include "app.h" #include #include #include #include "event_mouse.h" #include "event_layerclick.h" #include "docks/dock_toolbox.h" #include #include #include #include #include #include "docks/dialog_tooloptions.h" #include "ducktransform_matrix.h" #include "general.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 mapping; InputEntry(): input(0) { } }; struct Entry { float base; std::vector 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 canvas_view_; CanvasView::IsWorking is_working; WorkArea::PushState push_state; Gtk::Menu menu; std::set paths; Glib::TimeVal time; etl::handle action; TransformStack transform_stack; BrushConfig selected_brush_config; Gtk::ToggleToolButton *selected_brush_button; std::map brush_buttons; bool scan_directory(const String &path, int scan_sub_levels, std::set &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& get_canvas_view()const{return canvas_view_;} etl::handle 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("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 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 stream = file->read(); stream->read(buffer, size); stream->close(); } const char *pos = buffer; if (pos != NULL) while(read_row(&pos)) { } free(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::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::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; /*if(settings.get_value("brush.path_count",value)) { paths.clear(); int count = atoi(value.c_str()); for(int i = 0; i < count; ++i) if(settings.get_value(strprintf("brush.path_%d", i),value)) paths.insert(value); } else {*/ if (App::brushes_path=="") paths.insert(App::get_base_path()+ETL_DIRECTORY_SEPARATOR+"share"+ETL_DIRECTORY_SEPARATOR+"synfig"+ETL_DIRECTORY_SEPARATOR+"brushes"); else paths.insert(App::brushes_path); //} 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)paths.size())); int j = 0; for(std::set::const_iterator i = paths.begin(); i != paths.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 &out_files) { if (scan_sub_levels < 0) return false; Glib::RefPtr directory = Gio::File::create_for_path(path); Glib::RefPtr e; try { e = directory->enumerate_children(); } catch(Gio::Error&) { return false; } catch(Glib::FileError&) { return false; } Glib::RefPtr 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 files; for(std::set::const_iterator i = paths.begin(); i != paths.end(); ++i) scan_directory(*i, 1, files); // run through brush definition and assign a button Gtk::ToggleToolButton *first_button = NULL; for(std::set::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 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 = etl::handle::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(&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 = etl::handle::cast_dynamic(selected_layer); if (!layer) { etl::handle layer_switch = etl::handle::cast_dynamic(selected_layer); if (layer_switch) layer = etl::handle::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::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) { // TODO: "images" and "container:" literals get_canvas_interface() ->get_instance() ->get_file_system() ->directory_create("#images"); // generate name based on description String description, filename, filename_param; get_canvas_interface() ->get_instance() ->generate_new_name( layer, NULL, get_canvas_interface()->get_instance()->get_file_system(), description, filename, filename_param ); get_canvas_interface() ->get_instance() ->save_surface(layer->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 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 = max_rgb != 0 ? 1.0 - (min_rgb / max_rgb) : 0; Real hue = max_rgb == min_rgb ? 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(&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()->surface.get_w(); int h = action->stroke.get_layer()->surface.get_h(); 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(&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(); }