summaryrefslogtreecommitdiffstats
path: root/src/trace/potrace/inkscape-potrace.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/trace/potrace/inkscape-potrace.cpp')
-rw-r--r--src/trace/potrace/inkscape-potrace.cpp679
1 files changed, 679 insertions, 0 deletions
diff --git a/src/trace/potrace/inkscape-potrace.cpp b/src/trace/potrace/inkscape-potrace.cpp
new file mode 100644
index 0000000..d6e9b8f
--- /dev/null
+++ b/src/trace/potrace/inkscape-potrace.cpp
@@ -0,0 +1,679 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This is the C++ glue between Inkscape and Potrace
+ *
+ * Authors:
+ * Bob Jamison <rjamison@titan.com>
+ * Stéphane Gimenez <dev@gim.name>
+ *
+ * Copyright (C) 2004-2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ * Potrace, the wonderful tracer located at http://potrace.sourceforge.net,
+ * is provided by the generosity of Peter Selinger, to whom we are grateful.
+ *
+ */
+
+#include "inkscape-potrace.h"
+
+#include <glibmm/i18n.h>
+#include <gtkmm/main.h>
+#include <iomanip>
+
+#include "trace/filterset.h"
+#include "trace/quantize.h"
+#include "trace/imagemap-gdk.h"
+
+#include <inkscape.h>
+#include "desktop.h"
+#include "message-stack.h"
+
+#include "object/sp-path.h"
+
+#include <svg/path-string.h>
+#include "bitmap.h"
+
+using Glib::ustring;
+
+static void updateGui()
+{
+ //## Allow the GUI to update
+ Gtk::Main::iteration(false); //at least once, non-blocking
+ while( Gtk::Main::events_pending() )
+ Gtk::Main::iteration();
+
+}
+
+
+static void potraceStatusCallback(double /*progress*/, void *userData) /* callback fn */
+{
+ updateGui();
+
+ if (!userData)
+ return;
+
+ //g_message("progress: %f\n", progress);
+
+ //Inkscape::Trace::Potrace::PotraceTracingEngine *engine =
+ // (Inkscape::Trace::Potrace::PotraceTracingEngine *)userData;
+}
+
+
+namespace {
+ustring twohex( int value )
+{
+ return ustring::format(std::hex, std::setfill(L'0'), std::setw(2), value);
+}
+} // namespace
+
+
+//required by potrace
+namespace Inkscape {
+
+namespace Trace {
+
+namespace Potrace {
+
+
+/**
+ *
+ */
+PotraceTracingEngine::PotraceTracingEngine() :
+ keepGoing(1),
+ traceType(TRACE_BRIGHTNESS),
+ invert(false),
+ quantizationNrColors(8),
+ brightnessThreshold(0.45),
+ brightnessFloor(0),
+ cannyHighThreshold(0.65),
+ multiScanNrColors(8),
+ multiScanStack(true),
+ multiScanSmooth(false),
+ multiScanRemoveBackground(false)
+{
+ /* get default parameters */
+ potraceParams = potrace_param_default();
+ potraceParams->progress.callback = potraceStatusCallback;
+ potraceParams->progress.data = (void *)this;
+}
+
+PotraceTracingEngine::PotraceTracingEngine(TraceType traceType, bool invert, int quantizationNrColors, double brightnessThreshold, double brightnessFloor, double cannyHighThreshold, int multiScanNrColors, bool multiScanStack, bool multiScanSmooth, bool multiScanRemoveBackground) :
+ keepGoing(1), traceType(traceType), invert(invert), quantizationNrColors(quantizationNrColors), brightnessThreshold(brightnessThreshold), brightnessFloor(brightnessFloor), cannyHighThreshold(cannyHighThreshold), multiScanNrColors(multiScanNrColors) , multiScanStack(multiScanStack), multiScanSmooth(multiScanSmooth), multiScanRemoveBackground(multiScanRemoveBackground)
+{
+ potraceParams = potrace_param_default();
+ potraceParams->progress.callback = potraceStatusCallback;
+ potraceParams->progress.data = (void *)this;
+}
+
+
+PotraceTracingEngine::~PotraceTracingEngine()
+{
+ potrace_param_free(potraceParams);
+}
+
+
+
+
+struct Point
+{
+ double x;
+ double y;
+};
+
+
+/**
+ * Check a point against a list of points to see if it
+ * has already occurred.
+ */
+static bool hasPoint(std::vector<Point> &points, double x, double y)
+{
+ for (auto p : points)
+ {
+ if (p.x == x && p.y == y)
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Recursively descend the potrace_path_t node tree, writing paths in SVG
+ * format into the output stream. The Point vector is used to prevent
+ * redundant paths. Returns number of paths processed.
+ */
+static long writePaths(PotraceTracingEngine *engine, potrace_path_t *plist,
+ Inkscape::SVG::PathString& data, std::vector<Point> &points)
+{
+ long nodeCount = 0L;
+
+ potrace_path_t *node;
+ for (node=plist; node ; node=node->sibling)
+ {
+ potrace_curve_t *curve = &(node->curve);
+ //g_message("node->fm:%d\n", node->fm);
+ if (!curve->n)
+ continue;
+ const potrace_dpoint_t *pt = curve->c[curve->n - 1];
+ double x0 = 0.0;
+ double y0 = 0.0;
+ double x1 = 0.0;
+ double y1 = 0.0;
+ double x2 = pt[2].x;
+ double y2 = pt[2].y;
+ //Have we been here already?
+ if (hasPoint(points, x2, y2))
+ {
+ //g_message("duplicate point: (%f,%f)\n", x2, y2);
+ continue;
+ }
+ else
+ {
+ Point p;
+ p.x = x2; p.y = y2;
+ points.push_back(p);
+ }
+ data.moveTo(x2, y2);
+ nodeCount++;
+
+ for (int i=0 ; i<curve->n ; i++)
+ {
+ if (!engine->keepGoing)
+ return 0L;
+ pt = curve->c[i];
+ x0 = pt[0].x;
+ y0 = pt[0].y;
+ x1 = pt[1].x;
+ y1 = pt[1].y;
+ x2 = pt[2].x;
+ y2 = pt[2].y;
+ switch (curve->tag[i])
+ {
+ case POTRACE_CORNER:
+ data.lineTo(x1, y1).lineTo(x2, y2);
+ break;
+ case POTRACE_CURVETO:
+ data.curveTo(x0, y0, x1, y1, x2, y2);
+ break;
+ default:
+ break;
+ }
+ nodeCount++;
+ }
+ data.closePath();
+
+ for (potrace_path_t *child=node->childlist; child ; child=child->sibling)
+ {
+ nodeCount += writePaths(engine, child, data, points);
+ }
+ }
+
+ return nodeCount;
+
+}
+
+
+static GrayMap *filter(PotraceTracingEngine &engine, GdkPixbuf * pixbuf)
+{
+ if (!pixbuf)
+ return nullptr;
+
+ GrayMap *newGm = nullptr;
+
+ /*### Color quantization -- banding ###*/
+ if (engine.traceType == TRACE_QUANT)
+ {
+ RgbMap *rgbmap = gdkPixbufToRgbMap(pixbuf);
+ if (!rgbmap)
+ return nullptr;
+ //rgbMap->writePPM(rgbMap, "rgb.ppm");
+ newGm = quantizeBand(rgbmap,
+ engine.quantizationNrColors);
+ rgbmap->destroy(rgbmap);
+ //return newGm;
+ }
+
+ /*### Brightness threshold ###*/
+ else if ( engine.traceType == TRACE_BRIGHTNESS ||
+ engine.traceType == TRACE_BRIGHTNESS_MULTI )
+ {
+ GrayMap *gm = gdkPixbufToGrayMap(pixbuf);
+ if (!gm)
+ return nullptr;
+
+ newGm = GrayMapCreate(gm->width, gm->height);
+ if (!newGm)
+ {
+ gm->destroy(gm);
+ return nullptr;
+ }
+ double floor = 3.0 *
+ ( engine.brightnessFloor * 256.0 );
+ double cutoff = 3.0 *
+ ( engine.brightnessThreshold * 256.0 );
+ for (int y=0 ; y<gm->height ; y++)
+ {
+ for (int x=0 ; x<gm->width ; x++)
+ {
+ double brightness = (double)gm->getPixel(gm, x, y);
+ if (brightness >= floor && brightness < cutoff)
+ newGm->setPixel(newGm, x, y, GRAYMAP_BLACK); //black pixel
+ else
+ newGm->setPixel(newGm, x, y, GRAYMAP_WHITE); //white pixel
+ }
+ }
+
+ gm->destroy(gm);
+ //newGm->writePPM(newGm, "brightness.ppm");
+ //return newGm;
+ }
+
+ /*### Canny edge detection ###*/
+ else if (engine.traceType == TRACE_CANNY)
+ {
+ GrayMap *gm = gdkPixbufToGrayMap(pixbuf);
+ if (!gm)
+ return nullptr;
+ newGm = grayMapCanny(gm, 0.1, engine.cannyHighThreshold);
+ gm->destroy(gm);
+ //newGm->writePPM(newGm, "canny.ppm");
+ //return newGm;
+ }
+
+ /*### Do I invert the image? ###*/
+ if (newGm && engine.invert)
+ {
+ for (int y=0 ; y<newGm->height ; y++)
+ {
+ for (int x=0 ; x<newGm->width ; x++)
+ {
+ unsigned long brightness = newGm->getPixel(newGm, x, y);
+ brightness = 765 - brightness;
+ newGm->setPixel(newGm, x, y, brightness);
+ }
+ }
+ }
+
+ return newGm;//none of the above
+}
+
+
+static IndexedMap *filterIndexed(PotraceTracingEngine &engine, GdkPixbuf * pixbuf)
+{
+ if (!pixbuf)
+ return nullptr;
+
+ IndexedMap *newGm = nullptr;
+
+ RgbMap *gm = gdkPixbufToRgbMap(pixbuf);
+ if (!gm)
+ return nullptr;
+ if (engine.multiScanSmooth)
+ {
+ RgbMap *gaussMap = rgbMapGaussian(gm);
+ newGm = rgbMapQuantize(gaussMap, engine.multiScanNrColors);
+ gaussMap->destroy(gaussMap);
+ }
+ else
+ {
+ newGm = rgbMapQuantize(gm, engine.multiScanNrColors);
+ }
+ gm->destroy(gm);
+
+ if (newGm && (engine.traceType == TRACE_QUANT_MONO || engine.traceType == TRACE_BRIGHTNESS_MULTI))
+ {
+ //Turn to grays
+ for (int i=0 ; i<newGm->nrColors ; i++)
+ {
+ RGB rgb = newGm->clut[i];
+ int grayVal = (rgb.r + rgb.g + rgb.b) / 3;
+ rgb.r = rgb.g = rgb.b = grayVal;
+ newGm->clut[i] = rgb;
+ }
+ }
+
+ return newGm;
+}
+
+
+
+
+Glib::RefPtr<Gdk::Pixbuf>
+PotraceTracingEngine::preview(Glib::RefPtr<Gdk::Pixbuf> thePixbuf)
+{
+ GdkPixbuf *pixbuf = thePixbuf->gobj();
+
+ if ( traceType == TRACE_QUANT_COLOR ||
+ traceType == TRACE_QUANT_MONO ||
+ traceType == TRACE_BRIGHTNESS_MULTI) /* this is a lie: multipass doesn't use filterIndexed, but it's a better preview approx than filter() */
+ {
+ IndexedMap *gm = filterIndexed(*this, pixbuf);
+ if (!gm)
+ return Glib::RefPtr<Gdk::Pixbuf>(nullptr);
+
+ Glib::RefPtr<Gdk::Pixbuf> newBuf =
+ Glib::wrap(indexedMapToGdkPixbuf(gm), false);
+
+ gm->destroy(gm);
+
+ return newBuf;
+ }
+ else
+ {
+ GrayMap *gm = filter(*this, pixbuf);
+ if (!gm)
+ return Glib::RefPtr<Gdk::Pixbuf>(nullptr);
+
+ Glib::RefPtr<Gdk::Pixbuf> newBuf =
+ Glib::wrap(grayMapToGdkPixbuf(gm), false);
+
+ gm->destroy(gm);
+
+ return newBuf;
+ }
+}
+
+
+//*This is the core inkscape-to-potrace binding
+std::string PotraceTracingEngine::grayMapToPath(GrayMap *grayMap, long *nodeCount)
+{
+ if (!keepGoing)
+ {
+ g_warning("aborted");
+ return "";
+ }
+
+ potrace_bitmap_t *potraceBitmap = bm_new(grayMap->width, grayMap->height);
+ if (!potraceBitmap)
+ {
+ return "";
+ }
+ bm_clear(potraceBitmap, 0);
+
+ //##Read the data out of the GrayMap
+ for (int y=0 ; y<grayMap->height ; y++)
+ {
+ for (int x=0 ; x<grayMap->width ; x++)
+ {
+ BM_UPUT(potraceBitmap, x, y,
+ grayMap->getPixel(grayMap, x, y) ? 0 : 1);
+ }
+ }
+
+ //##Debug
+ /*
+ FILE *f = fopen("poimage.pbm", "wb");
+ bm_writepbm(f, bm);
+ fclose(f);
+ */
+
+ /* trace a bitmap*/
+ potrace_state_t *potraceState = potrace_trace(potraceParams,
+ potraceBitmap);
+
+ //## Free the Potrace bitmap
+ bm_free(potraceBitmap);
+
+ if (!keepGoing)
+ {
+ g_warning("aborted");
+ potrace_state_free(potraceState);
+ return "";
+ }
+
+ Inkscape::SVG::PathString data;
+
+ //## copy the path information into our d="" attribute string
+ std::vector<Point> points;
+ long thisNodeCount = writePaths(this, potraceState->plist, data, points);
+
+ /* free a potrace items */
+ potrace_state_free(potraceState);
+
+ if (!keepGoing)
+ return "";
+
+ if ( nodeCount)
+ *nodeCount = thisNodeCount;
+
+ return data.string();
+}
+
+
+
+/**
+ * This is called for a single scan
+ */
+std::vector<TracingEngineResult> PotraceTracingEngine::traceSingle(GdkPixbuf * thePixbuf)
+{
+
+ std::vector<TracingEngineResult> results;
+
+ if (!thePixbuf)
+ return results;
+
+ brightnessFloor = 0.0; //important to set this
+
+ GrayMap *grayMap = filter(*this, thePixbuf);
+ if (!grayMap)
+ return results;
+
+ long nodeCount = 0L;
+ std::string d = grayMapToPath(grayMap, &nodeCount);
+
+ grayMap->destroy(grayMap);
+
+ char const *style = "fill:#000000";
+
+ //g_message("### GOT '%s' \n", d);
+ TracingEngineResult result(style, d, nodeCount);
+ results.push_back(result);
+
+ return results;
+}
+
+
+/**
+ * This allows routines that already generate GrayMaps to skip image filtering,
+ * increasing performance.
+ */
+std::vector<TracingEngineResult> PotraceTracingEngine::traceGrayMap(GrayMap *grayMap)
+{
+
+ std::vector<TracingEngineResult> results;
+
+ brightnessFloor = 0.0; //important to set this
+
+ long nodeCount = 0L;
+ std::string d = grayMapToPath(grayMap, &nodeCount);
+
+ char const *style = "fill:#000000";
+
+ //g_message("### GOT '%s' \n", d);
+ TracingEngineResult result(style, d, nodeCount);
+ results.push_back(result);
+
+ return results;
+}
+
+/**
+ * Called for multiple-scanning algorithms
+ */
+std::vector<TracingEngineResult> PotraceTracingEngine::traceBrightnessMulti(GdkPixbuf * thePixbuf)
+{
+ std::vector<TracingEngineResult> results;
+
+ if ( thePixbuf ) {
+ double low = 0.2; //bottom of range
+ double high = 0.9; //top of range
+ double delta = (high - low ) / ((double)multiScanNrColors);
+
+ brightnessFloor = 0.0; //Set bottom to black
+
+ int traceCount = 0;
+
+ for ( brightnessThreshold = low ;
+ brightnessThreshold <= high ;
+ brightnessThreshold += delta) {
+ GrayMap *grayMap = filter(*this, thePixbuf);
+ if ( grayMap ) {
+ long nodeCount = 0L;
+ std::string d = grayMapToPath(grayMap, &nodeCount);
+
+ grayMap->destroy(grayMap);
+
+ if ( !d.empty() ) {
+ //### get style info
+ int grayVal = (int)(256.0 * brightnessThreshold);
+ ustring style = ustring::compose("fill-opacity:1.0;fill:#%1%2%3", twohex(grayVal), twohex(grayVal), twohex(grayVal) );
+
+ //g_message("### GOT '%s' \n", style.c_str());
+ TracingEngineResult result(style.raw(), d, nodeCount);
+ results.push_back(result);
+
+ if (!multiScanStack) {
+ brightnessFloor = brightnessThreshold;
+ }
+
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (desktop) {
+ ustring msg = ustring::compose(_("Trace: %1. %2 nodes"), traceCount++, nodeCount);
+ desktop->getMessageStack()->flash(Inkscape::NORMAL_MESSAGE, msg);
+ }
+ }
+ }
+ }
+
+ //# Remove the bottom-most scan, if requested
+ if (results.size() > 1 && multiScanRemoveBackground) {
+ results.erase(results.end() - 1);
+ }
+ }
+
+ return results;
+}
+
+
+/**
+ * Quantization
+ */
+std::vector<TracingEngineResult> PotraceTracingEngine::traceQuant(GdkPixbuf * thePixbuf)
+{
+ std::vector<TracingEngineResult> results;
+
+ if (thePixbuf) {
+ IndexedMap *iMap = filterIndexed(*this, thePixbuf);
+ if ( iMap ) {
+ //Create and clear a gray map
+ GrayMap *gm = GrayMapCreate(iMap->width, iMap->height);
+ for (int row=0 ; row<gm->height ; row++) {
+ for (int col=0 ; col<gm->width ; col++) {
+ gm->setPixel(gm, col, row, GRAYMAP_WHITE);
+ }
+ }
+
+ for (int colorIndex=0 ; colorIndex<iMap->nrColors ; colorIndex++) {
+ // Make a gray map for each color index
+ for (int row=0 ; row<iMap->height ; row++) {
+ for (int col=0 ; col<iMap->width ; col++) {
+ int indx = (int) iMap->getPixel(iMap, col, row);
+ if (indx == colorIndex) {
+ gm->setPixel(gm, col, row, GRAYMAP_BLACK); //black
+ } else if (!multiScanStack) {
+ gm->setPixel(gm, col, row, GRAYMAP_WHITE); //white
+ }
+ }
+ }
+
+ //## Now we have a traceable graymap
+ long nodeCount = 0L;
+ std::string d = grayMapToPath(gm, &nodeCount);
+
+ if ( !d.empty() ) {
+ //### get style info
+ RGB rgb = iMap->clut[colorIndex];
+ ustring style = ustring::compose("fill:#%1%2%3", twohex(rgb.r), twohex(rgb.g), twohex(rgb.b) );
+
+ //g_message("### GOT '%s' \n", style.c_str());
+ TracingEngineResult result(style.raw(), d, nodeCount);
+ results.push_back(result);
+
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (desktop) {
+ ustring msg = ustring::compose(_("Trace: %1. %2 nodes"), colorIndex, nodeCount);
+ desktop->getMessageStack()->flash(Inkscape::NORMAL_MESSAGE, msg);
+ }
+ }
+ }// for colorIndex
+
+ gm->destroy(gm);
+ iMap->destroy(iMap);
+ }
+
+ //# Remove the bottom-most scan, if requested
+ if (results.size() > 1 && multiScanRemoveBackground) {
+ results.erase(results.end() - 1);
+ }
+ }
+
+ return results;
+}
+
+
+/**
+ * This is the working method of this interface, and all
+ * implementing classes. Take a GdkPixbuf, trace it, and
+ * return the path data that is compatible with the d="" attribute
+ * of an SVG <path> element.
+ */
+std::vector<TracingEngineResult>
+PotraceTracingEngine::trace(Glib::RefPtr<Gdk::Pixbuf> pixbuf)
+{
+
+ GdkPixbuf *thePixbuf = pixbuf->gobj();
+
+ //Set up for messages
+ keepGoing = 1;
+
+ if ( traceType == TRACE_QUANT_COLOR ||
+ traceType == TRACE_QUANT_MONO )
+ {
+ return traceQuant(thePixbuf);
+ }
+ else if ( traceType == TRACE_BRIGHTNESS_MULTI )
+ {
+ return traceBrightnessMulti(thePixbuf);
+ }
+ else
+ {
+ return traceSingle(thePixbuf);
+ }
+}
+
+
+/**
+ * Abort the thread that is executing getPathDataFromPixbuf()
+ */
+void PotraceTracingEngine::abort()
+{
+ //g_message("PotraceTracingEngine::abort()\n");
+ keepGoing = 0;
+}
+
+
+
+
+} // namespace Potrace
+} // namespace Trace
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :