summaryrefslogtreecommitdiffstats
path: root/src/trace/trace.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/trace/trace.cpp')
-rw-r--r--src/trace/trace.cpp584
1 files changed, 584 insertions, 0 deletions
diff --git a/src/trace/trace.cpp b/src/trace/trace.cpp
new file mode 100644
index 0000000..802f497
--- /dev/null
+++ b/src/trace/trace.cpp
@@ -0,0 +1,584 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * A generic interface for plugging different
+ * autotracers into Inkscape.
+ *
+ * Authors:
+ * Bob Jamison <rjamison@earthlink.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2004-2006 Bob Jamison
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "trace/potrace/inkscape-potrace.h"
+
+#include <limits>
+
+#include <glibmm/i18n.h>
+#include <gtkmm/main.h>
+
+#include <2geom/transforms.h>
+
+#include "desktop.h"
+#include "document.h"
+#include "document-undo.h"
+#include "inkscape.h"
+#include "message-stack.h"
+#include "selection.h"
+
+#include "display/cairo-utils.h"
+#include "display/drawing.h"
+#include "display/drawing-shape.h"
+
+#include "object/sp-item.h"
+#include "object/sp-shape.h"
+#include "object/sp-image.h"
+
+#include "ui/icon-names.h"
+
+#include "xml/repr.h"
+#include "xml/attribute-record.h"
+
+#include "siox.h"
+
+namespace Inkscape {
+namespace Trace {
+
+SPImage *Tracer::getSelectedSPImage()
+{
+
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (!desktop)
+ {
+ g_warning("Trace: No active desktop");
+ return nullptr;
+ }
+
+ Inkscape::MessageStack *msgStack = desktop->getMessageStack();
+
+ Inkscape::Selection *sel = desktop->getSelection();
+ if (!sel)
+ {
+ char *msg = _("Select an <b>image</b> to trace");
+ msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
+ //g_warning(msg);
+ return nullptr;
+ }
+
+ if (sioxEnabled)
+ {
+ SPImage *img = nullptr;
+ auto list = sel->items();
+ std::vector<SPItem *> items;
+ sioxShapes.clear();
+
+ /*
+ First, things are selected top-to-bottom, so we need to invert
+ them as bottom-to-top so that we can discover the image and any
+ SPItems above it
+ */
+ for (auto i=list.begin() ; list.end()!=i ; ++i)
+ {
+ if (!SP_IS_ITEM(*i))
+ {
+ continue;
+ }
+ SPItem *item = *i;
+ items.insert(items.begin(), item);
+ }
+ std::vector<SPItem *>::iterator iter;
+ for (iter = items.begin() ; iter!= items.end() ; ++iter)
+ {
+ SPItem *item = *iter;
+ if (SP_IS_IMAGE(item))
+ {
+ if (img) //we want only one
+ {
+ char *msg = _("Select only one <b>image</b> to trace");
+ msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
+ return nullptr;
+ }
+ img = SP_IMAGE(item);
+ }
+ else // if (img) //# items -after- the image in tree (above it in Z)
+ {
+ if (SP_IS_SHAPE(item))
+ {
+ SPShape *shape = SP_SHAPE(item);
+ sioxShapes.push_back(shape);
+ }
+ }
+ }
+
+ if (!img || sioxShapes.size() < 1)
+ {
+ char *msg = _("Select one image and one or more shapes above it");
+ msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
+ return nullptr;
+ }
+ return img;
+ }
+ else
+ //### SIOX not enabled. We want exactly one image selected
+ {
+ SPItem *item = sel->singleItem();
+ if (!item)
+ {
+ char *msg = _("Select an <b>image</b> to trace"); //same as above
+ msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
+ //g_warning(msg);
+ return nullptr;
+ }
+
+ if (!SP_IS_IMAGE(item))
+ {
+ char *msg = _("Select an <b>image</b> to trace");
+ msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
+ //g_warning(msg);
+ return nullptr;
+ }
+
+ SPImage *img = SP_IMAGE(item);
+
+ return img;
+ }
+
+}
+
+
+
+typedef org::siox::SioxImage SioxImage;
+typedef org::siox::SioxObserver SioxObserver;
+typedef org::siox::Siox Siox;
+
+
+class TraceSioxObserver : public SioxObserver
+{
+public:
+
+ /**
+ *
+ */
+ TraceSioxObserver (void *contextArg) :
+ SioxObserver(contextArg)
+ {}
+
+ /**
+ *
+ */
+ ~TraceSioxObserver () override
+ = default;
+
+ /**
+ * Informs the observer how much has been completed.
+ * Return false if the processing should be aborted.
+ */
+ bool progress(float /*percentCompleted*/) override
+ {
+ //Tracer *tracer = (Tracer *)context;
+ //## Allow the GUI to update
+ Gtk::Main::iteration(false); //at least once, non-blocking
+ while( Gtk::Main::events_pending() )
+ Gtk::Main::iteration();
+ return true;
+ }
+
+ /**
+ * Send an error string to the Observer. Processing will
+ * be halted.
+ */
+ void error(const std::string &/*msg*/) override
+ {
+ //Tracer *tracer = (Tracer *)context;
+ }
+
+
+};
+
+
+
+
+
+Glib::RefPtr<Gdk::Pixbuf> Tracer::sioxProcessImage(SPImage *img, Glib::RefPtr<Gdk::Pixbuf>origPixbuf)
+{
+ if (!sioxEnabled)
+ return origPixbuf;
+
+ if (origPixbuf == lastOrigPixbuf)
+ return lastSioxPixbuf;
+
+ //g_message("siox: start");
+
+ SioxImage simage(origPixbuf->gobj());
+
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (!desktop)
+ {
+ g_warning("%s", _("Trace: No active desktop"));
+ return Glib::RefPtr<Gdk::Pixbuf>(nullptr);
+ }
+
+ Inkscape::MessageStack *msgStack = desktop->getMessageStack();
+
+ Inkscape::Selection *sel = desktop->getSelection();
+ if (!sel)
+ {
+ char *msg = _("Select an <b>image</b> to trace");
+ msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
+ //g_warning(msg);
+ return Glib::RefPtr<Gdk::Pixbuf>(nullptr);
+ }
+
+ Inkscape::DrawingItem *aImg = img->get_arenaitem(desktop->dkey);
+ //g_message("img: %d %d %d %d\n", aImg->bbox.x0, aImg->bbox.y0,
+ // aImg->bbox.x1, aImg->bbox.y1);
+
+ double width = aImg->geometricBounds()->width();
+ double height = aImg->geometricBounds()->height();
+
+ double iwidth = simage.getWidth();
+ double iheight = simage.getHeight();
+
+ double iwscale = width / iwidth;
+ double ihscale = height / iheight;
+
+ std::vector<Inkscape::DrawingItem *> arenaItems;
+ std::vector<SPShape *>::iterator iter;
+ for (iter = sioxShapes.begin() ; iter!=sioxShapes.end() ; ++iter)
+ {
+ SPItem *item = *iter;
+ Inkscape::DrawingItem *aItem = item->get_arenaitem(desktop->dkey);
+ arenaItems.push_back(aItem);
+ }
+
+ //g_message("%d arena items\n", arenaItems.size());
+
+ //g_message("siox: start selection");
+
+ for (int row=0 ; row<iheight ; row++)
+ {
+ double ypos = aImg->geometricBounds()->top() + ihscale * (double) row;
+ for (int col=0 ; col<simage.getWidth() ; col++)
+ {
+ //Get absolute X,Y position
+ double xpos = aImg->geometricBounds()->left() + iwscale * (double)col;
+ Geom::Point point(xpos, ypos);
+ point *= aImg->transform();
+ //point *= imgMat;
+ //point = desktop->doc2dt(point);
+ //g_message("x:%f y:%f\n", point[0], point[1]);
+ bool weHaveAHit = false;
+ std::vector<Inkscape::DrawingItem *>::iterator aIter;
+ for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; ++aIter)
+ {
+ Inkscape::DrawingItem *arenaItem = *aIter;
+ arenaItem->drawing().update();
+ if (arenaItem->pick(point, 1.0f, 1))
+ {
+ weHaveAHit = true;
+ break;
+ }
+ }
+
+ if (weHaveAHit)
+ {
+ //g_message("hit!\n");
+ simage.setConfidence(col, row,
+ Siox::UNKNOWN_REGION_CONFIDENCE);
+ }
+ else
+ {
+ //g_message("miss!\n");
+ simage.setConfidence(col, row,
+ Siox::CERTAIN_BACKGROUND_CONFIDENCE);
+ }
+ }
+ }
+
+ //g_message("siox: selection done");
+
+ //## ok we have our pixel buf
+ TraceSioxObserver observer(this);
+ Siox sengine(&observer);
+ SioxImage result = sengine.extractForeground(simage, 0xffffff);
+ if (!result.isValid())
+ {
+ g_warning("%s", _("Invalid SIOX result"));
+ return Glib::RefPtr<Gdk::Pixbuf>(nullptr);
+ }
+
+ //result.writePPM("siox2.ppm");
+
+ Glib::RefPtr<Gdk::Pixbuf> newPixbuf = Glib::wrap(result.getGdkPixbuf());
+
+ //g_message("siox: done");
+
+ lastSioxPixbuf = newPixbuf;
+
+ return newPixbuf;
+}
+
+
+Glib::RefPtr<Gdk::Pixbuf> Tracer::getSelectedImage()
+{
+
+
+ SPImage *img = getSelectedSPImage();
+ if (!img)
+ return Glib::RefPtr<Gdk::Pixbuf>(nullptr);
+
+ if (!img->pixbuf)
+ return Glib::RefPtr<Gdk::Pixbuf>(nullptr);
+
+ GdkPixbuf *raw_pb = img->pixbuf->getPixbufRaw(false);
+ GdkPixbuf *trace_pb = gdk_pixbuf_copy(raw_pb);
+ if (img->pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_CAIRO) {
+ convert_pixels_argb32_to_pixbuf(
+ gdk_pixbuf_get_pixels(trace_pb),
+ gdk_pixbuf_get_width(trace_pb),
+ gdk_pixbuf_get_height(trace_pb),
+ gdk_pixbuf_get_rowstride(trace_pb));
+ }
+
+ Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(trace_pb, false);
+
+ if (sioxEnabled)
+ {
+ Glib::RefPtr<Gdk::Pixbuf> sioxPixbuf =
+ sioxProcessImage(img, pixbuf);
+ if (!sioxPixbuf)
+ {
+ return pixbuf;
+ }
+ else
+ {
+ return sioxPixbuf;
+ }
+ }
+ else
+ {
+ return pixbuf;
+ }
+
+}
+
+
+
+//#########################################################################
+//# T R A C E
+//#########################################################################
+
+void Tracer::enableSiox(bool enable)
+{
+ sioxEnabled = enable;
+}
+
+
+void Tracer::traceThread()
+{
+ //## Remember. NEVER leave this method without setting
+ //## engine back to NULL
+
+ //## Prepare our kill flag. We will watch this later to
+ //## see if the main thread wants us to stop
+ keepGoing = true;
+
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (!desktop)
+ {
+ g_warning("Trace: No active desktop\n");
+ return;
+ }
+
+ Inkscape::MessageStack *msgStack = desktop->getMessageStack();
+
+ Inkscape::Selection *selection = desktop->getSelection();
+
+ if (!SP_ACTIVE_DOCUMENT)
+ {
+ char *msg = _("Trace: No active document");
+ msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
+ //g_warning(msg);
+ engine = nullptr;
+ return;
+ }
+ SPDocument *doc = SP_ACTIVE_DOCUMENT;
+ doc->ensureUpToDate();
+
+
+ SPImage *img = getSelectedSPImage();
+ if (!img)
+ {
+ engine = nullptr;
+ return;
+ }
+
+ GdkPixbuf *trace_pb = gdk_pixbuf_copy(img->pixbuf->getPixbufRaw(false));
+ if (img->pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_CAIRO) {
+ convert_pixels_argb32_to_pixbuf(
+ gdk_pixbuf_get_pixels(trace_pb),
+ gdk_pixbuf_get_width(trace_pb),
+ gdk_pixbuf_get_height(trace_pb),
+ gdk_pixbuf_get_rowstride(trace_pb));
+ }
+
+ Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(trace_pb, false);
+
+ pixbuf = sioxProcessImage(img, pixbuf);
+
+ if (!pixbuf)
+ {
+ char *msg = _("Trace: Image has no bitmap data");
+ msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
+ //g_warning(msg);
+ engine = nullptr;
+ return;
+ }
+
+ msgStack->flash(Inkscape::NORMAL_MESSAGE, _("Trace: Starting trace..."));
+ desktop->updateCanvasNow();
+
+ std::vector<TracingEngineResult> results =
+ engine->trace(pixbuf);
+ //printf("nrPaths:%d\n", results.size());
+ int nrPaths = results.size();
+
+ //### Check if we should stop
+ if (!keepGoing || nrPaths<1)
+ {
+ engine = nullptr;
+ return;
+ }
+
+ //### Get pointers to the <image> and its parent
+ //XML Tree being used directly here while it shouldn't be.
+ Inkscape::XML::Node *imgRepr = img->getRepr();
+ Inkscape::XML::Node *par = imgRepr->parent();
+
+ //### Get some information for the new transform()
+ double x = imgRepr->getAttributeDouble("x", 0.0);
+ double y = imgRepr->getAttributeDouble("y", 0.0);
+ double width = imgRepr->getAttributeDouble("width", 0.0);
+ double height = imgRepr->getAttributeDouble("height", 0.0);
+
+ double iwidth = (double)pixbuf->get_width();
+ double iheight = (double)pixbuf->get_height();
+
+ double iwscale = width / iwidth;
+ double ihscale = height / iheight;
+
+ Geom::Translate trans(x, y);
+ Geom::Scale scal(iwscale, ihscale);
+
+ //# Convolve scale, translation, and the original transform
+ Geom::Affine tf(scal * trans);
+ tf *= img->transform;
+
+
+ //#OK. Now let's start making new nodes
+
+ Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
+ Inkscape::XML::Node *groupRepr = nullptr;
+
+ //# if more than 1, make a <g>roup of <path>s
+ if (nrPaths > 1)
+ {
+ groupRepr = xml_doc->createElement("svg:g");
+ par->addChild(groupRepr, imgRepr);
+ }
+
+ long totalNodeCount = 0L;
+
+ for (auto result : results)
+ {
+ totalNodeCount += result.getNodeCount();
+
+ Inkscape::XML::Node *pathRepr = xml_doc->createElement("svg:path");
+ pathRepr->setAttributeOrRemoveIfEmpty("style", result.getStyle());
+ pathRepr->setAttributeOrRemoveIfEmpty("d", result.getPathData());
+
+ if (nrPaths > 1)
+ groupRepr->addChild(pathRepr, nullptr);
+ else
+ par->addChild(pathRepr, imgRepr);
+
+ //### Apply the transform from the image to the new shape
+ SPObject *reprobj = doc->getObjectByRepr(pathRepr);
+ if (reprobj)
+ {
+ SPItem *newItem = SP_ITEM(reprobj);
+ newItem->doWriteTransform(tf);
+ }
+ if (nrPaths == 1)
+ {
+ selection->clear();
+ selection->add(pathRepr);
+ }
+ Inkscape::GC::release(pathRepr);
+ }
+
+ // If we have a group, then focus on, then forget it
+ if (nrPaths > 1)
+ {
+ selection->clear();
+ selection->add(groupRepr);
+ Inkscape::GC::release(groupRepr);
+ }
+
+ //## inform the document, so we can undo
+ DocumentUndo::done(doc, _("Trace bitmap"), INKSCAPE_ICON("bitmap-trace"));
+
+ engine = nullptr;
+
+ char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
+ msgStack->flash(Inkscape::NORMAL_MESSAGE, msg);
+ g_free(msg);
+
+}
+
+
+
+
+
+void Tracer::trace(TracingEngine *theEngine)
+{
+ //Check if we are already running
+ if (engine)
+ return;
+
+ engine = theEngine;
+
+ traceThread();
+}
+
+
+
+
+
+void Tracer::abort()
+{
+
+ //## Inform Trace's working thread
+ keepGoing = false;
+
+ if (engine)
+ {
+ engine->abort();
+ }
+
+}
+
+
+
+} // namespace Trace
+
+} // namespace Inkscape
+
+
+//#########################################################################
+//# E N D O F F I L E
+//#########################################################################
+