Clone in a terminal:
Commit 4b1db9d
Johan Mattsson - 2014-08-22T19:56:35+00:00
4b1db9d6e221b93b8db2120372bd93b793cf5128johan.mattsson.m@gmail.com

Speed optimization for path simplification and other improvements

Changed files (11)

Diff

diff --git a/birdfont/GtkWindow.vala b/birdfont/GtkWindow.valaindex 4a2e211..af46fff 100644--- a/birdfont/GtkWindow.vala+++ b/birdfont/GtkWindow.vala@@ -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 () {diff --git a/libbirdfont/ExportTool.vala b/libbirdfont/ExportTool.valaindex d0d223f..54c5433 100644--- a/libbirdfont/ExportTool.vala+++ b/libbirdfont/ExportTool.vala@@ -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; diff --git a/libbirdfont/FileDialogTab.vala b/libbirdfont/FileDialogTab.valaindex d3129a6..dde262c 100644--- a/libbirdfont/FileDialogTab.vala+++ b/libbirdfont/FileDialogTab.vala@@ -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; 		diff --git a/libbirdfont/KerningDisplay.vala b/libbirdfont/KerningDisplay.valaindex 92467dd..747a68a 100644--- a/libbirdfont/KerningDisplay.vala+++ b/libbirdfont/KerningDisplay.vala@@ -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; diff --git a/libbirdfont/MenuTab.vala b/libbirdfont/MenuTab.valaindex 2b88d00..5832d8b 100644--- a/libbirdfont/MenuTab.vala+++ b/libbirdfont/MenuTab.vala@@ -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 ();+	} }  }diff --git a/libbirdfont/NativeWindow.vala b/libbirdfont/NativeWindow.valaindex 0a31d14..9cd6edc 100644--- a/libbirdfont/NativeWindow.vala+++ b/libbirdfont/NativeWindow.vala@@ -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); }  }diff --git a/libbirdfont/OverView.vala b/libbirdfont/OverView.valaindex bb5ee1a..67c118e 100644--- a/libbirdfont/OverView.vala+++ b/libbirdfont/OverView.vala@@ -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) {diff --git a/libbirdfont/Path.vala b/libbirdfont/Path.valaindex 5bf3a76..9431342 100644--- a/libbirdfont/Path.vala+++ b/libbirdfont/Path.vala@@ -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;+	} }  }diff --git a/libbirdfont/PenTool.vala b/libbirdfont/PenTool.valaindex 5c8722d..b5ec8eb 100644--- a/libbirdfont/PenTool.vala+++ b/libbirdfont/PenTool.vala@@ -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;+	} }  }diff --git a/libbirdfont/SvgParser.vala b/libbirdfont/SvgParser.valaindex 4fd4bb1..1dae20c 100644--- a/libbirdfont/SvgParser.vala+++ b/libbirdfont/SvgParser.vala@@ -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) {diff --git a/libbirdfont/Task.vala b/libbirdfont/Task.valanew file mode 100644index 0000000..0465bab--- /dev/null+++ b/libbirdfont/Task.vala@@ -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 ();+	}+}++}

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112