Code

add sketch mode to pencil tool
authorjohanengelen <johanengelen@users.sourceforge.net>
Sat, 13 Dec 2008 17:31:18 +0000 (17:31 +0000)
committerjohanengelen <johanengelen@users.sourceforge.net>
Sat, 13 Dec 2008 17:31:18 +0000 (17:31 +0000)
src/pencil-context.cpp
src/pencil-context.h
src/preferences-skeleton.h
src/ui/dialog/inkscape-preferences.cpp
src/ui/dialog/inkscape-preferences.h

index a80f565faf4725ea5059647b311ae59ccc705cbd..1b13445d6dbd5eed8911a6850f7aaaeddd9a689b 100644 (file)
@@ -30,7 +30,7 @@
 #include "preferences.h"
 #include "snap.h"
 #include "pixmaps/cursor-pencil.xpm"
-#include "display/bezier-utils.h"
+#include <2geom/bezier-utils.h>
 #include "display/canvas-bpath.h"
 #include <glibmm/i18n.h>
 #include "libnr/in-svg-plane.h"
@@ -52,6 +52,7 @@ static gint pencil_handle_button_press(SPPencilContext *const pc, GdkEventButton
 static gint pencil_handle_motion_notify(SPPencilContext *const pc, GdkEventMotion const &mevent);
 static gint pencil_handle_button_release(SPPencilContext *const pc, GdkEventButton const &revent);
 static gint pencil_handle_key_press(SPPencilContext *const pc, guint const keyval, guint const state);
+static gint pencil_handle_key_release(SPPencilContext *const pc, guint const keyval, guint const state);
 
 static void spdc_set_startpoint(SPPencilContext *pc, Geom::Point const p);
 static void spdc_set_endpoint(SPPencilContext *pc, Geom::Point const p);
@@ -59,6 +60,7 @@ static void spdc_finish_endpoint(SPPencilContext *pc);
 static void spdc_add_freehand_point(SPPencilContext *pc, Geom::Point p, guint state);
 static void fit_and_split(SPPencilContext *pc);
 static void interpolate(SPPencilContext *pc);
+static void sketch_interpolate(SPPencilContext *pc);
 
 static SPDrawContextClass *pencil_parent_class;
 static Geom::Point pencil_drag_origin_w(0, 0);
@@ -122,6 +124,10 @@ sp_pencil_context_init(SPPencilContext *pc)
     pc->npoints = 0;
     pc->state = SP_PENCIL_CONTEXT_IDLE;
     pc->req_tangent = Geom::Point(0, 0);
+
+    // since SPPencilContext is not properly constructed...
+    pc->sketch_interpolation = Geom::Path();
+    pc->sketch_n = 0;
 }
 
 /**
@@ -193,6 +199,10 @@ sp_pencil_context_root_handler(SPEventContext *const ec, GdkEvent *event)
             ret = pencil_handle_key_press(pc, get_group0_keyval (&event->key), event->key.state);
             break;
 
+        case GDK_KEY_RELEASE:
+            ret = pencil_handle_key_release(pc, get_group0_keyval (&event->key), event->key.state);
+            break;
+
         default:
             break;
     }
@@ -442,26 +452,41 @@ pencil_handle_button_release(SPPencilContext *const pc, GdkEventButton const &re
                 ret = TRUE;
                 break;
             case SP_PENCIL_CONTEXT_FREEHAND:
-                /* Finish segment now */
-                /// \todo fixme: Clean up what follows (Lauris)
-                if (anchor) {
-                    p = anchor->dp;
-                }
-                pc->ea = anchor;
-                /* Write curves to object */
+                if (revent.state & GDK_MOD1_MASK) {
+                    /* sketch mode: interpolate the sketched path and improve the current output path with the new interpolation. don't finish sketch */
+                    if (anchor) {
+                        p = anchor->dp;
+                    }
+                    pc->ea = anchor;
 
-                dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing freehand"));
+                    dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Sketching: release <b>Alt</b> to finalize current curve"));
 
-                interpolate(pc);
-                spdc_concat_colors_and_flush(pc, FALSE);
-                pc->sa = NULL;
-                pc->ea = NULL;
-                if (pc->green_anchor) {
-                    pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor);
+                    sketch_interpolate(pc);
+
+                    pc->state = SP_PENCIL_CONTEXT_SKETCH;
+                } else {
+                    /* Finish segment now */
+                    /// \todo fixme: Clean up what follows (Lauris)
+                    if (anchor) {
+                        p = anchor->dp;
+                    }
+                    pc->ea = anchor;
+                    /* Write curves to object */
+
+                    dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing freehand"));
+
+                    interpolate(pc);
+                    spdc_concat_colors_and_flush(pc, FALSE);
+                    pc->sa = NULL;
+                    pc->ea = NULL;
+                    if (pc->green_anchor) {
+                        pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor);
+                    }
+                    pc->state = SP_PENCIL_CONTEXT_IDLE;
                 }
