/** * @file * Pixel art tracing settings dialog - implementation. */ /* Authors: * Bob Jamison * Stéphane Gimenez * Vinícius dos Santos Oliveira * Other dudes from The Inkscape Organization * * Copyright (C) 2004-2013 Authors * * Released under GNU GPL, read the file 'COPYING' for more information */ #ifdef HAVE_CONFIG_H # include #endif #ifdef GLIBMM_DISABLE_DEPRECATED # undef GLIBMM_DISABLE_DEPRECATED # include # include # define GLIBMM_DISABLE_DEPRECATED 1 #else // GLIBMM_DISABLE_DEPRECATED # include # include #endif // GLIBMM_DISABLE_DEPRECATED #include "pixelartdialog.h" #include #include #include #include "ui/widget/spinbutton.h" #include "ui/widget/frame.h" #include "desktop.h" #include "desktop-tracker.h" #include "message-stack.h" #include "selection.h" #include "sp-image.h" #include "display/cairo-utils.h" #include "libdepixelize/kopftracer2011.h" #include "document.h" #include "svg/svg.h" #include "svg/svg-color.h" #include "svg/css-ostringstream.h" #include "document-undo.h" #ifdef HAVE_OPENMP #include #endif // HAVE_OPENMP namespace Inkscape { namespace UI { namespace Dialog { /** * A dialog for adjusting pixel art -> vector tracing parameters */ class PixelArtDialogImpl : public PixelArtDialog { public: PixelArtDialogImpl(); ~PixelArtDialogImpl(); private: struct Input { Glib::RefPtr pixbuf; SVGLength x; SVGLength y; }; struct Output { Output(Tracer::Splines splines, SVGLength x, SVGLength y) : splines(splines), x(x), y(y) {} Tracer::Splines splines; SVGLength x; SVGLength y; }; void setDesktop(SPDesktop *desktop); void setTargetDesktop(SPDesktop *desktop); //############ Events void responseCallback(int response_id); //############ UI Logic Tracer::Kopf2011::Options options(); void vectorize(); void processLibdepixelize(const Input &image); void importOutput(const Output &out); void setDefaults(); void updatePreview(); bool ignorePreview; bool pendingPreview; //############ UI Gtk::HBox buttonsHBox; Gtk::Button *mainOkButton; Gtk::Button *mainCancelButton; Gtk::Button *mainResetButton; Gtk::VBox heuristicsVBox; UI::Widget::Frame heuristicsFrame; Gtk::HBox curvesMultiplierHBox; Gtk::Label curvesMultiplierLabel; Inkscape::UI::Widget::SpinButton curvesMultiplierSpinner; Gtk::HBox islandsWeightHBox; Gtk::Label islandsWeightLabel; Inkscape::UI::Widget::SpinButton islandsWeightSpinner; Gtk::HBox sparsePixelsMultiplierHBox; Gtk::Label sparsePixelsMultiplierLabel; Inkscape::UI::Widget::SpinButton sparsePixelsMultiplierSpinner; Gtk::HBox sparsePixelsRadiusHBox; Gtk::Label sparsePixelsRadiusLabel; Inkscape::UI::Widget::SpinButton sparsePixelsRadiusSpinner; Gtk::VBox outputVBox; UI::Widget::Frame outputFrame; Gtk::RadioButtonGroup outputGroup; Gtk::RadioButton voronoiRadioButton; Gtk::RadioButton noOptimizeRadioButton; #if LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH Gtk::RadioButton optimizeRadioButton; #endif // LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH //############ UI Logic data SPDesktop *desktop; DesktopTracker deskTrack; sigc::connection desktopChangeConn; //############ Threads void workerThread(); void onWorkerThreadFinished(); Glib::Thread *thread; volatile gint abortThread; // C++11's atomic stuff is sooo much nicer Glib::Dispatcher dispatcher; std::vector queue; std::vector output; Tracer::Kopf2011::Options lastOptions; }; void PixelArtDialogImpl::setDesktop(SPDesktop *desktop) { Panel::setDesktop(desktop); deskTrack.setBase(desktop); } void PixelArtDialogImpl::setTargetDesktop(SPDesktop *desktop) { this->desktop = desktop; } PixelArtDialogImpl::PixelArtDialogImpl() : PixelArtDialog(), ignorePreview(false), pendingPreview(false) { Gtk::Box *contents = _getContents(); // Heuristics { curvesMultiplierLabel.set_label(_("_Curves (multiplier):")); curvesMultiplierLabel.set_use_underline(true); curvesMultiplierLabel.set_mnemonic_widget(curvesMultiplierSpinner); curvesMultiplierLabel.set_tooltip_text(_("Favors connections that are part of a long curve")); curvesMultiplierSpinner.set_increments(0.125, 0); curvesMultiplierSpinner.set_digits(3); curvesMultiplierSpinner.set_range(-10, 10); curvesMultiplierSpinner.get_adjustment()->signal_value_changed() .connect(sigc::mem_fun(*this, &PixelArtDialogImpl::updatePreview)); curvesMultiplierHBox.pack_start(curvesMultiplierLabel, false, false); curvesMultiplierHBox.pack_end(curvesMultiplierSpinner, false, false); heuristicsVBox.pack_start(curvesMultiplierHBox, false, false); islandsWeightLabel.set_label(_("_Islands (weight):")); islandsWeightLabel.set_use_underline(true); islandsWeightLabel.set_mnemonic_widget(islandsWeightSpinner); islandsWeightLabel.set_tooltip_text(_("Avoid single disconnected pixels")); islandsWeightSpinner.set_tooltip_text(_("A constant vote value")); islandsWeightSpinner.set_increments(1, 0); islandsWeightSpinner.set_range(-20, 20); islandsWeightSpinner.get_adjustment()->signal_value_changed() .connect(sigc::mem_fun(*this, &PixelArtDialogImpl::updatePreview)); islandsWeightHBox.pack_start(islandsWeightLabel, false, false); islandsWeightHBox.pack_end(islandsWeightSpinner, false, false); heuristicsVBox.pack_start(islandsWeightHBox, false, false); sparsePixelsRadiusLabel.set_label(_("Sparse pixels (window _radius):")); sparsePixelsRadiusLabel.set_use_underline(true); sparsePixelsRadiusLabel.set_mnemonic_widget(sparsePixelsRadiusSpinner); sparsePixelsRadiusSpinner.set_increments(1, 0); sparsePixelsRadiusSpinner.set_range(2, 8); sparsePixelsRadiusSpinner.get_adjustment()->signal_value_changed() .connect(sigc::mem_fun(*this, &PixelArtDialogImpl::updatePreview)); sparsePixelsRadiusSpinner.set_tooltip_text(_("The radius of the window analyzed")); sparsePixelsMultiplierLabel.set_label(_("Sparse pixels (_multiplier):")); sparsePixelsMultiplierLabel.set_use_underline(true); sparsePixelsMultiplierLabel.set_mnemonic_widget(sparsePixelsMultiplierSpinner); sparsePixelsMultiplierSpinner.set_increments(0.125, 0); sparsePixelsMultiplierSpinner.set_digits(3); sparsePixelsMultiplierSpinner.set_range(-10, 10); sparsePixelsMultiplierSpinner.get_adjustment()->signal_value_changed() .connect(sigc::mem_fun(*this, &PixelArtDialogImpl::updatePreview)); { char const *str = _("Favors connections that are part of foreground color"); sparsePixelsRadiusLabel.set_tooltip_text(str); sparsePixelsMultiplierLabel.set_tooltip_text(str); } { char const *str = _("The heuristic computed vote will be multiplied by this value"); curvesMultiplierSpinner.set_tooltip_text(str); sparsePixelsMultiplierSpinner.set_tooltip_text(str); } sparsePixelsRadiusHBox.pack_start(sparsePixelsRadiusLabel, false, false); sparsePixelsRadiusHBox.pack_end(sparsePixelsRadiusSpinner, false, false); heuristicsVBox.pack_start(sparsePixelsRadiusHBox, false, false); sparsePixelsMultiplierHBox.pack_start(sparsePixelsMultiplierLabel, false, false); sparsePixelsMultiplierHBox.pack_end(sparsePixelsMultiplierSpinner, false, false); heuristicsVBox.pack_start(sparsePixelsMultiplierHBox, false, false); heuristicsFrame.set_label(_("Heuristics")); heuristicsFrame.add(heuristicsVBox); contents->pack_start(heuristicsFrame, false, false); } // Output { voronoiRadioButton.set_label(_("_Voronoi diagram")); voronoiRadioButton.set_tooltip_text(_("Output composed of straight lines")); voronoiRadioButton.set_use_underline(true); outputGroup = voronoiRadioButton.get_group(); outputVBox.pack_start(voronoiRadioButton, false, false); noOptimizeRadioButton.set_label(_("Convert to _B-spline curves")); noOptimizeRadioButton.set_tooltip_text(_("Preserve staircasing artifacts")); noOptimizeRadioButton.set_use_underline(true); noOptimizeRadioButton.set_group(outputGroup); outputVBox.pack_start(noOptimizeRadioButton, false, false); #if LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH optimizeRadioButton.set_label(_("_Smooth curves")); optimizeRadioButton.set_tooltip_text(_("The Kopf-Lischinski algorithm")); optimizeRadioButton.set_use_underline(true); optimizeRadioButton.set_group(outputGroup); outputVBox.pack_start(optimizeRadioButton, false, false); #endif // LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH outputFrame.set_label(_("Output")); outputFrame.add(outputVBox); contents->pack_start(outputFrame, true, false); } // Buttons { mainResetButton = addResponseButton(_("Reset"), GTK_RESPONSE_HELP, true); mainResetButton ->set_tooltip_text(_("Reset all settings to defaults")); //## The OK button mainCancelButton = addResponseButton(_("_Stop"), GTK_RESPONSE_CANCEL); if (mainCancelButton) { mainCancelButton->set_tooltip_text(_("Abort a trace in progress")); mainCancelButton->set_sensitive(false); } mainOkButton = addResponseButton(_("_OK"), GTK_RESPONSE_OK); mainOkButton->set_tooltip_text(_("Execute the trace")); contents->pack_start(buttonsHBox); } setDefaults(); show_all_children(); desktopChangeConn = deskTrack.connectDesktopChanged( sigc::mem_fun(*this, &PixelArtDialogImpl::setTargetDesktop) ); deskTrack.connect(GTK_WIDGET(gobj())); signalResponse().connect(sigc::mem_fun(*this, &PixelArtDialogImpl::responseCallback)); dispatcher.connect( sigc::mem_fun(*this, &PixelArtDialogImpl::onWorkerThreadFinished) ); } void PixelArtDialogImpl::responseCallback(int response_id) { if (response_id == GTK_RESPONSE_OK) { vectorize(); } else if (response_id == GTK_RESPONSE_CANCEL) { // libdepixelize's interface need to be extended to allow aborts g_atomic_int_set(&abortThread, true); } else if (response_id == GTK_RESPONSE_HELP) { setDefaults(); } else { hide(); return; } } Tracer::Kopf2011::Options PixelArtDialogImpl::options() { Tracer::Kopf2011::Options options; options.curvesMultiplier = curvesMultiplierSpinner.get_value(); options.islandsWeight = islandsWeightSpinner.get_value_as_int(); options.sparsePixelsMultiplier = sparsePixelsMultiplierSpinner.get_value(); options.sparsePixelsRadius = sparsePixelsRadiusSpinner.get_value_as_int(); #if LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH options.optimize = optimizeRadioButton.get_active(); #else // LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH options.optimize = false; #endif // LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH options.nthreads = Inkscape::Preferences::get() ->getIntLimited("/options/threading/numthreads", #ifdef HAVE_OPENMP omp_get_num_procs(), #else 1, #endif // HAVE_OPENMP 1, 256); return options; } void PixelArtDialogImpl::vectorize() { Inkscape::MessageStack *msgStack = desktop->messageStack(); if ( !desktop->selection ) { char *msg = _("Select an image to trace"); msgStack->flash(Inkscape::ERROR_MESSAGE, msg); return; } auto items = desktop->selection->items(); for(auto i=items.begin(); i!=items.end();++i){ if ( !SP_IS_IMAGE(*i) ) continue; SPImage *img = SP_IMAGE(*i); Input input; input.pixbuf = Glib::wrap(img->pixbuf->getPixbufRaw(), true); input.x = img->x; input.y = img->y; if ( input.pixbuf->get_width() > 256 || input.pixbuf->get_height() > 256 ) { char *msg = _("Image looks too big. Process may take a while and it is" " wise to save your document before continuing." "\n\nContinue the procedure (without saving)?"); Gtk::MessageDialog dialog(msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK_CANCEL, true); if ( dialog.run() != Gtk::RESPONSE_OK ) continue; } queue.push_back(input); } if ( !queue.size() ) { char *msg = _("Select an image to trace"); msgStack->flash(Inkscape::ERROR_MESSAGE, msg); return; } mainCancelButton->set_sensitive(true); mainOkButton->set_sensitive(false); lastOptions = options(); g_atomic_int_set(&abortThread, false); thread = Glib::Thread::create(sigc::mem_fun(*this, &PixelArtDialogImpl::workerThread), /*joinable =*/true); } void PixelArtDialogImpl::processLibdepixelize(const Input &input) { Tracer::Splines out; if ( input.pixbuf->get_width() > 256 || input.pixbuf->get_height() > 256 ) { char *msg = _("Image looks too big. Process may take a while and it is" " wise to save your document before continuing." "\n\nContinue the procedure (without saving)?"); Gtk::MessageDialog dialog(msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK_CANCEL, true); if ( dialog.run() != Gtk::RESPONSE_OK ) return; } if ( voronoiRadioButton.get_active() ) { output.push_back(Output(Tracer::Kopf2011::to_voronoi(input.pixbuf, lastOptions), input.x, input.y)); } else { output.push_back(Output(Tracer::Kopf2011::to_splines(input.pixbuf, lastOptions), input.x, input.y)); } } void PixelArtDialogImpl::importOutput(const Output &output) { Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); Inkscape::XML::Node *group = xml_doc->createElement("svg:g"); for ( Tracer::Splines::const_iterator it = output.splines.begin(), end = output.splines.end() ; it != end ; ++it ) { Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); { SPCSSAttr *css = sp_repr_css_attr_new(); { gchar b[64]; sp_svg_write_color(b, sizeof(b), SP_RGBA32_U_COMPOSE(unsigned(it->rgba[0]), unsigned(it->rgba[1]), unsigned(it->rgba[2]), unsigned(it->rgba[3]))); sp_repr_css_set_property(css, "fill", b); } { Inkscape::CSSOStringStream osalpha; osalpha << float(it->rgba[3]) / 255.; sp_repr_css_set_property(css, "fill-opacity", osalpha.str().c_str()); } sp_repr_css_set(repr, css, "style"); sp_repr_css_attr_unref(css); } gchar *str = sp_svg_write_path(it->pathVector); repr->setAttribute("d", str); g_free(str); group->appendChild(repr); Inkscape::GC::release(repr); } { group->setAttribute("transform", (std::string("translate(") + sp_svg_length_write_with_units(output.x) + ' ' + sp_svg_length_write_with_units(output.y) + ')').c_str()); } desktop->currentLayer()->appendChildRepr(group); Inkscape::GC::release(group); DocumentUndo::done(desktop->doc(), SP_VERB_SELECTION_PIXEL_ART, _("Trace pixel art")); // Flush pending updates desktop->doc()->ensureUpToDate(); } void PixelArtDialogImpl::setDefaults() { ignorePreview = true; curvesMultiplierSpinner.set_value(Tracer::Kopf2011::Options ::CURVES_MULTIPLIER); islandsWeightSpinner.set_value(Tracer::Kopf2011::Options::ISLANDS_WEIGHT); sparsePixelsRadiusSpinner.set_value(Tracer::Kopf2011::Options ::SPARSE_PIXELS_RADIUS); sparsePixelsMultiplierSpinner.set_value(Tracer::Kopf2011::Options ::SPARSE_PIXELS_MULTIPLIER); #if LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH optimizeRadioButton.set_active(); #else // LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH noOptimizeRadioButton.set_active(); #endif // LIBDEPIXELIZE_INKSCAPE_ENABLE_SMOOTH ignorePreview = false; if ( pendingPreview ) updatePreview(); } void PixelArtDialogImpl::updatePreview() { if ( ignorePreview ) { pendingPreview = true; return; } // TODO: update preview pendingPreview = false; } void PixelArtDialogImpl::workerThread() { for ( std::vector::iterator it = queue.begin(), end = queue.end() ; it != end && !g_atomic_int_get(&abortThread) ; ++it ) { processLibdepixelize(*it); } queue.clear(); dispatcher(); } void PixelArtDialogImpl::onWorkerThreadFinished() { thread->join(); thread = NULL; for ( std::vector::const_iterator it = output.begin(), end = output.end() ; it != end ; ++it ) { importOutput(*it); } output.clear(); mainCancelButton->set_sensitive(false); mainOkButton->set_sensitive(true); } /** * Factory method. Use this to create a new PixelArtDialog */ PixelArtDialog &PixelArtDialog::getInstance() { PixelArtDialog *dialog = new PixelArtDialogImpl(); return *dialog; } PixelArtDialogImpl::~PixelArtDialogImpl() { desktopChangeConn.disconnect(); } } //namespace Dialog } //namespace UI } //namespace Inkscape /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :