Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
tracedialog.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/* Authors:
7 * Bob Jamison
8 * Marc Jeanmougin <marc.jeanmougin@telecom-paristech.fr>
9 * PBS <pbs3141@gmail.com>
10 * Others - see git history.
11 *
12 * Copyright (C) 2019-2022 Authors
13 *
14 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
15 */
16
17#include "tracedialog.h"
18
19#include <gtkmm/comboboxtext.h>
20#include <gtkmm/dropdown.h>
21#include <gtkmm/eventcontrollerfocus.h>
22#include <gtkmm/frame.h>
23#include <gtkmm/grid.h>
24#include <gtkmm/picture.h>
25#include <gtkmm/checkbutton.h>
26#include <gtkmm/progressbar.h>
27#include <gtkmm/stack.h>
28
29#include "desktop.h"
30#include "preferences.h"
34#include "ui/builder-utils.h"
35#include "ui/util.h"
36#include "ui/widget/bin.h"
37
38namespace Inkscape::UI::Dialog {
39namespace {
40
41constexpr auto cbt_ss_map = std::to_array({
47});
48
49constexpr auto cbt_ms_map = std::to_array({
54});
55
56enum class EngineType
57{
58 Potrace,
59 Autotrace,
60 Depixelize
61};
62
63struct TraceData
64{
65 std::unique_ptr<Trace::TracingEngine> engine;
67};
68
69class TraceDialogImpl : public TraceDialog
70{
71public:
72 TraceDialogImpl();
73 ~TraceDialogImpl() override;
74
75protected:
76 void selectionModified(Selection *selection, unsigned flags) override;
77 void selectionChanged(Selection *selection) override;
78
79private:
80 TraceData getTraceData() const;
81 void setDefaults();
82 void adjustParamsVisible();
83 void onTraceClicked();
84 void onAbortClicked();
85 bool previewsEnabled() const;
86 void schedulePreviewUpdate(int msecs, bool force = false);
87 void updatePreview(bool force = false);
88 void launchPreviewGeneration();
89
90 // Handles to ongoing asynchronous computations.
91 Trace::TraceFuture trace_future;
92 Trace::TraceFuture preview_future;
93
94 // Delayed preview generation.
95 sigc::connection preview_timeout_conn;
97
98 Glib::RefPtr<Gtk::Builder> builder;
99 UI::Widget::Bin bin;
100 Glib::RefPtr<Gtk::Adjustment> MS_scans, PA_curves, PA_islands, PA_sparse1, PA_sparse2;
101 Glib::RefPtr<Gtk::Adjustment> SS_AT_ET_T, SS_AT_FI_T, SS_BC_T, SS_CQ_T, SS_ED_T;
102 Glib::RefPtr<Gtk::Adjustment> optimize, smooth, speckles;
103 Gtk::DropDown &CBT_SS, &CBT_MS;
105 Gtk::CheckButton &CB_speckles, &CB_smooth, &CB_optimize, &CB_SIOX;
107 Gtk::CheckButton &RB_PA_voronoi;
108 Gtk::Button &B_RESET, &B_STOP, &B_OK, &B_Update;
109 Gtk::Box &mainBox;
110 Gtk::Notebook &choice_tab;
111 Gtk::Picture &previewArea;
112 Gtk::Box &orient_box;
113 Gtk::Frame &_preview_frame;
114 Gtk::Grid &_param_grid;
115 Gtk::CheckButton &_live_preview;
116 Gtk::Stack &stack;
117 Gtk::ProgressBar &progressbar;
118 Gtk::Box &boxchild1, &boxchild2;
119 sigc::scoped_connection _page_switched;
120};
121
122enum class Page
123{
124 SingleScan,
125 MultiScan,
126 PixelArt
127};
128
129TraceData TraceDialogImpl::getTraceData() const
130{
131 auto current_page = static_cast<Page>(choice_tab.get_current_page());
132
133 auto &cb_siox = current_page == Page::SingleScan ? CB_SIOX : CB_SIOX1;
134 bool enable_siox = cb_siox.get_active();
135
136 auto trace_type = current_page == Page::SingleScan
137 ? cbt_ss_map.at(CBT_SS.get_selected())
138 : cbt_ms_map.at(CBT_MS.get_selected());
139
140 EngineType engine_type;
141 if (current_page == Page::PixelArt) {
142 engine_type = EngineType::Depixelize;
143 } else {
144 switch (trace_type) {
148 engine_type = EngineType::Autotrace;
149 break;
150 default:
151 engine_type = EngineType::Potrace;
152 break;
153 }
154 }
155
156 auto setup_potrace = [&, this] {
157 auto eng = std::make_unique<Trace::Potrace::PotraceTracingEngine>(
158 trace_type, CB_invert.get_active(), (int)SS_CQ_T->get_value(), SS_BC_T->get_value(),
159 0, // Brightness floor
160 SS_ED_T->get_value(), (int)MS_scans->get_value(), CB_MS_stack.get_active(), CB_MS_smooth.get_active(),
161 CB_MS_rb.get_active());
162
163 auto &cb_optimize = current_page == Page::SingleScan ? CB_optimize : CB_optimize1;
164 eng->setOptiCurve(cb_optimize.get_active());
165 eng->setOptTolerance(optimize->get_value());
166
167 auto &cb_smooth = current_page == Page::SingleScan ? CB_smooth : CB_smooth1;
168 eng->setAlphaMax(cb_smooth.get_active() ? smooth->get_value() : 0);
169
170 auto &cb_speckles = current_page == Page::SingleScan ? CB_speckles : CB_speckles1;
171 eng->setTurdSize(cb_speckles.get_active() ? (int)speckles->get_value() : 0);
172
173 return eng;
174 };
175
176 auto setup_autotrace = [&, this] {
177 auto eng = std::make_unique<Trace::Autotrace::AutotraceTracingEngine>();
178
179 switch (trace_type) {
181 eng->setColorCount(2);
182 break;
184 eng->setColorCount(2);
185 eng->setCenterLine(true);
186 eng->setPreserveWidth(true);
187 break;
189 eng->setColorCount((int)MS_scans->get_value() + 1);
190 break;
191 default:
192 assert(false);
193 break;
194 }
195
196 eng->setFilterIterations((int)SS_AT_FI_T->get_value());
197 eng->setErrorThreshold(SS_AT_ET_T->get_value());
198
199 return eng;
200 };
201
202 auto setup_depixelize = [this] {
203 return std::make_unique<Trace::Depixelize::DepixelizeTracingEngine>(
205 PA_curves->get_value(), (int) PA_islands->get_value(),
206 (int) PA_sparse1->get_value(), PA_sparse2->get_value(),
207 CB_PA_optimize.get_active());
208 };
209
210 TraceData data;
211 switch (engine_type) {
212 case EngineType::Potrace: data.engine = setup_potrace(); break;
213 case EngineType::Autotrace: data.engine = setup_autotrace(); break;
214 case EngineType::Depixelize: data.engine = setup_depixelize(); break;
215 default: assert(false); break;
216 }
217 data.sioxEnabled = enable_siox;
218
219 return data;
220}
221
222void TraceDialogImpl::selectionChanged(Inkscape::Selection *selection)
223{
224 updatePreview();
225}
226
227void TraceDialogImpl::selectionModified(Selection *selection, unsigned flags)
228{
229 auto mask = SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG;
230 if ((flags & mask) == mask) {
231 // All flags set - preview instantly.
232 updatePreview();
233 } else if (flags & mask) {
234 // At least one flag set - preview after a long delay.
235 schedulePreviewUpdate(1000);
236 }
237}
238
239void TraceDialogImpl::setDefaults()
240{
241 MS_scans->set_value(8);
242 PA_curves->set_value(1);
243 PA_islands->set_value(5);
244 PA_sparse1->set_value(4);
245 PA_sparse2->set_value(1);
246 SS_AT_FI_T->set_value(4);
247 SS_AT_ET_T->set_value(2);
248 SS_BC_T->set_value(0.45);
249 SS_CQ_T->set_value(64);
250 SS_ED_T->set_value(.65);
251 optimize->set_value(0.2);
252 smooth->set_value(1);
253 speckles->set_value(2);
254 CB_invert.set_active(false);
255 CB_MS_smooth.set_active(true);
256 CB_MS_stack.set_active(true);
257 CB_MS_rb.set_active(false);
258 CB_speckles.set_active(true);
259 CB_smooth.set_active(true);
260 CB_optimize.set_active(true);
261 CB_speckles1.set_active(true);
262 CB_smooth1.set_active(true);
263 CB_optimize1.set_active(true);
264 CB_PA_optimize.set_active(false);
265 CB_SIOX.set_active(false);
266 CB_SIOX1.set_active(false);
267}
268
269void TraceDialogImpl::onAbortClicked()
270{
271 if (!trace_future) {
272 // Not tracing; nothing to cancel.
273 return;
274 }
275
276 stack.set_visible_child(boxchild1);
277 if (auto desktop = getDesktop()) desktop->clearWaitingCursor();
278 trace_future.cancel();
279}
280
281void TraceDialogImpl::onTraceClicked()
282{
283 if (trace_future) {
284 // Still tracing; wait for either finished or cancelled.
285 return;
286 }
287
288 // Attempt to fire off the tracer.
289 auto data = getTraceData();
290 trace_future = Trace::trace(std::move(data.engine), data.sioxEnabled,
291 // On progress:
292 [this] (double progress) {
293 progressbar.set_fraction(progress);
294 },
295 // On completion without cancelling:
296 [this] {
297 progressbar.set_fraction(1.0);
298 stack.set_visible_child(boxchild1);
299 if (auto desktop = getDesktop()) desktop->clearWaitingCursor();
300 trace_future.cancel();
301 }
302 );
303
304 if (trace_future) {
305 // Put the UI into the tracing state.
306 if (auto desktop = getDesktop()) desktop->setWaitingCursor();
307 stack.set_visible_child(boxchild2);
308 progressbar.set_fraction(0.0);
309 }
310}
311
314
315TraceDialogImpl::TraceDialogImpl()
316 : builder(create_builder("dialog-trace.glade"))
317 // Adjustment
318 , MS_scans (get_object<Gtk::Adjustment>(builder, "MS_scans"))
319 , PA_curves (get_object<Gtk::Adjustment>(builder, "PA_curves"))
320 , PA_islands (get_object<Gtk::Adjustment>(builder, "PA_islands"))
321 , PA_sparse1 (get_object<Gtk::Adjustment>(builder, "PA_sparse1"))
322 , PA_sparse2 (get_object<Gtk::Adjustment>(builder, "PA_sparse2"))
323 , SS_AT_FI_T (get_object<Gtk::Adjustment>(builder, "SS_AT_FI_T"))
324 , SS_AT_ET_T (get_object<Gtk::Adjustment>(builder, "SS_AT_ET_T"))
325 , SS_BC_T (get_object<Gtk::Adjustment>(builder, "SS_BC_T"))
326 , SS_CQ_T (get_object<Gtk::Adjustment>(builder, "SS_CQ_T"))
327 , SS_ED_T (get_object<Gtk::Adjustment>(builder, "SS_ED_T"))
328 , optimize (get_object<Gtk::Adjustment>(builder, "optimize"))
329 , smooth (get_object<Gtk::Adjustment>(builder, "smooth"))
330 , speckles (get_object<Gtk::Adjustment>(builder, "speckles"))
331 // ComboBoxText
332 , CBT_SS (get_widget<Gtk::DropDown> (builder, "CBT_SS"))
333 , CBT_MS (get_widget<Gtk::DropDown> (builder, "CBT_MS"))
334 // CheckButton
335 , CB_invert (get_widget<Gtk::CheckButton> (builder, "CB_invert"))
336 , CB_MS_smooth (get_widget<Gtk::CheckButton> (builder, "CB_MS_smooth"))
337 , CB_MS_stack (get_widget<Gtk::CheckButton> (builder, "CB_MS_stack"))
338 , CB_MS_rb (get_widget<Gtk::CheckButton> (builder, "CB_MS_rb"))
339 , CB_speckles (get_widget<Gtk::CheckButton> (builder, "CB_speckles"))
340 , CB_smooth (get_widget<Gtk::CheckButton> (builder, "CB_smooth"))
341 , CB_optimize (get_widget<Gtk::CheckButton> (builder, "CB_optimize"))
342 , CB_SIOX (get_widget<Gtk::CheckButton> (builder, "CB_SIOX"))
343 , CB_speckles1 (get_widget<Gtk::CheckButton> (builder, "CB_speckles1"))
344 , CB_smooth1 (get_widget<Gtk::CheckButton> (builder, "CB_smooth1"))
345 , CB_optimize1 (get_widget<Gtk::CheckButton> (builder, "CB_optimize1"))
346 , CB_SIOX1 (get_widget<Gtk::CheckButton> (builder, "CB_SIOX1"))
347 , CB_PA_optimize (get_widget<Gtk::CheckButton> (builder, "CB_PA_optimize"))
348 // RadioButton
349 , RB_PA_voronoi (get_widget<Gtk::CheckButton> (builder, "RB_PA_voronoi"))
350 // Button
351 , B_RESET (get_widget<Gtk::Button> (builder, "B_RESET"))
352 , B_STOP (get_widget<Gtk::Button> (builder, "B_STOP"))
353 , B_OK (get_widget<Gtk::Button> (builder, "B_OK"))
354 , B_Update (get_widget<Gtk::Button> (builder, "B_Update"))
355 // Box
356 , mainBox (get_widget<Gtk::Box> (builder, "mainBox"))
357 , choice_tab (get_widget<Gtk::Notebook> (builder, "choice_tab"))
358 , previewArea (get_widget<Gtk::Picture> (builder, "previewArea"))
359 , orient_box (get_widget<Gtk::Box> (builder, "orient_box"))
360 , _preview_frame (get_widget<Gtk::Frame> (builder, "_preview_frame"))
361 , _param_grid (get_widget<Gtk::Grid> (builder, "_param_grid"))
362 , _live_preview (get_widget<Gtk::CheckButton> (builder, "_live_preview"))
363 , stack (get_widget<Gtk::Stack> (builder, "stack"))
364 , progressbar (get_widget<Gtk::ProgressBar> (builder, "progressbar"))
365 , boxchild1 (get_widget<Gtk::Box> (builder, "boxchild1"))
366 , boxchild2 (get_widget<Gtk::Box> (builder, "boxchild2"))
367{
368 append(bin);
369 bin.set_child(mainBox);
370 bin.set_expand(true);
371
372 auto prefs = Preferences::get();
373
374 _live_preview.set_active(prefs->getBool(getPrefsPath() + "liveUpdate", true));
375
376 B_Update.signal_clicked().connect([this] { updatePreview(true); });
377 B_OK.signal_clicked().connect(sigc::mem_fun(*this, &TraceDialogImpl::onTraceClicked));
378 B_STOP.signal_clicked().connect(sigc::mem_fun(*this, &TraceDialogImpl::onAbortClicked));
379 B_RESET.signal_clicked().connect(sigc::mem_fun(*this, &TraceDialogImpl::setDefaults));
380
381 // attempt at making UI responsive: relocate preview to the right or bottom of dialog depending on dialog size
382 bin.connectBeforeResize([this] (int width, int height, int baseline) {
383 // skip bogus sizes
384 if (width >= 10 && height >= 10) {
385 // ratio: is dialog wide or is it tall?
386 double const ratio = width / static_cast<double>(height);
387 // g_warning("size alloc: %d x %d - %f", alloc.get_width(), alloc.get_height(), ratio);
388 constexpr double hysteresis = 0.01;
389 if (ratio < 1 - hysteresis) {
390 // narrow/tall
391 choice_tab.set_valign(Gtk::Align::START);
392 orient_box.set_orientation(Gtk::Orientation::VERTICAL);
393 } else if (ratio > 1 + hysteresis) {
394 // wide/short
395 orient_box.set_orientation(Gtk::Orientation::HORIZONTAL);
396 choice_tab.set_valign(Gtk::Align::FILL);
397 }
398 }
399 });
400
401 CBT_SS.property_selected().signal_changed().connect([this] { adjustParamsVisible(); });
402 adjustParamsVisible();
403
404 // watch for changes, but only in params that can impact preview bitmap
405 for (auto adj : {SS_BC_T, SS_ED_T, SS_CQ_T, SS_AT_FI_T, SS_AT_ET_T, /* optimize, smooth, speckles,*/ MS_scans, PA_curves, PA_islands, PA_sparse1, PA_sparse2 }) {
406 adj->signal_value_changed().connect([this] { updatePreview(); });
407 }
408 for (auto checkbtn : {&CB_invert, &CB_MS_rb, /* CB_MS_smooth, CB_MS_stack, CB_optimize1, CB_optimize, */ &CB_PA_optimize, &CB_SIOX1, &CB_SIOX, /* CB_smooth1, CB_smooth, CB_speckles1, CB_speckles, */ &_live_preview}) {
409 checkbtn->signal_toggled().connect([this] { updatePreview(); });
410 }
411 for (auto combo : {&CBT_SS, &CBT_MS}) {
412 combo->property_selected().signal_changed().connect([this] { updatePreview(); });
413 }
414 _page_switched = choice_tab.signal_switch_page().connect([this] (Gtk::Widget *, unsigned) { updatePreview(); });
415
416 auto focus = Gtk::EventControllerFocus::create();
417 focus->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
418 add_controller(focus);
419 focus->signal_enter().connect([this] { updatePreview(); });
420}
421
422TraceDialogImpl::~TraceDialogImpl()
423{
425 prefs->setBool(getPrefsPath() + "liveUpdate", _live_preview.get_active());
426 preview_timeout_conn.disconnect();
427}
428
429bool TraceDialogImpl::previewsEnabled() const
430{
431 return _live_preview.get_active() && is_widget_effectively_visible(this);
432}
433
434void TraceDialogImpl::schedulePreviewUpdate(int msecs, bool force)
435{
436 if (!previewsEnabled() && !force) {
437 return;
438 }
439
440 // Restart timeout.
441 preview_timeout_conn.disconnect();
442 preview_timeout_conn = Glib::signal_timeout().connect([this] {
443 updatePreview(true);
444 return false;
445 }, msecs);
446}
447
448void TraceDialogImpl::updatePreview(bool force)
449{
450 if (!previewsEnabled() && !force) {
451 return;
452 }
453
454 preview_timeout_conn.disconnect();
455
456 if (preview_future) {
457 // Preview generation already running - flag for recomputation when finished.
459 return;
460 }
461
463
464 auto data = getTraceData();
465 preview_future = Trace::preview(std::move(data.engine), data.sioxEnabled,
466 // On completion:
467 [this] (Glib::RefPtr<Gdk::Pixbuf> result) {
468 previewArea.set_paintable(Gdk::Texture::create_for_pixbuf(result));
469 preview_future.cancel();
470
471 // Recompute if invalidated during computation.
472 if (preview_pending_recompute) {
473 updatePreview();
474 }
475 }
476 );
477
478 if (!preview_future) {
479 // On instant failure:
480 previewArea.set_paintable({});
481 }
482}
483
484void TraceDialogImpl::adjustParamsVisible()
485{
486 int constexpr start_row = 2;
487 int option = CBT_SS.get_selected();
488 if (option >= 3) option = 3;
489 int show1 = start_row + option;
490 int show2 = show1;
491 if (option == 3) ++show2;
492
493 for (int row = start_row; row < start_row + 5; ++row) {
494 for (int col = 0; col < 4; ++col) {
495 if (auto widget = _param_grid.get_child_at(col, row)) {
496 if (row == show1 || row == show2) {
497 widget->set_visible(true);
498 } else {
499 widget->set_visible(false);
500 }
501 }
502 }
503 }
504}
505
506} // namespace
507
508std::unique_ptr<TraceDialog> TraceDialog::create()
509{
510 return std::make_unique<TraceDialogImpl>();
511}
512
513} // namespace Inkscape::UI::Dialog
514
515/*
516 Local Variables:
517 mode:c++
518 c-file-style:"stroustrup"
519 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
520 indent-tabs-mode:nil
521 fill-column:99
522 End:
523*/
524// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
SimpleSnap option
Bin: widget that can hold one child, useful as a base class of custom widgets.
Gtk builder utilities.
Preference storage class.
Definition preferences.h:61
static Preferences * get()
Access the singleton Preferences object.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
void clearWaitingCursor()
Definition desktop.cpp:1166
void setWaitingCursor()
Definition desktop.cpp:1155
Css & result
Editable view implementation.
This is the C++ glue between Inkscape and Autotrace.
SymmetricMatrix< N > adj(const ConstBaseSymmetricMatrix< N > &S)
Definition desktop.h:50
TraceFuture trace(std::unique_ptr< TracingEngine > engine, bool sioxEnabled, std::function< void(double)> onprogress, std::function< void()> onfinished)
Launch an asynchronous trace operation taking as input engine and sioxEnabled.
Definition trace.cpp:331
Dialog code.
Definition desktop.h:117
Glib::RefPtr< Ob > get_object(Glib::RefPtr< Gtk::Builder > const &builder, char const *id)
W & get_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id)
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
Helper class to stream background task notifications as a series of messages.
Singleton class to access the preferences file in a convenient way.
void append(std::vector< T > &vec, std::vector< T > const &other)
Definition sanitize.cpp:55
static const Point data[]
SPDesktop * desktop
double height
double width
sigc::scoped_connection _page_switched
Gtk::CheckButton & CB_MS_smooth
Glib::RefPtr< Gtk::Adjustment > SS_AT_ET_T
Gtk::Picture & previewArea
Glib::RefPtr< Gtk::Adjustment > smooth
Gtk::Box & boxchild2
Gtk::Box & mainBox
Gtk::CheckButton & CB_MS_stack
Trace::TraceFuture preview_future
Glib::RefPtr< Gtk::Adjustment > SS_AT_FI_T
Gtk::Button & B_OK
Glib::RefPtr< Gtk::Builder > builder
Glib::RefPtr< Gtk::Adjustment > PA_curves
Gtk::DropDown & CBT_SS
Gtk::CheckButton & CB_optimize
Glib::RefPtr< Gtk::Adjustment > optimize
Trace::TraceFuture trace_future
Gtk::CheckButton & CB_optimize1
Gtk::CheckButton & CB_SIOX
Gtk::CheckButton & CB_speckles
Gtk::Button & B_STOP
Gtk::CheckButton & CB_SIOX1
Gtk::Grid & _param_grid
sigc::connection preview_timeout_conn
Gtk::DropDown & CBT_MS
Gtk::Box & orient_box
Glib::RefPtr< Gtk::Adjustment > SS_BC_T
UI::Widget::Bin bin
Gtk::CheckButton & CB_invert
Gtk::CheckButton & _live_preview
Gtk::ProgressBar & progressbar
Gtk::CheckButton & CB_speckles1
Gtk::CheckButton & CB_smooth1
Gtk::CheckButton & CB_MS_rb
Gtk::CheckButton & CB_PA_optimize
Glib::RefPtr< Gtk::Adjustment > MS_scans
Glib::RefPtr< Gtk::Adjustment > PA_islands
Glib::RefPtr< Gtk::Adjustment > SS_ED_T
bool sioxEnabled
Glib::RefPtr< Gtk::Adjustment > speckles
Gtk::Button & B_RESET
Gtk::CheckButton & RB_PA_voronoi
bool preview_pending_recompute
Gtk::Stack & stack
Glib::RefPtr< Gtk::Adjustment > PA_sparse2
Gtk::Frame & _preview_frame
Gtk::Box & boxchild1
Gtk::Button & B_Update
std::unique_ptr< Trace::TracingEngine > engine
Glib::RefPtr< Gtk::Adjustment > PA_sparse1
Gtk::CheckButton & CB_smooth
Glib::RefPtr< Gtk::Adjustment > SS_CQ_T
Gtk::Notebook & choice_tab
Bitmap tracing settings dialog - second implementation.
bool is_widget_effectively_visible(Gtk::Widget const *widget)
Definition util.cpp:82