-                pc->state = SP_PENCIL_CONTEXT_IDLE;
                 ret = TRUE;
                 break;
+            case SP_PENCIL_CONTEXT_SKETCH:
             default:
                 break;
         }
@@ -553,6 +578,34 @@ pencil_handle_key_press(SPPencilContext *const pc, guint const keyval, guint con
     return ret;
 }
 
+static gint
+pencil_handle_key_release(SPPencilContext *const pc, guint const keyval, guint const /*state*/)
+{
+    gint ret = FALSE;
+    switch (keyval) {
+        case GDK_Alt_L:
+        case GDK_Alt_R:
+        case GDK_Meta_L:
+        case GDK_Meta_R:
+            if (pc->state == SP_PENCIL_CONTEXT_SKETCH) {
+                spdc_concat_colors_and_flush(pc, FALSE);
+                pc->sketch_interpolation = Geom::Path();
+                pc->sketch_n = 0;
+                pc->sa = NULL;
+                pc->ea = NULL;
+                if (pc->green_anchor) {
+                    pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor);
+                }
+                pc->state = SP_PENCIL_CONTEXT_IDLE;
+                ret = TRUE;
+            }
+            break;
+        default:
+            break;
+    }
+    return ret;
+}
+
 /**
  * Reset points and set new starting point.
  */
@@ -678,7 +731,7 @@ interpolate(SPPencilContext *pc)
     // worst case gives us a segment per point
     int max_segs = 4*n_points;
 
-    int const n_segs = sp_bezier_fit_cubic_r(b, points, n_points,
+    int const n_segs = Geom::bezier_fit_cubic_r(b, points, n_points,
                                              tolerance_sq, max_segs);
 
     if ( n_segs > 0)
@@ -711,6 +764,94 @@ interpolate(SPPencilContext *pc)
 }
 
 
