Commit 4b1db9d
Speed optimization for path simplification and other improvements
Changed files (11)
modifiedbirdfont/GtkWindow.valamodifiedlibbirdfont/ExportTool.valamodifiedlibbirdfont/FileDialogTab.valamodifiedlibbirdfont/KerningDisplay.valamodifiedlibbirdfont/MenuTab.valamodifiedlibbirdfont/NativeWindow.valamodifiedlibbirdfont/OverView.valamodifiedlibbirdfont/Path.valamodifiedlibbirdfont/PenTool.valamodifiedlibbirdfont/SvgParser.valaaddedlibbirdfont/Task.vala
Diff
@@ -58,6 +58,8 @@ public class GtkWindow : Gtk.Window, NativeWindow { ToolboxCanvas toolbox; + Task background_task = new Task ();+ public GtkWindow (string title) { scrollbar = new Scrollbar (Orientation.VERTICAL, new Adjustment (0, 0, 1, 1, 0.01, 0.1)); ((Gtk.Window)this).set_title ("BirdFont");@@ -497,6 +499,11 @@ public class GtkWindow : Gtk.Window, NativeWindow { import_svg_item.activate.connect (() => { SvgParser.import (); }); import_svg_item.add_accelerator ("activate", accel_group, 'I', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE); + Gtk.MenuItem simplify_item = new Gtk.MenuItem.with_mnemonic (t_("Simpl_ify path"));+ edit_menu.append (simplify_item);+ simplify_item.activate.connect (() => { MenuTab.simplify_path (); }); + simplify_item.add_accelerator ("activate", accel_group, 'S', Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK, Gtk.AccelFlags.VISIBLE);+ Gtk.MenuItem close_path_item = new Gtk.MenuItem.with_mnemonic (t_("Close _Path")); edit_menu.append (close_path_item); close_path_item.activate.connect (() => { PenTool.close_all_paths (); });@@ -756,9 +763,6 @@ public class GtkWindow : Gtk.Window, NativeWindow { } public void file_chooser (string title, FileChooser fc, uint flags) {- MenuTab.show_file_dialog_tab (title, fc);- return;- string? fn = null; if ((flags & FileChooser.LOAD) > 0) {@@ -898,6 +902,25 @@ public class GtkWindow : Gtk.Window, NativeWindow { public void hide_tooltip () { tooltip_window.hide (); }++ public void run_background_thread (Task t) {+ unowned Thread<void*> bg;+ + MenuTab.start_background_thread ();+ background_task = t;+ + try {+ bg = Thread.create<void*> (this.background_thread, true);+ } catch (GLib.Error e) {+ warning (e.message);+ }+ }++ public void* background_thread () { + background_task.run ();+ MenuTab.stop_background_thread ();+ return null;+ } /** Run export in a background thread. */ public void export_font () {@@ -29,7 +29,6 @@ public class ExportTool : GLib.Object { public static string export_current_glyph_to_string (bool only_selected_paths = false) { Glyph glyph = MainWindow.get_current_glyph ();- Font font = BirdFont.get_current_font (); string name; StringBuilder s; @@ -125,9 +125,6 @@ public class FileDialogTab : FontDisplay { double y = 20 * MainWindow.units; int s = 0; bool color = (scroll % 2) == 0;- string l, r;- string[] p;- double k; this.allocation = allocation; @@ -266,7 +266,6 @@ public class KerningDisplay : FontDisplay { private double get_kerning_for_handle (int handle) { string a, b;- Font font; GlyphRange? gr_left, gr_right; bool got_pair; @@ -403,6 +403,53 @@ public class MenuTab : FontDisplay { public static void show_file_dialog_tab (string title, FileChooser action) { MainWindow.get_tab_bar ().add_tab (new FileDialogTab (title, action)); }+ + public static void simplify_path () {+ Task t;+ + t = new Task ();+ t.task.connect (simplify);+ MainWindow.native_window.run_background_thread (t);+ }+ + private static void simplify () {+ Glyph g = MainWindow.get_current_glyph ();+ Gee.ArrayList<Path> paths = new Gee.ArrayList<Path> ();+ + // selected objects+ foreach (Path p in g.active_paths) {+ paths.add (PenTool.simplify (p, false));+ }+ + // selected segments+ if (paths.size == 0) {+ foreach (Path p in g.path_list) {+ g.add_active_path (p);+ }+ + foreach (Path p in g.active_paths) {+ paths.add (PenTool.simplify (p, true));+ }+ }+ + g.store_undo_state ();+ + foreach (Path p in g.active_paths) {+ g.path_list.remove (p);+ }++ foreach (Path p in g.active_paths) {+ g.path_list.remove (p);+ }+ + foreach (Path p in paths) {+ g.add_path (p);+ g.add_active_path (p);+ }++ g.active_paths.clear ();+ g.update_view ();+ } } }@@ -66,6 +66,9 @@ public interface NativeWindow : GLib.Object { /** Load images in a background thread. */ public abstract void load_background_image ();++ /** Load images in a background thread. */+ public abstract void run_background_thread (Task t); } }@@ -595,8 +595,6 @@ public class OverView : FontDisplay { } public override void key_press (uint keyval) {- GlyphCollection? gc;- redraw_area (0, 0, allocation.width, allocation.height); if (KeyBindings.modifier == CTRL) {@@ -1315,7 +1315,10 @@ public class Path { edit_point.set_position (ox, oy); } - public static bool all_of (EditPoint start, EditPoint stop, RasterIterator iter, int steps = -1) {+ public static bool all_of (EditPoint start, EditPoint stop,+ RasterIterator iter, int steps = -1,+ double min_t = 0, double max_t = 1) {+ PointType right = PenTool.to_curve (start.get_right_handle ().type); PointType left = PenTool.to_curve (stop.get_left_handle ().type); @@ -1324,11 +1327,11 @@ public class Path { } if (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE) {- return all_of_double (start.x, start.y, start.get_right_handle ().x, start.get_right_handle ().y, stop.get_left_handle ().x, stop.get_left_handle ().y, stop.x, stop.y, iter, steps);+ return all_of_double (start.x, start.y, start.get_right_handle ().x, start.get_right_handle ().y, stop.get_left_handle ().x, stop.get_left_handle ().y, stop.x, stop.y, iter, steps, min_t, max_t); } else if (right == PointType.QUADRATIC && left == PointType.QUADRATIC) {- return all_of_quadratic_curve (start.x, start.y, start.get_right_handle ().x, start.get_right_handle ().y, stop.x, stop.y, iter, steps);+ return all_of_quadratic_curve (start.x, start.y, start.get_right_handle ().x, start.get_right_handle ().y, stop.x, stop.y, iter, steps, min_t, max_t); } else if (right == PointType.CUBIC && left == PointType.CUBIC) {- return all_of_curve (start.x, start.y, start.get_right_handle ().x, start.get_right_handle ().y, stop.get_left_handle ().x, stop.get_left_handle ().y, stop.x, stop.y, iter, steps);+ return all_of_curve (start.x, start.y, start.get_right_handle ().x, start.get_right_handle ().y, stop.get_left_handle ().x, stop.get_left_handle ().y, stop.x, stop.y, iter, steps, min_t, max_t); } if (start.x == stop.x && start.y == stop.y) {@@ -1346,7 +1349,9 @@ public class Path { y = bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y); } - private static bool all_of_double (double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, RasterIterator iter, double steps = 400) {+ private static bool all_of_double (double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, + RasterIterator iter, double steps = 400, double min_t = 0, double max_t = 1) {+ double px = x1; double py = y1; @@ -1358,27 +1363,34 @@ public class Path { middle_y = y1 + (y2 - y1) / 2; for (int i = 0; i < steps; i++) {- t = i / steps;+ t = i / steps + min_t; px = quadratic_bezier_path (t, x0, x1, middle_x); py = quadratic_bezier_path (t, y0, y1, middle_y); - double_step = t / 2;- - if (!iter (px, py, double_step)) {+ double_step = t / 2;++ if (t > max_t) { return false; }- + + if (!iter (px, py, double_step)) {+ return false;+ } } for (int i = 0; i < steps; i++) {- t = i / steps;+ t = i / steps + min_t; px = quadratic_bezier_path (t, middle_x, x2, x3); py = quadratic_bezier_path (t, middle_y, y2, y3); double_step = 0.5 + t / 2;- ++ if (t > max_t) {+ return false;+ }+ if (!iter (px, py, double_step)) { return false; }@@ -1387,18 +1399,23 @@ public class Path { return true; } - private static bool all_of_quadratic_curve (double x0, double y0, double x1, double y1, double x2, double y2, RasterIterator iter, double steps = 400) {+ private static bool all_of_quadratic_curve (double x0, double y0, double x1, double y1, double x2, double y2,+ RasterIterator iter, double steps = 400, double min_t = 0, double max_t = 1) { double px = x1; double py = y1; double t; for (int i = 0; i < steps; i++) {- t = i / steps;+ t = i / steps + min_t; px = quadratic_bezier_path (t, x0, x1, x2); py = quadratic_bezier_path (t, y0, y1, y2); + if (t > max_t) {+ return false;+ }+ if (!iter (px, py, t)) { return false; }@@ -1407,18 +1424,23 @@ public class Path { return true; } - private static bool all_of_curve (double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, RasterIterator iter, double steps = 400) {+ private static bool all_of_curve (double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3,+ RasterIterator iter, double steps = 400, double min_t = 0, double max_t = 1) { double px = x1; double py = y1; double t; for (int i = 0; i < steps; i++) {- t = i / steps;+ t = i / steps + min_t; px = bezier_path (t, x0, x1, x2, x3); py = bezier_path (t, y0, y1, y2, y3);- ++ if (t > max_t) {+ return false;+ }+ if (!iter (px, py, t)) { return false; }@@ -2071,6 +2093,57 @@ public class Path { create_list (); }++ public static void find_closes_point_in_segment (EditPoint ep0, EditPoint ep1,+ double px, double py,+ out double nx, out double ny,+ double max_step = 200) {+ + double min_distance = double.MAX;+ double npx, npy;+ double min_t, max_t;+ double rmin_t, rmax_t;+ bool found;+ + npx = 0;+ npy = 0;+ + min_t = 0;+ max_t = 1;+ + rmin_t = 0;+ rmax_t = 1;+ + for (int step = 3; step <= max_step; step *= 2) {+ found = false;+ min_distance = double.MAX;+ Path.all_of (ep0, ep1, (xa, ya, ta) => {+ double d = Path.distance (px, xa, py, ya);+ + if (d < min_distance) {+ min_distance = d;+ npx = xa;+ npy = ya;+ rmin_t = ta - 1.0 / step;+ rmax_t = ta + 1.0 / step;+ found = true;+ }+ + return true;+ }, step, min_t, max_t);++ if (!found) {+ rmin_t = 1 - (1.0 / step);+ rmax_t = 1;+ }++ min_t = (rmin_t > 0) ? rmin_t : 0;+ max_t = (rmax_t < 1) ? rmax_t : 1;+ }+ + nx = npx;+ ny = npy;+ } } }@@ -298,38 +298,22 @@ public class PenTool : Tool { static void get_closes_point_in_segment (EditPoint ep0, EditPoint ep1, EditPoint ep2, double px, double py, out double nx, out double ny) {- - double min_distance = double.MAX;- double npx, npy;- - npx = 0;- npy = 0;+ double npx0, npy0;+ double npx1, npy1; - Path.all_of (ep0, ep1, (xa, ya, ta) => {- double d = Path.distance (px, xa, py, ya);- if (d < min_distance) {- min_distance = d;- npx = xa;- npy = ya;- }- return true;- }, 200);+ Path.find_closes_point_in_segment (ep0, ep1, px, py, out npx0, out npy0);+ Path.find_closes_point_in_segment (ep1, ep2, px, py, out npx1, out npy1); - Path.all_of (ep1, ep2, (xa, ya, ta) => {- double d = Path.distance (px, xa, py, ya);- if (d < min_distance) {- min_distance = d;- npx = xa;- npy = ya;- }- return true;- }, 200); -- nx = npx;- ny = npy;+ if (Path.distance (px, npx0, py, npy0) < Path.distance (px, npx1, py, npy1)) {+ nx = npx0;+ ny = npy0;+ } else {+ nx = npx1;+ ny = npy1;+ } } - static void get_path_distortion (EditPoint oe0, EditPoint oe1, EditPoint oe2,+ public static void get_path_distortion (EditPoint oe0, EditPoint oe1, EditPoint oe2, EditPoint ep1, EditPoint ep2, out double distortion_first, out double distortion_next) { double nx, ny;@@ -383,22 +367,23 @@ public class PenTool : Tool { selected_point = new EditPoint (); } - static void remove_point_simplify (PointSelection p) {+ /** @return path distortion. */+ public static double remove_point_simplify (PointSelection p) { double start_length, stop_length; double start_distortion, start_min_distortion, start_new_length; double stop_distortion, stop_min_distortion, stop_new_length;- double distortion; + double distortion, min_distortion; double prev_length_adjustment, next_length_adjustment; double prev_length_adjustment_reverse, next_length_adjustment_reverse; EditPoint ep1, ep2; EditPoint next, prev;- + return_if_fail (p.path.points.size > 0); if (p.path.points.size <= 2) { p.point.deleted = true; p.path.remove_deleted_points ();- return;+ return 0; } p.point.deleted = true;@@ -436,13 +421,11 @@ public class PenTool : Tool { next_length_adjustment = 0; prev_length_adjustment_reverse = 0; next_length_adjustment_reverse = 0;++ min_distortion = double.MAX; - - // FOR 10s- double min_distortion = double.MAX;- - for (double a = 0; a < 50; a += 10) {- for (double b = 0; b < 50; b += 10) {+ for (double a = 0; a < 50; a += 5) {+ for (double b = 0; b < 50; b += 5) { ep1.get_right_handle ().length = start_length + a; ep2.get_left_handle ().length = stop_length + b; @@ -465,8 +448,8 @@ public class PenTool : Tool { stop_length += next_length_adjustment; min_distortion = double.MAX;- for (double a = -10; a < 10; a += 1) {- for (double b = -10; b < 10; b += 1) {+ for (double a = -5; a < 5; a += 1) {+ for (double b = -5; b < 5; b += 1) { ep1.get_right_handle ().length = start_length + a; ep2.get_left_handle ().length = stop_length + b; @@ -524,6 +507,8 @@ public class PenTool : Tool { p.point.deleted = true; p.path.remove_deleted_points (); p.path.update_region_boundaries ();+ + return min_distortion; } /** Retain selected points even if path is copied after running reverse. */@@ -2043,6 +2028,91 @@ public class PenTool : Tool { } } }+ + public static Path simplify (Path path, bool selected_segments) {+ PointSelection ps;+ EditPoint ep;+ Path p1, p2, new_path;+ Gee.ArrayList<double?> distortion = new Gee.ArrayList<double?> (); + double d;+ double min_distortion;+ bool update_distortion;+ double threshold, distortion_sum;+ int pi;+ + p1 = path.copy ();+ p2 = path.copy ();+ + for (int i = 0; i < p1.points.size - 1; i++) {+ ep = p2.points.get (i); // even points+ + if (!selected_segments || ep.selected) {+ ps = new PointSelection (ep, p2); + d = PenTool.remove_point_simplify (ps);+ distortion.add (d);+ } else {+ distortion.add (double.MAX);+ }++ ep = p1.points.get (i + 1); // odd points+ + if (!selected_segments || ep.selected) {+ ps = new PointSelection (ep, p1); + d = PenTool.remove_point_simplify (ps);+ distortion.add (d);+ } else {+ distortion.add (double.MAX);+ }+ }++ min_distortion = double.MAX;+ foreach (double? ds in distortion) {+ if ((double) ds < min_distortion) {+ min_distortion = (double) ds;+ }+ }++ if (min_distortion < 0.1) {+ threshold = 0.1;+ } else if (min_distortion < 1) {+ threshold = 0.1;+ } else if (min_distortion < 5) {+ threshold = 5;+ } else {+ threshold = 10;+ }+ + update_distortion = false;+ new_path = new Path ();+ distortion_sum = 0;+ do {+ new_path = path.copy ();+ update_distortion = false;+ distortion_sum = 0;+ + pi = 0;+ for (int i = 0; i < distortion.size; i++) {+ d = (double) distortion.get (i);+ distortion_sum += d;+ if (distortion_sum < threshold) {+ ep = new_path.points.get (pi);+ ps = new PointSelection (ep, new_path); + d = PenTool.remove_point_simplify (ps);+ + if (d > threshold) {+ distortion.set (i, d);+ update_distortion = true;+ break;+ }+ } else {+ distortion_sum = 0;+ pi++;+ }+ }+ } while (update_distortion);+ + return new_path;+ } } }@@ -58,7 +58,6 @@ public class SvgParser { StringBuilder sb = new StringBuilder (); SvgParser parser = new SvgParser (); TextReader tr;- int i_tag, end_tag; foreach (string l in lines) { if (l.index_of ("Illustrator") > -1 || l.index_of ("illustrator") > -1) {new file mode 100644@@ -0,0 +1,29 @@+/*+ Copyright (C) 2014 Johan Mattsson++ This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version.++ This library 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 + Lesser General Public License for more details.+*/++namespace BirdFont {++public class Task : GLib.Object {+ + public signal void task ();+ + public Task () {+ }+ + public void run () {+ task ();+ }+}++}