From 35a96bde514a8897f6f0fcc41c5833bf63df2e2a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 27 Apr 2024 18:29:01 +0200 Subject: Adding upstream version 1.0.2. Signed-off-by: Daniel Baumann --- src/trace/trace.cpp | 613 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 613 insertions(+) create mode 100644 src/trace/trace.cpp (limited to 'src/trace/trace.cpp') diff --git a/src/trace/trace.cpp b/src/trace/trace.cpp new file mode 100644 index 0000000..4ffff86 --- /dev/null +++ b/src/trace/trace.cpp @@ -0,0 +1,613 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * A generic interface for plugging different + * autotracers into Inkscape. + * + * Authors: + * Bob Jamison + * Jon A. Cruz + * 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 "inkscape.h" +#include "desktop.h" + +#include "document.h" +#include "document-undo.h" +#include "message-stack.h" +#include +#include +#include "selection.h" +#include "xml/repr.h" +#include "xml/attribute-record.h" +#include <2geom/transforms.h> +#include "verbs.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 "siox.h" +#include "imagemap-gdk.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 image to trace"); + msgStack->flash(Inkscape::ERROR_MESSAGE, msg); + //g_warning(msg); + return nullptr; + } + + if (sioxEnabled) + { + SPImage *img = nullptr; + auto list = sel->items(); + std::vector 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::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 image 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 image 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 image 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 Tracer::sioxProcessImage(SPImage *img, Glib::RefPtrorigPixbuf) +{ + if (!sioxEnabled) + return origPixbuf; + + if (origPixbuf == lastOrigPixbuf) + return lastSioxPixbuf; + + //g_message("siox: start"); + + //Convert from gdk, so a format we know. By design, the pixel + //format in PackedPixelMap is identical to what is needed by SIOX + SioxImage simage(origPixbuf->gobj()); + + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (!desktop) + { + g_warning("%s", _("Trace: No active desktop")); + return Glib::RefPtr(nullptr); + } + + Inkscape::MessageStack *msgStack = desktop->getMessageStack(); + + Inkscape::Selection *sel = desktop->getSelection(); + if (!sel) + { + char *msg = _("Select an image to trace"); + msgStack->flash(Inkscape::ERROR_MESSAGE, msg); + //g_warning(msg); + return Glib::RefPtr(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 arenaItems; + std::vector::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()); + + //PackedPixelMap *dumpMap = PackedPixelMapCreate( + // simage.getWidth(), simage.getHeight()); + + //g_message("siox: start selection"); + + for (int row=0 ; rowgeometricBounds()->top() + ihscale * (double) row; + for (int col=0 ; colgeometricBounds()->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::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"); + //dumpMap->setPixelLong(dumpMap, col, row, 0L); + simage.setConfidence(col, row, + Siox::UNKNOWN_REGION_CONFIDENCE); + } + else + { + //g_message("miss!\n"); + //dumpMap->setPixelLong(dumpMap, col, row, + // simage.getPixel(col, row)); + simage.setConfidence(col, row, + Siox::CERTAIN_BACKGROUND_CONFIDENCE); + } + } + } + + //g_message("siox: selection done"); + + //dumpMap->writePPM(dumpMap, "siox1.ppm"); + //dumpMap->destroy(dumpMap); + + //## 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(nullptr); + } + + //result.writePPM("siox2.ppm"); + + Glib::RefPtr newPixbuf = Glib::wrap(result.getGdkPixbuf()); + + //g_message("siox: done"); + + lastSioxPixbuf = newPixbuf; + + return newPixbuf; +} + + +Glib::RefPtr Tracer::getSelectedImage() +{ + + + SPImage *img = getSelectedSPImage(); + if (!img) + return Glib::RefPtr(nullptr); + + if (!img->pixbuf) + return Glib::RefPtr(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 pixbuf = Glib::wrap(trace_pb, false); + + if (sioxEnabled) + { + Glib::RefPtr 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 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 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 and its parent + //XML Tree being used directly here while it shouldn't be. + Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->getRepr(); + Inkscape::XML::Node *par = imgRepr->parent(); + + //### Get some information for the new transform() + double x = 0.0; + double y = 0.0; + double width = 0.0; + double height = 0.0; + double dval = 0.0; + + if (sp_repr_get_double(imgRepr, "x", &dval)) + x = dval; + if (sp_repr_get_double(imgRepr, "y", &dval)) + y = dval; + + if (sp_repr_get_double(imgRepr, "width", &dval)) + width = dval; + if (sp_repr_get_double(imgRepr, "height", &dval)) + height = dval; + + 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 roup of 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, SP_VERB_SELECTION_TRACE, _("Trace bitmap")); + + 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; + +#if HAVE_THREADS + //Ensure that thread support is running + if (!Glib::thread_supported()) + Glib::thread_init(); + + //Create our thread and run it + Glib::Thread::create( + sigc::mem_fun(*this, &Tracer::traceThread), false); +#else + traceThread(); +#endif + +} + + + + + +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 +//######################################################################### + -- cgit v1.2.3