From cca66b9ec4e494c1d919bff0f71a820d8afab1fa Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:24:48 +0200 Subject: Adding upstream version 1.2.2. Signed-off-by: Daniel Baumann --- src/ui/dialog/tracedialog.cpp | 485 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 485 insertions(+) create mode 100644 src/ui/dialog/tracedialog.cpp (limited to 'src/ui/dialog/tracedialog.cpp') diff --git a/src/ui/dialog/tracedialog.cpp b/src/ui/dialog/tracedialog.cpp new file mode 100644 index 0000000..114e341 --- /dev/null +++ b/src/ui/dialog/tracedialog.cpp @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Bitmap tracing settings dialog - second implementation. + */ +/* Authors: + * Marc Jeanmougin + * + * Copyright (C) 2019 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "tracedialog.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "desktop.h" +#include "display/cairo-utils.h" +#include "inkscape.h" +#include "io/resource.h" +#include "io/sys.h" +#include "selection.h" +#include "trace/autotrace/inkscape-autotrace.h" +#include "trace/depixelize/inkscape-depixelize.h" +#include "trace/potrace/inkscape-potrace.h" +#include "ui/util.h" + +// This maps the column ids in the glade file to useful enums +static const std::map trace_types = { + {"SS_BC", Inkscape::Trace::Potrace::TRACE_BRIGHTNESS}, + {"SS_ED", Inkscape::Trace::Potrace::TRACE_CANNY}, + {"SS_CQ", Inkscape::Trace::Potrace::TRACE_QUANT}, + {"SS_AT", Inkscape::Trace::Potrace::AUTOTRACE_SINGLE}, + {"SS_CT", Inkscape::Trace::Potrace::AUTOTRACE_CENTERLINE}, + + {"MS_BS", Inkscape::Trace::Potrace::TRACE_BRIGHTNESS_MULTI}, + {"MS_C", Inkscape::Trace::Potrace::TRACE_QUANT_COLOR}, + {"MS_BW", Inkscape::Trace::Potrace::TRACE_QUANT_MONO}, + {"MS_AT", Inkscape::Trace::Potrace::AUTOTRACE_MULTI}, +}; + +namespace Inkscape { +namespace UI { +namespace Dialog { + +class TraceDialogImpl2 : public TraceDialog { + public: + TraceDialogImpl2(); + ~TraceDialogImpl2() override; + + void selectionModified(Selection *selection, guint flags) override; + void selectionChanged(Inkscape::Selection *selection) override; +private: + Inkscape::Trace::Tracer tracer; + void traceProcess(bool do_i_trace); + void abort(); + + void previewCallback(bool force); + bool previewResize(const Cairo::RefPtr&); + void traceCallback(); + void onSetDefaults(); + void show_hide_params(); + void schedule_preview_update(); + static gboolean update_cb(gpointer user_data); + + Glib::RefPtr builder; + + Glib::RefPtr MS_scans, PA_curves, PA_islands, PA_sparse1, PA_sparse2, SS_AT_ET_T, SS_AT_FI_T, SS_BC_T, SS_CQ_T, + SS_ED_T, optimize, smooth, speckles; + Gtk::ComboBoxText *CBT_SS, *CBT_MS; + Gtk::CheckButton *CB_invert, *CB_MS_smooth, *CB_MS_stack, *CB_MS_rb, *CB_speckles, *CB_smooth, *CB_optimize, *CB_PA_optimize, + /* *CB_live,*/ *CB_SIOX; + Gtk::CheckButton* CB_SIOX1; + Gtk::CheckButton* CB_speckles1; + Gtk::CheckButton* CB_smooth1; + Gtk::CheckButton* CB_optimize1; + Gtk::RadioButton *RB_PA_voronoi; + Gtk::Button *B_RESET, *B_STOP, *B_OK, *B_Update; + Gtk::Box *mainBox; + Gtk::Notebook *choice_tab; + Glib::RefPtr scaledPreview; + Gtk::DrawingArea *previewArea; + Gtk::Box* orient_box; + Gtk::Frame* _preview_frame; + Gtk::Grid* _param_grid; + Gtk::CheckButton* _live_preview; + guint _source = 0; +}; + +enum Page { + SingleScan, MultiScan, PixelArt +}; + +void TraceDialogImpl2::traceProcess(bool do_i_trace) +{ + SPDesktop* desktop = getDesktop(); + if (desktop) + desktop->setWaitingCursor(); + + auto current_page = choice_tab->get_current_page(); + + auto cb_siox = current_page == SingleScan ? CB_SIOX : CB_SIOX1; + if (cb_siox->get_active()) + tracer.enableSiox(true); + else + tracer.enableSiox(false); + + Glib::ustring type = current_page == SingleScan ? CBT_SS->get_active_id() : CBT_MS->get_active_id(); + + bool use_autotrace = false; + Inkscape::Trace::Autotrace::AutotraceTracingEngine ate; // TODO + + auto potraceType = trace_types.find(type); + assert(potraceType != trace_types.end()); + switch (potraceType->second) { + case Inkscape::Trace::Potrace::AUTOTRACE_SINGLE: + use_autotrace = true; + ate.opts->color_count = 2; + break; + case Inkscape::Trace::Potrace::AUTOTRACE_CENTERLINE: + use_autotrace = true; + ate.opts->color_count = 2; + ate.opts->centerline = true; + ate.opts->preserve_width = true; + break; + case Inkscape::Trace::Potrace::AUTOTRACE_MULTI: + use_autotrace = true; + ate.opts->color_count = (int)MS_scans->get_value() + 1; + break; + default: + break; + } + + ate.opts->filter_iterations = (int) SS_AT_FI_T->get_value(); + ate.opts->error_threshold = SS_AT_ET_T->get_value(); + + Inkscape::Trace::Potrace::PotraceTracingEngine pte( + potraceType->second, CB_invert->get_active(), (int)SS_CQ_T->get_value(), SS_BC_T->get_value(), + 0., // Brightness floor + SS_ED_T->get_value(), (int)MS_scans->get_value(), CB_MS_stack->get_active(), CB_MS_smooth->get_active(), + CB_MS_rb->get_active()); + + auto cb_optimize = current_page == SingleScan ? CB_optimize : CB_optimize1; + pte.potraceParams->opticurve = cb_optimize->get_active(); + pte.potraceParams->opttolerance = optimize->get_value(); + + auto cb_smooth = current_page == SingleScan ? CB_smooth : CB_smooth1; + pte.potraceParams->alphamax = cb_smooth->get_active() ? smooth->get_value() : 0; + + auto cb_speckles = current_page == SingleScan ? CB_speckles : CB_speckles1; + pte.potraceParams->turdsize = cb_speckles->get_active() ? (int)speckles->get_value() : 0; + + //Inkscape::Trace::Autotrace::AutotraceTracingEngine ate; // TODO + Inkscape::Trace::Depixelize::DepixelizeTracingEngine dte( + RB_PA_voronoi->get_active() ? Inkscape::Trace::Depixelize::TraceType::TRACE_VORONOI : Inkscape::Trace::Depixelize::TraceType::TRACE_BSPLINES, + PA_curves->get_value(), (int) PA_islands->get_value(), + (int) PA_sparse1->get_value(), PA_sparse2->get_value(), + CB_PA_optimize->get_active()); + + //TODO: preview for multiscan: grayscale bitmap with matching number of gray levels? + // Currently there's one created using brightness threshold, which is wrong and misleading + Glib::RefPtr pixbuf = tracer.getSelectedImage(); + if (pixbuf) { + scaledPreview = use_autotrace ? ate.preview(pixbuf) : pte.preview(pixbuf); + } + else { + scaledPreview.reset(); + } + + previewArea->queue_draw(); + + if (do_i_trace){ + if (current_page == PixelArt){ + tracer.trace(&dte); + printf("dt\n"); + } else if (use_autotrace) { + tracer.trace(&ate); + printf("at\n"); + } else if (current_page == SingleScan || current_page == MultiScan) { + tracer.trace(&pte); + printf("pt\n"); + } + } + + if (desktop) + desktop->clearWaitingCursor(); +} + +bool TraceDialogImpl2::previewResize(const Cairo::RefPtr& cr) +{ + /* Checkerboard - is not applicable here; left for reference + if (auto wnd = dynamic_cast(this->get_toplevel())) { + auto color = wnd->get_style_context()->get_background_color(); + auto background = + gint32(0xff * color.get_red()) << 24 | + gint32(0xff * color.get_green()) << 16 | + gint32(0xff * color.get_blue()) << 8 | + 0xff; + + auto device_scale = get_scale_factor(); + Cairo::RefPtr pattern(new Cairo::Pattern(ink_cairo_pattern_create_checkerboard(background))); + cr->save(); + cr->scale(device_scale, device_scale); + cr->set_operator(Cairo::OPERATOR_SOURCE); + cr->set_source(pattern); + cr->paint(); + cr->restore(); + } */ + + if (scaledPreview) { + int width = scaledPreview->get_width(); + int height = scaledPreview->get_height(); + const Gtk::Allocation &vboxAlloc = previewArea->get_allocation(); + double scaleFX = vboxAlloc.get_width() / (double)width; + double scaleFY = vboxAlloc.get_height() / (double)height; + double scaleFactor = scaleFX > scaleFY ? scaleFY : scaleFX; + int newWidth = (int)(((double)width) * scaleFactor); + int newHeight = (int)(((double)height) * scaleFactor); + int offsetX = (vboxAlloc.get_width() - newWidth)/2; + int offsetY = (vboxAlloc.get_height() - newHeight)/2; + cr->scale(scaleFactor, scaleFactor); + Gdk::Cairo::set_source_pixbuf(cr, scaledPreview, offsetX / scaleFactor, offsetY / scaleFactor); + cr->paint(); + } + else { + cr->set_source_rgba(0, 0, 0, 0); + cr->paint(); + } + + return false; +} + +void TraceDialogImpl2::abort() +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (desktop) + desktop->clearWaitingCursor(); + tracer.abort(); +} + +void TraceDialogImpl2::selectionChanged(Inkscape::Selection *selection) { + // refresh preview when selecting or deselecting images (in general: when selection changes) + previewCallback(false); +} + +void TraceDialogImpl2::selectionModified(Selection *selection, guint flags) { + // Note: original refresh condition commended out, as selection modified fires when moving images around slowing on-canvas operations. + // Note: is there a use-case where preview needs to be refreshed when images are manipulated? I haven't found one. + +// if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG)) { + + // this condition is satisfied when imported image gets places on canvas; this is actually wrong + // and there should just be "selection changed", but there isn't + auto mask = SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG; + if ((flags & mask) == mask) { + previewCallback(false); + } +} + +void TraceDialogImpl2::onSetDefaults() +{ + MS_scans->set_value(8); + PA_curves->set_value(1); + PA_islands->set_value(5); + PA_sparse1->set_value(4); + PA_sparse2->set_value(1); + SS_AT_FI_T->set_value(4); + SS_AT_ET_T->set_value(2); + SS_BC_T->set_value(0.45); + SS_CQ_T->set_value(64); + SS_ED_T->set_value(.65); + optimize->set_value(0.2); + smooth->set_value(1); + speckles->set_value(2); + CB_invert->set_active(false); + CB_MS_smooth->set_active(true); + CB_MS_stack->set_active(true); + CB_MS_rb->set_active(false); + CB_speckles->set_active(true); + CB_smooth->set_active(true); + CB_optimize->set_active(true); + CB_speckles1->set_active(true); + CB_smooth1->set_active(true); + CB_optimize1->set_active(true); + CB_PA_optimize->set_active(false); + CB_SIOX->set_active(false); + CB_SIOX1->set_active(false); +} + +void TraceDialogImpl2::previewCallback(bool force) { + if (force || (_live_preview->get_active() && is_widget_effectively_visible(this))) { + traceProcess(false); + } +} + +void TraceDialogImpl2::traceCallback() { traceProcess(true); } + + +TraceDialogImpl2::TraceDialogImpl2() + : TraceDialog() +{ + const std::string req_widgets[] = { "MS_scans", "PA_curves", "PA_islands", "PA_sparse1", "PA_sparse2", + "SS_AT_FI_T", "SS_AT_ET_T", "SS_BC_T", "SS_CQ_T", "SS_ED_T", + "optimize", "smooth", "speckles", "CB_invert", "CB_MS_smooth", + "CB_MS_stack", "CB_MS_rb", "CB_speckles", "CB_smooth", "CB_optimize", + "CB_speckles1", "CB_smooth1", "CB_optimize1", "CB_SIOX1", + "CB_PA_optimize", /*"CB_live",*/ "CB_SIOX", "CBT_SS", "CBT_MS", + "B_RESET", "B_STOP", "B_OK", "mainBox", "choice_tab", + /*"choice_scan",*/ "previewArea", "_live_preview" }; + auto gladefile = get_filename_string(Inkscape::IO::Resource::UIS, "dialog-trace.glade"); + try { + builder = Gtk::Builder::create_from_file(gladefile); + } catch (const Glib::Error &ex) { + g_warning("Glade file loading failed for filter effect dialog"); + return; + } + + Glib::RefPtr test; + for (std::string w : req_widgets) { + test = builder->get_object(w); + if (!test) { + g_warning("Required widget %s does not exist", w.c_str()); + return; + } + } + +#define GET_O(name) \ + tmp = builder->get_object(#name); \ + name = Glib::RefPtr::cast_dynamic(tmp); + + Glib::RefPtr tmp; + +#define GET_W(name) builder->get_widget(#name, name); + GET_O(MS_scans) + GET_O(PA_curves) + GET_O(PA_islands) + GET_O(PA_sparse1) + GET_O(PA_sparse2) + GET_O(SS_AT_FI_T) + GET_O(SS_AT_ET_T) + GET_O(SS_BC_T) + GET_O(SS_CQ_T) + GET_O(SS_ED_T) + GET_O(optimize) + GET_O(smooth) + GET_O(speckles) + + GET_W(CB_invert) + GET_W(CB_MS_smooth) + GET_W(CB_MS_stack) + GET_W(CB_MS_rb) + GET_W(CB_speckles) + GET_W(CB_smooth) + GET_W(CB_optimize) + GET_W(CB_speckles1) + GET_W(CB_smooth1) + GET_W(CB_optimize1) + GET_W(CB_PA_optimize) + GET_W(CB_SIOX) + GET_W(CB_SIOX1) + GET_W(RB_PA_voronoi) + GET_W(CBT_SS) + GET_W(CBT_MS) + GET_W(B_RESET) + GET_W(B_STOP) + GET_W(B_OK) + GET_W(B_Update) + GET_W(mainBox) + GET_W(choice_tab) + GET_W(previewArea) + GET_W(orient_box) + GET_W(_preview_frame) + GET_W(_param_grid) + GET_W(_live_preview) +#undef GET_W +#undef GET_O + add(*mainBox); + + Inkscape::Preferences* prefs = Inkscape::Preferences::get(); + + _live_preview->set_active(prefs->getBool(getPrefsPath() + "liveUpdate", true)); + + B_Update->signal_clicked().connect([=](){ previewCallback(true); }); + B_OK->signal_clicked().connect(sigc::mem_fun(*this, &TraceDialogImpl2::traceCallback)); + B_STOP->signal_clicked().connect(sigc::mem_fun(*this, &TraceDialogImpl2::abort)); + B_RESET->signal_clicked().connect(sigc::mem_fun(*this, &TraceDialogImpl2::onSetDefaults)); + previewArea->signal_draw().connect(sigc::mem_fun(*this, &TraceDialogImpl2::previewResize)); + + // attempt at making UI responsive: relocate preview to the right or bottom of dialog depending on dialog size + this->signal_size_allocate().connect([=](const Gtk::Allocation& alloc){ + // skip bogus sizes + if (alloc.get_width() < 10 || alloc.get_height() < 10) return; + // ratio: is dialog wide or is it tall? + double ratio = alloc.get_width() / static_cast(alloc.get_height()); + // g_warning("size alloc: %d x %d - %f", alloc.get_width(), alloc.get_height(), ratio); + const auto hysteresis = 0.01; + if (ratio < 1 - hysteresis) { + // narrow/tall + choice_tab->set_valign(Gtk::ALIGN_START); + orient_box->set_orientation(Gtk::ORIENTATION_VERTICAL); + } + else if (ratio > 1 + hysteresis) { + // wide/short + orient_box->set_orientation(Gtk::ORIENTATION_HORIZONTAL); + choice_tab->set_valign(Gtk::ALIGN_FILL); + } + }); + + CBT_SS->signal_changed().connect([=](){ show_hide_params(); }); + show_hide_params(); + + // watch for changes, but only in params that can impact preview bitmap + 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 }) { + adj->signal_value_changed().connect([=](){ schedule_preview_update(); }); + } + 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}) { + checkbtn->signal_toggled().connect([=](){ schedule_preview_update(); }); + } + for (auto combo : {CBT_SS, CBT_MS}) { + combo->signal_changed().connect([=](){ schedule_preview_update(); }); + } + choice_tab->signal_switch_page().connect([=](Gtk::Widget*, guint){ schedule_preview_update(); }); + + signal_set_focus_child().connect([=](Gtk::Widget* w){ + if (w) schedule_preview_update(); + }); +} + +TraceDialogImpl2::~TraceDialogImpl2() { + Inkscape::Preferences* prefs = Inkscape::Preferences::get(); + prefs->setBool(getPrefsPath() + "liveUpdate", _live_preview->get_active()); + + if (_source) { + g_source_destroy(g_main_context_find_source_by_id(nullptr, _source)); + } +} + +gboolean TraceDialogImpl2::update_cb(gpointer user_data) { + auto self = static_cast(user_data); + self->previewCallback(false); + self->_source = 0; + return FALSE; +} + +void TraceDialogImpl2::schedule_preview_update() { + if (!_live_preview->get_active() || _source) return; + + _source = g_idle_add(&TraceDialogImpl2::update_cb, this); +} + +void TraceDialogImpl2::show_hide_params() { + int start_row = 2; + int option = CBT_SS->get_active_row_number(); + if (option >= 3) option = 3; + int show1 = start_row + option; + int show2 = start_row + option; + if (option >= 3) ++show2; + + for (int row = start_row; row < start_row + 5; ++row) { + for (int col = 0; col < 4; ++col) { + if (auto widget = _param_grid->get_child_at(col, row)) { + if (row == show1 || row == show2) widget->show(); else widget->hide(); + } + } + } +} + +TraceDialog &TraceDialog::getInstance() +{ + TraceDialog *dialog = new TraceDialogImpl2(); + return *dialog; +} + + + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape -- cgit v1.2.3