diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
commit | cca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch) | |
tree | 146f39ded1c938019e1ed42d30923c2ac9e86789 /src/trace | |
parent | Initial commit. (diff) | |
download | inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.tar.xz inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.zip |
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/trace')
-rw-r--r-- | src/trace/CMakeLists.txt | 31 | ||||
-rw-r--r-- | src/trace/README | 18 | ||||
-rw-r--r-- | src/trace/autotrace/inkscape-autotrace.cpp | 238 | ||||
-rw-r--r-- | src/trace/autotrace/inkscape-autotrace.h | 101 | ||||
-rw-r--r-- | src/trace/depixelize/inkscape-depixelize.cpp | 140 | ||||
-rw-r--r-- | src/trace/depixelize/inkscape-depixelize.h | 100 | ||||
-rw-r--r-- | src/trace/filterset.cpp | 428 | ||||
-rw-r--r-- | src/trace/filterset.h | 57 | ||||
-rw-r--r-- | src/trace/imagemap-gdk.cpp | 185 | ||||
-rw-r--r-- | src/trace/imagemap-gdk.h | 50 | ||||
-rw-r--r-- | src/trace/imagemap.cpp | 353 | ||||
-rw-r--r-- | src/trace/imagemap.h | 302 | ||||
-rw-r--r-- | src/trace/pool.h | 119 | ||||
-rw-r--r-- | src/trace/potrace/bitmap.h | 118 | ||||
-rw-r--r-- | src/trace/potrace/inkscape-potrace.cpp | 679 | ||||
-rw-r--r-- | src/trace/potrace/inkscape-potrace.h | 145 | ||||
-rw-r--r-- | src/trace/quantize.cpp | 599 | ||||
-rw-r--r-- | src/trace/quantize.h | 23 | ||||
-rw-r--r-- | src/trace/siox.cpp | 1736 | ||||
-rw-r--r-- | src/trace/siox.h | 654 | ||||
-rw-r--r-- | src/trace/trace.cpp | 584 | ||||
-rw-r--r-- | src/trace/trace.h | 258 |
22 files changed, 6918 insertions, 0 deletions
diff --git a/src/trace/CMakeLists.txt b/src/trace/CMakeLists.txt new file mode 100644 index 0000000..45b3125 --- /dev/null +++ b/src/trace/CMakeLists.txt @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +set(trace_SRC + filterset.cpp + imagemap.cpp + imagemap-gdk.cpp + quantize.cpp + siox.cpp + trace.cpp + + potrace/inkscape-potrace.cpp + autotrace/inkscape-autotrace.cpp + depixelize/inkscape-depixelize.cpp + + # ------- + # Headers + filterset.h + imagemap-gdk.h + imagemap.h + pool.h + quantize.h + siox.h + trace.h + + potrace/bitmap.h + potrace/inkscape-potrace.h + autotrace/inkscape-autotrace.h + depixelize/inkscape-depixelize.h +) + +add_inkscape_source("${trace_SRC}") + diff --git a/src/trace/README b/src/trace/README new file mode 100644 index 0000000..f436aa0 --- /dev/null +++ b/src/trace/README @@ -0,0 +1,18 @@ + +This directory contains code for converting bitmap images to vector images. +The subdirectories contain code for the three tracers used in Inkscape: potrace +(external dependency), autotrace, and libdepixelize (currently in src/3rdparty). + + +To do: + +* Think about conceptually changing how the tracing works: Ideally, it should + be three steps clearly separated: + 1/ Preprocessing (color reduction, blurring, background removing, image + inversion...) + 2/ Tracing the preprocessed image, using some tracing engine + 3/ Post-processing (suppressing speckles, optimizing paths...) + + The main problem is that the tracing engine sometimes *also* does 1 and 3, so + there is some discussion to have whether this can be done. + diff --git a/src/trace/autotrace/inkscape-autotrace.cpp b/src/trace/autotrace/inkscape-autotrace.cpp new file mode 100644 index 0000000..d147890 --- /dev/null +++ b/src/trace/autotrace/inkscape-autotrace.cpp @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * This is the C++ glue between Inkscape and Autotrace + *//* + * + * Authors: + * Marc Jeanmougin + * + * Copyright (C) 2018 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + * + */ + +#include "inkscape-autotrace.h" + +extern "C" { +#include "3rdparty/autotrace/autotrace.h" +#include "3rdparty/autotrace/output.h" +#include "3rdparty/autotrace/spline.h" +} + +#include <glibmm/i18n.h> +#include <gtkmm/main.h> +#include <iomanip> + +#include "trace/filterset.h" +#include "trace/imagemap-gdk.h" +#include "trace/quantize.h" + +#include "desktop.h" +#include "message-stack.h" +#include <inkscape.h> + +#include "object/sp-path.h" + +#include <svg/path-string.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(); +// } + +namespace Inkscape { + +namespace Trace { + +namespace Autotrace { + +static guchar* to_3channels(GdkPixbuf* input) { + if (!input) { + return nullptr; + } + int imgsize = gdk_pixbuf_get_height(input) * gdk_pixbuf_get_width(input); + guchar *out = (guchar*)malloc(3 * imgsize); + if (!out) { + g_warning("Autotrace::to_3channels: can not allocate memory for %d pixel image.", imgsize); + return nullptr; + } + int x=0; + guchar* pix = gdk_pixbuf_get_pixels (input); + int rs = gdk_pixbuf_get_rowstride (input); + for(int row=0;row<gdk_pixbuf_get_height(input);row++) { + for (int col=0;col<gdk_pixbuf_get_width(input);col++) { + guchar alpha = *(pix + row * rs + col * 4 + 3); + guchar white = 255 - alpha; + for(int chan=0;chan<3;chan++) { + // guchar *pnew = (pix + row * rs + col * 3 + chan); + guchar *pold = (pix + row * rs + col * 4 + chan); + out[x++] = (guchar)(((int)(*pold) * (int)alpha / 256) + white); + } + } + } + return out; +} + + +/** + * + */ +AutotraceTracingEngine::AutotraceTracingEngine() + : keepGoing(1) + , traceType(TRACE_OUTLINE) + , invert(false) +{ + /* get default parameters */ + opts = at_fitting_opts_new(); + opts->background_color = at_color_new(255,255,255); + autotrace_init(); +} + +AutotraceTracingEngine::~AutotraceTracingEngine() { at_fitting_opts_free(opts); } + + + +// TODO +Glib::RefPtr<Gdk::Pixbuf> AutotraceTracingEngine::preview(Glib::RefPtr<Gdk::Pixbuf> thePixbuf) { + //auto x = thePixbuf.copy(); + guchar *pb = to_3channels(thePixbuf->gobj()); + if (!pb) { + return Glib::RefPtr<Gdk::Pixbuf>(); + } + return Gdk::Pixbuf::create_from_data(pb, thePixbuf->get_colorspace(), false, 8, thePixbuf->get_width(), + thePixbuf->get_height(), (thePixbuf->get_width() * 3), + [](const guint8 *pb) { free(const_cast<guint8 *>(pb)); }); +} + +int test_cancel (void* keepGoing){return !(* ((int*)keepGoing));} + +/** + * 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> AutotraceTracingEngine::trace(Glib::RefPtr<Gdk::Pixbuf> pixbuf) +{ + GdkPixbuf *pb1 = pixbuf->gobj(); + guchar *pb = to_3channels(pb1); + if (!pb) { + return std::vector<TracingEngineResult>(); + } + + at_bitmap *bitmap = +// at_bitmap_new(gdk_pixbuf_get_width(pb), gdk_pixbuf_get_height(pb), gdk_pixbuf_get_n_channels(pb)); +// bitmap->bitmap = gdk_pixbuf_get_pixels(pb); + at_bitmap_new(gdk_pixbuf_get_width(pb1), gdk_pixbuf_get_height(pb1), 3); + free(bitmap->bitmap); // should create at_bitmap with bitmap->bitmap = pb + bitmap->bitmap = pb; + + at_splines_type *splines = at_splines_new_full(bitmap, opts, NULL, NULL, NULL, NULL, test_cancel, &keepGoing); + // at_output_write_func wfunc = at_output_get_handler_by_suffix("svg"); + // at_spline_writer *wfunc = at_output_get_handler_by_suffix("svg"); + + + int height = splines->height; + // const at_splines_type spline = *splines; + at_spline_list_array_type spline = *splines; + + unsigned this_list; + at_spline_list_type list; + at_color last_color = { 0, 0, 0 }; + + std::stringstream theStyle; + std::stringstream thePath; + char color[10]; + int nNodes = 0; + + std::vector<TracingEngineResult> res; + + // at_splines_write(wfunc, stdout, "", NULL, splines, NULL, NULL); + + for (this_list = 0; this_list < SPLINE_LIST_ARRAY_LENGTH(spline); this_list++) { + unsigned this_spline; + at_spline_type first; + + list = SPLINE_LIST_ARRAY_ELT(spline, this_list); + first = SPLINE_LIST_ELT(list, 0); + + if (this_list == 0 || !at_color_equal(&list.color, &last_color)) { + if (this_list > 0) { + if (!(spline.centerline || list.open)) { + thePath << "z"; + nNodes++; + } + TracingEngineResult ter(theStyle.str(), thePath.str(), nNodes); + res.push_back(ter); + theStyle.clear(); + thePath.clear(); + nNodes = 0; + } + sprintf(color, "#%02x%02x%02x;", list.color.r, list.color.g, list.color.b); + + theStyle << ((spline.centerline || list.open) ? "stroke:" : "fill:") << color + << ((spline.centerline || list.open) ? "fill:" : "stroke:") << "none"; + } + thePath << "M" << START_POINT(first).x << " " << height - START_POINT(first).y; + nNodes++; + for (this_spline = 0; this_spline < SPLINE_LIST_LENGTH(list); this_spline++) { + at_spline_type s = SPLINE_LIST_ELT(list, this_spline); + + if (SPLINE_DEGREE(s) == AT_LINEARTYPE) { + thePath << "L" << END_POINT(s).x << " " << height - END_POINT(s).y; + nNodes++; + } + else { + thePath << "C" << CONTROL1(s).x << " " << height - CONTROL1(s).y << " " << CONTROL2(s).x << " " + << height - CONTROL2(s).y << " " << END_POINT(s).x << " " << height - END_POINT(s).y; + nNodes++; + } + last_color = list.color; + } + } + if (!(spline.centerline || list.open)) + thePath << "z"; + nNodes++; + if (SPLINE_LIST_ARRAY_LENGTH(spline) > 0) { + TracingEngineResult ter(theStyle.str(), thePath.str(), nNodes); + res.push_back(ter); + theStyle.clear(); + thePath.clear(); + nNodes = 0; + } + + return res; +} + + +/** + * Abort the thread that is executing getPathDataFromPixbuf() + */ +void AutotraceTracingEngine::abort() +{ + // g_message("PotraceTracingEngine::abort()\n"); + keepGoing = 0; +} + + + +} // namespace Autotrace +} // 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 : diff --git a/src/trace/autotrace/inkscape-autotrace.h b/src/trace/autotrace/inkscape-autotrace.h new file mode 100644 index 0000000..f12eb35 --- /dev/null +++ b/src/trace/autotrace/inkscape-autotrace.h @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * This is the C++ glue between Inkscape and Autotrace + *//* + * + * Authors: + * Marc Jeanmougin + * + * Copyright (C) 2018 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + * + * Autotrace is available at http://github.com/autotrace/autotrace. + * + */ + +#ifndef __INKSCAPE_AUTOTRACE_H__ +#define __INKSCAPE_AUTOTRACE_H__ + +#include "3rdparty/autotrace/autotrace.h" +#include <trace/trace.h> + +namespace Inkscape { + +namespace Trace { + +namespace Autotrace { +enum TraceType { TRACE_CENTERLINE, TRACE_OUTLINE }; + +class AutotraceTracingEngine : public TracingEngine { + + public: + /** + * + */ + AutotraceTracingEngine(); + + /** + * + */ + ~AutotraceTracingEngine() override; + + + /** + * Sets/gets parameters + */ + // TODO + + /** + * This is the working method of this implementing class, 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> trace(Glib::RefPtr<Gdk::Pixbuf> pixbuf) override; + + /** + * Abort the thread that is executing getPathDataFromPixbuf() + */ + void abort() override; + + /** + * + */ + Glib::RefPtr<Gdk::Pixbuf> preview(Glib::RefPtr<Gdk::Pixbuf> pixbuf); + + /** + * + */ + int keepGoing; + + //private: + // autotrace_param_t *autotraceParams; + TraceType traceType; + at_fitting_opts_type *opts; + + //## do I invert at the end? + bool invert; + +}; // class AutotraceTracingEngine + + + +} // namespace Autotrace +} // namespace Trace +} // namespace Inkscape + + +#endif //__INKSCAPE_POTRACE_H__ + + +/* + 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 : diff --git a/src/trace/depixelize/inkscape-depixelize.cpp b/src/trace/depixelize/inkscape-depixelize.cpp new file mode 100644 index 0000000..a61ff8b --- /dev/null +++ b/src/trace/depixelize/inkscape-depixelize.cpp @@ -0,0 +1,140 @@ +// 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-depixelize.h" + +#include <glibmm/i18n.h> +#include <gtkmm/main.h> +#include <gtkmm.h> +#include <iomanip> + +#include "desktop.h" +#include "message-stack.h" +#include "helper/geom.h" +#include "object/sp-path.h" + +#include "display/cairo-templates.h" + +#include <svg/path-string.h> +#include <svg/svg.h> +#include <svg/svg-color.h> +#include "svg/css-ostringstream.h" + +using Glib::ustring; + + + +namespace Inkscape { + +namespace Trace { + +namespace Depixelize { + + +/** + * + */ +DepixelizeTracingEngine::DepixelizeTracingEngine() + : keepGoing(1) + , traceType(TRACE_VORONOI) +{ + params = new ::Tracer::Kopf2011::Options(); +} + + + +DepixelizeTracingEngine::DepixelizeTracingEngine(TraceType traceType, double curves, int islands, int sparsePixels, + double sparseMultiplier, bool optimize) + : keepGoing(1) + , traceType(traceType) +{ + params = new ::Tracer::Kopf2011::Options(); + params->curvesMultiplier = curves; + params->islandsWeight = islands; + params->sparsePixelsRadius = sparsePixels; + params->sparsePixelsMultiplier = sparseMultiplier; + params->optimize = optimize; + params->nthreads = Inkscape::Preferences::get()->getIntLimited("/options/threading/numthreads", +#ifdef HAVE_OPENMP + omp_get_num_procs(), +#else + 1, +#endif // HAVE_OPENMP + 1, 256); +} + +DepixelizeTracingEngine::~DepixelizeTracingEngine() { delete params; } + +std::vector<TracingEngineResult> DepixelizeTracingEngine::trace(Glib::RefPtr<Gdk::Pixbuf> pixbuf) +{ + std::vector<TracingEngineResult> res; + + if (pixbuf->get_width() > 256 || 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 res; + } + } + + ::Tracer::Splines splines; + + if (traceType == TRACE_VORONOI) + splines = ::Tracer::Kopf2011::to_voronoi(pixbuf, *params); + else + splines = ::Tracer::Kopf2011::to_splines(pixbuf, *params); + + for (::Tracer::Splines::const_iterator it = splines.begin(), end = splines.end(); it != end; ++it) { + 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]))); + Inkscape::CSSOStringStream osalpha; + osalpha << float(it->rgba[3]) / 255.; + gchar* style = g_strdup_printf("fill:%s;fill-opacity:%s;", b, osalpha.str().c_str()); + printf("%s\n", style); + TracingEngineResult r(style, sp_svg_write_path(it->pathVector), count_pathvector_nodes(it->pathVector)); + res.push_back(r); + g_free(style); + } + return res; +} + +void DepixelizeTracingEngine::abort() { keepGoing = 0; } + +Glib::RefPtr<Gdk::Pixbuf> DepixelizeTracingEngine::preview(Glib::RefPtr<Gdk::Pixbuf> pixbuf) { return pixbuf; } + + +} // namespace Depixelize +} // 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 : diff --git a/src/trace/depixelize/inkscape-depixelize.h b/src/trace/depixelize/inkscape-depixelize.h new file mode 100644 index 0000000..85f6509 --- /dev/null +++ b/src/trace/depixelize/inkscape-depixelize.h @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is the C++ glue between Inkscape and Potrace + * + * Copyright (C) 2019 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. + * + */ + +#ifndef __INKSCAPE_DEPIXTRACE_H__ +#define __INKSCAPE_DEPIXTRACE_H__ + +#include <trace/trace.h> +#include "3rdparty/libdepixelize/kopftracer2011.h" + +struct GrayMap_def; +typedef GrayMap_def GrayMap; + +namespace Inkscape { + +namespace Trace { + +namespace Depixelize { + +enum TraceType + { + TRACE_VORONOI, + TRACE_BSPLINES + }; + + +class DepixelizeTracingEngine : public TracingEngine +{ + + public: + + /** + * + */ + DepixelizeTracingEngine(); + DepixelizeTracingEngine(TraceType traceType, double curves, int islands, int sparsePixels, double sparseMultiplier, bool optimize); + + /** + * + */ + ~DepixelizeTracingEngine() override; + + /** + * This is the working method of this implementing class, 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> trace( + Glib::RefPtr<Gdk::Pixbuf> pixbuf) override; + + /** + * Abort the thread that is executing getPathDataFromPixbuf() + */ + void abort() override; + + /** + * + */ + Glib::RefPtr<Gdk::Pixbuf> preview(Glib::RefPtr<Gdk::Pixbuf> pixbuf); + + /** + * + */ + int keepGoing; + + ::Tracer::Kopf2011::Options *params; + TraceType traceType; + +};//class PotraceTracingEngine + + + +} // namespace Depixelize +} // namespace Trace +} // namespace Inkscape + + +#endif //__INKSCAPE_TRACE_H__ + + +/* + 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 : diff --git a/src/trace/filterset.cpp b/src/trace/filterset.cpp new file mode 100644 index 0000000..331d4e3 --- /dev/null +++ b/src/trace/filterset.cpp @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Some filters for Potrace in Inkscape + * + * 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. + */ + +#include <cstdio> +#include <cstdlib> + +#include "imagemap-gdk.h" +#include "filterset.h" +#include "quantize.h" + +/*######################################################################### +### G A U S S I A N (smoothing) +#########################################################################*/ + +/** + * + */ +static int gaussMatrix[] = +{ + 2, 4, 5, 4, 2, + 4, 9, 12, 9, 4, + 5, 12, 15, 12, 5, + 4, 9, 12, 9, 4, + 2, 4, 5, 4, 2 +}; + + +/** + * + */ +GrayMap *grayMapGaussian(GrayMap *me) +{ + int width = me->width; + int height = me->height; + int firstX = 2; + int lastX = width-3; + int firstY = 2; + int lastY = height-3; + + GrayMap *newGm = GrayMapCreate(width, height); + if (!newGm) + return nullptr; + + for (int y = 0 ; y<height ; y++) + { + for (int x = 0 ; x<width ; x++) + { + /* image boundaries */ + if (x<firstX || x>lastX || y<firstY || y>lastY) + { + newGm->setPixel(newGm, x, y, me->getPixel(me, x, y)); + continue; + } + + /* all other pixels */ + int gaussIndex = 0; + unsigned long sum = 0; + for (int i= y-2 ; i<=y+2 ; i++) + { + for (int j= x-2; j<=x+2 ; j++) + { + int weight = gaussMatrix[gaussIndex++]; + sum += me->getPixel(me, j, i) * weight; + } + } + sum /= 159; + newGm->setPixel(newGm, x, y, sum); + } + } + + return newGm; +} + + + + + +/** + * + */ +RgbMap *rgbMapGaussian(RgbMap *me) +{ + int width = me->width; + int height = me->height; + int firstX = 2; + int lastX = width-3; + int firstY = 2; + int lastY = height-3; + + RgbMap *newGm = RgbMapCreate(width, height); + if (!newGm) + return nullptr; + + for (int y = 0 ; y<height ; y++) + { + for (int x = 0 ; x<width ; x++) + { + /* image boundaries */ + if (x<firstX || x>lastX || y<firstY || y>lastY) + { + newGm->setPixelRGB(newGm, x, y, me->getPixel(me, x, y)); + continue; + } + + /* all other pixels */ + int gaussIndex = 0; + int sumR = 0; + int sumG = 0; + int sumB = 0; + for (int i= y-2 ; i<=y+2 ; i++) + { + for (int j= x-2; j<=x+2 ; j++) + { + int weight = gaussMatrix[gaussIndex++]; + RGB rgb = me->getPixel(me, j, i); + sumR += weight * (int)rgb.r; + sumG += weight * (int)rgb.g; + sumB += weight * (int)rgb.b; + } + } + RGB rout; + rout.r = ( sumR / 159 ) & 0xff; + rout.g = ( sumG / 159 ) & 0xff; + rout.b = ( sumB / 159 ) & 0xff; + newGm->setPixelRGB(newGm, x, y, rout); + } + } + + return newGm; + +} + + + + +/*######################################################################### +### C A N N Y E D G E D E T E C T I O N +#########################################################################*/ + + +static int sobelX[] = +{ + -1, 0, 1 , + -2, 0, 2 , + -1, 0, 1 +}; + +static int sobelY[] = +{ + 1, 2, 1 , + 0, 0, 0 , + -1, -2, -1 +}; + + + +/** + * Perform Sobel convolution on a GrayMap + */ +static GrayMap *grayMapSobel(GrayMap *gm, + double dLowThreshold, double dHighThreshold) +{ + int width = gm->width; + int height = gm->height; + int firstX = 1; + int lastX = width-2; + int firstY = 1; + int lastY = height-2; + + GrayMap *newGm = GrayMapCreate(width, height); + if (!newGm) + return nullptr; + + for (int y = 0 ; y<height ; y++) + { + for (int x = 0 ; x<width ; x++) + { + unsigned long sum = 0; + /* image boundaries */ + if (x<firstX || x>lastX || y<firstY || y>lastY) + { + sum = 0; + } + else + { + /* ### SOBEL FILTERING #### */ + long sumX = 0; + long sumY = 0; + int sobelIndex = 0; + for (int i= y-1 ; i<=y+1 ; i++) + { + for (int j= x-1; j<=x+1 ; j++) + { + sumX += gm->getPixel(gm, j, i) * + sobelX[sobelIndex++]; + } + } + + sobelIndex = 0; + for (int i= y-1 ; i<=y+1 ; i++) + { + for (int j= x-1; j<=x+1 ; j++) + { + sumY += gm->getPixel(gm, j, i) * + sobelY[sobelIndex++]; + } + } + /*### GET VALUE ### */ + sum = abs(sumX) + abs(sumY); + + if (sum > 765) + sum = 765; + +#if 0 + /*### GET ORIENTATION (slow, pedantic way) ### */ + double orient = 0.0; + if (sumX==0) + { + if (sumY==0) + orient = 0.0; + else if (sumY<0) + { + sumY = -sumY; + orient = 90.0; + } + else + orient = 90.0; + } + else + { + orient = 57.295779515 * atan2( ((double)sumY),((double)sumX) ); + if (orient < 0.0) + orient += 180.0; + } + + /*### GET EDGE DIRECTION ### */ + int edgeDirection = 0; + if (orient < 22.5) + edgeDirection = 0; + else if (orient < 67.5) + edgeDirection = 45; + else if (orient < 112.5) + edgeDirection = 90; + else if (orient < 157.5) + edgeDirection = 135; +#else + /*### GET EDGE DIRECTION (fast way) ### */ + int edgeDirection = 0; /*x,y=0*/ + if (sumX==0) + { + if (sumY!=0) + edgeDirection = 90; + } + else + { + /*long slope = sumY*1024/sumX;*/ + long slope = (sumY << 10)/sumX; + if (slope > 2472 || slope< -2472) /*tan(67.5)*1024*/ + edgeDirection = 90; + else if (slope > 414) /*tan(22.5)*1024*/ + edgeDirection = 45; + else if (slope < -414) /*-tan(22.5)*1024*/ + edgeDirection = 135; + } + +#endif + /* printf("%ld %ld %f %d\n", sumX, sumY, orient, edgeDirection); */ + + /*### Get two adjacent pixels in edge direction ### */ + unsigned long leftPixel; + unsigned long rightPixel; + if (edgeDirection == 0) + { + leftPixel = gm->getPixel(gm, x-1, y); + rightPixel = gm->getPixel(gm, x+1, y); + } + else if (edgeDirection == 45) + { + leftPixel = gm->getPixel(gm, x-1, y+1); + rightPixel = gm->getPixel(gm, x+1, y-1); + } + else if (edgeDirection == 90) + { + leftPixel = gm->getPixel(gm, x, y-1); + rightPixel = gm->getPixel(gm, x, y+1); + } + else /*135 */ + { + leftPixel = gm->getPixel(gm, x-1, y-1); + rightPixel = gm->getPixel(gm, x+1, y+1); + } + + /*### Compare current value to adjacent pixels ### */ + /*### if less that either, suppress it ### */ + if (sum < leftPixel || sum < rightPixel) + sum = 0; + else + { + unsigned long highThreshold = + (unsigned long)(dHighThreshold * 765.0); + unsigned long lowThreshold = + (unsigned long)(dLowThreshold * 765.0); + if (sum >= highThreshold) + sum = 765; /* EDGE. 3*255 this needs to be settable */ + else if (sum < lowThreshold) + sum = 0; /* NONEDGE */ + else + { + if ( gm->getPixel(gm, x-1, y-1)> highThreshold || + gm->getPixel(gm, x , y-1)> highThreshold || + gm->getPixel(gm, x+1, y-1)> highThreshold || + gm->getPixel(gm, x-1, y )> highThreshold || + gm->getPixel(gm, x+1, y )> highThreshold || + gm->getPixel(gm, x-1, y+1)> highThreshold || + gm->getPixel(gm, x , y+1)> highThreshold || + gm->getPixel(gm, x+1, y+1)> highThreshold) + sum = 765; /* EDGE fix me too */ + else + sum = 0; /* NONEDGE */ + } + } + + + }/* else */ + if (sum==0) /* invert light & dark */ + sum = 765; + else + sum = 0; + newGm->setPixel(newGm, x, y, sum); + }/* for (x) */ + }/* for (y) */ + + return newGm; +} + + + + + +/** + * + */ +GrayMap * +grayMapCanny(GrayMap *gm, double lowThreshold, double highThreshold) +{ + if (!gm) + return nullptr; + + GrayMap *cannyGm = grayMapSobel(gm, lowThreshold, highThreshold); + if (!cannyGm) + return nullptr; + /*cannyGm->writePPM(cannyGm, "canny.ppm");*/ + + return cannyGm; +} + + + +/*######################################################################### +### Q U A N T I Z A T I O N +#########################################################################*/ + +/** + * Experimental. Work on this later + */ +GrayMap *quantizeBand(RgbMap *rgbMap, int nrColors) +{ + + RgbMap *gaussMap = rgbMapGaussian(rgbMap); + if (!gaussMap) + return nullptr; + //gaussMap->writePPM(gaussMap, "rgbgauss.ppm"); + + IndexedMap *qMap = rgbMapQuantize(gaussMap, nrColors); + gaussMap->destroy(gaussMap); + if (!qMap) + return nullptr; + //qMap->writePPM(qMap, "rgbquant.ppm"); + + GrayMap *gm = GrayMapCreate(rgbMap->width, rgbMap->height); + if (!gm) + return nullptr; + + // RGB is quantized. There should now be a small set of (R+G+B) + for (int y=0 ; y<qMap->height ; y++) + { + for (int x=0 ; x<qMap->width ; x++) + { + RGB rgb = qMap->getPixelValue(qMap, x, y); + int sum = rgb.r + rgb.g + rgb.b; + if (sum & 1) + sum = 765; + else + sum = 0; + // printf("%d %d %d : %d\n", rgb.r, rgb.g, rgb.b, index); + gm->setPixel(gm, x, y, sum); + } + } + + qMap->destroy(qMap); + + return gm; +} + + +/*######################################################################### +### E N D O F F I L E +#########################################################################*/ + + + + + + + + + + diff --git a/src/trace/filterset.h b/src/trace/filterset.h new file mode 100644 index 0000000..d8da650 --- /dev/null +++ b/src/trace/filterset.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Some filters for Potrace in Inkscape + * + * 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. + */ + +#ifndef __FILTERSET_H__ +#define __FILTERSET_H__ + +#include "imagemap.h" + +#include <gdk-pixbuf/gdk-pixbuf.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Apply gaussian blur to an GrayMap + */ +GrayMap *grayMapGaussian(GrayMap *gmap); + +/** + * Apply gaussian bluf to an RgbMap + */ +RgbMap *rgbMapGaussian(RgbMap *rgbmap); + +/** + * + */ +GrayMap *grayMapCanny(GrayMap *gmap, + double lowThreshold, double highThreshold); + +/** + * + */ +GrayMap *quantizeBand(RgbMap *rgbmap, int nrColors); + + + +#ifdef __cplusplus +} +#endif + + +#endif /* __FILTERSET_H__ */ + +/*######################################################################### +### E N D O F F I L E +#########################################################################*/ diff --git a/src/trace/imagemap-gdk.cpp b/src/trace/imagemap-gdk.cpp new file mode 100644 index 0000000..10fe27e --- /dev/null +++ b/src/trace/imagemap-gdk.cpp @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#include <cstdlib> + +#include "imagemap-gdk.h" + + +/*######################################################################### +## G R A Y M A P +#########################################################################*/ + +GrayMap *gdkPixbufToGrayMap(GdkPixbuf *buf) +{ + if (!buf) + return nullptr; + + int width = gdk_pixbuf_get_width(buf); + int height = gdk_pixbuf_get_height(buf); + guchar *pixdata = gdk_pixbuf_get_pixels(buf); + int rowstride = gdk_pixbuf_get_rowstride(buf); + int n_channels = gdk_pixbuf_get_n_channels(buf); + + GrayMap *grayMap = GrayMapCreate(width, height); + if (!grayMap) + return nullptr; + + //### Fill in the odd cells with RGB values + int x,y; + int row = 0; + for (y=0 ; y<height ; y++) + { + guchar *p = pixdata + row; + for (x=0 ; x<width ; x++) + { + int alpha = (int)p[3]; + int white = 3 * (255-alpha); + unsigned long sample = (int)p[0] + (int)p[1] +(int)p[2]; + unsigned long bright = sample * alpha / 256 + white; + grayMap->setPixel(grayMap, x, y, bright); + p += n_channels; + } + row += rowstride; + } + + return grayMap; +} + +GdkPixbuf *grayMapToGdkPixbuf(GrayMap *grayMap) +{ + if (!grayMap) + return nullptr; + + guchar *pixdata = (guchar *) + malloc(sizeof(guchar) * grayMap->width * grayMap->height * 3); + if (!pixdata) + { + g_warning("grayMapToGdkPixbuf: can not allocate memory for conversion."); + return nullptr; + } + + int n_channels = 3; + int rowstride = grayMap->width * 3; + + GdkPixbuf *buf = gdk_pixbuf_new_from_data(pixdata, GDK_COLORSPACE_RGB, + 0, 8, grayMap->width, grayMap->height, + rowstride, (GdkPixbufDestroyNotify)g_free, nullptr); + + //### Fill in the odd cells with RGB values + int x,y; + int row = 0; + for (y=0 ; y<grayMap->height ; y++) + { + guchar *p = pixdata + row; + for (x=0 ; x<grayMap->width ; x++) + { + unsigned long pix = grayMap->getPixel(grayMap, x, y) / 3; + p[0] = p[1] = p[2] = (guchar)(pix & 0xff); + p += n_channels; + } + row += rowstride; + } + + return buf; +} + + +/*######################################################################### +## R G B M A P +#########################################################################*/ + +RgbMap *gdkPixbufToRgbMap(GdkPixbuf *buf) +{ + if (!buf) + return nullptr; + + int width = gdk_pixbuf_get_width(buf); + int height = gdk_pixbuf_get_height(buf); + guchar *pixdata = gdk_pixbuf_get_pixels(buf); + int rowstride = gdk_pixbuf_get_rowstride(buf); + int n_channels = gdk_pixbuf_get_n_channels(buf); + + RgbMap *rgbMap = RgbMapCreate(width, height); + if (!rgbMap) + return nullptr; + + //### Fill in the cells with RGB values + int x,y; + int row = 0; + for (y=0 ; y<height ; y++) + { + guchar *p = pixdata + row; + for (x=0 ; x<width ; x++) + { + int alpha = (int)p[3]; + int white = 255 - alpha; + int r = (int)p[0]; r = r * alpha / 256 + white; + int g = (int)p[1]; g = g * alpha / 256 + white; + int b = (int)p[2]; b = b * alpha / 256 + white; + + rgbMap->setPixel(rgbMap, x, y, r, g, b); + p += n_channels; + } + row += rowstride; + } + + return rgbMap; +} + + + +/*######################################################################### +## I N D E X E D M A P +#########################################################################*/ + + +GdkPixbuf *indexedMapToGdkPixbuf(IndexedMap *iMap) +{ + if (!iMap) + return nullptr; + + guchar *pixdata = (guchar *) + malloc(sizeof(guchar) * iMap->width * iMap->height * 3); + if (!pixdata) + { + g_warning("indexedMapToGdkPixbuf: can not allocate memory for conversion."); + return nullptr; + } + + int n_channels = 3; + int rowstride = iMap->width * 3; + + GdkPixbuf *buf = gdk_pixbuf_new_from_data(pixdata, GDK_COLORSPACE_RGB, + 0, 8, iMap->width, iMap->height, + rowstride, (GdkPixbufDestroyNotify)g_free, nullptr); + + //### Fill in the cells with RGB values + int x,y; + int row = 0; + for (y=0 ; y<iMap->height ; y++) + { + guchar *p = pixdata + row; + for (x=0 ; x<iMap->width ; x++) + { + RGB rgb = iMap->getPixelValue(iMap, x, y); + p[0] = rgb.r & 0xff; + p[1] = rgb.g & 0xff; + p[2] = rgb.b & 0xff; + p += n_channels; + } + row += rowstride; + } + + return buf; +} + +/*######################################################################### +## E N D O F F I L E +#########################################################################*/ diff --git a/src/trace/imagemap-gdk.h b/src/trace/imagemap-gdk.h new file mode 100644 index 0000000..a96395b --- /dev/null +++ b/src/trace/imagemap-gdk.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2013 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef __GRAYMAP_GDK_H__ +#define __GRAYMAP_GDK_H__ + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#include "imagemap.h" + +#include <gdk-pixbuf/gdk-pixbuf.h> + +/*######################################################################### +### I M A G E M A P --- GDK +#########################################################################*/ + + + +#ifdef __cplusplus +extern "C" { +#endif + +GrayMap *gdkPixbufToGrayMap(GdkPixbuf *buf); +GdkPixbuf *grayMapToGdkPixbuf(GrayMap *grayMap); +RgbMap *gdkPixbufToRgbMap(GdkPixbuf *buf); +GdkPixbuf *indexedMapToGdkPixbuf(IndexedMap *iMap); + + +#ifdef __cplusplus +} +#endif + + +#endif /* __GRAYMAP_GDK_H__ */ + +/*######################################################################### +### E N D O F F I L E +#########################################################################*/ diff --git a/src/trace/imagemap.cpp b/src/trace/imagemap.cpp new file mode 100644 index 0000000..ff54b7e --- /dev/null +++ b/src/trace/imagemap.cpp @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#include <cstdlib> + +#include "imagemap.h" + +#include "io/sys.h" + +/*######################################################################### +### G R A Y M A P +#########################################################################*/ + + +static void gSetPixel(GrayMap *me, int x, int y, unsigned long val) +{ + if (val>765) + val = 765; + unsigned long *pix = me->rows[y] + x; + *pix = val; +} + +static unsigned long gGetPixel(GrayMap *me, int x, int y) +{ + unsigned long *pix = me->rows[y] + x; + return *pix; +} + + +static int gWritePPM(GrayMap *me, char *fileName) +{ + if (!fileName) + return FALSE; + + FILE *f = fopen(fileName, "wb"); + if (!f) + return FALSE; + + fprintf(f, "P6 %d %d 255\n", me->width, me->height); + + for (int y=0 ; y<me->height; y++) + { + for (int x=0 ; x<me->width ; x++) + { + unsigned long pix = me->getPixel(me, x, y) / 3; + unsigned char pixb = (unsigned char) (pix & 0xff); + fputc(pixb, f); + fputc(pixb, f); + fputc(pixb, f); + } + } + fclose(f); + return TRUE; +} + + +static void gDestroy(GrayMap *me) +{ + if (me->pixels) + free(me->pixels); + if (me->rows) + free(me->rows); + free(me); +} + +GrayMap *GrayMapCreate(int width, int height) +{ + + GrayMap *me = (GrayMap *)malloc(sizeof(GrayMap)); + if (!me) + return nullptr; + + /** methods **/ + me->setPixel = gSetPixel; + me->getPixel = gGetPixel; + me->writePPM = gWritePPM; + me->destroy = gDestroy; + + /** fields **/ + me->width = width; + me->height = height; + me->pixels = (unsigned long *) + malloc(sizeof(unsigned long) * width * height); + if (!me->pixels) + { + g_warning("GrayMapCreate: can not allocate memory for %d x %d image.", width, height); + free(me); + return nullptr; + } + me->rows = (unsigned long **) + malloc(sizeof(unsigned long *) * height); + if (!me->rows) + { + g_warning("GrayMapCreate: can not allocate memory for index of %d x %d image.", width, height); + free(me->pixels); + free(me); + return nullptr; + } + + unsigned long *row = me->pixels; + for (int i=0 ; i<height ; i++) + { + me->rows[i] = row; + row += width; + } + + return me; +} + + + +/*######################################################################### +### R G B M A P +#########################################################################*/ + + + +static void rSetPixel(RgbMap *me, int x, int y, int r, int g, int b) +{ + RGB *pix = me->rows[y] + x; + pix->r = r; + pix->g = g; + pix->b = b; +} + +static void rSetPixelRGB(RgbMap *me, int x, int y, RGB rgb) +{ + RGB *pix = me->rows[y] + x; + *pix = rgb; +} + +static RGB rGetPixel(RgbMap *me, int x, int y) +{ + RGB *pix = me->rows[y] + x; + return *pix; +} + + + +static int rWritePPM(RgbMap *me, char *fileName) +{ + if (!fileName) + return FALSE; + + FILE *f = fopen(fileName, "wb"); + if (!f) + return FALSE; + + fprintf(f, "P6 %d %d 255\n", me->width, me->height); + + for (int y=0 ; y<me->height; y++) + { + for (int x=0 ; x<me->width ; x++) + { + RGB rgb = me->getPixel(me, x, y); + fputc(rgb.r, f); + fputc(rgb.g, f); + fputc(rgb.b, f); + } + } + fclose(f); + return TRUE; +} + + +static void rDestroy(RgbMap *me) +{ + if (me->pixels){ + free(me->pixels); + } + if (me->rows){ + free(me->rows); + } + free(me); +} + + + +RgbMap *RgbMapCreate(int width, int height) +{ + + RgbMap *me = (RgbMap *)malloc(sizeof(RgbMap)); + if (!me){ + return nullptr; + } + + /** methods **/ + me->setPixel = rSetPixel; + me->setPixelRGB = rSetPixelRGB; + me->getPixel = rGetPixel; + me->writePPM = rWritePPM; + me->destroy = rDestroy; + + + /** fields **/ + me->width = width; + me->height = height; + me->pixels = (RGB *) malloc(sizeof(RGB) * width * height); + if (!me->pixels){ + g_warning("RgbMapCreate: can not allocate memory for %d x %d image.", width, height); + free(me); + return nullptr; + } + me->rows = (RGB **) malloc(sizeof(RGB *) * height); + if (!me->rows){ + g_warning("RgbMapCreate: can not allocate memory for index of %d x %d image.", width, height); + free(me->pixels); //allocated as me->pixels is not NULL here: see previous check + free(me); + return nullptr; + } + + RGB *row = me->pixels; + for (int i=0 ; i<height ; i++){ + me->rows[i] = row; + row += width; + } + + return me; +} + + + + +/*######################################################################### +### I N D E X E D M A P +#########################################################################*/ + + + +static void iSetPixel(IndexedMap *me, int x, int y, unsigned int index) +{ + unsigned int *pix = me->rows[y] + x; + *pix = index; +} + + +static unsigned int iGetPixel(IndexedMap *me, int x, int y) +{ + unsigned int *pix = me->rows[y] + x; + return *pix; +} + +static RGB iGetPixelValue(IndexedMap *me, int x, int y) +{ + unsigned int *pix = me->rows[y] + x; + RGB rgb = me->clut[((*pix)&0xff)]; + return rgb; +} + + + +static int iWritePPM(IndexedMap *me, char *fileName) +{ + if (!fileName) + return FALSE; + + FILE *f = fopen(fileName, "wb"); + if (!f) + return FALSE; + + fprintf(f, "P6 %d %d 255\n", me->width, me->height); + + for (int y=0 ; y<me->height; y++) + { + for (int x=0 ; x<me->width ; x++) + { + RGB rgb = me->getPixelValue(me, x, y); + fputc(rgb.r, f); + fputc(rgb.g, f); + fputc(rgb.b, f); + } + } + fclose(f); + return TRUE; +} + + +static void iDestroy(IndexedMap *me) +{ + if (me->pixels){ + free(me->pixels); + } + if (me->rows){ + free(me->rows); + } + free(me); +} + + + +IndexedMap *IndexedMapCreate(int width, int height) +{ + + IndexedMap *me = (IndexedMap *)malloc(sizeof(IndexedMap)); + if (!me) + return nullptr; + + /** methods **/ + me->setPixel = iSetPixel; + me->getPixel = iGetPixel; + me->getPixelValue = iGetPixelValue; + me->writePPM = iWritePPM; + me->destroy = iDestroy; + + + /** fields **/ + me->width = width; + me->height = height; + me->pixels = (unsigned int *) malloc(sizeof(unsigned int) * width * height); + if (!me->pixels){ + g_warning("IndexedMapCreate: can not allocate memory for %d x %d image.", width, height); + free(me); + return nullptr; + } + me->rows = (unsigned int **) malloc(sizeof(unsigned int *) * height); + if (!me->rows){ + g_warning("IndexedMapCreate: can not allocate memory for index of %d x %d image.", width, height); + free(me->pixels); //allocated as me->pixels is not NULL here: see previous check + free(me); + return nullptr; + } + + unsigned int *row = me->pixels; + for (int i=0 ; i<height ; i++){ + me->rows[i] = row; + row += width; + } + + me->nrColors = 0; + + RGB rgb; + rgb.r = rgb.g = rgb.b = 0; + for (auto & i : me->clut){ + i = rgb; + } + + return me; +} + + + + + + +/*######################################################################### +### E N D O F F I L E +#########################################################################*/ diff --git a/src/trace/imagemap.h b/src/trace/imagemap.h new file mode 100644 index 0000000..4a938da --- /dev/null +++ b/src/trace/imagemap.h @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef __IMAGEMAP_H__ +#define __IMAGEMAP_H__ + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + + +/*######################################################################### +### G R A Y M A P +#########################################################################*/ + + +typedef struct GrayMap_def GrayMap; + +#define GRAYMAP_BLACK 0 +#define GRAYMAP_WHITE 765 + +/** + * + */ +struct GrayMap_def +{ + + /*################# + ### METHODS + #################*/ + + /** + * + */ + void (*setPixel)(GrayMap *me, int x, int y, unsigned long val); + + /** + * + */ + unsigned long (*getPixel)(GrayMap *me, int x, int y); + + /** + * + */ + int (*writePPM)(GrayMap *me, char *fileName); + + + + /** + * + */ + void (*destroy)(GrayMap *me); + + + + /*################# + ### FIELDS + #################*/ + + /** + * + */ + int width; + + /** + * + */ + int height; + + /** + * The pixel array + */ + unsigned long *pixels; + + /** + * Pointer to the beginning of each row + */ + unsigned long **rows; + +}; + +#ifdef __cplusplus +extern "C" { +#endif + +GrayMap *GrayMapCreate(int width, int height); + +#ifdef __cplusplus +} +#endif + + + +/*######################################################################### +### R G B M A P +#########################################################################*/ + +struct RGB +{ + unsigned char r; + unsigned char g; + unsigned char b; +}; + + + +typedef struct RgbMap_def RgbMap; + +/** + * + */ +struct RgbMap_def +{ + + /*################# + ### METHODS + #################*/ + + /** + * + */ + void (*setPixel)(RgbMap *me, int x, int y, int r, int g, int b); + + + /** + * + */ + void (*setPixelRGB)(RgbMap *me, int x, int y, RGB rgb); + + /** + * + */ + RGB (*getPixel)(RgbMap *me, int x, int y); + + /** + * + */ + int (*writePPM)(RgbMap *me, char *fileName); + + + + /** + * + */ + void (*destroy)(RgbMap *me); + + + + /*################# + ### FIELDS + #################*/ + + /** + * + */ + int width; + + /** + * + */ + int height; + + /** + * The allocated array of pixels + */ + RGB *pixels; + + /** + * Pointers to the beginning of each row of pixels + */ + RGB **rows; + +}; + + + +#ifdef __cplusplus +extern "C" { +#endif + +RgbMap *RgbMapCreate(int width, int height); + +#ifdef __cplusplus +} +#endif + + + + +/*######################################################################### +### I N D E X E D M A P +#########################################################################*/ + + +typedef struct IndexedMap_def IndexedMap; + +/** + * + */ +struct IndexedMap_def +{ + + /*################# + ### METHODS + #################*/ + + /** + * + */ + void (*setPixel)(IndexedMap *me, int x, int y, unsigned int index); + + + /** + * + */ + unsigned int (*getPixel)(IndexedMap *me, int x, int y); + + /** + * + */ + RGB (*getPixelValue)(IndexedMap *me, int x, int y); + + /** + * + */ + int (*writePPM)(IndexedMap *me, char *fileName); + + + + /** + * + */ + void (*destroy)(IndexedMap *me); + + + + /*################# + ### FIELDS + #################*/ + + /** + * + */ + int width; + + /** + * + */ + int height; + + /** + * The allocated array of pixels + */ + unsigned int *pixels; + + /** + * Pointers to the beginning of each row of pixels + */ + unsigned int **rows; + + /** + * + */ + int nrColors; + + /** + * Color look up table + */ + RGB clut[256]; + +}; + + + +#ifdef __cplusplus +extern "C" { +#endif + +IndexedMap *IndexedMapCreate(int width, int height); + +#ifdef __cplusplus +} +#endif + + + + +#endif /* __IMAGEMAP_H__ */ + +/*######################################################################### +### E N D O F F I L E +#########################################################################*/ diff --git a/src/trace/pool.h b/src/trace/pool.h new file mode 100644 index 0000000..4c088b8 --- /dev/null +++ b/src/trace/pool.h @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Pool memory allocation + * + * Authors: + * Stéphane Gimenez <dev@gim.name> + * + * Copyright (C) 2004-2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +// not thread safe (a pool cannot be shared by threads safely) + +/* +-- principle: + + - user operations on a pool of objects of type T are: + - T *draw() : obtain a unused slot to store an object T + - void drop(T *) : release a slot + +-- implementation: + + - a pool for objects T is: + + * blocks[64] : an array of allocated blocks of memory: + |---0--> block with capacity 64 + |---1--> block with capacity 64 + |---2--> block with capacity 128 + |---3--> block with capacity 128 + |---4--> block with capacity 256 + |---5--> block with capacity 256 + |---6--> block with capacity 512 + |---7--> not yet allocated + : + |---k--> not yet allocated (future capacity ~ 2^(6+k/2)) + : + '--63--> not yet allocated + * cblock : the index of the next unallocated block (here 7). + * next : a pointer to an unused slot inside an allocated bloc + + - the first bytes of an unallocated slot inside a bloc are used to store a + pointer to some other unallocated slot. (this way, we keep a list of all + unused slots starting at <next>) + + - insertions and deletions in this list are done at the root <next>. + if <next> points to NULL (no slots are available) when a draw() + operation is performed a new block is allocated, and the unused slots + list is filled with the allocated slots. + + - memory is freed only at pool's deletion. + +*/ + +#include <cstdlib> + +template <typename T> +class pool { + + public: + + pool() + { + cblock = 0; + size = sizeof(T) > sizeof(void *) ? sizeof(T) : sizeof(void *); + next = nullptr; + for (auto & k : block) { + k = nullptr; + } + } + + ~pool() + { + for (int k = 0; k < cblock; k++) { + free(block[k]); + } + } + + T *draw() + { + if (!next) addblock(); + void *p = next; + next = *(void **)p; + return (T *) p; + } + + void drop(T *p) + { + *(void **)p = next; + next = (void *) p; + } + + private: + + int size; + int cblock; + void *block[64]; //enough to store unlimited number of objects, if 64 is changed: see constructor too + void *next; + + void addblock() + { + int i = cblock++; + int blocksize = 1 << (6 + (i/2)); + //printf("pool allocating block: %d (size:%d)...", i, blocksize);//debug + block[i] = (void *)malloc(blocksize * size); + if (!block[i]) throw std::bad_alloc(); + char *p = (char *)block[i]; + for (int k = 0; k < blocksize - 1; k++) + { + *(void**)p = (void *)(p + size); + p += size; + } + *(void **)p = next; + next = block[i]; + //printf("done\n");//debug + } + +}; + diff --git a/src/trace/potrace/bitmap.h b/src/trace/potrace/bitmap.h new file mode 100644 index 0000000..2fb07e8 --- /dev/null +++ b/src/trace/potrace/bitmap.h @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * inline macros for accessing bitmaps + */ +/* Copyright (C) 2001-2015 Peter Selinger. + This file is part of Potrace. It is free software and it is covered + by the GNU General Public License. See the file COPYING for details. */ + +#ifndef BITMAP_H +#define BITMAP_H + +#include <cstring> +#include <cstdlib> +#include <cerrno> +#include <cstddef> + +/* The bitmap type is defined in potracelib.h */ +#include "potracelib.h" + +/* The present file defines some convenient macros and static inline + functions for accessing bitmaps. Since they only produce inline + code, they can be conveniently shared by the library and frontends, + if desired */ + +/* ---------------------------------------------------------------------- */ +/* some measurements */ + +#define BM_WORDSIZE ((int)sizeof(potrace_word)) +#define BM_WORDBITS (8*BM_WORDSIZE) +#define BM_HIBIT (((potrace_word)1)<<(BM_WORDBITS-1)) +#define BM_ALLBITS (~(potrace_word)0) + +/* macros for accessing pixel at index (x,y). U* macros omit the + bounds check. */ + +#define bm_scanline(bm, y) ((bm)->map + (ptrdiff_t)(y)*(ptrdiff_t)(bm)->dy) +#define bm_index(bm, x, y) (&bm_scanline(bm, y)[(x)/BM_WORDBITS]) +#define bm_mask(x) (BM_HIBIT >> ((x) & (BM_WORDBITS-1))) +#define bm_range(x, a) ((int)(x) >= 0 && (int)(x) < (a)) +#define bm_safe(bm, x, y) (bm_range(x, (bm)->w) && bm_range(y, (bm)->h)) +#define BM_UGET(bm, x, y) ((*bm_index(bm, x, y) & bm_mask(x)) != 0) +#define BM_USET(bm, x, y) (*bm_index(bm, x, y) |= bm_mask(x)) +#define BM_UCLR(bm, x, y) (*bm_index(bm, x, y) &= ~bm_mask(x)) +#define BM_UINV(bm, x, y) (*bm_index(bm, x, y) ^= bm_mask(x)) +#define BM_UPUT(bm, x, y, b) ((b) ? BM_USET(bm, x, y) : BM_UCLR(bm, x, y)) +#define BM_GET(bm, x, y) (bm_safe(bm, x, y) ? BM_UGET(bm, x, y) : 0) +#define BM_SET(bm, x, y) (bm_safe(bm, x, y) ? BM_USET(bm, x, y) : 0) +#define BM_CLR(bm, x, y) (bm_safe(bm, x, y) ? BM_UCLR(bm, x, y) : 0) +#define BM_INV(bm, x, y) (bm_safe(bm, x, y) ? BM_UINV(bm, x, y) : 0) +#define BM_PUT(bm, x, y, b) (bm_safe(bm, x, y) ? BM_UPUT(bm, x, y, b) : 0) + +/* free the given bitmap. Leaves errno untouched. */ +static inline void bm_free(potrace_bitmap_t *bm) { + if (bm) { + free(bm->map); + } + free(bm); +} + +/* return new un-initialized bitmap. NULL with errno on error. + Assumes w, h >= 0. */ +static inline potrace_bitmap_t *bm_new(int w, int h) { + potrace_bitmap_t *bm; + int dy = w == 0 ? 0 : (w - 1) / BM_WORDBITS + 1; + ptrdiff_t size = (ptrdiff_t)dy * (ptrdiff_t)h * (ptrdiff_t)BM_WORDSIZE; + + /* check for overflow error */ + if (size < 0 || (h != 0 && dy != 0 && size / h / dy != BM_WORDSIZE)) { + errno = ENOMEM; + return nullptr; + } + + bm = (potrace_bitmap_t *) malloc(sizeof(potrace_bitmap_t)); + if (!bm) { + return nullptr; + } + bm->w = w; + bm->h = h; + bm->dy = dy; + bm->map = (potrace_word *) malloc(size); + if (!bm->map) { + g_warning("bm_new: can not allocate memory for bitmap (%td).", size); + free(bm); + return nullptr; + } + return bm; +} + +/* clear the given bitmap. Set all bits to c. */ +static inline void bm_clear(potrace_bitmap_t *bm, int c) { + /* Note: if the bitmap was created with bm_new, then it is + guaranteed that size will fit into the ptrdiff_t type. */ + ptrdiff_t size = (ptrdiff_t)bm->dy * (ptrdiff_t)bm->h * (ptrdiff_t)BM_WORDSIZE; + memset(bm->map, c ? -1 : 0, size); +} + +/* duplicate the given bitmap. Return NULL on error with errno set. */ +static inline potrace_bitmap_t *bm_dup(const potrace_bitmap_t *bm) { + potrace_bitmap_t *bm1 = bm_new(bm->w, bm->h); + ptrdiff_t size = (ptrdiff_t)bm->dy * (ptrdiff_t)bm->h * (ptrdiff_t)BM_WORDSIZE; + if (!bm1) { + return nullptr; + } + memcpy(bm1->map, bm->map, size); + return bm1; +} + +/* invert the given bitmap. */ +static inline void bm_invert(potrace_bitmap_t *bm) { + ptrdiff_t i; + ptrdiff_t size = (ptrdiff_t)bm->dy * (ptrdiff_t)bm->h; + + for (i = 0; i < size; i++) { + bm->map[i] ^= BM_ALLBITS; + } +} + +#endif /* BITMAP_H */ 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 : diff --git a/src/trace/potrace/inkscape-potrace.h b/src/trace/potrace/inkscape-potrace.h new file mode 100644 index 0000000..c4c7bf4 --- /dev/null +++ b/src/trace/potrace/inkscape-potrace.h @@ -0,0 +1,145 @@ +// 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. + * + */ + +#ifndef __INKSCAPE_POTRACE_H__ +#define __INKSCAPE_POTRACE_H__ + +#include <trace/trace.h> +#include <potracelib.h> + +struct GrayMap_def; +typedef GrayMap_def GrayMap; + +namespace Inkscape { + +namespace Trace { + +namespace Potrace { + +enum TraceType + { + TRACE_BRIGHTNESS, + TRACE_BRIGHTNESS_MULTI, + TRACE_CANNY, + TRACE_QUANT, + TRACE_QUANT_COLOR, + TRACE_QUANT_MONO, + // Used in tradedialog.cpp + AUTOTRACE_SINGLE, + AUTOTRACE_MULTI, + AUTOTRACE_CENTERLINE + }; + + +class PotraceTracingEngine : public TracingEngine +{ + + public: + + /** + * + */ + PotraceTracingEngine(); + PotraceTracingEngine(TraceType traceType, bool invert, int quantizationNrColors, double brightnessThreshold, double brightnessFloor, double cannyHighThreshold, int multiScanNrColors, bool multiScanStack, bool multiScanSmooth, bool multiScanRemoveBackground); + + /** + * + */ + ~PotraceTracingEngine() override; + + /** + * This is the working method of this implementing class, 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> trace( + Glib::RefPtr<Gdk::Pixbuf> pixbuf) override; + + /** + * Abort the thread that is executing getPathDataFromPixbuf() + */ + void abort() override; + + /** + * + */ + Glib::RefPtr<Gdk::Pixbuf> preview(Glib::RefPtr<Gdk::Pixbuf> pixbuf); + + /** + * + */ + int keepGoing; + + std::vector<TracingEngineResult>traceGrayMap(GrayMap *grayMap); + + potrace_param_t *potraceParams; + TraceType traceType; + + //## do I invert at the end? + bool invert; + + //## Color-->b&w quantization + int quantizationNrColors; + + //## brightness items + double brightnessThreshold; + double brightnessFloor; //usually 0.0 + + //## canny items + double cannyHighThreshold; + + //## Color-->multiscan quantization + int multiScanNrColors; + bool multiScanStack; //do we tile or stack? + bool multiScanSmooth;//do we use gaussian filter? + bool multiScanRemoveBackground; //do we remove the bottom trace? + + private: + /** + * This is the actual wrapper of the call to Potrace. nodeCount + * returns the count of nodes created. May be NULL if ignored. + */ + std::string grayMapToPath(GrayMap *gm, long *nodeCount); + + std::vector<TracingEngineResult>traceBrightnessMulti(GdkPixbuf *pixbuf); + std::vector<TracingEngineResult>traceQuant(GdkPixbuf *pixbuf); + std::vector<TracingEngineResult>traceSingle(GdkPixbuf *pixbuf); + + +};//class PotraceTracingEngine + + + +} // namespace Potrace +} // namespace Trace +} // namespace Inkscape + + +#endif //__INKSCAPE_POTRACE_H__ + + +/* + 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 : diff --git a/src/trace/quantize.cpp b/src/trace/quantize.cpp new file mode 100644 index 0000000..228cb47 --- /dev/null +++ b/src/trace/quantize.cpp @@ -0,0 +1,599 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Quantization for Inkscape + * + * Authors: + * Stéphane Gimenez <dev@gim.name> + * + * Copyright (C) 2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <cassert> +#include <cstdio> +#include <cstdlib> +#include <new> +#include <glib.h> + +#include "pool.h" +#include "imagemap.h" +#include "quantize.h" + +typedef struct Ocnode_def Ocnode; + +/** + * an octree node datastructure + */ +struct Ocnode_def +{ + Ocnode *parent; // parent node + Ocnode **ref; // node's reference + Ocnode *child[8]; // children + int nchild; // number of children + int width; // width level of this node + RGB rgb; // rgb's prefix of that node + unsigned long weight; // number of pixels this node accounts for + unsigned long rs, gs, bs; // sum of pixels colors this node accounts for + int nleaf; // number of leaves under this node + unsigned long mi; // minimum impact +}; + +/* +-- algorithm principle: + +- inspired by the octree method, we associate a tree to a given color map + +- nodes in those trees have this shape: + + parent + | + color_prefix(stored in rgb):width + colors_sum(stored in rs,gs,bs)/weight + / | \ + child1 child2 child3 + +- (grayscale) trees associated to pixels with colors 87 = 0b1010111 and + 69 = 0b1000101 are: + + . . <-- roots of the trees + | | + 1010111:0 and 1000101:0 <-- color prefixes, written in binary form + 87/1 69/1 <-- color sums, written in decimal form + +- the result of merging the two trees is: + + . + | + 10:5 <----- longest common prefix and binary width + 156/2 <---. of the covered color range. + / \ | + 1000101:0 1010111:0 '- sum of colors and quantity of pixels + 69/1 87/1 this node accounts for + + one should consider three cases when two trees are to be merged: + - one tree range is included in the range of the other one, and the first + tree has to be inserted as a child (or merged with the corresponding + child) of the other. + - their ranges are the same, and their children have to be merged under + a single root. + - ranges have no intersection, and a fork node has to be created (like in + the given example). + +- a tree for an image is built dividing the image in 2 parts and merging + the trees obtained recursively for the two parts. a tree for a one pixel + part is a leaf like one of those which were given above. + +- last, this tree is reduced a specified number of leaves, deleting first + leaves with minimal impact i.e. [ weight * 2^(2*parentwidth) ] value : + a fair approximation of the impact a leaf removal would have on the final + result : it's the corresponding covered area times the square of the + introduced color distance. + + deletion of a node A below a node with only two children is done as + follows : + + - when the brother is a leaf, the brother is deleted as well, both nodes + are then represented by their father. + + | | + . ==> . + / \ + A . + + - otherwise the deletion of A deletes also his father, which plays no + role anymore: + + | | + . ==> \ + / \ | + A . . + / \ / \ + + in that way, every leaf removal operation really decreases the remaining + total number of leaves by one. + +- very last, color indexes are attributed to leaves; associated colors are + averages, computed from weight and color components sums. + +-- improvements to the usual octree method: + +- since this algorithm shall often be used to perform quantization using a + very low (2-16) set of colors and not with a usual 256 value, we choose + more carefully which nodes are to be deleted. + +- depth of leaves is not fixed to an arbitrary number (which should be 8 + when color components are in 0-255), so there is no need to go down to a + depth of 8 for each pixel (at full precision), unless it is really + required. + +- tree merging also fastens the overall tree building, and intermediate + processing could be done. + +- a huge optimization against the stupid removal algorithm (i.e. find a best + match over the whole tree, remove it and do it again) was implemented: + nodes are marked with the minimal impact of the removal of a leaf below + it. we proceed to the removal recursively. we stop when current removal + level is above the current node minimal, otherwise reached leaves are + removed, and every change over minimal impacts is propagated back to the + whole tree when the recursion ends. + +-- specific optimizations + +- pool allocation is used to allocate nodes (increased performance on large + images). + +*/ + +inline RGB operator>>(RGB rgb, int s) +{ + RGB res; + res.r = rgb.r >> s; res.g = rgb.g >> s; res.b = rgb.b >> s; + return res; +} +inline bool operator==(RGB rgb1, RGB rgb2) +{ + return (rgb1.r == rgb2.r && rgb1.g == rgb2.g && rgb1.b == rgb2.b); +} + +inline int childIndex(RGB rgb) +{ + return (((rgb.r)&1)<<2) | (((rgb.g)&1)<<1) | (((rgb.b)&1)); +} + +/** + * allocate a new node + */ +inline Ocnode *ocnodeNew(pool<Ocnode> *pool) +{ + Ocnode *node = pool->draw(); + node->ref = nullptr; + node->parent = nullptr; + node->nchild = 0; + for (auto & i : node->child) i = nullptr; + node->mi = 0; + return node; +} + +inline void ocnodeFree(pool<Ocnode> *pool, Ocnode *node) { + pool->drop(node); +} + + +/** + * free a full octree + */ +static void octreeDelete(pool<Ocnode> *pool, Ocnode *node) +{ + if (!node) return; + for (auto & i : node->child) + octreeDelete(pool, i); + ocnodeFree(pool, node); +} + +/** + * pretty-print an octree, debugging purposes + */ +#if 0 +static void ocnodePrint(Ocnode *node, int indent) +{ + if (!node) return; + printf("width:%d weight:%lu rgb:%6x nleaf:%d mi:%lu\n", + node->width, + node->weight, + (unsigned int)( + ((node->rs / node->weight) << 16) + + ((node->gs / node->weight) << 8) + + (node->bs / node->weight)), + node->nleaf, + node->mi + ); + for (int i = 0; i < 8; i++) if (node->child[i]) + { + for (int k = 0; k < indent; k++) printf(" ");//indentation + printf("[%d:%p] ", i, node->child[i]); + ocnodePrint(node->child[i], indent+2); + } +} + +void octreePrint(Ocnode *node) +{ + printf("<<octree>>\n"); + if (node) printf("[r:%p] ", node); ocnodePrint(node, 2); +} +#endif + +/** + * builds a single <rgb> color leaf at location <ref> + */ +static void ocnodeLeaf(pool<Ocnode> *pool, Ocnode **ref, RGB rgb) +{ + assert(ref); + Ocnode *node = ocnodeNew(pool); + node->width = 0; + node->rgb = rgb; + node->rs = rgb.r; node->gs = rgb.g; node->bs = rgb.b; + node->weight = 1; + node->nleaf = 1; + node->mi = 0; + node->ref = ref; + *ref = node; +} + +/** + * merge nodes <node1> and <node2> at location <ref> with parent <parent> + */ +static int octreeMerge(pool<Ocnode> *pool, Ocnode *parent, Ocnode **ref, Ocnode *node1, Ocnode *node2) +{ + assert(ref); + if (!node1 && !node2) return 0; + assert(node1 != node2); + if (parent && !*ref) parent->nchild++; + if (!node1) + { + *ref = node2; node2->ref = ref; node2->parent = parent; + return node2->nleaf; + } + if (!node2) + { + *ref = node1; node1->ref = ref; node1->parent = parent; + return node1->nleaf; + } + int dwitdth = node1->width - node2->width; + if (dwitdth > 0 && node1->rgb == node2->rgb >> dwitdth) + { + //place node2 below node1 + { *ref = node1; node1->ref = ref; node1->parent = parent; } + int i = childIndex(node2->rgb >> (dwitdth - 1)); + node1->rs += node2->rs; node1->gs += node2->gs; node1->bs += node2->bs; + node1->weight += node2->weight; + node1->mi = 0; + if (node1->child[i]) node1->nleaf -= node1->child[i]->nleaf; + node1->nleaf += + octreeMerge(pool, node1, &node1->child[i], node1->child[i], node2); + return node1->nleaf; + } + else if (dwitdth < 0 && node2->rgb == node1->rgb >> (-dwitdth)) + { + //place node1 below node2 + { *ref = node2; node2->ref = ref; node2->parent = parent; } + int i = childIndex(node1->rgb >> (-dwitdth - 1)); + node2->rs += node1->rs; node2->gs += node1->gs; node2->bs += node1->bs; + node2->weight += node1->weight; + node2->mi = 0; + if (node2->child[i]) node2->nleaf -= node2->child[i]->nleaf; + node2->nleaf += + octreeMerge(pool, node2, &node2->child[i], node2->child[i], node1); + return node2->nleaf; + } + else + { + //nodes have either no intersection or the same root + Ocnode *newnode; + newnode = ocnodeNew(pool); + newnode->rs = node1->rs + node2->rs; + newnode->gs = node1->gs + node2->gs; + newnode->bs = node1->bs + node2->bs; + newnode->weight = node1->weight + node2->weight; + { *ref = newnode; newnode->ref = ref; newnode->parent = parent; } + if (dwitdth == 0 && node1->rgb == node2->rgb) + { + //merge the nodes in <newnode> + newnode->width = node1->width; // == node2->width + newnode->rgb = node1->rgb; // == node2->rgb + newnode->nchild = 0; + newnode->nleaf = 0; + if (node1->nchild == 0 && node2->nchild == 0) + newnode->nleaf = 1; + else + for (int i = 0; i < 8; i++) + if (node1->child[i] || node2->child[i]) + newnode->nleaf += + octreeMerge(pool, newnode, &newnode->child[i], + node1->child[i], node2->child[i]); + ocnodeFree(pool, node1); ocnodeFree(pool, node2); + return newnode->nleaf; + } + else + { + //use <newnode> as a fork node with children <node1> and <node2> + int newwidth = + node1->width > node2->width ? node1->width : node2->width; + RGB rgb1 = node1->rgb >> (newwidth - node1->width); + RGB rgb2 = node2->rgb >> (newwidth - node2->width); + //according to the previous tests <rgb1> != <rgb2> before the loop + while (!(rgb1 == rgb2)) + { rgb1 = rgb1 >> 1; rgb2 = rgb2 >> 1; newwidth++; }; + newnode->width = newwidth; + newnode->rgb = rgb1; // == rgb2 + newnode->nchild = 2; + newnode->nleaf = node1->nleaf + node2->nleaf; + int i1 = childIndex(node1->rgb >> (newwidth - node1->width - 1)); + int i2 = childIndex(node2->rgb >> (newwidth - node2->width - 1)); + node1->parent = newnode; + node1->ref = &newnode->child[i1]; + newnode->child[i1] = node1; + node2->parent = newnode; + node2->ref = &newnode->child[i2]; + newnode->child[i2] = node2; + return newnode->nleaf; + } + } +} + +/** + * upatade mi value for leaves + */ +static inline void ocnodeMi(Ocnode *node) +{ + node->mi = node->parent ? + node->weight << (2 * node->parent->width) : 0; +} + +/** + * remove leaves whose prune impact value is lower than <lvl>. at most + * <count> leaves are removed, and <count> is decreased on each removal. + * all parameters including minimal impact values are regenerated. + */ +static void ocnodeStrip(pool<Ocnode> *pool, Ocnode **ref, int *count, unsigned long lvl) +{ + Ocnode *node = *ref; + if (!count || !node) return; + assert(ref == node->ref); + if (node->nchild == 0) // leaf node + { + if (!node->mi) ocnodeMi(node); //mi generation may be required + if (node->mi > lvl) return; //leaf is above strip level + ocnodeFree(pool, node); + *ref = nullptr; + (*count)--; + } + else + { + if (node->mi && node->mi > lvl) //node is above strip level + return; + node->nchild = 0; + node->nleaf = 0; + node->mi = 0; + Ocnode **lonelychild = nullptr; + for (auto & i : node->child) if (i) + { + ocnodeStrip(pool, &i, count, lvl); + if (i) + { + lonelychild = &i; + node->nchild++; + node->nleaf += i->nleaf; + if (!node->mi || node->mi > i->mi) + node->mi = i->mi; + } + } + // tree adjustments + if (node->nchild == 0) + { + (*count)++; + node->nleaf = 1; + ocnodeMi(node); + } + else if (node->nchild == 1) + { + if ((*lonelychild)->nchild == 0) + { + //remove the <lonelychild> leaf under a 1 child node + node->nchild = 0; + node->nleaf = 1; + ocnodeMi(node); + ocnodeFree(pool, *lonelychild); + *lonelychild = nullptr; + } + else + { + //make a bridge to <lonelychild> over a 1 child node + (*lonelychild)->parent = node->parent; + (*lonelychild)->ref = ref; + ocnodeFree(pool, node); + *ref = *lonelychild; + } + } + } +} + +/** + * reduce the leaves of an octree to a given number + */ +static void octreePrune(pool<Ocnode> *pool, Ocnode **ref, int ncolor) +{ + assert(ref); + assert(ncolor > 0); + //printf("pruning down to %d colors:\n", ncolor);//debug + int n = (*ref)->nleaf - ncolor; + if (!*ref || n <= 0) return; + while (n > 0) + { + //printf("removals to go: %10d\t", n);//debug + //printf("current prune impact: %10lu\n", (*ref)->mi);//debug + //calling strip with global minimum impact of the tree + ocnodeStrip(pool, ref, &n, (*ref)->mi); + } +} + +/** + * build an octree associated to the area of a color map <rgbmap>, + * included in the specified (x1,y1)--(x2,y2) rectangle. + */ +static void octreeBuildArea(pool<Ocnode> *pool, RgbMap *rgbmap, Ocnode **ref, + int x1, int y1, int x2, int y2, int ncolor) +{ + int dx = x2 - x1, dy = y2 - y1; + int xm = x1 + dx/2, ym = y1 + dy/2; + Ocnode *ref1 = nullptr; + Ocnode *ref2 = nullptr; + if (dx == 1 && dy == 1) + ocnodeLeaf(pool, ref, rgbmap->getPixel(rgbmap, x1, y1)); + else if (dx > dy) + { + octreeBuildArea(pool, rgbmap, &ref1, x1, y1, xm, y2, ncolor); + octreeBuildArea(pool, rgbmap, &ref2, xm, y1, x2, y2, ncolor); + octreeMerge(pool, nullptr, ref, ref1, ref2); + } + else + { + octreeBuildArea(pool, rgbmap, &ref1, x1, y1, x2, ym, ncolor); + octreeBuildArea(pool, rgbmap, &ref2, x1, ym, x2, y2, ncolor); + octreeMerge(pool, nullptr, ref, ref1, ref2); + } + + //octreePrune(ref, 2*ncolor); + //affects result quality for almost same performance :/ +} + +/** + * build an octree associated to the <rgbmap> color map, + * pruned to <ncolor> colors. + */ +static Ocnode *octreeBuild(pool<Ocnode> *pool, RgbMap *rgbmap, int ncolor) +{ + //create the octree + Ocnode *node = nullptr; + octreeBuildArea(pool, + rgbmap, &node, + 0, 0, rgbmap->width, rgbmap->height, ncolor + ); + + //prune the octree + octreePrune(pool, &node, ncolor); + + //octreePrint(node);//debug + + return node; +} + +/** + * compute the color palette associated to an octree. + */ +static void octreeIndex(Ocnode *node, RGB *rgbpal, int *index) +{ + if (!node) return; + if (node->nchild == 0) + { + rgbpal[*index].r = node->rs / node->weight; + rgbpal[*index].g = node->gs / node->weight; + rgbpal[*index].b = node->bs / node->weight; + (*index)++; + } + else + for (auto & i : node->child) + if (i) + octreeIndex(i, rgbpal, index); +} + +/** + * compute the squared distance between two colors + */ +static int distRGB(RGB rgb1, RGB rgb2) +{ + return + (rgb1.r - rgb2.r) * (rgb1.r - rgb2.r) + + (rgb1.g - rgb2.g) * (rgb1.g - rgb2.g) + + (rgb1.b - rgb2.b) * (rgb1.b - rgb2.b); +} + +/** + * find the index of closest color in a palette + */ +static int findRGB(RGB *rgbpal, int ncolor, RGB rgb) +{ + //assert(ncolor > 0); + //assert(rgbpal); + int index = -1, dist = 0; + for (int k = 0; k < ncolor; k++) + { + int d = distRGB(rgbpal[k], rgb); + if (index == -1 || d < dist) { dist = d; index = k; } + } + return index; +} + +/** + * (qsort) compare two colors for brightness + */ +static int compRGB(const void *a, const void *b) +{ + RGB *ra = (RGB *)a, *rb = (RGB *)b; + return (ra->r + ra->g + ra->b) - (rb->r + rb->g + rb->b); +} + +/** + * quantize an RGB image to a reduced number of colors. + */ +IndexedMap *rgbMapQuantize(RgbMap *rgbmap, int ncolor) +{ + assert(rgbmap); + assert(ncolor > 0); + + IndexedMap *newmap = nullptr; + + pool<Ocnode> pool; + + Ocnode *tree = nullptr; + try { + tree = octreeBuild(&pool, rgbmap, ncolor); + } + catch (std::bad_alloc &ex) { + //should do smthg else? + g_warning("rgbMapQuantize: Failed to allocate enough memory to during octreeBuild"); + } + + if (tree) { + RGB *rgbpal = new RGB[ncolor]; + int indexes = 0; + octreeIndex(tree, rgbpal, &indexes); + + octreeDelete(&pool, tree); + + // stacking with increasing contrasts + qsort((void *)rgbpal, indexes, sizeof(RGB), compRGB); + + // make the new map + newmap = IndexedMapCreate(rgbmap->width, rgbmap->height); + if (newmap) { + // fill in the color lookup table + for (int i = 0; i < indexes; i++) { + newmap->clut[i] = rgbpal[i]; + } + newmap->nrColors = indexes; + + // fill in new map pixels + for (int y = 0; y < rgbmap->height; y++) { + for (int x = 0; x < rgbmap->width; x++) { + RGB rgb = rgbmap->getPixel(rgbmap, x, y); + int index = findRGB(rgbpal, ncolor, rgb); + newmap->setPixel(newmap, x, y, index); + } + } + } + delete[] rgbpal; + } + + return newmap; +} diff --git a/src/trace/quantize.h b/src/trace/quantize.h new file mode 100644 index 0000000..43942f5 --- /dev/null +++ b/src/trace/quantize.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Quantization for Inkscape + * + * Authors: + * Stéphane Gimenez <dev@gim.name> + * + * Copyright (C) 2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef __QUANTIZE_H__ +#define __QUANTIZE_H__ + +#include "imagemap.h" + +/** + * Quantize an RGB image to a reduced number of colors. + */ +IndexedMap *rgbMapQuantize(RgbMap *rgbmap, int nrColors); + +#endif /* __QUANTIZE_H__ */ diff --git a/src/trace/siox.cpp b/src/trace/siox.cpp new file mode 100644 index 0000000..49ecfae --- /dev/null +++ b/src/trace/siox.cpp @@ -0,0 +1,1736 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Copyright 2005, 2006 by Gerald Friedland, Kristian Jantz and Lars Knipping + + Conversion to C++ for Inkscape by Bob Jamison + + Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#include "siox.h" + +#include <cmath> +#include <cstdarg> +#include <map> +#include <algorithm> +#include <cstdlib> + + +namespace org +{ + +namespace siox +{ + + + +//######################################################################## +//# C L A B +//######################################################################## + +/** + * Convert integer A, R, G, B values into an pixel value. + */ +static unsigned long getRGB(int a, int r, int g, int b) +{ + if (a<0) a=0; + else if (a>255) a=255; + + if (r<0) r=0; + else if (r>255) r=255; + + if (g<0) g=0; + else if (g>255) g=255; + + if (b<0) b=0; + else if (b>255) b=255; + + return (a<<24)|(r<<16)|(g<<8)|b; +} + + + +/** + * Convert float A, R, G, B values (0.0-1.0) into an pixel value. + */ +static unsigned long getRGB(float a, float r, float g, float b) +{ + return getRGB((int)(a * 256.0), + (int)(r * 256.0), + (int)(g * 256.0), + (int)(b * 256.0)); +} + + + +//######################################### +//# Root approximations for large speedup. +//# By njh! +//######################################### +static const int ROOT_TAB_SIZE = 16; +static float cbrt_table[ROOT_TAB_SIZE +1]; + +double CieLab::cbrt(double x) +{ + double y = cbrt_table[int(x*ROOT_TAB_SIZE )]; // assuming x \in [0, 1] + y = (2.0 * y + x/(y*y))/3.0; + y = (2.0 * y + x/(y*y))/3.0; // polish twice + return y; +} + +static float qn_table[ROOT_TAB_SIZE +1]; + +double CieLab::qnrt(double x) +{ + double y = qn_table[int(x*ROOT_TAB_SIZE )]; // assuming x \in [0, 1] + double Y = y*y; + y = (4.0*y + x/(Y*Y))/5.0; + Y = y*y; + y = (4.0*y + x/(Y*Y))/5.0; // polish twice + return y; +} + +double CieLab::pow24(double x) +{ + double onetwo = x*qnrt(x); + return onetwo*onetwo; +} + + +static bool _clab_inited_ = false; +void CieLab::init() +{ + if (!_clab_inited_) + { + cbrt_table[0] = pow(float(1)/float(ROOT_TAB_SIZE*2), 0.3333); + qn_table[0] = pow(float(1)/float(ROOT_TAB_SIZE*2), 0.2); + for(int i = 1; i < ROOT_TAB_SIZE +1; i++) + { + cbrt_table[i] = pow(float(i)/float(ROOT_TAB_SIZE), 0.3333); + qn_table[i] = pow(float(i)/float(ROOT_TAB_SIZE), 0.2); + } + _clab_inited_ = true; + } +} + + + +/** + * Construct this CieLab from an ARGB value + */ +CieLab::CieLab(unsigned long rgb) +{ + init(); + + int ir = (rgb>>16) & 0xff; + int ig = (rgb>> 8) & 0xff; + int ib = (rgb ) & 0xff; + + float fr = ((float)ir) / 255.0; + float fg = ((float)ig) / 255.0; + float fb = ((float)ib) / 255.0; + + //printf("fr:%f fg:%f fb:%f\n", fr, fg, fb); + if (fr > 0.04045) + //fr = (float) pow((fr + 0.055) / 1.055, 2.4); + fr = (float) pow24((fr + 0.055) / 1.055); + else + fr = fr / 12.92; + + if (fg > 0.04045) + //fg = (float) pow((fg + 0.055) / 1.055, 2.4); + fg = (float) pow24((fg + 0.055) / 1.055); + else + fg = fg / 12.92; + + if (fb > 0.04045) + //fb = (float) pow((fb + 0.055) / 1.055, 2.4); + fb = (float) pow24((fb + 0.055) / 1.055); + else + fb = fb / 12.92; + + // Use white = D65 + const float x = fr * 0.4124 + fg * 0.3576 + fb * 0.1805; + const float y = fr * 0.2126 + fg * 0.7152 + fb * 0.0722; + const float z = fr * 0.0193 + fg * 0.1192 + fb * 0.9505; + + float vx = x / 0.95047; + float vy = y; + float vz = z / 1.08883; + + //printf("vx:%f vy:%f vz:%f\n", vx, vy, vz); + if (vx > 0.008856) + //vx = (float) pow(vx, 0.3333); + vx = (float) cbrt(vx); + else + vx = (7.787 * vx) + (16.0 / 116.0); + + if (vy > 0.008856) + //vy = (float) pow(vy, 0.3333); + vy = (float) cbrt(vy); + else + vy = (7.787 * vy) + (16.0 / 116.0); + + if (vz > 0.008856) + //vz = (float) pow(vz, 0.3333); + vz = (float) cbrt(vz); + else + vz = (7.787 * vz) + (16.0 / 116.0); + + C = 0; + L = 116.0 * vy - 16.0; + A = 500.0 * (vx - vy); + B = 200.0 * (vy - vz); +} + + + +/** + * Return this CieLab's value converted to an ARGB value + */ +unsigned long CieLab::toRGB() +{ + float vy = (L + 16.0) / 116.0; + float vx = A / 500.0 + vy; + float vz = vy - B / 200.0; + + float vx3 = vx * vx * vx; + float vy3 = vy * vy * vy; + float vz3 = vz * vz * vz; + + if (vy3 > 0.008856) + vy = vy3; + else + vy = (vy - 16.0 / 116.0) / 7.787; + + if (vx3 > 0.008856) + vx = vx3; + else + vx = (vx - 16.0 / 116.0) / 7.787; + + if (vz3 > 0.008856) + vz = vz3; + else + vz = (vz - 16.0 / 116.0) / 7.787; + + vx *= 0.95047; //use white = D65 + vz *= 1.08883; + + float vr =(float)(vx * 3.2406 + vy * -1.5372 + vz * -0.4986); + float vg =(float)(vx * -0.9689 + vy * 1.8758 + vz * 0.0415); + float vb =(float)(vx * 0.0557 + vy * -0.2040 + vz * 1.0570); + + if (vr > 0.0031308) + vr = (float)(1.055 * pow(vr, (1.0 / 2.4)) - 0.055); + else + vr = 12.92 * vr; + + if (vg > 0.0031308) + vg = (float)(1.055 * pow(vg, (1.0 / 2.4)) - 0.055); + else + vg = 12.92 * vg; + + if (vb > 0.0031308) + vb = (float)(1.055 * pow(vb, (1.0 / 2.4)) - 0.055); + else + vb = 12.92 * vb; + + return getRGB(0.0, vr, vg, vb); +} + + +/** + * Squared Euclidean distance between this and another color + */ +float CieLab::diffSq(const CieLab &other) +{ + float sum=0.0; + sum += (L - other.L) * (L - other.L); + sum += (A - other.A) * (A - other.A); + sum += (B - other.B) * (B - other.B); + return sum; +} + +/** + * Computes squared euclidean distance in CieLab space for two colors + * given as RGB values. + */ +float CieLab::diffSq(unsigned int rgb1, unsigned int rgb2) +{ + CieLab c1(rgb1); + CieLab c2(rgb2); + float euclid = c1.diffSq(c2); + return euclid; +} + + +/** + * Computes squared euclidean distance in CieLab space for two colors + * given as RGB values. + */ +float CieLab::diff(unsigned int rgb0, unsigned int rgb1) +{ + return (float) sqrt(diffSq(rgb0, rgb1)); +} + + + +//######################################################################## +//# T U P E L +//######################################################################## + +/** + * Helper class for storing the minimum distances to a cluster centroid + * in background and foreground and the index to the centroids in each + * signature for a given color. + */ +class Tupel { +public: + + Tupel() + { + minBgDist = 0.0f; + indexMinBg = 0; + minFgDist = 0.0f; + indexMinFg = 0; + } + Tupel(float minBgDistArg, long indexMinBgArg, + float minFgDistArg, long indexMinFgArg) + { + minBgDist = minBgDistArg; + indexMinBg = indexMinBgArg; + minFgDist = minFgDistArg; + indexMinFg = indexMinFgArg; + } + Tupel(const Tupel &other) + { + minBgDist = other.minBgDist; + indexMinBg = other.indexMinBg; + minFgDist = other.minFgDist; + indexMinFg = other.indexMinFg; + } + Tupel &operator=(const Tupel &other) + = default; + virtual ~Tupel() + = default; + + float minBgDist; + long indexMinBg; + float minFgDist; + long indexMinFg; + + }; + + + +//######################################################################## +//# S I O X I M A G E +//######################################################################## + +/** + * Create an image with the given width and height + */ +SioxImage::SioxImage(unsigned int widthArg, unsigned int heightArg) +{ + init(widthArg, heightArg); +} + +/** + * Copy constructor + */ +SioxImage::SioxImage(const SioxImage &other) +{ + pixdata = nullptr; + cmdata = nullptr; + assign(other); +} + +/** + * Assignment + */ +SioxImage &SioxImage::operator=(const SioxImage &other) +{ + assign(other); + return *this; +} + + +/** + * Clean up after use. + */ +SioxImage::~SioxImage() +{ + if (pixdata) delete[] pixdata; + if (cmdata) delete[] cmdata; +} + +/** + * Error logging + */ +void SioxImage::error(const char *fmt, ...) +{ + char msgbuf[256]; + va_list args; + va_start(args, fmt); + vsnprintf(msgbuf, 255, fmt, args); + va_end(args) ; +#ifdef HAVE_GLIB + g_warning("SioxImage error: %s\n", msgbuf); +#else + fprintf(stderr, "SioxImage error: %s\n", msgbuf); +#endif +} + + +/** + * Returns true if the previous operation on this image + * was successful, else false. + */ +bool SioxImage::isValid() +{ + return valid; +} + +/** + * Sets whether an operation was successful, and whether + * this image should be considered a valid one. + * was successful, else false. + */ +void SioxImage::setValid(bool val) +{ + valid = val; +} + + +/** + * Set a pixel at the x,y coordinates to the given value. + * If the coordinates are out of range, do nothing. + */ +void SioxImage::setPixel(unsigned int x, + unsigned int y, + unsigned int pixval) +{ + if (x >= width || y >= height) + { + error("setPixel: out of bounds (%d,%d)/(%d,%d)", + x, y, width, height); + return; + } + unsigned long offset = width * y + x; + pixdata[offset] = pixval; +} + +/** + * Set a pixel at the x,y coordinates to the given r, g, b values. + * If the coordinates are out of range, do nothing. + */ +void SioxImage::setPixel(unsigned int x, unsigned int y, + unsigned int a, + unsigned int r, + unsigned int g, + unsigned int b) +{ + if (x >= width || y >= height) + { + error("setPixel: out of bounds (%d,%d)/(%d,%d)", + x, y, width, height); + return; + } + unsigned long offset = width * y + x; + unsigned int pixval = ((a << 24) & 0xff000000) | + ((r << 16) & 0x00ff0000) | + ((g << 8) & 0x0000ff00) | + ((b ) & 0x000000ff); + pixdata[offset] = pixval; +} + + + +/** + * Get a pixel at the x,y coordinates given. If + * the coordinates are out of range, return 0; + */ +unsigned int SioxImage::getPixel(unsigned int x, unsigned int y) +{ + if (x >= width || y >= height) + { + error("getPixel: out of bounds (%d,%d)/(%d,%d)", + x, y, width, height); + return 0L; + } + unsigned long offset = width * y + x; + return pixdata[offset]; +} + +/** + * Return the image data buffer + */ +unsigned int *SioxImage::getImageData() +{ + return pixdata; +} + +/** + * Set a confidence value at the x,y coordinates to the given value. + * If the coordinates are out of range, do nothing. + */ +void SioxImage::setConfidence(unsigned int x, + unsigned int y, + float confval) +{ + if (x >= width || y >= height) + { + error("setConfidence: out of bounds (%d,%d)/(%d,%d)", + x, y, width, height); + return; + } + unsigned long offset = width * y + x; + cmdata[offset] = confval; +} + +/** + * Get a confidence valueat the x,y coordinates given. If + * the coordinates are out of range, return 0; + */ +float SioxImage::getConfidence(unsigned int x, unsigned int y) +{ + if (x >= width || y >= height) + { + error("getConfidence: out of bounds (%d,%d)/(%d,%d)", + x, y, width, height); + return 0.0; + } + unsigned long offset = width * y + x; + return cmdata[offset]; +} + +/** + * Return the confidence data buffer + */ +float *SioxImage::getConfidenceData() +{ + return cmdata; +} + + +/** + * Return the width of this image + */ +int SioxImage::getWidth() +{ + return width; +} + +/** + * Return the height of this image + */ +int SioxImage::getHeight() +{ + return height; +} + +/** + * Initialize values. Used by constructors + */ +void SioxImage::init(unsigned int widthArg, unsigned int heightArg) +{ + valid = true; + width = widthArg; + height = heightArg; + imageSize = width * height; + pixdata = new unsigned int[imageSize]; + cmdata = new float[imageSize]; + for (unsigned long i=0 ; i<imageSize ; i++) + { + pixdata[i] = 0; + cmdata[i] = 0.0; + } +} + +/** + * Assign values to that of another + */ +void SioxImage::assign(const SioxImage &other) +{ + if (pixdata) delete[] pixdata; + if (cmdata) delete[] cmdata; + valid = other.valid; + width = other.width; + height = other.height; + imageSize = width * height; + pixdata = new unsigned int[imageSize]; + cmdata = new float[imageSize]; + for (unsigned long i=0 ; i<imageSize ; i++) + { + pixdata[i] = other.pixdata[i]; + cmdata[i] = other.cmdata[i]; + } +} + + +/** + * Write the image to a PPM file + */ +bool SioxImage::writePPM(const std::string &fileName) +{ + + FILE *f = fopen(fileName.c_str(), "wb"); + if (!f) + return false; + + fprintf(f, "P6 %u %u 255\n", width, height); + + for (unsigned int y=0 ; y<height; y++) + { + for (unsigned int x=0 ; x<width ; x++) + { + unsigned int rgb = getPixel(x, y); + //unsigned int alpha = (rgb>>24) & 0xff; + unsigned int r = ((rgb>>16) & 0xff); + unsigned int g = ((rgb>> 8) & 0xff); + unsigned int b = ((rgb ) & 0xff); + fputc((unsigned char) r, f); + fputc((unsigned char) g, f); + fputc((unsigned char) b, f); + } + } + fclose(f); + return true; +} + + +#ifdef HAVE_GLIB + +/** + * Create an image from a GdkPixbuf + */ +SioxImage::SioxImage(GdkPixbuf *buf) +{ + if (!buf) + return; + + unsigned int width = gdk_pixbuf_get_width(buf); + unsigned int height = gdk_pixbuf_get_height(buf); + init(width, height); //DO THIS NOW!! + + + guchar *pixldata = gdk_pixbuf_get_pixels(buf); + int rowstride = gdk_pixbuf_get_rowstride(buf); + int n_channels = gdk_pixbuf_get_n_channels(buf); + + //### Fill in the cells with RGB values + int row = 0; + for (unsigned int y=0 ; y<height ; y++) + { + guchar *p = pixldata + row; + for (unsigned int x=0 ; x<width ; x++) + { + int r = (int)p[0]; + int g = (int)p[1]; + int b = (int)p[2]; + int alpha = (int)p[3]; + + setPixel(x, y, alpha, r, g, b); + p += n_channels; + } + row += rowstride; + } + +} + + +/** + * Create a GdkPixbuf from this image + */ +GdkPixbuf *SioxImage::getGdkPixbuf() +{ + bool has_alpha = true; + int n_channels = has_alpha ? 4 : 3; + + guchar *pixdata = (guchar *) + malloc(sizeof(guchar) * width * height * n_channels); + if (!pixdata) + { + error("SioxImage::getGdkPixbuf: can not allocate memory for %d x %d x %d image.", width, height, n_channels); + return nullptr; + } + + int rowstride = width * n_channels; + + GdkPixbuf *buf = gdk_pixbuf_new_from_data(pixdata, + GDK_COLORSPACE_RGB, + has_alpha, 8, width, height, + rowstride, (GdkPixbufDestroyNotify)free, nullptr); + + //### Fill in the cells with RGB values + int row = 0; + for (unsigned int y=0 ; y < height ; y++) + { + guchar *p = pixdata + row; + for (unsigned x=0 ; x < width ; x++) + { + unsigned int rgb = getPixel(x, y); + p[0] = (rgb >> 16) & 0xff;//r + p[1] = (rgb >> 8) & 0xff;//g + p[2] = (rgb ) & 0xff;//b + if ( n_channels > 3 ) + { + p[3] = (rgb >> 24) & 0xff;//a + } + p += n_channels; + } + row += rowstride; + } + return buf; +} + +#endif /* GLIB */ + + + + +//######################################################################## +//# S I O X +//######################################################################## + +//############## +//## PUBLIC +//############## + +/** + * Confidence corresponding to a certain foreground region (equals one). + */ +const float Siox::CERTAIN_FOREGROUND_CONFIDENCE=1.0f; + +/** + * Confidence for a region likely being foreground. + */ +const float Siox::FOREGROUND_CONFIDENCE=0.8f; + +/** + * Confidence for foreground or background type being equally likely. + */ +const float Siox::UNKNOWN_REGION_CONFIDENCE=0.5f; + +/** + * Confidence for a region likely being background. + */ +const float Siox::BACKGROUND_CONFIDENCE=0.1f; + +/** + * Confidence corresponding to a certain background reagion (equals zero). + */ +const float Siox::CERTAIN_BACKGROUND_CONFIDENCE=0.0f; + +/** + * Construct a Siox engine + */ +Siox::Siox() : + sioxObserver(nullptr), + keepGoing(true), + width(0), + height(0), + pixelCount(0), + image(nullptr), + cm(nullptr), + labelField(nullptr) +{ + init(); +} + +/** + * Construct a Siox engine + */ +Siox::Siox(SioxObserver *observer) : + sioxObserver(observer), + keepGoing(true), + width(0), + height(0), + pixelCount(0), + image(nullptr), + cm(nullptr), + labelField(nullptr) +{ + init(); +} + + +/** + * + */ +Siox::~Siox() +{ + cleanup(); +} + + +/** + * Error logging + */ +void Siox::error(const char *fmt, ...) +{ + char msgbuf[256]; + va_list args; + va_start(args, fmt); + vsnprintf(msgbuf, 255, fmt, args); + va_end(args) ; +#ifdef HAVE_GLIB + g_warning("Siox error: %s\n", msgbuf); +#else + fprintf(stderr, "Siox error: %s\n", msgbuf); +#endif +} + +/** + * Trace logging + */ +void Siox::trace(const char *fmt, ...) +{ + char msgbuf[256]; + va_list args; + va_start(args, fmt); + vsnprintf(msgbuf, 255, fmt, args); + va_end(args) ; +#ifdef HAVE_GLIB + g_message("Siox: %s\n", msgbuf); +#else + fprintf(stdout, "Siox: %s\n", msgbuf); +#endif +} + + + +/** + * Progress reporting + */ +bool Siox::progressReport(float percentCompleted) +{ + if (!sioxObserver) + return true; + + bool ret = sioxObserver->progress(percentCompleted); + + if (!ret) + { + trace("User selected abort"); + keepGoing = false; + } + + return ret; +} + + + + +/** + * Extract the foreground of the original image, according + * to the values in the confidence matrix. + */ +SioxImage Siox::extractForeground(const SioxImage &originalImage, + unsigned int backgroundFillColor) +{ + trace("### Start"); + + init(); + keepGoing = true; + + SioxImage workImage = originalImage; + + //# fetch some info from the image + width = workImage.getWidth(); + height = workImage.getHeight(); + pixelCount = width * height; + image = workImage.getImageData(); + cm = workImage.getConfidenceData(); + labelField = new int[pixelCount]; + + trace("### Creating signatures"); + + //#### create color signatures + std::vector<CieLab> knownBg; + std::vector<CieLab> knownFg; + CieLab *imageClab = new CieLab[pixelCount]; + for (unsigned long i=0 ; i<pixelCount ; i++) + { + float conf = cm[i]; + unsigned int pix = image[i]; + CieLab lab(pix); + imageClab[i] = lab; + if (conf <= BACKGROUND_CONFIDENCE) + knownBg.push_back(lab); + else if (conf >= FOREGROUND_CONFIDENCE) + knownFg.push_back(lab); + } + + /* + std::vector<CieLab> imageClab; + for (int y = 0 ; y < workImage.getHeight() ; y++) + for (int x = 0 ; x < workImage.getWidth() ; x++) + { + float cm = workImage.getConfidence(x, y); + unsigned int pix = workImage.getPixel(x, y); + CieLab lab(pix); + imageClab.push_back(lab); + if (cm <= BACKGROUND_CONFIDENCE) + knownBg.push_back(lab); //note: uses CieLab(rgb) + else if (cm >= FOREGROUND_CONFIDENCE) + knownFg.push_back(lab); + } + */ + + if (!progressReport(10.0)) + { + error("User aborted"); + workImage.setValid(false); + delete[] imageClab; + delete[] labelField; + return workImage; + } + + trace("knownBg:%u knownFg:%u", static_cast<unsigned int>(knownBg.size()), static_cast<unsigned int>(knownFg.size())); + + + std::vector<CieLab> bgSignature ; + if (!colorSignature(knownBg, bgSignature, 3)) + { + error("Could not create background signature"); + workImage.setValid(false); + delete[] imageClab; + delete[] labelField; + return workImage; + } + + if (!progressReport(30.0)) + { + error("User aborted"); + workImage.setValid(false); + delete[] imageClab; + delete[] labelField; + return workImage; + } + + + std::vector<CieLab> fgSignature ; + if (!colorSignature(knownFg, fgSignature, 3)) + { + error("Could not create foreground signature"); + workImage.setValid(false); + delete[] imageClab; + delete[] labelField; + return workImage; + } + + //trace("### bgSignature:%d", bgSignature.size()); + + if (bgSignature.empty()) + { + // segmentation impossible + error("Signature size is < 1. Segmentation is impossible"); + workImage.setValid(false); + delete[] imageClab; + delete[] labelField; + return workImage; + } + + if (!progressReport(30.0)) + { + error("User aborted"); + workImage.setValid(false); + delete[] imageClab; + delete[] labelField; + return workImage; + } + + + // classify using color signatures, + // classification cached in hashmap for drb and speedup purposes + trace("### Analyzing image"); + + std::map<unsigned int, Tupel> hs; + + unsigned int progressResolution = pixelCount / 10; + + for (unsigned int i=0; i<pixelCount; i++) + { + if (i % progressResolution == 0) + { + float progress = + 30.0 + 60.0 * (float)i / (float)pixelCount; + //trace("### progress:%f", progress); + if (!progressReport(progress)) + { + error("User aborted"); + delete[] imageClab; + delete[] labelField; + workImage.setValid(false); + return workImage; + } + } + + if (cm[i] >= FOREGROUND_CONFIDENCE) + { + cm[i] = CERTAIN_FOREGROUND_CONFIDENCE; + } + else if (cm[i] <= BACKGROUND_CONFIDENCE) + { + cm[i] = CERTAIN_BACKGROUND_CONFIDENCE; + } + else // somewhere in between + { + bool isBackground = true; + std::map<unsigned int, Tupel>::iterator iter = hs.find(i); + if (iter != hs.end()) //found + { + Tupel tupel = iter->second; + isBackground = tupel.minBgDist <= tupel.minFgDist; + } + else + { + CieLab lab = imageClab[i]; + float minBg = lab.diffSq(bgSignature[0]); + int minIndex = 0; + for (unsigned int j=1; j<bgSignature.size() ; j++) + { + float d = lab.diffSq(bgSignature[j]); + if (d<minBg) + { + minBg = d; + minIndex = j; + } + } + Tupel tupel(0.0f, 0, 0.0f, 0); + tupel.minBgDist = minBg; + tupel.indexMinBg = minIndex; + float minFg = 1.0e6f; + minIndex = -1; + for (unsigned int j = 0 ; j < fgSignature.size() ; j++) + { + float d = lab.diffSq(fgSignature[j]); + if (d < minFg) + { + minFg = d; + minIndex = j; + } + } + tupel.minFgDist = minFg; + tupel.indexMinFg = minIndex; + if (fgSignature.empty()) + { + isBackground = (minBg <= clusterSize); + // remove next line to force behaviour of old algorithm + //error("foreground signature does not exist"); + //delete[] labelField; + //workImage.setValid(false); + //return workImage; + } + else + { + isBackground = minBg < minFg; + } + hs[image[i]] = tupel; + } + + if (isBackground) + cm[i] = CERTAIN_BACKGROUND_CONFIDENCE; + else + cm[i] = CERTAIN_FOREGROUND_CONFIDENCE; + } + } + + + delete[] imageClab; + + trace("### postProcessing"); + + + //#### postprocessing + smooth(cm, width, height, 0.333f, 0.333f, 0.333f); // average + normalizeMatrix(cm, pixelCount); + erode(cm, width, height); + keepOnlyLargeComponents(UNKNOWN_REGION_CONFIDENCE, 1.0/*sizeFactorToKeep*/); + + //for (int i=0; i < 2/*smoothness*/; i++) + // smooth(cm, width, height, 0.333f, 0.333f, 0.333f); // average + + normalizeMatrix(cm, pixelCount); + + for (unsigned int i=0; i < pixelCount; i++) + { + if (cm[i] >= UNKNOWN_REGION_CONFIDENCE) + cm[i] = CERTAIN_FOREGROUND_CONFIDENCE; + else + cm[i] = CERTAIN_BACKGROUND_CONFIDENCE; + } + + keepOnlyLargeComponents(UNKNOWN_REGION_CONFIDENCE, 1.5/*sizeFactorToKeep*/); + fillColorRegions(); + dilate(cm, width, height); + + if (!progressReport(100.0)) + { + error("User aborted"); + delete[] labelField; + workImage.setValid(false); + return workImage; + } + + + //#### We are done. Now clear everything but the background + for (unsigned long i = 0; i<pixelCount ; i++) + { + float conf = cm[i]; + if (conf < FOREGROUND_CONFIDENCE) + image[i] = backgroundFillColor; + } + + delete[] labelField; + + trace("### Done"); + keepGoing = false; + return workImage; +} + + + +//############## +//## PRIVATE +//############## + +/** + * Initialize the Siox engine to its 'pristine' state. + * Performed at the beginning of extractForeground(). + */ +void Siox::init() +{ + limits[0] = 0.64f; + limits[1] = 1.28f; + limits[2] = 2.56f; + + float negLimits[3]; + negLimits[0] = -limits[0]; + negLimits[1] = -limits[1]; + negLimits[2] = -limits[2]; + + clusterSize = sqrEuclideanDist(limits, 3, negLimits); +} + + +/** + * Clean up any debris from processing. + */ +void Siox::cleanup() +{ +} + + + + +/** + * Stage 1 of the color signature work. 'dims' will be either + * 2 for grays, or 3 for colors + */ +void Siox::colorSignatureStage1(CieLab *points, + unsigned int leftBase, + unsigned int rightBase, + unsigned int recursionDepth, + unsigned int *clusterCount, + const unsigned int dims) +{ + + unsigned int currentDim = recursionDepth % dims; + CieLab point = points[leftBase]; + float min = point(currentDim); + float max = min; + + for (unsigned int i = leftBase + 1; i < rightBase ; i++) + { + point = points[i]; + float curval = point(currentDim); + if (curval < min) min = curval; + if (curval > max) max = curval; + } + + //Do the Rubner-rule split (sounds like a dance) + if (max - min > limits[currentDim]) + { + float pivotPoint = (min + max) / 2.0; //average + unsigned int left = leftBase; + unsigned int right = rightBase - 1; + + //# partition points according to the dimension + while (true) + { + while ( true ) + { + point = points[left]; + if (point(currentDim) > pivotPoint) + break; + left++; + } + while ( true ) + { + point = points[right]; + if (point(currentDim) <= pivotPoint) + break; + right--; + } + + if (left > right) + break; + + point = points[left]; + points[left] = points[right]; + points[right] = point; + + left++; + right--; + } + + //# Recurse and create sub-trees + colorSignatureStage1(points, leftBase, left, + recursionDepth + 1, clusterCount, dims); + colorSignatureStage1(points, left, rightBase, + recursionDepth + 1, clusterCount, dims); + } + else + { + //create a leaf + CieLab newpoint; + + newpoint.C = rightBase - leftBase; + + for (; leftBase < rightBase ; leftBase++) + { + newpoint.add(points[leftBase]); + } + + //printf("clusters:%d\n", *clusters); + + if (newpoint.C != 0) + newpoint.mul(1.0 / (float)newpoint.C); + points[*clusterCount] = newpoint; + (*clusterCount)++; + } +} + + + +/** + * Stage 2 of the color signature work + */ +void Siox::colorSignatureStage2(CieLab *points, + unsigned int leftBase, + unsigned int rightBase, + unsigned int recursionDepth, + unsigned int *clusterCount, + const float threshold, + const unsigned int dims) +{ + unsigned int currentDim = recursionDepth % dims; + CieLab point = points[leftBase]; + float min = point(currentDim); + float max = min; + + for (unsigned int i = leftBase+ 1; i < rightBase; i++) + { + point = points[i]; + float curval = point(currentDim); + if (curval < min) min = curval; + if (curval > max) max = curval; + } + + //Do the Rubner-rule split (sounds like a dance) + if (max - min > limits[currentDim]) + { + float pivotPoint = (min + max) / 2.0; //average + unsigned int left = leftBase; + unsigned int right = rightBase - 1; + + //# partition points according to the dimension + while (true) + { + while ( true ) + { + point = points[left]; + if (point(currentDim) > pivotPoint) + break; + left++; + } + while ( true ) + { + point = points[right]; + if (point(currentDim) <= pivotPoint) + break; + right--; + } + + if (left > right) + break; + + point = points[left]; + points[left] = points[right]; + points[right] = point; + + left++; + right--; + } + + //# Recurse and create sub-trees + colorSignatureStage2(points, leftBase, left, + recursionDepth + 1, clusterCount, threshold, dims); + colorSignatureStage2(points, left, rightBase, + recursionDepth + 1, clusterCount, threshold, dims); + } + else + { + //### Create a leaf + unsigned int sum = 0; + for (unsigned int i = leftBase; i < rightBase; i++) + sum += points[i].C; + + if ((float)sum >= threshold) + { + float scale = (float)(rightBase - leftBase); + CieLab newpoint; + + for (; leftBase < rightBase; leftBase++) + newpoint.add(points[leftBase]); + + if (scale != 0.0) + newpoint.mul(1.0 / scale); + points[*clusterCount] = newpoint; + (*clusterCount)++; + } + } +} + + + +/** + * Main color signature method + */ +bool Siox::colorSignature(const std::vector<CieLab> &inputVec, + std::vector<CieLab> &result, + const unsigned int dims) +{ + + unsigned int length = inputVec.size(); + + if (length < 1) // no error. just don't do anything + return true; + + CieLab *input = new CieLab [length]; + + if (!input) + { + error("Could not allocate buffer for signature"); + return false; + } + for (unsigned int i=0 ; i < length ; i++) + { + input[i] = inputVec[i]; + } + + unsigned int stage1length = 0; + colorSignatureStage1(input, 0, length, 0, &stage1length, dims); + + unsigned int stage2length = 0; + colorSignatureStage2(input, 0, stage1length, 0, &stage2length, length * 0.001, dims); + + result.clear(); + for (unsigned int i=0 ; i < stage2length ; i++) + { + result.push_back(input[i]); + } + + delete[] input; + + return true; +} + + + +/** + * + */ +void Siox::keepOnlyLargeComponents(float threshold, + double sizeFactorToKeep) +{ + for (unsigned long idx = 0 ; idx<pixelCount ; idx++) + labelField[idx] = -1; + + int curlabel = 0; + int maxregion= 0; + int maxblob = 0; + + // slow but easy to understand: + std::vector<int> labelSizes; + for (unsigned long i=0 ; i<pixelCount ; i++) + { + int regionCount = 0; + if (labelField[i] == -1 && cm[i] >= threshold) + { + regionCount = depthFirstSearch(i, threshold, curlabel++); + labelSizes.push_back(regionCount); + } + + if (regionCount>maxregion) + { + maxregion = regionCount; + maxblob = curlabel-1; + } + } + + for (unsigned int i=0 ; i<pixelCount ; i++) + { + if (labelField[i] != -1) + { + // remove if the component is to small + if (labelSizes[labelField[i]] * sizeFactorToKeep < maxregion) + cm[i] = CERTAIN_BACKGROUND_CONFIDENCE; + + // add maxblob always to foreground + if (labelField[i] == maxblob) + cm[i] = CERTAIN_FOREGROUND_CONFIDENCE; + } + } + +} + + + +int Siox::depthFirstSearch(int startPos, + float threshold, int curLabel) +{ + // stores positions of labeled pixels, where the neighbours + // should still be checked for processing: + + //trace("startPos:%d threshold:%f curLabel:%d", + // startPos, threshold, curLabel); + + std::vector<int> pixelsToVisit; + int componentSize = 0; + + if (labelField[startPos]==-1 && cm[startPos]>=threshold) + { + labelField[startPos] = curLabel; + componentSize++; + pixelsToVisit.push_back(startPos); + } + + + while (!pixelsToVisit.empty()) + { + int pos = pixelsToVisit[pixelsToVisit.size() - 1]; + pixelsToVisit.erase(pixelsToVisit.end() - 1); + unsigned int x = pos % width; + unsigned int y = pos / width; + + // check all four neighbours + int left = pos - 1; + if (((int)x)-1>=0 && labelField[left]==-1 && cm[left]>=threshold) + { + labelField[left]=curLabel; + componentSize++; + pixelsToVisit.push_back(left); + } + + int right = pos + 1; + if (x+1 < width && labelField[right]==-1 && cm[right]>=threshold) + { + labelField[right]=curLabel; + componentSize++; + pixelsToVisit.push_back(right); + } + + int top = pos - width; + if (((int)y)-1>=0 && labelField[top]==-1 && cm[top]>=threshold) + { + labelField[top]=curLabel; + componentSize++; + pixelsToVisit.push_back(top); + } + + int bottom = pos + width; + if (y+1 < height && labelField[bottom]==-1 + && cm[bottom]>=threshold) + { + labelField[bottom]=curLabel; + componentSize++; + pixelsToVisit.push_back(bottom); + } + + } + return componentSize; +} + + + +/** + * + */ +void Siox::fillColorRegions() +{ + for (unsigned long idx = 0 ; idx<pixelCount ; idx++) + labelField[idx] = -1; + + //int maxRegion=0; // unused now + std::vector<int> pixelsToVisit; + for (unsigned long i=0; i<pixelCount; i++) + { // for all pixels + if (labelField[i]!=-1 || cm[i]<UNKNOWN_REGION_CONFIDENCE) + { + continue; // already visited or bg + } + + unsigned int origColor = image[i]; + unsigned long curLabel = i+1; + labelField[i] = curLabel; + cm[i] = CERTAIN_FOREGROUND_CONFIDENCE; + + // int componentSize = 1; + pixelsToVisit.push_back(i); + // depth first search to fill region + while (!pixelsToVisit.empty()) + { + int pos = pixelsToVisit[pixelsToVisit.size() - 1]; + pixelsToVisit.erase(pixelsToVisit.end() - 1); + unsigned int x=pos % width; + unsigned int y=pos / width; + // check all four neighbours + int left = pos-1; + if (((int)x)-1 >= 0 && labelField[left] == -1 + && CieLab::diff(image[left], origColor)<1.0) + { + labelField[left]=curLabel; + cm[left]=CERTAIN_FOREGROUND_CONFIDENCE; + // ++componentSize; + pixelsToVisit.push_back(left); + } + int right = pos+1; + if (x+1 < width && labelField[right]==-1 + && CieLab::diff(image[right], origColor)<1.0) + { + labelField[right]=curLabel; + cm[right]=CERTAIN_FOREGROUND_CONFIDENCE; + // ++componentSize; + pixelsToVisit.push_back(right); + } + int top = pos - width; + if (((int)y)-1>=0 && labelField[top]==-1 + && CieLab::diff(image[top], origColor)<1.0) + { + labelField[top]=curLabel; + cm[top]=CERTAIN_FOREGROUND_CONFIDENCE; + // ++componentSize; + pixelsToVisit.push_back(top); + } + int bottom = pos + width; + if (y+1 < height && labelField[bottom]==-1 + && CieLab::diff(image[bottom], origColor)<1.0) + { + labelField[bottom]=curLabel; + cm[bottom]=CERTAIN_FOREGROUND_CONFIDENCE; + // ++componentSize; + pixelsToVisit.push_back(bottom); + } + } + //if (componentSize>maxRegion) { + // maxRegion=componentSize; + //} + } +} + + + + +/** + * Applies the morphological dilate operator. + * + * Can be used to close small holes in the given confidence matrix. + */ +void Siox::dilate(float *cm, int xres, int yres) +{ + + for (int y=0; y<yres; y++) + { + for (int x=0; x<xres-1; x++) + { + int idx=(y*xres)+x; + if (cm[idx+1]>cm[idx]) + cm[idx]=cm[idx+1]; + } + } + + for (int y=0; y<yres; y++) + { + for (int x=xres-1; x>=1; x--) + { + int idx=(y*xres)+x; + if (cm[idx-1]>cm[idx]) + cm[idx]=cm[idx-1]; + } + } + + for (int y=0; y<yres-1; y++) + { + for (int x=0; x<xres; x++) + { + int idx=(y*xres)+x; + if (cm[((y+1)*xres)+x] > cm[idx]) + cm[idx]=cm[((y+1)*xres)+x]; + } + } + + for (int y=yres-1; y>=1; y--) + { + for (int x=0; x<xres; x++) + { + int idx=(y*xres)+x; + if (cm[((y-1)*xres)+x] > cm[idx]) + cm[idx]=cm[((y-1)*xres)+x]; + } + } +} + +/** + * Applies the morphological erode operator. + */ +void Siox::erode(float *cm, int xres, int yres) +{ + for (int y=0; y<yres; y++) + { + for (int x=0; x<xres-1; x++) + { + int idx=(y*xres)+x; + if (cm[idx+1] < cm[idx]) + cm[idx]=cm[idx+1]; + } + } + for (int y=0; y<yres; y++) + { + for (int x=xres-1; x>=1; x--) + { + int idx=(y*xres)+x; + if (cm[idx-1] < cm[idx]) + cm[idx]=cm[idx-1]; + } + } + for (int y=0; y<yres-1; y++) + { + for (int x=0; x<xres; x++) + { + int idx=(y*xres)+x; + if (cm[((y+1)*xres)+x] < cm[idx]) + cm[idx]=cm[((y+1)*xres)+x]; + } + } + for (int y=yres-1; y>=1; y--) + { + for (int x=0; x<xres; x++) + { + int idx=(y*xres)+x; + if (cm[((y-1)*xres)+x] < cm[idx]) + cm[idx]=cm[((y-1)*xres)+x]; + } + } +} + + + +/** + * Normalizes the matrix to values to [0..1]. + */ +void Siox::normalizeMatrix(float *cm, int cmSize) +{ + float max= -1000000.0f; + for (int i=0; i<cmSize; i++) + if (cm[i] > max) max=cm[i]; + + //good to use STL, but max() is not iterative + //float max = *std::max(cm, cm + cmSize); + + if (max<=0.0 || max==1.0) + return; + + float alpha=1.00f/max; + premultiplyMatrix(alpha, cm, cmSize); +} + +/** + * Multiplies matrix with the given scalar. + */ +void Siox::premultiplyMatrix(float alpha, float *cm, int cmSize) +{ + for (int i=0; i<cmSize; i++) + cm[i]=alpha*cm[i]; +} + +/** + * Blurs confidence matrix with a given symmetrically weighted kernel. + * + * In the standard case confidence matrix entries are between 0...1 and + * the weight factors sum up to 1. + */ +void Siox::smooth(float *cm, int xres, int yres, + float f1, float f2, float f3) +{ + for (int y=0; y<yres; y++) + { + for (int x=0; x<xres-2; x++) + { + int idx=(y*xres)+x; + cm[idx]=f1*cm[idx]+f2*cm[idx+1]+f3*cm[idx+2]; + } + } + for (int y=0; y<yres; y++) + { + for (int x=xres-1; x>=2; x--) + { + int idx=(y*xres)+x; + cm[idx]=f3*cm[idx-2]+f2*cm[idx-1]+f1*cm[idx]; + } + } + for (int y=0; y<yres-2; y++) + { + for (int x=0; x<xres; x++) + { + int idx=(y*xres)+x; + cm[idx]=f1*cm[idx]+f2*cm[((y+1)*xres)+x]+f3*cm[((y+2)*xres)+x]; + } + } + for (int y=yres-1; y>=2; y--) + { + for (int x=0; x<xres; x++) + { + int idx=(y*xres)+x; + cm[idx]=f3*cm[((y-2)*xres)+x]+f2*cm[((y-1)*xres)+x]+f1*cm[idx]; + } + } +} + +/** + * Squared Euclidean distance of p and q. + */ +float Siox::sqrEuclideanDist(float *p, int pSize, float *q) +{ + float sum=0.0; + for (int i=0; i<pSize; i++) + { + float v = p[i] - q[i]; + sum += v*v; + } + return sum; +} + + + + + + +} // namespace siox +} // namespace org + +//######################################################################## +//# E N D O F F I L E +//######################################################################## + diff --git a/src/trace/siox.h b/src/trace/siox.h new file mode 100644 index 0000000..10eb62b --- /dev/null +++ b/src/trace/siox.h @@ -0,0 +1,654 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_SIOX_H +#define SEEN_SIOX_H +/* + * Copyright 2005, 2006 by Gerald Friedland, Kristian Jantz and Lars Knipping + * + * Conversion to C++ for Inkscape by Bob Jamison + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +/* + * Note by Bob Jamison: + * After translating the siox.org Java API to C++ and receiving an + * education into this wonderful code, I began again, + * and started this version using lessons learned. This version is + * an attempt to provide an dependency-free SIOX engine that anyone + * can use in their project with minimal effort. + * + * Many thanks to the fine people at siox.org. + */ + +#include <string> +#include <vector> + +#define HAVE_GLIB + +#ifdef HAVE_GLIB +#include <glib.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#endif + + +namespace org +{ + +namespace siox +{ + + +//######################################################################## +//# C L A B +//######################################################################## + +/** + * + */ +class CieLab +{ +public: + + /** + * + */ + CieLab() + { + init(); + C = 0; + L = A = B = 0.0f; + } + + + /** + * + */ + CieLab(unsigned long rgb); + + + /** + * + */ + CieLab(float lArg, float aArg, float bArg) + { + init(); + C = 0; + L = lArg; + A = aArg; + B = bArg; + } + + + /** + * + */ + CieLab(const CieLab &other) + { + init(); + C = other.C; + L = other.L; + A = other.A; + B = other.B; + } + + + /** + * + */ + CieLab &operator=(const CieLab &other) + { + init(); + C = other.C; + L = other.L; + A = other.A; + B = other.B; + return *this; + } + + /** + * + */ + virtual ~CieLab() + = default; + + /** + * Retrieve a CieLab value via index. + */ + virtual float operator()(unsigned int index) + { + if (index==0) return L; + else if (index==1) return A; + else if (index==2) return B; + else return 0; + } + + + /** + * + */ + virtual void add(const CieLab &other) + { + C += other.C; + L += other.L; + A += other.A; + B += other.B; + } + + + /** + * + */ + virtual void mul(float scale) + { + L *= scale; + A *= scale; + B *= scale; + } + + + /** + * + */ + virtual unsigned long toRGB(); + + /** + * Approximate cube roots + */ + double cbrt(double x); + + /** + * + */ + double qnrt(double x); + + /** + * Raise to the 2.4 power + */ + double pow24(double x); + + /** + * Squared Euclidean distance between this and another color + */ + float diffSq(const CieLab &other); + + /** + * Computes squared euclidean distance in CieLab space for two colors + * given as RGB values. + */ + static float diffSq(unsigned int rgb1, unsigned int rgb2); + + /** + * Computes squared euclidean distance in CieLab space for two colors + * given as RGB values. + */ + static float diff(unsigned int rgb0, unsigned int rgb1); + + + unsigned int C; + float L; + float A; + float B; + +private: + + /** + * + */ + void init(); + + +}; + + +//######################################################################## +//# S I O X I M A G E +//######################################################################## + +/** + * This is a generic image type that provides a consistent interface + * to Siox, so that developers will not need to worry about data arrays. + */ +class SioxImage +{ +public: + + /** + * Create an image with the given width and height + */ + SioxImage(unsigned int width, unsigned int height); + + /** + * Copy constructor + */ + SioxImage(const SioxImage &other); + + /** + * Assignment + */ + SioxImage &operator=(const SioxImage &other); + + /** + * Clean up after use. + */ + virtual ~SioxImage(); + + /** + * Returns true if the previous operation on this image + * was successful, else false. + */ + virtual bool isValid(); + + /** + * Sets whether an operation was successful, and whether + * this image should be considered a valid one. + * was successful, else false. + */ + virtual void setValid(bool val); + + /** + * Set a pixel at the x,y coordinates to the given value. + * If the coordinates are out of range, do nothing. + */ + virtual void setPixel(unsigned int x, + unsigned int y, + unsigned int pixval); + + /** + * Set a pixel at the x,y coordinates to the given r, g, b values. + * If the coordinates are out of range, do nothing. + */ + virtual void setPixel(unsigned int x, unsigned int y, + unsigned int a, + unsigned int r, + unsigned int g, + unsigned int b); + + /** + * Get a pixel at the x,y coordinates given. If + * the coordinates are out of range, return 0 + */ + virtual unsigned int getPixel(unsigned int x, unsigned int y); + + + /** + * Return the image data buffer + */ + virtual unsigned int *getImageData(); + + /** + * Set a confidence value at the x,y coordinates to the given value. + * If the coordinates are out of range, do nothing. + */ + virtual void setConfidence(unsigned int x, + unsigned int y, + float conf); + + /** + * Get a confidence value at the x,y coordinates given. If + * the coordinates are out of range, return 0 + */ + virtual float getConfidence(unsigned int x, unsigned int y); + + /** + * Return the confidence data buffer + */ + virtual float *getConfidenceData(); + + /** + * Return the width of this image + */ + virtual int getWidth(); + + /** + * Return the height of this image + */ + virtual int getHeight(); + + /** + * Saves this image as a simple color PPM + */ + bool writePPM(const std::string &fileName); + + + +#ifdef HAVE_GLIB + + /** + * Special constructor to create an image from a GdkPixbuf. + */ + SioxImage(GdkPixbuf *buf); + + /** + * Creates a GdkPixbuf from this image. The user must + * remember to destroy the image when no longer needed. + * with g_free(pixbuf) + */ + GdkPixbuf *getGdkPixbuf(); + +#endif + +private: + + SioxImage() + = default; + + /** + * Assign values to that of another + */ + void assign(const SioxImage &other); + + /** + * Initialize values. Used by constructors + */ + void init(unsigned int width, unsigned int height); + + bool valid; + + unsigned int width; + + unsigned int height; + + unsigned long imageSize; + + /** + * Pixel data + */ + unsigned int *pixdata; + + /** + * Confidence matrix data + */ + float *cmdata; + +private: + + /** + * Error logging + */ + void error(const char *fmt, ...) G_GNUC_PRINTF(2,3); + +}; + + + +//######################################################################## +//# S I O X O B S E R V E R +//######################################################################## +class Siox; + +/** + * This is a class for observing the progress of a Siox engine. Overload + * the methods in your subclass to get the desired behaviour. + */ +class SioxObserver +{ +public: + + /** + * Constructor. Context can point to anything, and is usually + * used to point to a C++ object or C state object, to delegate + * callback processing to something else. Use NULL to ignore. + */ + SioxObserver(void *contextArg) : context(nullptr) + { context = contextArg; } + + /** + * Destructor + */ + virtual ~SioxObserver() + = default; + + /** + * Informs the observer how much has been completed. + * Return false if the processing should be aborted. + */ + virtual bool progress(float /*percentCompleted*/) + { + return true; + } + + /** + * Send an error string to the Observer. Processing will + * be halted. + */ + virtual void error(const std::string &/*msg*/) + { + } + +protected: + + void *context; + +}; + + + +//######################################################################## +//# S I O X +//######################################################################## + +/** + * + */ +class Siox +{ +public: + + /** + * Confidence corresponding to a certain foreground region (equals one). + */ + static const float CERTAIN_FOREGROUND_CONFIDENCE; //=1.0f; + + /** + * Confidence for a region likely being foreground. + */ + static const float FOREGROUND_CONFIDENCE; //=0.8f; + + /** + * Confidence for foreground or background type being equally likely. + */ + static const float UNKNOWN_REGION_CONFIDENCE; //=0.5f; + + /** + * Confidence for a region likely being background. + */ + static const float BACKGROUND_CONFIDENCE; //=0.1f; + + /** + * Confidence corresponding to a certain background reagion (equals zero). + */ + static const float CERTAIN_BACKGROUND_CONFIDENCE; //=0.0f; + + /** + * Construct a Siox engine + */ + Siox(); + + /** + * Construct a Siox engine. Use null to ignore + */ + Siox(SioxObserver *observer); + + /** + * + */ + virtual ~Siox(); + + /** + * Extract the foreground of the original image, according + * to the values in the confidence matrix. If the operation fails, + * sioxImage.isValid() will be false. + * backgroundFillColor is any ARGB color, such as 0xffffff (white) + * or 0x000000 (black) + */ + virtual SioxImage extractForeground(const SioxImage &originalImage, + unsigned int backgroundFillColor); + +private: + + SioxObserver *sioxObserver; + + /** + * Progress reporting + */ + bool progressReport(float percentCompleted); + + /** + * Flag this as false during processing to abort + */ + bool keepGoing; + + /** + * Image width + */ + unsigned int width; + + /** + * Image height + */ + unsigned int height; + + /** + * Image size in pixels + */ + unsigned long pixelCount; + + /** + * Image data + */ + unsigned int *image; + + /** + * Image confidence matrix + */ + float *cm; + + /** + * Markup for image editing + */ + int *labelField; + + + /** + * Our signature limits + */ + float limits[3]; + + /** + * Maximum distance of two lab values. + */ + float clusterSize; + + /** + * Initialize the Siox engine to its 'pristine' state. + * Performed at the beginning of extractForeground(). + */ + void init(); + + /** + * Clean up any debris from processing. + */ + void cleanup(); + + /** + * Error logging + */ + void error(const char *fmt, ...) G_GNUC_PRINTF(2,3); + + /** + * Trace logging + */ + void trace(const char *fmt, ...) G_GNUC_PRINTF(2,3); + + /** + * Stage 1 of the color signature work. 'dims' will be either + * 2 for grays, or 3 for colors + */ + void colorSignatureStage1(CieLab *points, + unsigned int leftBase, + unsigned int rightBase, + unsigned int recursionDepth, + unsigned int *clusters, + const unsigned int dims); + + /** + * Stage 2 of the color signature work + */ + void colorSignatureStage2(CieLab *points, + unsigned int leftBase, + unsigned int rightBase, + unsigned int recursionDepth, + unsigned int *clusters, + const float threshold, + const unsigned int dims); + + /** + * Main color signature method + */ + bool colorSignature(const std::vector<CieLab> &inputVec, + std::vector<CieLab> &result, + const unsigned int dims); + + + /** + * + */ + void keepOnlyLargeComponents(float threshold, + double sizeFactorToKeep); + + /** + * + */ + int depthFirstSearch(int startPos, float threshold, int curLabel); + + + /** + * + */ + void fillColorRegions(); + + /** + * Applies the morphological dilate operator. + * + * Can be used to close small holes in the given confidence matrix. + */ + void dilate(float *cm, int xres, int yres); + + /** + * Applies the morphological erode operator. + */ + void erode(float *cm, int xres, int yres); + + /** + * Normalizes the matrix to values to [0..1]. + */ + void normalizeMatrix(float *cm, int cmSize); + + /** + * Multiplies matrix with the given scalar. + */ + void premultiplyMatrix(float alpha, float *cm, int cmSize); + + /** + * Blurs confidence matrix with a given symmetrically weighted kernel. + */ + void smooth(float *cm, int xres, int yres, + float f1, float f2, float f3); + + /** + * Squared Euclidean distance of p and q. + */ + float sqrEuclideanDist(float *p, int pSize, float *q); + +}; + + + + +} // namespace siox +} // namespace org + +#endif // SEEN_SIOX_H +//######################################################################## +//# E N D O F F I L E +//######################################################################## 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 +//######################################################################### + diff --git a/src/trace/trace.h b/src/trace/trace.h new file mode 100644 index 0000000..ddc4b96 --- /dev/null +++ b/src/trace/trace.h @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Bob Jamison <rjamison@titan.com> + * + * Copyright (C) 2004-2006 Bob Jamison + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_TRACE_H +#define SEEN_TRACE_H + +# include <cstring> + +#include <glibmm/refptr.h> +#include <gdkmm/pixbuf.h> +#include <utility> +#include <vector> + +class SPImage; +class SPItem; +class SPShape; + +namespace Inkscape { + +namespace Trace { + + + +/** + * + */ +class TracingEngineResult +{ + +public: + + /** + * + */ + TracingEngineResult(std::string theStyle, + std::string thePathData, + long theNodeCount) : + style(std::move(theStyle)), + pathData(std::move(thePathData)), + nodeCount(theNodeCount) + {} + + TracingEngineResult(const TracingEngineResult &other) + { assign(other); } + + virtual TracingEngineResult &operator=(const TracingEngineResult &other) + { assign(other); return *this; } + + + /** + * + */ + virtual ~TracingEngineResult() + = default; + + + /** + * + */ + std::string getStyle() + { return style; } + + /** + * + */ + std::string getPathData() + { return pathData; } + + /** + * + */ + long getNodeCount() + { return nodeCount; } + +private: + + void assign(const TracingEngineResult &other) + { + style = other.style; + pathData = other.pathData; + nodeCount = other.nodeCount; + } + + std::string style; + + std::string pathData; + + long nodeCount; + +}; + + + +/** + * A generic interface for plugging different + * autotracers into Inkscape. + */ +class TracingEngine +{ + + public: + + /** + * + */ + TracingEngine() + = default; + + /** + * + */ + virtual ~TracingEngine() + = default; + + /** + * This is the working method of this interface, and all + * implementing classes. Take a GdkPixbuf, trace it, and + * return a style attribute and the path data that is + * compatible with the d="" attribute + * of an SVG <path> element. + */ + virtual std::vector<TracingEngineResult> trace( + Glib::RefPtr<Gdk::Pixbuf> /*pixbuf*/) = 0; + + /** + * Abort the thread that is executing getPathDataFromPixbuf() + */ + virtual void abort() = 0; + + + +};//class TracingEngine + + + + + + + + + +/** + * This simple class allows a generic wrapper around a given + * TracingEngine object. Its purpose is to provide a gateway + * to a variety of tracing engines, while maintaining a + * consistent interface. + */ +class Tracer +{ + +public: + + + /** + * + */ + Tracer() + { + engine = nullptr; + sioxEnabled = false; + } + + + + /** + * + */ + ~Tracer() + = default; + + + /** + * A convenience method to allow other software to 'see' the + * same image that this class sees. + */ + Glib::RefPtr<Gdk::Pixbuf> getSelectedImage(); + + /** + * This is the main working method. Trace the selected image, if + * any, and create a <path> element from it, inserting it into + * the current document. + */ + void trace(TracingEngine *engine); + + + /** + * Abort the thread that is executing convertImageToPath() + */ + void abort(); + + /** + * Whether we want to enable SIOX subimage selection. + */ + void enableSiox(bool enable); + + +private: + + /** + * This is the single path code that is called by its counterpart above. + * Threaded method that does single bitmap--->path conversion. + */ + void traceThread(); + + /** + * This is true during execution. Setting it to false (like abort() + * does) should inform the threaded code that it needs to stop + */ + bool keepGoing; + + /** + * During tracing, this is Non-null, and refers to the + * engine that is currently doing the tracing. + */ + TracingEngine *engine; + + /** + * Get the selected image. Also check for any SPItems over it, in + * case the user wants SIOX pre-processing. + */ + SPImage *getSelectedSPImage(); + + std::vector<SPShape *> sioxShapes; + + bool sioxEnabled; + + /** + * Process a GdkPixbuf, according to which areas have been + * obscured in the GUI. + */ + Glib::RefPtr<Gdk::Pixbuf> sioxProcessImage(SPImage *img, Glib::RefPtr<Gdk::Pixbuf> origPixbuf); + + Glib::RefPtr<Gdk::Pixbuf> lastSioxPixbuf; + Glib::RefPtr<Gdk::Pixbuf> lastOrigPixbuf; + +};//class Tracer + + + + +} // namespace Trace + +} // namespace Inkscape + + + +#endif // SEEN_TRACE_H + +//######################################################################### +//# E N D O F F I L E +//######################################################################### + |