Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
inkscape-potrace.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * This is the C++ glue between Inkscape and Potrace
4 *
5 * Authors:
6 * Bob Jamison <rjamison@titan.com>
7 * Stéphane Gimenez <dev@gim.name>
8 *
9 * Copyright (C) 2004-2006 Authors
10 *
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 *
13 * Potrace, the wonderful tracer located at http://potrace.sourceforge.net,
14 * is provided by the generosity of Peter Selinger, to whom we are grateful.
15 *
16 */
17#include <iomanip>
18#include <potracelib.h>
19
20#include "inkscape-potrace.h"
21#include "bitmap.h"
22
23#include "async/progress.h"
24#include "trace/filterset.h"
25#include "trace/quantize.h"
26#include "trace/imagemap-gdk.h"
28
29namespace {
30
31struct potrace_state_deleter { void operator()(potrace_state_t *p) { potrace_state_free(p); }; };
32using potrace_state_uniqptr = std::unique_ptr<potrace_state_t, potrace_state_deleter>;
33
34struct potrace_bitmap_deleter { void operator()(potrace_bitmap_t *p) { bm_free(p); }; };
35using potrace_bitmap_uniqptr = std::unique_ptr<potrace_bitmap_t, potrace_bitmap_deleter>;
36
37Glib::ustring twohex(int value)
38{
39 return Inkscape::ustring::format_classic(std::hex, std::setfill('0'), std::setw(2), value);
40}
41
42} // namespace
43
44namespace Inkscape {
45namespace Trace {
46namespace Potrace {
47
49
50PotraceTracingEngine::PotraceTracingEngine(TraceType traceType, bool invert, int quantizationNrColors, double brightnessThreshold, double brightnessFloor, double cannyHighThreshold, int multiScanNrColors, bool multiScanStack, bool multiScanSmooth, bool multiScanRemoveBackground)
51 : traceType(traceType)
52 , invert(invert)
53 , quantizationNrColors(quantizationNrColors)
54 , brightnessThreshold(brightnessThreshold)
55 , brightnessFloor(brightnessFloor)
56 , cannyHighThreshold(cannyHighThreshold)
57 , multiScanNrColors(multiScanNrColors)
58 , multiScanStack(multiScanStack)
59 , multiScanSmooth(multiScanSmooth)
60 , multiScanRemoveBackground(multiScanRemoveBackground) { common_init(); }
61
63{
64 potraceParams = potrace_param_default();
65}
66
71
73{
74 potraceParams->opticurve = opticurve;
75}
76
78{
79 potraceParams->opttolerance = opttolerance;
80}
81
83{
84 potraceParams->alphamax = alphamax;
85}
86
88{
89 potraceParams->turdsize = turdsize;
90}
91
96void PotraceTracingEngine::writePaths(potrace_path_t *paths, Geom::PathBuilder &builder, std::unordered_set<Geom::Point> &points, Async::Progress<double> &progress) const
97{
98 auto to_geom = [] (potrace_dpoint_t const &c) {
99 return Geom::Point(c.x, c.y);
100 };
101
102 for (auto path = paths; path; path = path->sibling) {
103 progress.throw_if_cancelled();
104
105 auto const &curve = path->curve;
106 // g_message("node->fm:%d\n", node->fm);
107 if (curve.n == 0) {
108 continue;
109 }
110
111 auto seg = curve.c[curve.n - 1];
112 auto const pt = to_geom(seg[2]);
113 // Have we been here already?
114 auto inserted = points.emplace(pt).second;
115 if (!inserted) {
116 // g_message("duplicate point: (%f,%f)\n", x2, y2);
117 continue;
118 }
119 builder.moveTo(pt);
120
121 for (int i = 0; i < curve.n; i++) {
122 auto seg = curve.c[i];
123 switch (curve.tag[i]) {
124 case POTRACE_CORNER:
125 builder.lineTo(to_geom(seg[1]));
126 builder.lineTo(to_geom(seg[2]));
127 break;
128 case POTRACE_CURVETO:
129 builder.curveTo(to_geom(seg[0]), to_geom(seg[1]), to_geom(seg[2]));
130 break;
131 default:
132 break;
133 }
134 }
135 builder.closePath();
136
137 for (auto child = path->childlist; child; child = child->sibling) {
138 writePaths(child, builder, points, progress);
139 }
140 }
141}
142
143std::optional<GrayMap> PotraceTracingEngine::filter(Glib::RefPtr<Gdk::Pixbuf> const &pixbuf) const
144{
145 std::optional<GrayMap> map;
146
148
149 // Color quantization -- banding
150 auto rgbmap = gdkPixbufToRgbMap(pixbuf);
151 // rgbMap->writePPM(rgbMap, "rgb.ppm");
153
155
156 // Brightness threshold
157 auto gm = gdkPixbufToGrayMap(pixbuf);
158 map = GrayMap(gm.width, gm.height);
159
160 double floor = 3.0 * brightnessFloor * 256.0;
161 double cutoff = 3.0 * brightnessThreshold * 256.0;
162 for (int y = 0; y < gm.height; y++) {
163 for (int x = 0; x < gm.width; x++) {
164 double brightness = gm.getPixel(x, y);
165 bool black = brightness >= floor && brightness < cutoff;
166 map->setPixel(x, y, black ? GrayMap::BLACK : GrayMap::WHITE);
167 }
168 }
169
170 // map->writePPM(map, "brightness.ppm");
171
172 } else if (traceType == TraceType::CANNY) {
173
174 // Canny edge detection
175 auto gm = gdkPixbufToGrayMap(pixbuf);
177 // map->writePPM(map, "canny.ppm");
178
179 }
180
181 // Invert the image if necessary.
182 if (map && invert) {
183 for (int y = 0; y < map->height; y++) {
184 for (int x = 0; x < map->width; x++) {
185 auto brightness = map->getPixel(x, y);
186 brightness = GrayMap::WHITE - brightness;
187 map->setPixel(x, y, brightness);
188 }
189 }
190 }
191
192 return map;
193}
194
195IndexedMap PotraceTracingEngine::filterIndexed(Glib::RefPtr<Gdk::Pixbuf> const &pixbuf) const
196{
197 auto map = gdkPixbufToRgbMap(pixbuf);
198
199 if (multiScanSmooth) {
201 }
202
204
205 auto tomono = [] (RGB c) -> RGB {
206 unsigned char s = ((int)c.r + (int)c.g + (int)c.b) / 3;
207 return { s, s, s };
208 };
209
211 // Turn to grays
212 for (auto &c : imap.clut) {
213 c = tomono(c);
214 }
215 }
216
217 return imap;
218}
219
220Glib::RefPtr<Gdk::Pixbuf> PotraceTracingEngine::preview(Glib::RefPtr<Gdk::Pixbuf> const &pixbuf)
221{
224 traceType == TraceType::BRIGHTNESS_MULTI) // this is a lie: multipass doesn't use filterIndexed, but it's a better preview approx than filter()
225 {
226 auto gm = filterIndexed(pixbuf);
227
228 return indexedMapToGdkPixbuf(gm);
229
230 } else {
231
232 auto gm = filter(pixbuf);
233 if (!gm) {
234 return {};
235 }
236
237 return grayMapToGdkPixbuf(*gm);
238 }
239}
240
245{
246 auto potraceBitmap = potrace_bitmap_uniqptr(bm_new(grayMap.width, grayMap.height));
247 if (!potraceBitmap) {
248 return {};
249 }
250
251 bm_clear(potraceBitmap.get(), 0);
252
253 // Read the data out of the GrayMap
254 for (int y = 0; y < grayMap.height; y++) {
255 for (int x = 0; x < grayMap.width; x++) {
256 BM_UPUT(potraceBitmap, x, y, grayMap.getPixel(x, y) ? 0 : 1);
257 }
258 }
259
260 progress.throw_if_cancelled();
261
262 //##Debug
263 /*
264 FILE *f = fopen("poimage.pbm", "wb");
265 bm_writepbm(f, bm);
266 fclose(f);
267 */
268
269 // Trace the bitmap.
270
271 auto throttled = Async::ProgressStepThrottler(progress, 0.02);
272
273 potraceParams->progress.data = &throttled;
274 potraceParams->progress.callback = [] (double progress, void *data) { reinterpret_cast<decltype(throttled)*>(data)->report(progress); };
275 auto potraceState = potrace_state_uniqptr(potrace_trace(potraceParams, potraceBitmap.get()));
276
277 potraceBitmap.reset();
278
279 progress.throw_if_cancelled();
280
281 // Extract the paths into a pathvector and return it.
283 std::unordered_set<Geom::Point> points;
284 writePaths(potraceState->plist, builder, points, progress);
285 return builder.peek();
286}
287
291TraceResult PotraceTracingEngine::traceSingle(Glib::RefPtr<Gdk::Pixbuf> const &pixbuf, Async::Progress<double> &progress)
292{
293 brightnessFloor = 0.0; // important to set this, since used by filter()
294
295 auto grayMap = filter(pixbuf);
296 if (!grayMap) {
297 return {};
298 }
299
300 progress.report_or_throw(0.2);
301
302 auto sub_gm = Async::SubProgress(progress, 0.2, 0.8);
303 auto pv = grayMapToPath(*grayMap, sub_gm);
304
305 TraceResult results;
306 results.emplace_back("fill:#000000", std::move(pv));
307 return results;
308}
309
314{
315 auto pv = grayMapToPath(grayMap, progress);
316
317 TraceResult results;
318 results.emplace_back("fill:#000000", std::move(pv));
319 return results;
320}
321
325TraceResult PotraceTracingEngine::traceBrightnessMulti(Glib::RefPtr<Gdk::Pixbuf> const &pixbuf, Async::Progress<double> &progress)
326{
327 double constexpr low = 0.2; // bottom of range
328 double constexpr high = 0.9; // top of range
329 double const delta = (high - low) / multiScanNrColors;
330
331 brightnessFloor = 0.0; // Set bottom to black
332
333 TraceResult results;
334
335 for (int i = 0; i < multiScanNrColors; i++) {
336 auto subprogress = Async::SubProgress(progress, (double)i / multiScanNrColors, 1.0 / multiScanNrColors);
337
338 brightnessThreshold = low + delta * i;
339
340 auto grayMap = filter(pixbuf);
341 if (!grayMap) {
342 continue;
343 }
344
345 subprogress.report_or_throw(0.2);
346
347 auto sub_gmtopath = Async::SubProgress(subprogress, 0.2, 0.8);
348 auto pv = grayMapToPath(*grayMap, sub_gmtopath);
349 if (pv.empty()) {
350 continue;
351 }
352
353 // get style info
354 int grayVal = 256.0 * brightnessThreshold;
355 auto style = Glib::ustring::compose("fill-opacity:1.0;fill:#%1%2%3", twohex(grayVal), twohex(grayVal), twohex(grayVal));
356
357 // g_message("### GOT '%s' \n", style.c_str());
358 results.emplace_back(style.raw(), std::move(pv));
359
360 if (!multiScanStack) {
362 }
363
364 subprogress.report_or_throw(1.0);
365 }
366
367 // Remove the bottom-most scan, if requested.
368 if (results.size() > 1 && multiScanRemoveBackground) {
369 results.pop_back();
370 }
371
372 return results;
373}
374
378TraceResult PotraceTracingEngine::traceQuant(Glib::RefPtr<Gdk::Pixbuf> const &pixbuf, Async::Progress<double> &progress)
379{
380 auto imap = filterIndexed(pixbuf);
381
382 // Create and clear a gray map
383 auto gm = GrayMap(imap.width, imap.height);
384 for (int row = 0; row < gm.height; row++) {
385 for (int col = 0; col < gm.width; col++) {
386 gm.setPixel(col, row, GrayMap::WHITE);
387 }
388 }
389
390 TraceResult results;
391
392 for (int colorIndex = 0; colorIndex < imap.nrColors; colorIndex++) {
393 auto subprogress = Async::SubProgress(progress, (double)colorIndex / imap.nrColors, 1.0 / imap.nrColors);
394
395 // Update the graymap for the current color index
396 for (int row = 0; row < imap.height; row++) {
397 for (int col = 0; col < imap.width; col++) {
398 int index = imap.getPixel(col, row);
399 if (index == colorIndex) {
400 gm.setPixel(col, row, GrayMap::BLACK);
401 } else if (!multiScanStack) {
402 gm.setPixel(col, row, GrayMap::WHITE);
403 }
404 }
405 }
406
407 subprogress.report_or_throw(0.2);
408
409 // Now we have a traceable graymap
410 auto sub_gmtopath = Async::SubProgress(subprogress, 0.2, 0.8);
411 auto pv = grayMapToPath(gm, sub_gmtopath);
412
413 if (!pv.empty()) {
414 // get style info
415 auto rgb = imap.clut[colorIndex];
416 auto style = Glib::ustring::compose("fill:#%1%2%3", twohex(rgb.r), twohex(rgb.g), twohex(rgb.b));
417 results.emplace_back(style.raw(), std::move(pv));
418 }
419
420 subprogress.report_or_throw(1.0);
421 }
422
423 // Remove the bottom-most scan, if requested.
424 if (results.size() > 1 && multiScanRemoveBackground) {
425 results.pop_back();
426 }
427
428 return results;
429}
430
431TraceResult PotraceTracingEngine::trace(Glib::RefPtr<Gdk::Pixbuf> const &pixbuf, Async::Progress<double> &progress)
432{
434 return traceQuant(pixbuf, progress);
436 return traceBrightnessMulti(pixbuf, progress);
437 } else {
438 return traceSingle(pixbuf, progress);
439 }
440}
441
442} // namespace Potrace
443} // namespace Trace
444} // namespace Inkscape
445
446/*
447 Local Variables:
448 mode:c++
449 c-file-style:"stroustrup"
450 c-file-offsets:((innamespace . 0)(inline-open . 0))
451 indent-tabs-mode:nil
452 fill-column:99
453 End:
454*/
455// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
Store paths to a PathVector.
Definition path-sink.h:226
Sequence of subpaths.
Definition pathvector.h:122
Two-dimensional point that doubles as a vector.
Definition point.h:66
A Progress object that throttles reports to a given step size.
Definition progress.h:82
An interface for tasks to report progress and check for cancellation.
Definition progress.h:23
void report_or_throw(T const &... progress)
Report a progress value, throwing CancelledException if cancelled.
Definition progress.h:29
void throw_if_cancelled() const
Throw CancelledException if cancelled.
Definition progress.h:35
A Progress object representing a sub-task of another Progress.
Definition progress.h:52
TraceResult traceBrightnessMulti(Glib::RefPtr< Gdk::Pixbuf > const &pixbuf, Async::Progress< double > &progress)
Called for multiple-scanning algorithms.
TraceResult trace(Glib::RefPtr< Gdk::Pixbuf > const &pixbuf, Async::Progress< double > &progress) override
This is the working method of this interface, and all implementing classes.
TraceResult traceSingle(Glib::RefPtr< Gdk::Pixbuf > const &pixbuf, Async::Progress< double > &progress)
This is called for a single scan.
TraceResult traceGrayMap(GrayMap const &grayMap, Async::Progress< double > &progress)
This allows routines that already generate GrayMaps to skip image filtering, increasing performance.
Glib::RefPtr< Gdk::Pixbuf > preview(Glib::RefPtr< Gdk::Pixbuf > const &pixbuf) override
Generate a quick preview without any actual tracing.
Geom::PathVector grayMapToPath(GrayMap const &gm, Async::Progress< double > &progress)
This is the actual wrapper of the call to Potrace.
TraceResult traceQuant(Glib::RefPtr< Gdk::Pixbuf > const &pixbuf, Async::Progress< double > &progress)
Quantization.
IndexedMap filterIndexed(Glib::RefPtr< Gdk::Pixbuf > const &pixbuf) const
std::optional< GrayMap > filter(Glib::RefPtr< Gdk::Pixbuf > const &pixbuf) const
void writePaths(potrace_path_t *paths, Geom::PathBuilder &builder, std::unordered_set< Geom::Point > &points, Async::Progress< double > &progress) const
Recursively descend the potrace_path_t node tree paths, writing paths to builder.
std::unordered_map< std::string, std::unique_ptr< SPDocument > > map
double c[8][4]
auto floor(Geom::Rect const &rect)
Definition geom.h:131
TODO: insert short description here.
potrace_path_s potrace_path_t
vector< vector< Point > > paths
Definition metro.cpp:36
RgbMap gdkPixbufToRgbMap(Glib::RefPtr< Gdk::Pixbuf > const &buf)
Glib::RefPtr< Gdk::Pixbuf > grayMapToGdkPixbuf(GrayMap const &map)
GrayMap grayMapCanny(GrayMap const &gm, double dLowThreshold, double dHighThreshold)
Perform Sobel convolution on a GrayMap.
RgbMap rgbMapGaussian(RgbMap const &me)
Apply gaussian blur to an RgbMap.
Definition filterset.cpp:71
GrayMap gdkPixbufToGrayMap(Glib::RefPtr< Gdk::Pixbuf > const &buf)
GrayMap quantizeBand(RgbMap const &rgbMap, int nrColors)
std::vector< TraceResultItem > TraceResult
Definition trace.h:42
Glib::RefPtr< Gdk::Pixbuf > indexedMapToGdkPixbuf(IndexedMap const &map)
IndexedMap rgbMapQuantize(RgbMap const &rgbmap, int ncolor)
quantize an RGB image to a reduced number of colors.
Definition quantize.cpp:515
Glib::ustring format_classic(T const &... args)
Helper class to stream background task notifications as a series of messages.
Ocnode * child[8]
Definition quantize.cpp:33
RGB rgb
Definition quantize.cpp:36
void invert(const double v[16], double alpha[16])
static const Point data[]
static unsigned long constexpr WHITE
Definition imagemap.h:46
static unsigned long constexpr BLACK
Definition imagemap.h:45
T getPixel(int x, int y) const
Definition imagemap.h:35
Definition curve.h:24
int delta
int index
static potrace_bitmap_t * bm_new(int w, int h)
Definition bitmap.h:62
static void bm_free(potrace_bitmap_t *bm)
Definition bitmap.h:53
static void bm_clear(potrace_bitmap_t *bm, int c)
Definition bitmap.h:90
Glib::RefPtr< Gtk::Builder > builder