Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
sketch-fitter.cpp
Go to the documentation of this file.
1/*
2 * sb-to-bez Toy - Tests conversions from sbasis to cubic bezier.
3 *
4 * Copyright 2007 jf barraud.
5 * 2008 njh
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it either under the terms of the GNU Lesser General Public
9 * License version 2.1 as published by the Free Software Foundation
10 * (the "LGPL") or, at your option, under the terms of the Mozilla
11 * Public License Version 1.1 (the "MPL"). If you do not alter this
12 * notice, a recipient may use your version of this file under either
13 * the MPL or the LGPL.
14 *
15 * You should have received a copy of the LGPL along with this library
16 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 * You should have received a copy of the MPL along with this library
19 * in the file COPYING-MPL-1.1
20 *
21 * The contents of this file are subject to the Mozilla Public License
22 * Version 1.1 (the "License"); you may not use this file except in
23 * compliance with the License. You may obtain a copy of the License at
24 * http://www.mozilla.org/MPL/
25 *
26 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
27 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
28 * the specific language governing rights and limitations.
29 *
30 */
31
32// mainly experimental atm...
33// do not expect to find anything understandable here atm.
34
35#include <2geom/d2.h>
36#include <2geom/sbasis.h>
38#include <2geom/sbasis-math.h>
40#include <2geom/bezier-utils.h>
41
42#include <2geom/circle.h>
43
44#include <toys/path-cairo.h>
46
47#define ZERO 1e-7
48
49using std::vector;
50using namespace Geom;
51using namespace std;
52
53#include <stdio.h>
54#include <gsl/gsl_poly.h>
55
56std::vector<Point> neighbors(std::vector<Point> const &pts, unsigned idx, double radius){
57 std::vector<Point> res;
58 Point p0 = pts[idx];
59 for (auto p : pts){
60 if ( L2(p-p0) < radius ) res.push_back(p);
61 }
62 return res;
63}
64
65double curvature(Point const &a, Point const &b, Point const &c){
66 Line med_ab = Line( (a+b)/2, (a+b)/2+rot90(b-a) );
67 Line med_bc = Line( (b+c)/2, (b+c)/2+rot90(c-b) );
68 OptCrossing o = intersection(med_ab, med_bc);
69 if (o){
70 Point oo = med_ab.pointAt(o->ta);
71 return(1./L2(oo-a));
72 }
73 else
74 return 0;
75}
76
77double avarageCurvature(std::vector<Point> const &pts, unsigned idx, double radius){
78 std::vector<Point> ngbrs = neighbors(pts, idx, radius);
79 if (ngbrs.size()<3) return 0;
80 double k=0;
81 double mass = 0;
82 for (unsigned i=0; i<5; i++){
83 unsigned ia = 0, ib = 0, ic = 0;
84 ia = rand()%ngbrs.size();
85 while (ib == ia)
86 ib = rand()%ngbrs.size();
87 while (ic == ia || ic == ib)
88 ic = rand()%ngbrs.size();
89 k += curvature(pts[ia],pts[ib],pts[ic]);
90 mass += 1; //smaller mass to closer triplets?
91 }
92 k /= mass;
93 return k;
94}
95
96Point massCenter(std::vector<Point> const &pts){
97 Point g = Point(0,0);
98 for (unsigned i=0; i<pts.size(); i++){
99 g += pts[i]/pts.size();
100 }
101 return g;
102}
103
104Line meanSquareLine(std::vector<Point> const &pts){
105 Point g = massCenter(pts);
106 double a = 0, b = 0, c = 0;
107 for (auto pt : pts){
108 a += (pt[Y]-g[Y])*(pt[Y]-g[Y]);
109 b +=-(pt[X]-g[X])*(pt[Y]-g[Y]);
110 c += (pt[X]-g[X])*(pt[X]-g[X]);
111 }
112 double eigen = ( (a+c) - sqrt((a-c)*(a-c)+4*b*b) )/2;
113 Point u(-b,a-eigen);
114 return Line(g, g+u);
115}
116
117void tighten(std::vector<Point> &pts, double radius, bool linear){
118 for (unsigned i=0; i<pts.size(); i++){
119 std::vector<Point> ngbrs = neighbors(pts,i,radius);
120 if (linear){
121 Line d = meanSquareLine(ngbrs);
122 Point proj = projection( pts[i], d );
123 double t = 2./3.;
124 pts[i] = pts[i]*(1-t) + proj*t;
125 }else if (ngbrs.size()>=3) {
126 Circle c;
127 c.fit(ngbrs);
128 Point o = c.center();
129 double r = c.radius();
130 pts[i] = o + unit_vector(pts[i]-o)*r;
131 }
132 }
133}
134
135double dist_to(std::vector<Point> const &pts, Point const &p, unsigned *idx=NULL){
136 double d,d_min = std::numeric_limits<float>::infinity();
137 if (idx) *idx = pts.size();
138 for (unsigned i = 0; i<pts.size(); i++){
139 d = L2(pts[i]-p);
140 if ( d < d_min ){
141 d_min = d;
142 if (idx) *idx = i;
143 }
144 }
145 return d_min;
146}
147
148void fuse_close_points(std::vector<Point> &pts, double dist_min){
149 if (pts.size()==0) return;
150 std::vector<Point> reduced_pts;
151 reduced_pts.push_back(pts[0]);
152 for (auto & pt : pts){
153 double d = dist_to(reduced_pts, pt);
154 if ( d > dist_min ) reduced_pts.push_back(pt);
155 }
156 pts = reduced_pts;
157 return;
158}
159
160
161unsigned nearest_after(std::vector<Point>const &pts, unsigned idx, double *dist = NULL){
162 if ( idx >= pts.size()-1 ) return pts.size();
163 Point p = pts[idx];
164 unsigned res = idx+1;
165 double d_min = L2(p-pts[res]);
166 for (unsigned i=idx+2; i<pts.size(); i++){
167 double d = L2(p-pts[i]);
168 if (d < d_min) {
169 d_min = d;
170 res = i;
171 }
172 }
173 if (dist) *dist = d_min;
174 return res;
175}
176
177//TEST ME: use direction information to separate exaeco?
178void sort_nearest(std::vector<Point> &pts, double no_longer_than = 0){
179 double d;
180 Point p;
181 for (unsigned i=0; i<pts.size()-1; i++){
182 unsigned j = nearest_after(pts,i,&d);
183 if (no_longer_than >0.1 && d > no_longer_than){
184 pts.erase(pts.begin()+i+1, pts.end());
185 return;
186 }
187 p = pts[i+1];
188 pts[i+1] = pts[j];
189 pts[j] = p;
190 }
191}
192
193//FIXME: optimize me if further used...
194void sort_nearest_bis(std::vector<Point> &pts, double radius){
195 double d;
196 Point p;
197 for (unsigned i=0; i<pts.size()-1; i++){
198 bool already_visited = true;
199 unsigned next = 0; // silence warning
200 while ( i < pts.size()-1 && already_visited ){
201 next = nearest_after(pts,i,&d);
202 already_visited = false;
203 for (unsigned k=0; k<i; k++){
204 double d_k_next = L2( pts[next] - pts[k]);
205 if ( d_k_next < d && d_k_next < radius ){
206 already_visited = true;
207 pts.erase(pts.begin()+next);
208 break;
209 }
210 }
211 }
212 if (!already_visited){
213 p = pts[i+1];
214 pts[i+1] = pts[next];
215 pts[next] = p;
216 }
217 }
218}
219
220Path ordered_fit(std::vector<Point> &pts, double tol){
221 unsigned n_points = pts.size();
222 Geom::Point * b = g_new(Geom::Point, 4*n_points);
223 Geom::Point * points = g_new(Geom::Point, 4*n_points);
224 for (unsigned int i = 0; i < pts.size(); i++) {
225 points[i] = pts[i];
226 }
227 int max_segs = 4*n_points;
228 int const n_segs = bezier_fit_cubic_r(b, points, n_points,
229 tol*tol, max_segs);
230 Path res;
231 if ( n_segs > 0){
232 res = Path(b[0]);
233 for (int i=0; i<n_segs; i++){
234 res.appendNew<CubicBezier>(b[4*i+1],b[4*i+2],b[4*i+3]);
235 }
236 }
237 g_free(b);
238 g_free(points);
239 return res;
240}
241
242//-----------------------------------------------------------------------------------------
243//-----------------------------------------------------------------------------------------
244//-----------------------------------------------------------------------------------------
245
246
247std::vector<Point> eat(std::vector<Point> const &pts, double sampling){
248 std::vector<bool> visited(pts.size(),false);
249 std::vector<Point> res;
250 Point p = pts.front();
251 //Point q = p;
252 res.push_back(p);
253 while(true){
254 double num_nghbrs = 0;
255 Point next(0,0);
256 for(unsigned i = 0; i < pts.size(); i++) {
257 if (!visited[i] && L2(pts[i]-p)<sampling){
258 //TODO: rotate pts[i] so that last step was in dir -pi...
259 //dir += atan2(pts[i]-p);
260 visited[i] = true;
261 next+= pts[i]-p;
262 num_nghbrs += 1;
263 }
264 }
265 if (num_nghbrs == 0) break;
266 //q=p;
267 next *= 1./num_nghbrs;
268 p += next;
269 res.push_back(p);
270 }
271 return res;
272}
273
274
275
276
277
278//-----------------------------------------------------------------------------------------
279//-----------------------------------------------------------------------------------------
280//-----------------------------------------------------------------------------------------
281//-----------------------------------------------------------------------------------------
282//-----------------------------------------------------------------------------------------
283//-----------------------------------------------------------------------------------------
284
285double exp_rescale(double x)
286{
287 return pow(10, x*5-2);
288}
289std::string exp_formatter(double x)
290{
292}
293
294class SketchFitterToy: public Toy {
295
296 enum menu_item_t
297 {
298 SHOW_MENU = 0,
299 TEST_TIGHTEN,
300 TEST_EAT_BY_STEP,
301 TEST_TIGHTEN_EAT,
302 TEST_CURVATURE,
303 TEST_SORT,
304 TEST_NUMERICAL,
305 SHOW_HELP,
306 TOTAL_ITEMS // this one must be the last item
307 };
308
309 enum handle_label_t
310 {
311 };
312
313 enum toggle_label_t
314 {
315 DRAW_MOUSES = 0,
316 DRAW_IMPROVED_MOUSES,
317 DRAW_STROKE,
318 TIGHTEN_USE_CIRCLE,
319 SORT_BIS,
320 TOTAL_TOGGLES // this one must be the last item
321 };
322
323 enum slider_label_t
324 {
325 TIGHTEN_NBHD_SIZE = 0,
326 TIGHTEN_ITERRATIONS,
327 EAT_NBHD_SIZE,
328 SORT_RADIUS,
329 FUSE_RADIUS,
330 INTERPOLATE_RADIUS,
331 CURVATURE_NBHD_SIZE,
332 POINT_CHOOSER,
333 TOTAL_SLIDERS // this one must be the last item
334 };
335
336 static const char* menu_items[TOTAL_ITEMS];
337 static const char keys[TOTAL_ITEMS];
338
339 void fit_empty(){}
340 void first_time(int /*argc*/, char** /*argv*/) override
341 {
342 draw_f = &SketchFitterToy::draw_menu;
343 fit_f = &SketchFitterToy::fit_empty;
344 }
345
346 void init_common()
347 {
348 set_common_control_geometry = true;
349 set_control_geometry = true;
350
351 handles.clear();
352 handles.push_back(&(toggles[DRAW_MOUSES]));
353 handles.push_back(&(toggles[DRAW_IMPROVED_MOUSES]));
354 handles.push_back(&(toggles[DRAW_STROKE]));
355
356 //sliders.clear();
357 //toggles.clear();
358 //handles.clear();
359 }
360 void init_common_ctrl_geom(cairo_t* /*cr*/, int width, int /*height*/, std::ostringstream* /*notify*/)
361 {
362 if ( set_common_control_geometry )
363 {
364 set_common_control_geometry = false;
365 Point p(10, 20), d(width/3-20,25);
366 toggles[DRAW_MOUSES].bounds = Rect(p, p + d);
367 p += Point ((width)/3, 0);
368 toggles[DRAW_IMPROVED_MOUSES].bounds = Rect(p, p + d);
369 p += Point ((width)/3, 0);
370 toggles[DRAW_STROKE].bounds = Rect(p, p + d);
371 }
372 }
373 virtual void draw_common( cairo_t *cr, std::ostringstream *notify,
374 int width, int height, bool /*save*/, std::ostringstream */*timer_stream*/)
375 {
376 init_common_ctrl_geom(cr, width, height, notify);
377 if(!mouses.empty() && toggles[DRAW_MOUSES].on ) {
378 //cairo_move_to(cr, mouses[0]);
379 //for(unsigned i = 0; i < mouses.size(); i++) {
380 // cairo_line_to(cr, mouses[i]);
381 //}
382 for(auto & mouse : mouses) {
383 draw_cross(cr, mouse);
384 }
385 cairo_set_source_rgba (cr, 0., 0., 0., .25);
386 cairo_set_line_width (cr, 0.5);
387 cairo_stroke(cr);
388 }
389
390 if(!improved_mouses.empty() && toggles[DRAW_IMPROVED_MOUSES].on ) {
391 cairo_move_to(cr, improved_mouses[0]);
392 for(auto & improved_mouse : improved_mouses) {
393 draw_cross(cr, improved_mouse);
394 }
395 cairo_set_source_rgba (cr, 1., 0., 0., 1);
396 cairo_set_line_width (cr, .75);
397 cairo_stroke(cr);
398 }
399
400 if(!stroke.empty() && toggles[DRAW_STROKE].on) {
401 cairo_pw_d2_sb(cr, stroke);
402 cairo_set_source_rgba (cr, 0., 0., 1., 1);
403 cairo_set_line_width (cr, .75);
404 cairo_stroke(cr);
405 }
406
407 *notify << "Press SHIFT to continue sketching. 'Z' to apply changes";
408 }
409
410
411//-----------------------------------------------------------------------------------------
412// Tighten: tries to move the points toward the common curve
413//-----------------------------------------------------------------------------------------
414 void init_tighten()
415 {
416 init_common();
417 handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE]));
418 handles.push_back(&(sliders[TIGHTEN_ITERRATIONS]));
419 handles.push_back(&(toggles[TIGHTEN_USE_CIRCLE]));
420 }
421 void init_tighten_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int width, int height)
422 {
423 if ( set_control_geometry ){
424 set_control_geometry = false;
425 sliders[TIGHTEN_NBHD_SIZE ].geometry(Point(50, height - 35*2), 180);
426 sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180);
427
428 Point p(width-250, height - 50), d(225,25);
429 toggles[TIGHTEN_USE_CIRCLE].bounds = Rect(p, p + d);
430 }
431 }
432 void fit_tighten(){
433 improved_mouses = mouses;
434 double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value());
435 for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){
436 tighten(improved_mouses, radius, !toggles[TIGHTEN_USE_CIRCLE].on);
437 }
438 }
439 void draw_tighten(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
440 draw_common(cr, notify, width, height, save, timer_stream);
441 init_tighten_ctrl_geom(cr, notify, width, height);
442 }
443
444//-----------------------------------------------------------------------------------------
445// Eat by step: eats the curve moving at each step in the average direction of the neighbors.
446//-----------------------------------------------------------------------------------------
447 void init_eat()
448 {
449 init_common();
450 handles.push_back(&(sliders[EAT_NBHD_SIZE]));
451 }
452 void init_eat_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
453 {
454 if ( set_control_geometry ){
455 set_control_geometry = false;
456 sliders[EAT_NBHD_SIZE].geometry(Point(50, height - 35*(0+2)), 180);
457 }
458 }
459 void fit_eat(){
460 double radius = exp_rescale(sliders[EAT_NBHD_SIZE].value());
461 improved_mouses = mouses;
462
463 tighten(improved_mouses, 20, true);
464
466 improved_mouses = eat(improved_mouses, radius);
467 Path p(improved_mouses[0]);
468 for(unsigned i = 1; i < improved_mouses.size(); i++) {
469 p.appendNew<LineSegment>(improved_mouses[i]);
470 }
471 stroke = p.toPwSb();
472 }
473 void draw_eat(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
474 draw_common(cr, notify, width, height, save, timer_stream);
475 init_eat_ctrl_geom(cr, notify, width, height);
476 }
477
478//-----------------------------------------------------------------------------------------
479// Tighten + Eat
480//-----------------------------------------------------------------------------------------
481 void init_tighten_eat()
482 {
483 init_common();
484 handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE]));
485 handles.push_back(&(sliders[TIGHTEN_ITERRATIONS]));
486 handles.push_back(&(sliders[EAT_NBHD_SIZE]));
487 }
488 void init_tighten_eat_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
489 {
490 if ( set_control_geometry ){
491 set_control_geometry = false;
492 sliders[TIGHTEN_NBHD_SIZE ].geometry(Point(50, height - 35*2), 180);
493 sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180);
494 sliders[EAT_NBHD_SIZE ].geometry(Point(50, height - 35*4), 180);
495 }
496 }
497 void fit_tighten_eat(){
498 improved_mouses = mouses;
499 double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value());
500 for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){
501 tighten(improved_mouses, radius, toggles[0].on);
502 }
504 radius = exp_rescale(sliders[EAT_NBHD_SIZE].value());
505 improved_mouses = eat(improved_mouses, radius);
506 Path p(improved_mouses[0]);
507 for(unsigned i = 1; i < improved_mouses.size(); i++) {
508 p.appendNew<LineSegment>(improved_mouses[i]);
509 }
510 stroke = p.toPwSb();
511 }
512 void draw_tighten_eat(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
513 draw_common(cr, notify, width, height, save, timer_stream);
514 init_tighten_eat_ctrl_geom(cr, notify, width, height);
515 }
516
517//-----------------------------------------------------------------------------------------
518// Sort: tighten, then sort and eventually fuse.
519//-----------------------------------------------------------------------------------------
520 void init_sort()
521 {
522 init_common();
523 handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE]));
524 handles.push_back(&(sliders[TIGHTEN_ITERRATIONS]));
525 handles.push_back(&(sliders[SORT_RADIUS]));
526 handles.push_back(&(sliders[FUSE_RADIUS]));
527 handles.push_back(&(sliders[INTERPOLATE_RADIUS]));
528 handles.push_back(&(toggles[TIGHTEN_USE_CIRCLE]));
529 handles.push_back(&(toggles[SORT_BIS]));
530 }
531 void init_sort_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int width, int height)
532 {
533 if ( set_control_geometry ){
534 set_control_geometry = false;
535 sliders[TIGHTEN_NBHD_SIZE].geometry(Point(50, height - 35*2), 180);
536 sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180);
537 sliders[SORT_RADIUS].geometry(Point(50, height - 35*4), 180);
538 sliders[FUSE_RADIUS].geometry(Point(50, height - 35*5), 180);
539 sliders[INTERPOLATE_RADIUS].geometry(Point(50, height - 35*6), 180);
540
541 Point p(width-250, height - 50), d(225,25);
542 toggles[TIGHTEN_USE_CIRCLE].bounds = Rect(p, p + d);
543 p += Point(0,-30);
544 toggles[SORT_BIS].bounds = Rect(p, p + d);
545 }
546 }
547 void fit_sort(){
548 improved_mouses = mouses;
549 double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value());
550 for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){
551 tighten(improved_mouses, radius, !toggles[TIGHTEN_USE_CIRCLE].on);
552 }
553 double max_jump = exp_rescale(sliders[SORT_RADIUS].value());
554 if (toggles[SORT_BIS].on){
555 sort_nearest_bis(improved_mouses, max_jump);
556 }else{
557 sort_nearest(improved_mouses, max_jump);
558 }
559 radius = exp_rescale(sliders[FUSE_RADIUS].value());
560 fuse_close_points(improved_mouses, radius);
561
562 radius = exp_rescale(sliders[INTERPOLATE_RADIUS].value());
563 Path p = ordered_fit(improved_mouses, radius/5);
564 stroke = p.toPwSb();
565 }
566 void draw_sort(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
567 draw_common(cr, notify, width, height, save, timer_stream);
568 init_sort_ctrl_geom(cr, notify, width, height);
569
570 if(!improved_mouses.empty() && toggles[DRAW_IMPROVED_MOUSES].on ) {
571 cairo_move_to(cr, improved_mouses[0]);
572 for(unsigned i = 1; i < improved_mouses.size(); i++) {
573 cairo_line_to(cr, improved_mouses[i]);
574 }
575 cairo_set_source_rgba (cr, 1., 0., 0., 1);
576 cairo_set_line_width (cr, .75);
577 cairo_stroke(cr);
578 }
579 }
580
581//-----------------------------------------------------------------------------------------
582// Average curvature.
583//-----------------------------------------------------------------------------------------
584 void init_curvature()
585 {
586 init_common();
587 handles.push_back(&(sliders[CURVATURE_NBHD_SIZE]));
588 handles.push_back(&(sliders[POINT_CHOOSER]));
589 }
590 void init_curvature_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height)
591 {
592 if ( set_control_geometry ){
593 set_control_geometry = false;
594 sliders[CURVATURE_NBHD_SIZE].geometry(Point(50, height - 60), 180);
595 sliders[POINT_CHOOSER ].geometry(Point(50, height - 90), 180);
596 }
597 }
598 //just for fun!
599 void fit_curvature(){
600 std::vector<double> curvatures(mouses.size(),0);
601 std::vector<double> lengths(mouses.size(),0);
602 for (unsigned i=0; i<mouses.size(); i++){
603 double radius = exp_rescale(sliders[CURVATURE_NBHD_SIZE].value());
604 std::vector<Point> ngbrs = neighbors(mouses,i,radius);
605 if ( ngbrs.size()>2 ){
606 Circle c;
607 c.fit(ngbrs);
608 curvatures[i] = 1./c.radius();
609 Point v = (i<mouses.size()-1) ? mouses[i+1]-mouses[i] : mouses[i]-mouses[i-1];
610 if (cross(v, c.center()-mouses[i]) > 0 )
611 curvatures[i] *= -1;
612 }else{
613 curvatures[i] = 0;
614 }
615 if (i>0){
616 lengths[i] = lengths[i-1] + L2(mouses[i]-mouses[i-1]);
617 }
618 }
619 Piecewise<SBasis> k = interpolate( lengths, curvatures , 1);
622 stroke = Geom::integral(v) + mouses[0];
623
624 Point sp = stroke.lastValue()-stroke.firstValue();
625 Point mp = mouses.back()-mouses.front();
626 Affine mat1 = Affine(sp[X], sp[Y], -sp[Y], sp[X], stroke.firstValue()[X], stroke.firstValue()[Y]);
627 Affine mat2 = Affine(mp[X], mp[Y], -mp[Y], mp[X], mouses[0][X], mouses[0][Y]);
628 mat1 = mat1.inverse()*mat2;
629 stroke = stroke*mat1;
630
631 }
632 void draw_curvature(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
633 draw_common(cr, notify, width, height, save, timer_stream);
634 init_curvature_ctrl_geom(cr, notify, width, height);
635 if(!mouses.empty()) {
636 double radius = exp_rescale(sliders[CURVATURE_NBHD_SIZE].value());
637 unsigned i = unsigned( (mouses.size()-1)*sliders[POINT_CHOOSER].value()/100. );
638 std::vector<Point> ngbrs = neighbors(mouses,i,radius);
639 if ( ngbrs.size()>2 ){
640 draw_cross(cr, mouses[i]);
641 Circle c;
642 c.fit(ngbrs);
643 cairo_arc(cr, c.center(X), c.center(Y), c.radius(), 0, 2*M_PI);
644 cairo_set_source_rgba (cr, 1., 0., 0., 1);
645 cairo_set_line_width (cr, .75);
646 cairo_stroke(cr);
647 }
648 cairo_pw_d2_sb(cr, stroke);
649 }
650 }
651
652//-----------------------------------------------------------------------------------------
653// Brutal optimization, number of segment fixed.
654//-----------------------------------------------------------------------------------------
655 void init_numerical()
656 {
657 init_common();
658 //sliders.push_back(Slider(0, 10, 1, 1, "Number of curves"));
659 //handles.push_back(&(sliders[0]));
660 }
661 void init_numerical_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int /*height*/)
662 {
663 if ( set_control_geometry ){
664 set_control_geometry = false;
665 //sliders[0].geometry(Point(50, height - 35*(0+2)), 180);
666 }
667 }
668 void fit_numerical(){
669 //Not implemented
670 }
671 void draw_numerical(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) {
672 draw_common(cr, notify, width, height, save, timer_stream);
673 init_numerical_ctrl_geom(cr, notify, width, height);
674 if(!mouses.empty()) {
675 cairo_pw_d2_sb(cr, stroke);
676 cairo_set_source_rgba (cr, 1., 0., 0., 1);
677 cairo_set_line_width (cr, .75);
678 cairo_stroke(cr);
679 }
680 }
681//-----------------------------------------------------------------------------------------
682//-----------------------------------------------------------------------------------------
683
684 void init_help()
685 {
686 handles.clear();
687 //sliders.clear();
688 //toggles.clear();
689 }
690 void draw_help( cairo_t * /*cr*/, std::ostringstream *notify,
691 int /*width*/, int /*height*/, bool /*save*/, std::ostringstream */*timer_stream*/)
692 {
693 *notify << "Tighten:\n";
694 *notify << " move points toward local\n";
695 *notify << " mean square line (or circle).\n";
696 *notify << "Eat:\n";
697 *notify << " eat points like a pacman; at each step, move to the\n";
698 *notify << " average of the not already visited neighbor points.\n";
699 *notify << "Sort:\n";
700 *notify << " move from one point to the nearest one.\n";
701 *notify << " Stop at the first jump longer than sort-radius\n";
702 *notify << "Sort-bis:\n";
703 *notify << " move from one point to the nearest one,\n";
704 *notify << " unless it was 'already visited' (i.e. it is closer to\n";
705 *notify << " an already sorted point with distance < sort-radius.\n";
706 *notify << "Fuse: \n";
707 *notify << " start from first point, remove all points closer to it\n";
708 *notify << " than fuse-radius, move to the first one that is not, and repeat.\n";
709 *notify << "Curvature: \n";
710 *notify << " Compute the curvature at a given point from the circle fitting the\n";
711 *notify << " nearby points (just for fun: the stroke is the 'integral' of this\n";
712 *notify << " average curvature)\n";
713 *notify << "Numerical: \n";
714 *notify << " still waiting for someone to implement me ;-)\n\n";
715 *notify << std::endl;
716 }
717
718//-----------------------------------------------------------------------------------------
719//-----------------------------------------------------------------------------------------
720
721public:
722 vector<Point> mouses;
723 int mouse_drag;
724 vector<Point> improved_mouses;
726
727 void mouse_pressed(Geom::Point const &pos, unsigned button, unsigned modifiers) override {
728 //toggle_events(toggles, pos, button);
729 Toy::mouse_pressed(pos, button, modifiers);
730 if(!selected) {
731 mouse_drag = 1;
732 if (!(modifiers & (GDK_SHIFT_MASK))){
733 mouses.clear();
734 }
735 }
736 }
737
738 void mouse_moved(Geom::Point const &pos, unsigned modifiers) override
739 {
740 if (mouse_drag) {
741 mouses.emplace_back(pos);
742 redraw();
743 } else {
744 Toy::mouse_moved(pos, modifiers);
745 }
746 }
747
748 void mouse_released(Geom::Point const &pos, unsigned button, unsigned modifiers) override {
749 mouse_drag = 0;
750 if(!mouses.empty()) {
751 (this->*fit_f)();
752 }
753 Toy::mouse_released(pos, button, modifiers);
754 }
755
756 void init_menu()
757 {
758 handles.clear();
759 //sliders.clear();
760 //toggles.clear();
761 }
762 void draw_menu( cairo_t * cr, std::ostringstream *notify,
763 int /*width*/, int /*height*/, bool /*save*/, std::ostringstream */*timer_stream*/)
764 {
765 *notify << "Sketch some shape on canvas (press SHIFT to use several 'strokes')\n";
766 *notify << "Each menu below will transform your input.\n";
767 *notify << "Press 'Z' to make the result the new input\n";
768 *notify << " \n \n \n";
769 *notify << std::endl;
770 for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i)
771 {
772 *notify << " " << keys[i] << " - " << menu_items[i] << std::endl;
773 }
774 if(!mouses.empty()) {
775 cairo_move_to(cr, mouses[0]);
776 for(auto & mouse : mouses) {
777 cairo_line_to(cr, mouse);
778 }
779 for(auto & mouse : mouses) {
780 draw_cross(cr, mouse);
781 }
782 cairo_set_source_rgba (cr, 0., 0., 0., .25);
783 cairo_set_line_width (cr, 0.5);
784 cairo_stroke(cr);
785 }
786 }
787
788 void key_hit(unsigned keyval, unsigned modifiers) override
789 {
790 char choice = std::toupper(keyval);
791 switch ( choice )
792 {
793 case 'A':
794 init_menu();
795 draw_f = &SketchFitterToy::draw_menu;
796 break;
797 case 'B':
798 init_tighten();
799 fit_f = &SketchFitterToy::fit_tighten;
800 draw_f = &SketchFitterToy::draw_tighten;
801 break;
802 case 'C':
803 init_eat();
804 fit_f = &SketchFitterToy::fit_eat;
805 draw_f = &SketchFitterToy::draw_eat;
806 break;
807 case 'D':
808 init_tighten_eat();
809 fit_f = &SketchFitterToy::fit_tighten_eat;
810 draw_f = &SketchFitterToy::draw_tighten_eat;
811 break;
812 case 'E':
813 init_sort();
814 fit_f = &SketchFitterToy::fit_sort;
815 draw_f = &SketchFitterToy::draw_sort;
816 break;
817 case 'F':
818 init_curvature();
819 fit_f = &SketchFitterToy::fit_curvature;
820 draw_f = &SketchFitterToy::draw_curvature;
821 break;
822 case 'G':
823 init_numerical();
824 fit_f = &SketchFitterToy::fit_numerical;
825 draw_f = &SketchFitterToy::draw_numerical;
826 break;
827 case 'H':
828 init_help();
829 draw_f = &SketchFitterToy::draw_help;
830 break;
831 case 'Z':
832 mouses = improved_mouses;
833 break;
834 }
835 redraw();
836 }
837
838 void draw( cairo_t *cr, std::ostringstream *notify,
839 int width, int height, bool save, std::ostringstream *timer_stream ) override
840 {
841 m_width = width;
842 m_height = height;
843 m_length = (m_width > m_height) ? m_width : m_height;
844 m_length *= 2;
845 (this->*draw_f)(cr, notify, width, height, save, timer_stream);
846 Toy::draw(cr, notify, width, height, save, timer_stream);
847 }
848
849
850 public:
851 SketchFitterToy()
852 {
853 srand ( time(NULL) );
854 sliders = std::vector<Slider>(TOTAL_SLIDERS, Slider(0., 1., 0, 0., ""));
855
856 sliders[TIGHTEN_NBHD_SIZE ] = Slider(0., 1., 0, 0.65, "neighborhood size");
857 sliders[TIGHTEN_NBHD_SIZE ].formatter(&exp_formatter);
858 sliders[TIGHTEN_ITERRATIONS] = Slider(0, 10, 1, 3, "iterrations");
859 sliders[EAT_NBHD_SIZE ] = Slider(0., 1., 0, 0.65, "eating neighborhood size");
860 sliders[EAT_NBHD_SIZE ].formatter(&exp_formatter);
861 sliders[SORT_RADIUS ] = Slider(0., 1., 0, 0.65, "sort radius");
862 sliders[SORT_RADIUS ].formatter(&exp_formatter);
863 sliders[FUSE_RADIUS ] = Slider(0., 1., 0, 0.65, "fuse radius");
864 sliders[FUSE_RADIUS ].formatter(&exp_formatter);
865 sliders[INTERPOLATE_RADIUS ] = Slider(0., 1., 0, 0.65, "intrepolate precision");
866 sliders[INTERPOLATE_RADIUS ].formatter(&exp_formatter);
867 sliders[CURVATURE_NBHD_SIZE] = Slider(0., 1., 0, 0.65, "curvature nbhd size");
868 sliders[CURVATURE_NBHD_SIZE].formatter(&exp_formatter);
869 sliders[POINT_CHOOSER ] = Slider(0, 100, 0, 50, "Point chooser(%)");
870
871 toggles = std::vector<Toggle>(TOTAL_TOGGLES, Toggle("",true));
872 toggles[DRAW_MOUSES] = Toggle("Draw mouses",true);
873 toggles[DRAW_IMPROVED_MOUSES] = Toggle("Draw new mouses",true);
874 toggles[DRAW_STROKE] = Toggle("Draw stroke",true);
875 toggles[TIGHTEN_USE_CIRCLE] = Toggle("Tighten: use circle",false);
876 toggles[SORT_BIS ] = Toggle("Sort: bis",false);
877 }
878
879 private:
880 typedef void (SketchFitterToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*);
881 draw_func_t draw_f;
882 typedef void (SketchFitterToy::* fit_func_t) ();
883 fit_func_t fit_f;
884 bool set_common_control_geometry;
885 bool set_control_geometry;
886 std::vector<Toggle> toggles;
887 std::vector<Slider> sliders;
888 double m_width, m_height, m_length;
889
890}; // end class SketchFitterToy
891
892
893const char* SketchFitterToy::menu_items[] =
894{
895 "show this menu",
896 "tighten",
897 "eat points step by step",
898 "tighten + eat",
899 "tighten + sort + fuse",
900 "curvature",
901 "numerical",
902 "help",
903};
904
905const char SketchFitterToy::keys[] =
906{
907 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'
908};
909
910int main(int argc, char **argv) {
911 init(argc, argv, new SketchFitterToy);
912 return 0;
913}
914
915/*
916 Local Variables:
917 mode:c++
918 c-file-style:"stroustrup"
919 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
920 indent-tabs-mode:nil
921 fill-column:99
922 End:
923*/
924// vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 :
OptRect tighten(Rect &r, Point n, Interval lu)
Definition aa.cpp:42
Basic intersection routines.
int main()
Bezier fitting algorithms.
Circle shape.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Bezier curve with compile-time specified order.
Set of all points at a fixed distance from the center.
Definition circle.h:55
void fit(std::vector< Point > const &points)
Fit the circle to the passed points using the least squares method.
Definition circle.cpp:282
Infinite line on a plane.
Definition line.h:53
Point pointAt(Coord t) const
Definition line.h:231
Sequence of contiguous curves, aka spline.
Definition path.h:353
Piecewise< D2< SBasis > > toPwSb() const
Definition path.cpp:388
size_type size() const
Natural size of the path.
Definition path.h:490
void appendNew(Args &&... args)
Append a new curve to the path.
Definition path.h:804
Function defined as discrete pieces.
Definition piecewise.h:71
bool empty() const
Definition piecewise.h:132
unsigned size() const
Definition piecewise.h:131
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
virtual void first_time(int, char **)
virtual void mouse_pressed(Geom::Point const &pos, unsigned button, unsigned modifiers)
virtual void mouse_moved(Geom::Point const &pos, unsigned modifiers)
vector< Handle * > handles
Handle * selected
virtual void mouse_released(Geom::Point const &pos, unsigned button, unsigned modifiers)
virtual void save(FILE *f)
virtual void key_hit(unsigned keyval, unsigned modifiers)
virtual void draw(cairo_t *cr, std::ostringstream *notify, int w, int h, bool save, std::ostringstream *timing_stream)
Colors::Color stroke
double c[8][4]
Lifts one dimensional objects into 2D.
static auto proj(Geom::Point const &p, int dim)
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
T pow(T const &t, int n)
Integer exponentiation for transforms.
Definition transforms.h:98
static double exp_rescale(double x)
size_t v
Various utility functions.
Definition affine.h:22
Piecewise< SBasis > curvature(D2< SBasis > const &M, double tol=.01)
Point projection(Point const &p, Line const &line)
Definition line.h:513
std::optional< Crossing > OptCrossing
Definition crossing.h:64
OptCrossing intersection(Ray const &r1, Line const &l2)
Definition line.h:545
Piecewise< D2< SBasis > > sectionize(D2< Piecewise< SBasis > > const &a)
Definition d2-sbasis.cpp:65
SBasisN< n > sqrt(SBasisN< n > const &a, int k)
Piecewise< SBasis > interpolate(std::vector< double > times, std::vector< double > values, unsigned smoothness=1)
Returns a Piecewise SBasis with prescribed values at prescribed times.
int bezier_fit_cubic_r(Point bezier[], Point const data[], int len, double error, unsigned max_beziers)
Fit a multi-segment Bezier curve to a set of digitized points, with possible weedout of identical poi...
D2< Piecewise< SBasis > > tan2(SBasis const &angle, double tol=.01, unsigned order=3)
Bezier integral(Bezier const &a)
Definition bezier.cpp:294
Piecewise< SBasis > cross(Piecewise< D2< SBasis > > const &a, Piecewise< D2< SBasis > > const &b)
SBasis L2(D2< SBasis > const &a, unsigned k)
Definition d2-sbasis.cpp:42
Point unit_vector(Point const &a)
D2< T > rot90(D2< T > const &a)
Definition d2.h:397
STL namespace.
void cairo_line_to(cairo_t *cr, Geom::Point p1)
void draw_cross(cairo_t *cr, Geom::Point h)
struct _cairo cairo_t
Definition path-cairo.h:16
void cairo_move_to(cairo_t *cr, Geom::Point p1)
void cairo_pw_d2_sb(cairo_t *cr, Geom::Piecewise< Geom::D2< Geom::SBasis > > const &p)
Linear linear(double ax, double b)
two-dimensional geometric operators.
some std functions to work with (pw)s-basis
Polynomial in symmetric power basis (S-basis)
std::vector< Point > eat(std::vector< Point > const &pts, double sampling)
Point massCenter(std::vector< Point > const &pts)
void sort_nearest_bis(std::vector< Point > &pts, double radius)
void sort_nearest(std::vector< Point > &pts, double no_longer_than=0)
std::string exp_formatter(double x)
Path ordered_fit(std::vector< Point > &pts, double tol)
void tighten(std::vector< Point > &pts, double radius, bool linear)
void fuse_close_points(std::vector< Point > &pts, double dist_min)
double dist_to(std::vector< Point > const &pts, Point const &p, unsigned *idx=NULL)
double exp_rescale(double x)
std::vector< Point > neighbors(std::vector< Point > const &pts, unsigned idx, double radius)
unsigned nearest_after(std::vector< Point >const &pts, unsigned idx, double *dist=NULL)
double avarageCurvature(std::vector< Point > const &pts, unsigned idx, double radius)
Line meanSquareLine(std::vector< Point > const &pts)
double height
double width
void cairo_set_source_rgba(cairo_t *cr, colour c)
std::string default_formatter(double x)
void redraw()
void init(int argc, char **argv, Toy *t, int width=600, int height=600)