/* * sb-to-bez Toy - Tests conversions from sbasis to cubic bezier. * * Copyright 2007 jf barraud. * 2008 njh * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation * (the "LGPL") or, at your option, under the terms of the Mozilla * Public License Version 1.1 (the "MPL"). If you do not alter this * notice, a recipient may use your version of this file under either * the MPL or the LGPL. * * You should have received a copy of the LGPL along with this library * in the file COPYING-LGPL-2.1; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * You should have received a copy of the MPL along with this library * in the file COPYING-MPL-1.1 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY * OF ANY KIND, either express or implied. See the LGPL or the MPL for * the specific language governing rights and limitations. * */ // mainly experimental atm... // do not expect to find anything understandable here atm. #include <2geom/d2.h> #include <2geom/sbasis.h> #include <2geom/sbasis-geometric.h> #include <2geom/sbasis-math.h> #include <2geom/basic-intersection.h> #include <2geom/bezier-utils.h> #include <2geom/circle.h> #include #include #define ZERO 1e-7 using std::vector; using namespace Geom; using namespace std; #include #include std::vector neighbors(std::vector const &pts, unsigned idx, double radius){ std::vector res; Point p0 = pts[idx]; for (auto p : pts){ if ( L2(p-p0) < radius ) res.push_back(p); } return res; } double curvature(Point const &a, Point const &b, Point const &c){ Line med_ab = Line( (a+b)/2, (a+b)/2+rot90(b-a) ); Line med_bc = Line( (b+c)/2, (b+c)/2+rot90(c-b) ); OptCrossing o = intersection(med_ab, med_bc); if (o){ Point oo = med_ab.pointAt(o->ta); return(1./L2(oo-a)); } else return 0; } double avarageCurvature(std::vector const &pts, unsigned idx, double radius){ std::vector ngbrs = neighbors(pts, idx, radius); if (ngbrs.size()<3) return 0; double k=0; double mass = 0; for (unsigned i=0; i<5; i++){ unsigned ia = 0, ib = 0, ic = 0; ia = rand()%ngbrs.size(); while (ib == ia) ib = rand()%ngbrs.size(); while (ic == ia || ic == ib) ic = rand()%ngbrs.size(); k += curvature(pts[ia],pts[ib],pts[ic]); mass += 1; //smaller mass to closer triplets? } k /= mass; return k; } Point massCenter(std::vector const &pts){ Point g = Point(0,0); for (unsigned i=0; i const &pts){ Point g = massCenter(pts); double a = 0, b = 0, c = 0; for (auto pt : pts){ a += (pt[Y]-g[Y])*(pt[Y]-g[Y]); b +=-(pt[X]-g[X])*(pt[Y]-g[Y]); c += (pt[X]-g[X])*(pt[X]-g[X]); } double eigen = ( (a+c) - sqrt((a-c)*(a-c)+4*b*b) )/2; Point u(-b,a-eigen); return Line(g, g+u); } void tighten(std::vector &pts, double radius, bool linear){ for (unsigned i=0; i ngbrs = neighbors(pts,i,radius); if (linear){ Line d = meanSquareLine(ngbrs); Point proj = projection( pts[i], d ); double t = 2./3.; pts[i] = pts[i]*(1-t) + proj*t; }else if (ngbrs.size()>=3) { Circle c; c.fit(ngbrs); Point o = c.center(); double r = c.radius(); pts[i] = o + unit_vector(pts[i]-o)*r; } } } double dist_to(std::vector const &pts, Point const &p, unsigned *idx=NULL){ double d,d_min = std::numeric_limits::infinity(); if (idx) *idx = pts.size(); for (unsigned i = 0; i &pts, double dist_min){ if (pts.size()==0) return; std::vector reduced_pts; reduced_pts.push_back(pts[0]); for (auto & pt : pts){ double d = dist_to(reduced_pts, pt); if ( d > dist_min ) reduced_pts.push_back(pt); } pts = reduced_pts; return; } unsigned nearest_after(std::vectorconst &pts, unsigned idx, double *dist = NULL){ if ( idx >= pts.size()-1 ) return pts.size(); Point p = pts[idx]; unsigned res = idx+1; double d_min = L2(p-pts[res]); for (unsigned i=idx+2; i &pts, double no_longer_than = 0){ double d; Point p; for (unsigned i=0; i0.1 && d > no_longer_than){ pts.erase(pts.begin()+i+1, pts.end()); return; } p = pts[i+1]; pts[i+1] = pts[j]; pts[j] = p; } } //FIXME: optimize me if further used... void sort_nearest_bis(std::vector &pts, double radius){ double d; Point p; for (unsigned i=0; i &pts, double tol){ unsigned n_points = pts.size(); Geom::Point * b = g_new(Geom::Point, 4*n_points); Geom::Point * points = g_new(Geom::Point, 4*n_points); for (unsigned int i = 0; i < pts.size(); i++) { points[i] = pts[i]; } int max_segs = 4*n_points; int const n_segs = bezier_fit_cubic_r(b, points, n_points, tol*tol, max_segs); Path res; if ( n_segs > 0){ res = Path(b[0]); for (int i=0; i(b[4*i+1],b[4*i+2],b[4*i+3]); } } g_free(b); g_free(points); return res; } //----------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------- std::vector eat(std::vector const &pts, double sampling){ std::vector visited(pts.size(),false); std::vector res; Point p = pts.front(); //Point q = p; res.push_back(p); while(true){ double num_nghbrs = 0; Point next(0,0); for(unsigned i = 0; i < pts.size(); i++) { if (!visited[i] && L2(pts[i]-p) >(); improved_mouses = eat(improved_mouses, radius); Path p(improved_mouses[0]); for(unsigned i = 1; i < improved_mouses.size(); i++) { p.appendNew(improved_mouses[i]); } stroke = p.toPwSb(); } void draw_eat(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { draw_common(cr, notify, width, height, save, timer_stream); init_eat_ctrl_geom(cr, notify, width, height); } //----------------------------------------------------------------------------------------- // Tighten + Eat //----------------------------------------------------------------------------------------- void init_tighten_eat() { init_common(); handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE])); handles.push_back(&(sliders[TIGHTEN_ITERRATIONS])); handles.push_back(&(sliders[EAT_NBHD_SIZE])); } void init_tighten_eat_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) { if ( set_control_geometry ){ set_control_geometry = false; sliders[TIGHTEN_NBHD_SIZE ].geometry(Point(50, height - 35*2), 180); sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180); sliders[EAT_NBHD_SIZE ].geometry(Point(50, height - 35*4), 180); } } void fit_tighten_eat(){ improved_mouses = mouses; double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value()); for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){ tighten(improved_mouses, radius, toggles[0].on); } stroke = Piecewise >(); radius = exp_rescale(sliders[EAT_NBHD_SIZE].value()); improved_mouses = eat(improved_mouses, radius); Path p(improved_mouses[0]); for(unsigned i = 1; i < improved_mouses.size(); i++) { p.appendNew(improved_mouses[i]); } stroke = p.toPwSb(); } void draw_tighten_eat(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { draw_common(cr, notify, width, height, save, timer_stream); init_tighten_eat_ctrl_geom(cr, notify, width, height); } //----------------------------------------------------------------------------------------- // Sort: tighten, then sort and eventually fuse. //----------------------------------------------------------------------------------------- void init_sort() { init_common(); handles.push_back(&(sliders[TIGHTEN_NBHD_SIZE])); handles.push_back(&(sliders[TIGHTEN_ITERRATIONS])); handles.push_back(&(sliders[SORT_RADIUS])); handles.push_back(&(sliders[FUSE_RADIUS])); handles.push_back(&(sliders[INTERPOLATE_RADIUS])); handles.push_back(&(toggles[TIGHTEN_USE_CIRCLE])); handles.push_back(&(toggles[SORT_BIS])); } void init_sort_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int width, int height) { if ( set_control_geometry ){ set_control_geometry = false; sliders[TIGHTEN_NBHD_SIZE].geometry(Point(50, height - 35*2), 180); sliders[TIGHTEN_ITERRATIONS].geometry(Point(50, height - 35*3), 180); sliders[SORT_RADIUS].geometry(Point(50, height - 35*4), 180); sliders[FUSE_RADIUS].geometry(Point(50, height - 35*5), 180); sliders[INTERPOLATE_RADIUS].geometry(Point(50, height - 35*6), 180); Point p(width-250, height - 50), d(225,25); toggles[TIGHTEN_USE_CIRCLE].bounds = Rect(p, p + d); p += Point(0,-30); toggles[SORT_BIS].bounds = Rect(p, p + d); } } void fit_sort(){ improved_mouses = mouses; double radius = exp_rescale(sliders[TIGHTEN_NBHD_SIZE].value()); for (unsigned i=1; i<=sliders[TIGHTEN_ITERRATIONS].value(); i++){ tighten(improved_mouses, radius, !toggles[TIGHTEN_USE_CIRCLE].on); } double max_jump = exp_rescale(sliders[SORT_RADIUS].value()); if (toggles[SORT_BIS].on){ sort_nearest_bis(improved_mouses, max_jump); }else{ sort_nearest(improved_mouses, max_jump); } radius = exp_rescale(sliders[FUSE_RADIUS].value()); fuse_close_points(improved_mouses, radius); radius = exp_rescale(sliders[INTERPOLATE_RADIUS].value()); Path p = ordered_fit(improved_mouses, radius/5); stroke = p.toPwSb(); } void draw_sort(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { draw_common(cr, notify, width, height, save, timer_stream); init_sort_ctrl_geom(cr, notify, width, height); if(!improved_mouses.empty() && toggles[DRAW_IMPROVED_MOUSES].on ) { cairo_move_to(cr, improved_mouses[0]); for(unsigned i = 1; i < improved_mouses.size(); i++) { cairo_line_to(cr, improved_mouses[i]); } cairo_set_source_rgba (cr, 1., 0., 0., 1); cairo_set_line_width (cr, .75); cairo_stroke(cr); } } //----------------------------------------------------------------------------------------- // Average curvature. //----------------------------------------------------------------------------------------- void init_curvature() { init_common(); handles.push_back(&(sliders[CURVATURE_NBHD_SIZE])); handles.push_back(&(sliders[POINT_CHOOSER])); } void init_curvature_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int height) { if ( set_control_geometry ){ set_control_geometry = false; sliders[CURVATURE_NBHD_SIZE].geometry(Point(50, height - 60), 180); sliders[POINT_CHOOSER ].geometry(Point(50, height - 90), 180); } } //just for fun! void fit_curvature(){ std::vector curvatures(mouses.size(),0); std::vector lengths(mouses.size(),0); for (unsigned i=0; i ngbrs = neighbors(mouses,i,radius); if ( ngbrs.size()>2 ){ Circle c; c.fit(ngbrs); curvatures[i] = 1./c.radius(); Point v = (i 0 ) curvatures[i] *= -1; }else{ curvatures[i] = 0; } if (i>0){ lengths[i] = lengths[i-1] + L2(mouses[i]-mouses[i-1]); } } Piecewise k = interpolate( lengths, curvatures , 1); Piecewise alpha = integral(k); Piecewise > v = sectionize(tan2(alpha)); stroke = integral(v) + mouses[0]; Point sp = stroke.lastValue()-stroke.firstValue(); Point mp = mouses.back()-mouses.front(); Affine mat1 = Affine(sp[X], sp[Y], -sp[Y], sp[X], stroke.firstValue()[X], stroke.firstValue()[Y]); Affine mat2 = Affine(mp[X], mp[Y], -mp[Y], mp[X], mouses[0][X], mouses[0][Y]); mat1 = mat1.inverse()*mat2; stroke = stroke*mat1; } void draw_curvature(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { draw_common(cr, notify, width, height, save, timer_stream); init_curvature_ctrl_geom(cr, notify, width, height); if(!mouses.empty()) { double radius = exp_rescale(sliders[CURVATURE_NBHD_SIZE].value()); unsigned i = unsigned( (mouses.size()-1)*sliders[POINT_CHOOSER].value()/100. ); std::vector ngbrs = neighbors(mouses,i,radius); if ( ngbrs.size()>2 ){ draw_cross(cr, mouses[i]); Circle c; c.fit(ngbrs); cairo_arc(cr, c.center(X), c.center(Y), c.radius(), 0, 2*M_PI); cairo_set_source_rgba (cr, 1., 0., 0., 1); cairo_set_line_width (cr, .75); cairo_stroke(cr); } cairo_pw_d2_sb(cr, stroke); } } //----------------------------------------------------------------------------------------- // Brutal optimization, number of segment fixed. //----------------------------------------------------------------------------------------- void init_numerical() { init_common(); //sliders.push_back(Slider(0, 10, 1, 1, "Number of curves")); //handles.push_back(&(sliders[0])); } void init_numerical_ctrl_geom(cairo_t* /*cr*/, std::ostringstream* /*notify*/, int /*width*/, int /*height*/) { if ( set_control_geometry ){ set_control_geometry = false; //sliders[0].geometry(Point(50, height - 35*(0+2)), 180); } } void fit_numerical(){ //Not implemented } void draw_numerical(cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) { draw_common(cr, notify, width, height, save, timer_stream); init_numerical_ctrl_geom(cr, notify, width, height); if(!mouses.empty()) { cairo_pw_d2_sb(cr, stroke); cairo_set_source_rgba (cr, 1., 0., 0., 1); cairo_set_line_width (cr, .75); cairo_stroke(cr); } } //----------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------- void init_help() { handles.clear(); //sliders.clear(); //toggles.clear(); } void draw_help( cairo_t * /*cr*/, std::ostringstream *notify, int /*width*/, int /*height*/, bool /*save*/, std::ostringstream */*timer_stream*/) { *notify << "Tighten:\n"; *notify << " move points toward local\n"; *notify << " mean square line (or circle).\n"; *notify << "Eat:\n"; *notify << " eat points like a pacman; at each step, move to the\n"; *notify << " average of the not already visited neighbor points.\n"; *notify << "Sort:\n"; *notify << " move from one point to the nearest one.\n"; *notify << " Stop at the first jump longer than sort-radius\n"; *notify << "Sort-bis:\n"; *notify << " move from one point to the nearest one,\n"; *notify << " unless it was 'already visited' (i.e. it is closer to\n"; *notify << " an already sorted point with distance < sort-radius.\n"; *notify << "Fuse: \n"; *notify << " start from first point, remove all points closer to it\n"; *notify << " than fuse-radius, move to the first one that is not, and repeat.\n"; *notify << "Curvature: \n"; *notify << " Compute the curvature at a given point from the circle fitting the\n"; *notify << " nearby points (just for fun: the stroke is the 'integral' of this\n"; *notify << " average curvature)\n"; *notify << "Numerical: \n"; *notify << " still waiting for someone to implement me ;-)\n\n"; *notify << std::endl; } //----------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------- public: vector mouses; int mouse_drag; vector improved_mouses; Piecewise > stroke; void mouse_pressed(GdkEventButton* e) override { //toggle_events(toggles, e); Toy::mouse_pressed(e); if(!selected) { mouse_drag = 1; if (!(e->state & (GDK_SHIFT_MASK))){ mouses.clear(); } } } void mouse_moved(GdkEventMotion* e) override { if(mouse_drag) { mouses.emplace_back(e->x, e->y); redraw(); } else { Toy::mouse_moved(e); } } void mouse_released(GdkEventButton* e) override { mouse_drag = 0; if(!mouses.empty()) { (this->*fit_f)(); } Toy::mouse_released(e); } void init_menu() { handles.clear(); //sliders.clear(); //toggles.clear(); } void draw_menu( cairo_t * cr, std::ostringstream *notify, int /*width*/, int /*height*/, bool /*save*/, std::ostringstream */*timer_stream*/) { *notify << "Sketch some shape on canvas (press SHIFT to use several 'strokes')\n"; *notify << "Each menu below will transform your input.\n"; *notify << "Press 'Z' to make the result the new input\n"; *notify << " \n \n \n"; *notify << std::endl; for (int i = SHOW_MENU; i < TOTAL_ITEMS; ++i) { *notify << " " << keys[i] << " - " << menu_items[i] << std::endl; } if(!mouses.empty()) { cairo_move_to(cr, mouses[0]); for(auto & mouse : mouses) { cairo_line_to(cr, mouse); } for(auto & mouse : mouses) { draw_cross(cr, mouse); } cairo_set_source_rgba (cr, 0., 0., 0., .25); cairo_set_line_width (cr, 0.5); cairo_stroke(cr); } } void key_hit(GdkEventKey *e) override { char choice = std::toupper(e->keyval); switch ( choice ) { case 'A': init_menu(); draw_f = &SketchFitterToy::draw_menu; break; case 'B': init_tighten(); fit_f = &SketchFitterToy::fit_tighten; draw_f = &SketchFitterToy::draw_tighten; break; case 'C': init_eat(); fit_f = &SketchFitterToy::fit_eat; draw_f = &SketchFitterToy::draw_eat; break; case 'D': init_tighten_eat(); fit_f = &SketchFitterToy::fit_tighten_eat; draw_f = &SketchFitterToy::draw_tighten_eat; break; case 'E': init_sort(); fit_f = &SketchFitterToy::fit_sort; draw_f = &SketchFitterToy::draw_sort; break; case 'F': init_curvature(); fit_f = &SketchFitterToy::fit_curvature; draw_f = &SketchFitterToy::draw_curvature; break; case 'G': init_numerical(); fit_f = &SketchFitterToy::fit_numerical; draw_f = &SketchFitterToy::draw_numerical; break; case 'H': init_help(); draw_f = &SketchFitterToy::draw_help; break; case 'Z': mouses = improved_mouses; break; } redraw(); } void draw( cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream ) override { m_width = width; m_height = height; m_length = (m_width > m_height) ? m_width : m_height; m_length *= 2; (this->*draw_f)(cr, notify, width, height, save, timer_stream); Toy::draw(cr, notify, width, height, save, timer_stream); } public: SketchFitterToy() { srand ( time(NULL) ); sliders = std::vector(TOTAL_SLIDERS, Slider(0., 1., 0, 0., "")); sliders[TIGHTEN_NBHD_SIZE ] = Slider(0., 1., 0, 0.65, "neighborhood size"); sliders[TIGHTEN_NBHD_SIZE ].formatter(&exp_formatter); sliders[TIGHTEN_ITERRATIONS] = Slider(0, 10, 1, 3, "iterrations"); sliders[EAT_NBHD_SIZE ] = Slider(0., 1., 0, 0.65, "eating neighborhood size"); sliders[EAT_NBHD_SIZE ].formatter(&exp_formatter); sliders[SORT_RADIUS ] = Slider(0., 1., 0, 0.65, "sort radius"); sliders[SORT_RADIUS ].formatter(&exp_formatter); sliders[FUSE_RADIUS ] = Slider(0., 1., 0, 0.65, "fuse radius"); sliders[FUSE_RADIUS ].formatter(&exp_formatter); sliders[INTERPOLATE_RADIUS ] = Slider(0., 1., 0, 0.65, "intrepolate precision"); sliders[INTERPOLATE_RADIUS ].formatter(&exp_formatter); sliders[CURVATURE_NBHD_SIZE] = Slider(0., 1., 0, 0.65, "curvature nbhd size"); sliders[CURVATURE_NBHD_SIZE].formatter(&exp_formatter); sliders[POINT_CHOOSER ] = Slider(0, 100, 0, 50, "Point chooser(%)"); toggles = std::vector(TOTAL_TOGGLES, Toggle("",true)); toggles[DRAW_MOUSES] = Toggle("Draw mouses",true); toggles[DRAW_IMPROVED_MOUSES] = Toggle("Draw new mouses",true); toggles[DRAW_STROKE] = Toggle("Draw stroke",true); toggles[TIGHTEN_USE_CIRCLE] = Toggle("Tighten: use circle",false); toggles[SORT_BIS ] = Toggle("Sort: bis",false); } private: typedef void (SketchFitterToy::* draw_func_t) (cairo_t*, std::ostringstream*, int, int, bool, std::ostringstream*); draw_func_t draw_f; typedef void (SketchFitterToy::* fit_func_t) (); fit_func_t fit_f; bool set_common_control_geometry; bool set_control_geometry; std::vector toggles; std::vector sliders; double m_width, m_height, m_length; }; // end class SketchFitterToy const char* SketchFitterToy::menu_items[] = { "show this menu", "tighten", "eat points step by step", "tighten + eat", "tighten + sort + fuse", "curvature", "numerical", "help", }; const char SketchFitterToy::keys[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H' }; int main(int argc, char **argv) { init(argc, argv, new SketchFitterToy); return 0; } /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype = cpp:expandtab:shiftwidth = 4:tabstop = 8:softtabstop = 4:encoding = utf-8:textwidth = 99 :