1 /*
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * This is a plug-in for GIMP.
5  *
6  * Plugin to convert a selection to a path.
7  *
8  * Copyright (C) 1999 Andy Thomas  alt@gimp.org
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
22  *
23  */
24 
25 /* Change log:-
26  * 0.1 First version.
27  */
28 
29 #include "config.h"
30 
31 #include <libgimp/gimp.h>
32 #include <libgimp/gimpui.h>
33 
34 #include "types.h"
35 
36 #include "selection-to-path.h"
37 
38 #include "libgimp/stdplugins-intl.h"
39 
40 #define SCALE_WIDTH  100
41 #define SCALE_DIGITS 8
42 
43 
44 static GSList * adjust_widgets = NULL;
45 
46 
47 /* Reset to recommended defaults */
48 void
reset_adv_dialog(void)49 reset_adv_dialog (void)
50 {
51   GSList    *list;
52   GtkObject *widget;
53   gdouble   *value;
54 
55   for (list = adjust_widgets; list; list = g_slist_next (list))
56     {
57       widget = GTK_OBJECT (list->data);
58       value  = (gdouble *) g_object_get_data (G_OBJECT (widget),
59                                               "default_value");
60 
61       if (GTK_IS_ADJUSTMENT (widget))
62         {
63           gtk_adjustment_set_value (GTK_ADJUSTMENT (widget),
64                                     *value);
65         }
66       else if (GTK_IS_TOGGLE_BUTTON (widget))
67         {
68           gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget),
69                                         (gboolean)(*value));
70         }
71       else
72         g_warning ("Internal widget list error");
73     }
74 }
75 
76 static gpointer
def_val(gdouble default_value)77 def_val (gdouble default_value)
78 {
79   gdouble *value = g_new0 (gdouble, 1);
80   *value = default_value;
81   return (value);
82 }
83 
84 GtkWidget *
dialog_create_selection_area(SELVALS * sels)85 dialog_create_selection_area (SELVALS *sels)
86 {
87   GtkWidget *scrolled_win;
88   GtkWidget *viewport;
89   GtkWidget *table;
90   GtkWidget *check;
91   GtkObject *adj;
92   gint       row;
93 
94   scrolled_win = gtk_scrolled_window_new (NULL, NULL);
95   gtk_widget_set_size_request (scrolled_win, -1, 400);
96   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win),
97                                        GTK_SHADOW_NONE);
98   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
99                                   GTK_POLICY_NEVER,
100                                   GTK_POLICY_ALWAYS);
101 
102   viewport = gtk_viewport_new (NULL, NULL);
103   gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE);
104   gtk_container_add (GTK_CONTAINER (scrolled_win), viewport);
105   gtk_widget_show (viewport);
106 
107   table = gtk_table_new (20, 3, FALSE);
108   gtk_table_set_row_spacings (GTK_TABLE (table), 6);
109   gtk_table_set_col_spacings (GTK_TABLE (table), 6);
110   gtk_container_add (GTK_CONTAINER (viewport), table);
111   gtk_widget_show (table);
112 
113   row = 0;
114 
115   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
116                               _("Align Threshold:"), SCALE_WIDTH, SCALE_DIGITS,
117                               sels->align_threshold,
118                               0.2, 2.0, 0.1, 0.1, 2,
119                               TRUE, 0, 0,
120                               _("If two endpoints are closer than this, "
121                                 "they are made to be equal."), NULL);
122   g_signal_connect (adj, "value-changed",
123                     G_CALLBACK (gimp_double_adjustment_update),
124                     &sels->align_threshold);
125   adjust_widgets = g_slist_append (adjust_widgets, adj);
126   g_object_set_data (G_OBJECT (adj), "default_value", def_val (0.5));
127 
128   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
129                               _("Corner Always Threshold:"), SCALE_WIDTH, SCALE_DIGITS,
130                               sels->corner_always_threshold,
131                               30, 180, 1, 1, 2,
132                               TRUE, 0, 0,
133                               _("If the angle defined by a point and its predecessors "
134                                 "and successors is smaller than this, it's a corner, "
135                                 "even if it's within 'corner_surround' pixels of a "
136                                 "point with a smaller angle."), NULL);
137   g_signal_connect (adj, "value-changed",
138                     G_CALLBACK (gimp_double_adjustment_update),
139                     &sels->corner_always_threshold);
140   adjust_widgets = g_slist_append (adjust_widgets, adj);
141   g_object_set_data (G_OBJECT (adj), "default_value", def_val (60.0));
142 
143   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
144                               _("Corner Surround:"), SCALE_WIDTH, SCALE_DIGITS,
145                               sels->corner_surround,
146                               3, 8, 1, 1, 0,
147                               TRUE, 0, 0,
148                               _("Number of points to consider when determining if a "
149 				"point is a corner or not."), NULL);
150   g_signal_connect (adj, "value-changed",
151                     G_CALLBACK (gimp_double_adjustment_update),
152                     &sels->corner_surround);
153   adjust_widgets = g_slist_append (adjust_widgets, adj);
154   g_object_set_data (G_OBJECT (adj), "default_value", def_val (4.0));
155 
156   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
157                               _("Corner Threshold:"), SCALE_WIDTH, SCALE_DIGITS,
158                               sels->corner_threshold,
159                               0, 180, 1, 1, 2,
160                               TRUE, 0, 0,
161                               _("If a point, its predecessors, and its successors "
162 				"define an angle smaller than this, it's a corner."),
163                               NULL);
164   g_signal_connect (adj, "value-changed",
165                     G_CALLBACK (gimp_double_adjustment_update),
166                     &sels->corner_threshold);
167   adjust_widgets = g_slist_append (adjust_widgets, adj);
168   g_object_set_data (G_OBJECT (adj), "default_value", def_val (100.0));
169 
170 
171   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
172                               _("Error Threshold:"), SCALE_WIDTH, SCALE_DIGITS,
173                               sels->error_threshold,
174                               0.2, 10, 0.1, 0.1, 2,
175                               TRUE, 0, 0,
176                               _("Amount of error at which a fitted spline is "
177                                 "unacceptable. If any pixel is further away "
178                                 "than this from the fitted curve, we try again."),
179                               NULL);
180   g_signal_connect (adj, "value-changed",
181                     G_CALLBACK (gimp_double_adjustment_update),
182                     &sels->error_threshold);
183   adjust_widgets = g_slist_append (adjust_widgets, adj);
184   g_object_set_data (G_OBJECT (adj), "default_value", def_val (0.40));
185 
186   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
187                               _("Filter Alternative Surround:"), SCALE_WIDTH, SCALE_DIGITS,
188                               sels->filter_alternative_surround,
189                               1, 10, 1, 1, 0,
190                               TRUE, 0, 0,
191                               _("A second number of adjacent points to consider "
192 				"when filtering."), NULL);
193   g_signal_connect (adj, "value-changed",
194                     G_CALLBACK (gimp_double_adjustment_update),
195                     &sels->filter_alternative_surround);
196   adjust_widgets = g_slist_append (adjust_widgets, adj);
197   g_object_set_data (G_OBJECT (adj), "default_value", def_val (1.0));
198 
199 
200   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
201                               _("Filter Epsilon:"), SCALE_WIDTH, SCALE_DIGITS,
202                               sels->filter_epsilon,
203                               5, 40, 1, 1, 2,
204                               TRUE, 0, 0,
205                               _("If the angles between the vectors produced by "
206 				"filter_surround and filter_alternative_surround "
207 				"points differ by more than this, use the one from "
208 				"filter_alternative_surround."), NULL);
209   g_signal_connect (adj, "value-changed",
210                     G_CALLBACK (gimp_double_adjustment_update),
211                     &sels->filter_epsilon);
212   adjust_widgets = g_slist_append (adjust_widgets, adj);
213   g_object_set_data (G_OBJECT (adj), "default_value", def_val (10.0));
214 
215   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
216                               _("Filter Iteration Count:"), SCALE_WIDTH, SCALE_DIGITS,
217                               sels->filter_iteration_count,
218                               4, 70, 1, 1, 0,
219                               TRUE, 0, 0,
220                               _("Number of times to smooth original data points.  "
221                                 "Increasing this number dramatically --- to 50 or "
222                                 "so --- can produce vastly better results. But if "
223                                 "any points that 'should' be corners aren't found, "
224                                 "the curve goes to hell around that point."), NULL);
225   g_signal_connect (adj, "value-changed",
226                     G_CALLBACK (gimp_double_adjustment_update),
227                     &sels->filter_iteration_count);
228   adjust_widgets = g_slist_append (adjust_widgets, adj);
229   g_object_set_data (G_OBJECT (adj), "default_value", def_val (4.0));
230 
231   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
232                               _("Filter Percent:"), SCALE_WIDTH, SCALE_DIGITS,
233                               sels->filter_percent,
234                               0, 1, 0.05, 0.01, 2,
235                               TRUE, 0, 0,
236                               _("To produce the new point, use the old point plus "
237 				"this times the neighbors."), NULL);
238   g_signal_connect (adj, "value-changed",
239                     G_CALLBACK (gimp_double_adjustment_update),
240                     &sels->filter_percent);
241   adjust_widgets = g_slist_append (adjust_widgets, adj);
242   g_object_set_data (G_OBJECT (adj), "default_value", def_val (0.33));
243 
244   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
245                               _("Filter Secondary Surround:"), SCALE_WIDTH, SCALE_DIGITS,
246                               sels->filter_secondary_surround,
247                               3, 10, 1, 1, 0,
248                               TRUE, 0, 0,
249                               _("Number of adjacent points to consider if "
250                                 "'filter_surround' points defines a straight line."),
251                               NULL);
252   g_signal_connect (adj, "value-changed",
253                     G_CALLBACK (gimp_double_adjustment_update),
254                     &sels->filter_secondary_surround);
255   adjust_widgets = g_slist_append (adjust_widgets, adj);
256   g_object_set_data (G_OBJECT (adj), "default_value", def_val (3.0));
257 
258   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
259                               _("Filter Surround:"), SCALE_WIDTH, SCALE_DIGITS,
260                               sels->filter_surround,
261                               2, 10, 1, 1, 0,
262                               TRUE, 0, 0,
263                               _("Number of adjacent points to consider when filtering."),
264                               NULL);
265   g_signal_connect (adj, "value-changed",
266                     G_CALLBACK (gimp_double_adjustment_update),
267                     &sels->filter_surround);
268   adjust_widgets = g_slist_append (adjust_widgets, adj);
269   g_object_set_data (G_OBJECT (adj), "default_value", def_val (2.0));
270 
271   check = gtk_check_button_new_with_label (_("Keep Knees"));
272   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), sels->keep_knees);
273   gtk_table_attach (GTK_TABLE (table), check, 1, 3, row, row + 1,
274                     GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
275   gimp_help_set_help_data (GTK_WIDGET (check),
276                            _("Says whether or not to remove 'knee' "
277                              "points after finding the outline."), NULL);
278   g_signal_connect (check, "toggled",
279                     G_CALLBACK (gimp_toggle_button_update),
280                     &sels->keep_knees);
281   gtk_widget_show (check);
282   adjust_widgets = g_slist_append (adjust_widgets, check);
283   g_object_set_data (G_OBJECT (check), "default_value", def_val ((gdouble)FALSE));
284   row++;
285 
286   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
287                               _("Line Reversion Threshold:"), SCALE_WIDTH, SCALE_DIGITS,
288                               sels->line_reversion_threshold,
289                               0.01, 0.2, 0.01, 0.01, 3,
290                               TRUE, 0, 0,
291                               _("If a spline is closer to a straight line than this, "
292 				"it remains a straight line, even if it would otherwise "
293 				"be changed back to a curve. This is weighted by the "
294 				"square of the curve length, to make shorter curves "
295 				"more likely to be reverted."), NULL);
296   g_signal_connect (adj, "value-changed",
297                     G_CALLBACK (gimp_double_adjustment_update),
298                     &sels->line_reversion_threshold);
299   adjust_widgets = g_slist_append (adjust_widgets, adj);
300   g_object_set_data (G_OBJECT (adj), "default_value", def_val (0.01));
301 
302   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
303                               _("Line Threshold:"), SCALE_WIDTH, SCALE_DIGITS,
304                               sels->line_threshold,
305                               0.2, 4, 0.1, 0.01, 2,
306                               TRUE, 0, 0,
307                               _("How many pixels (on the average) a spline can "
308 				"diverge from the line determined by its endpoints "
309 				"before it is changed to a straight line."), NULL);
310   g_signal_connect (adj, "value-changed",
311                     G_CALLBACK (gimp_double_adjustment_update),
312                     &sels->line_threshold);
313   adjust_widgets = g_slist_append (adjust_widgets, adj);
314   g_object_set_data (G_OBJECT (adj), "default_value", def_val (0.5));
315 
316   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
317                               _("Reparametrize Improvement:"), SCALE_WIDTH, SCALE_DIGITS,
318                               sels->reparameterize_improvement,
319                               0, 1, 0.05, 0.01, 2,
320                               TRUE, 0, 0,
321                               _("If reparameterization doesn't improve the fit by this "
322 				"much percent, stop doing it. ""Amount of error at which "
323 				"it is pointless to reparameterize."), NULL);
324   g_signal_connect (adj, "value-changed",
325                     G_CALLBACK (gimp_double_adjustment_update),
326                     &sels->reparameterize_improvement);
327   adjust_widgets = g_slist_append (adjust_widgets, adj);
328   g_object_set_data (G_OBJECT (adj), "default_value", def_val (0.01));
329 
330   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
331                               _("Reparametrize Threshold:"), SCALE_WIDTH, SCALE_DIGITS,
332                               sels->reparameterize_threshold,
333                               1, 50, 0.5, 0.5, 2,
334                               TRUE, 0, 0,
335                               _("Amount of error at which it is pointless to reparameterize.  "
336                                 "This happens, for example, when we are trying to fit the "
337                                 "outline of the outside of an 'O' with a single spline. "
338                                 "The initial fit is not good enough for the Newton-Raphson "
339                                 "iteration to improve it.  It may be that it would be better "
340                                 "to detect the cases where we didn't find any corners."), NULL);
341   g_signal_connect (adj, "value-changed",
342                     G_CALLBACK (gimp_double_adjustment_update),
343                     &sels->reparameterize_threshold);
344   adjust_widgets = g_slist_append (adjust_widgets, adj);
345   g_object_set_data (G_OBJECT (adj), "default_value", def_val (1.0));
346 
347   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
348                               _("Subdivide Search:"), SCALE_WIDTH, SCALE_DIGITS,
349                               sels->subdivide_search,
350                               0.05, 1, 0.05, 0.01, 2,
351                               TRUE, 0, 0,
352                               _("Percentage of the curve away from the worst point "
353 				"to look for a better place to subdivide."), NULL);
354   g_signal_connect (adj, "value-changed",
355                     G_CALLBACK (gimp_double_adjustment_update),
356                     &sels->subdivide_search);
357   adjust_widgets = g_slist_append (adjust_widgets, adj);
358   g_object_set_data (G_OBJECT (adj), "default_value", def_val (0.1));
359 
360   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
361                               _("Subdivide Surround:"), SCALE_WIDTH, SCALE_DIGITS,
362                               sels->subdivide_surround,
363                               2, 10, 1, 1, 0,
364                               TRUE, 0, 0,
365                               _("Number of points to consider when deciding whether "
366 				"a given point is a better place to subdivide."),
367                               NULL);
368   g_signal_connect (adj, "value-changed",
369                     G_CALLBACK (gimp_double_adjustment_update),
370                     &sels->subdivide_surround);
371   adjust_widgets = g_slist_append (adjust_widgets, adj);
372   g_object_set_data (G_OBJECT (adj), "default_value", def_val (4.0));
373 
374   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
375                               _("Subdivide Threshold:"), SCALE_WIDTH, SCALE_DIGITS,
376                               sels->subdivide_threshold,
377                               0.01, 1, 0.01, 0.01, 2,
378                               TRUE, 0, 0,
379                               _("How many pixels a point can diverge from a straight "
380 				"line and still be considered a better place to "
381 				"subdivide."), NULL);
382   g_signal_connect (adj, "value-changed",
383                     G_CALLBACK (gimp_double_adjustment_update),
384                     &sels->subdivide_threshold);
385   adjust_widgets = g_slist_append (adjust_widgets, adj);
386   g_object_set_data (G_OBJECT (adj), "default_value", def_val (0.03));
387 
388   adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
389                               _("Tangent Surround:"), SCALE_WIDTH, SCALE_DIGITS,
390                               sels->tangent_surround,
391                               2, 10, 1, 1, 0,
392                               TRUE, 0, 0,
393                               _("Number of points to look at on either side of a "
394 				"point when computing the approximation to the "
395 				"tangent at that point."), NULL);
396   g_signal_connect (adj, "value-changed",
397                     G_CALLBACK (gimp_double_adjustment_update),
398                     &sels->tangent_surround);
399   adjust_widgets = g_slist_append (adjust_widgets, adj);
400   g_object_set_data (G_OBJECT (adj), "default_value", def_val (3.0));
401 
402   return scrolled_win;
403 }
404