+/* interpolates the sketched curve and tweaks the current sketch interpolation*/
+static void
+sketch_interpolate(SPPencilContext *pc)
+{
+    g_assert( pc->ps.size() > 1 );
+
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    double const tol = 100.0 * 0.4; //prefs->getDoubleLimited("/tools/freehand/pencil/tolerance", 10.0, 1.0, 100.0) * 0.4;
+    double const tolerance_sq = 0.02 * square( pc->desktop->w2d().descrim() * 
+                                               tol) * exp(0.2*tol - 2);
+
+    bool average_all_sketches = prefs->getBool("/tools/freehand/pencil/average_all_sketches", true);
+
+    g_assert(is_zero(pc->req_tangent)
+             || is_unit_vector(pc->req_tangent));
+    Geom::Point const tHatEnd(0, 0);
+
+    guint n_points  = pc->ps.size();
+    pc->red_curve->reset();
+    pc->red_curve_is_valid = false;
+
+    Geom::Point * b = g_new(Geom::Point, 4*n_points);
+    Geom::Point * points = g_new(Geom::Point, 4*n_points);
+    for (unsigned i = 0; i < pc->ps.size(); i++) { 
+        points[i] = pc->ps[i];
+    }
+
+    // worst case gives us a segment per point
+    int max_segs = 4*n_points;
+
+    int const n_segs = Geom::bezier_fit_cubic_r(b, points, n_points,
+                                             tolerance_sq, max_segs);
+
+    if ( n_segs > 0)
+    {
+        // only take first cubic and average its coefficients with the old ones
+
+        Geom::Point coeffs[4];
+        if ( pc->sketch_n > 0 ) {
+            Geom::CubicBezier const * oldcubic = dynamic_cast<Geom::CubicBezier const *>( &pc->sketch_interpolation.front() );
+            g_assert(oldcubic);
+            for (unsigned i = 0; i < 4; i++){
+                if (average_all_sketches) {
+                    // Average = (sum of all) / n
+                    //         = (sum of all + new one) / n+1
+                    //         = ((old average)*n + new one) / n+1
+                    coeffs[i] = ((*oldcubic)[i] * pc->sketch_n  +  b[i]) / (pc->sketch_n + 1);
+                } else {
+                    coeffs[i] = 0.5 * (b[i] + (*oldcubic)[i]);
+                }
+            }
+        } else {
+            for (unsigned i = 0; i < 4; i++){
+                coeffs[i] = b[i];
+            }
+        }
+        pc->sketch_n++;
+
+        pc->sketch_interpolation.start(coeffs[0]);
+        pc->sketch_interpolation.appendNew<Geom::CubicBezier>(coeffs[1], coeffs[2], coeffs[3]);
+        
+        Geom::PathVector temp;
+        temp.push_back(pc->sketch_interpolation);
+
+        pc->green_curve->reset();
+        pc->green_curve->set_pathvector(temp);
+        sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->green_curve);
+
+        /* Fit and draw and copy last point */
+        g_assert(!pc->green_curve->is_empty());
+
+        /* Set up direction of next curve. */
+        {
+            Geom::CubicBezier const * last_seg = dynamic_cast<Geom::CubicBezier const *>(pc->green_curve->last_segment());
+            g_assert( last_seg );      // Relevance: validity of (*last_seg)[2]
+            pc->p[0] = last_seg->finalPoint();
+            pc->npoints = 1;
+            Geom::Point const req_vec( pc->p[0] - (*last_seg)[2] );
+            pc->req_tangent = ( ( Geom::is_zero(req_vec) || !in_svg_plane(req_vec) )
+                                ? Geom::Point(0, 0)
+                                : Geom::unit_vector(req_vec) );
+        }
+    }
+    g_free(b);
+    g_free(points);
+    pc->ps.clear();
+}
+
 static void
 fit_and_split(SPPencilContext *pc)
 {
@@ -722,7 +863,7 @@ fit_and_split(SPPencilContext *pc)
     g_assert(is_zero(pc->req_tangent)
              || is_unit_vector(pc->req_tangent));
     Geom::Point const tHatEnd(0, 0);
-    int const n_segs = sp_bezier_fit_cubic_full(b, NULL, pc->p, pc->npoints,
+    int const n_segs = Geom::bezier_fit_cubic_full(b, NULL, pc->p, pc->npoints,
                                                 pc->req_tangent, tHatEnd, 
                                                 tolerance_sq, 1);
     if ( n_segs > 0
index 0845c82ba9234b7e0e6b2e06383fbce3583d1bff..dd59c0466f3a1b98fc0c91710732b262921fe8e5 100644 (file)
@@ -17,7 +17,8 @@
 enum PencilState {
     SP_PENCIL_CONTEXT_IDLE,
     SP_PENCIL_CONTEXT_ADDLINE,
-    SP_PENCIL_CONTEXT_FREEHAND
+    SP_PENCIL_CONTEXT_FREEHAND,
+    SP_PENCIL_CONTEXT_SKETCH
 };
 
 /**
@@ -31,8 +32,11 @@ struct SPPencilContext : public SPDrawContext {
 
     bool is_drawing;
     bool prev_snap_was_succesful;
-    
+
     std::vector<Geom::Point> ps;
+
+    Geom::Path sketch_interpolation; // the current proposal from the sketched paths
+    unsigned sketch_n; // number of sketches done
 };
 
 /// The SPPencilContext vtable (empty).
index 68df4e730965f2a939b0143c28551ab9503bf71f..dd6a9a92f3fed86906358694430b8f176a424c0d 100644 (file)
@@ -76,7 +76,7 @@ static char const preferences_skeleton[] =
 "    </group>\n"
 "    <group id=\"freehand\"\n"
 "         style=\"fill:none;fill-rule:evenodd;stroke:black;stroke-opacity:1;stroke-linejoin:miter;stroke-linecap:butt;\">\n"
-"      <eventcontext id=\"pencil\" tolerance=\"4.0\" selcue=\"1\" style=\"stroke-width:1px;\" usecurrent=\"0\"/>\n"
+"      <eventcontext id=\"pencil\" tolerance=\"4.0\" selcue=\"1\" style=\"stroke-width:1px;\" usecurrent=\"0\" average_all_sketches=\"1\"/>\n"
 "      <eventcontext id=\"pen\" mode=\"drag\" selcue=\"1\" style=\"stroke-width:1px;\" usecurrent=\"0\"/>\n"
 "    </group>\n"
 "    <eventcontext id=\"calligraphic\" style=\"fill:black;fill-opacity:1;fill-rule:nonzero;stroke:none;\"\n"
index b1a14e7817c373c43067684832a56e22c7cd862b..d656e2a537b970be878be298df81ada73e3933f5 100644 (file)
@@ -387,6 +387,7 @@ void InkscapePreferences::initPageTools()
     _page_tools.add_line( true, "", _t_cvg_convert_whole_groups, "",
                             _("Treat groups as a single object during conversion to guides rather than converting each child separately."));
 
+    _pencil_average_all_sketches.init ( _("Average all sketches"), "/tools/freehand/pencil/average_all_sketches", false);
     _calligrapy_use_abs_size.init ( _("Width is in absolute units"), "/tools/calligraphic/abs_width", false);
     _calligrapy_keep_selected.init ( _("Select new path"), "/tools/calligraphic/keep_selected", true);
     _connector_ignore_text.init( _("Don't attach connectors to text objects"), "/tools/connector/ignoretext", true);
@@ -469,6 +470,9 @@ void InkscapePreferences::initPageTools()
     this->AddSelcueCheckbox(_page_pencil, "/tools/freehand/pencil", true);
     this->AddNewObjectsStyle(_page_pencil, "/tools/freehand/pencil");
     this->AddDotSizeSpinbutton(_page_pencil, "/tools/freehand/pencil", 3.0);
+    _page_pencil.add_group_header( _("Sketch mode"));
+    _page_pencil.add_line( true, "", _pencil_average_all_sketches, "",
+                            _("If on, the sketch result will be the normal average of all sketches made, instead of averaging the old result with the new sketch."));
 
     //Pen
     this->AddPage(_page_pen, _("Pen"), iter_tools, PREFS_PAGE_TOOLS_PEN);
index b439f25dd26ad1b7e425045d4dc2248d8305110f..1c5a3a85f667c0bb09217e0864c2977beea6794e 100644 (file)
@@ -149,6 +149,8 @@ protected:
        PrefSpinButton _win_trans_blur;  /**< The dialog transparency setting for when the dialog is out of focus. */
        PrefSpinButton _win_trans_time;  /**< How much time to go from one transparency setting to another */
 
+    PrefCheckButton _pencil_average_all_sketches;
+
     PrefCheckButton _calligrapy_use_abs_size;
     PrefCheckButton _calligrapy_keep_selected;