1 /*
2 * This is the C++ glue between Inkscape and Potrace
3 *
4 * Authors:
5 * Bob Jamison <rjamison@titan.com>
6 * Stéphane Gimenez <dev@gim.name>
7 *
8 * Copyright (C) 2004-2006 Authors
9 *
10 * Released under GNU GPL, read the file 'COPYING' for more information
11 *
12 * Potrace, the wonderful tracer located at http://potrace.sourceforge.net,
13 * is provided by the generosity of Peter Selinger, to whom we are grateful.
14 *
15 */
17 #include "inkscape-potrace.h"
19 #include <glibmm/i18n.h>
20 #include <gtkmm.h>
22 #include "trace/filterset.h"
23 #include "trace/quantize.h"
24 #include "trace/imagemap-gdk.h"
26 #include <inkscape.h>
27 #include <desktop-handles.h>
28 #include "message-stack.h"
29 #include <sp-path.h>
30 #include <svg/path-string.h>
31 #include "curve.h"
32 #include "bitmap.h"
36 static void updateGui()
37 {
38 //## Allow the GUI to update
39 Gtk::Main::iteration(false); //at least once, non-blocking
40 while( Gtk::Main::events_pending() )
41 Gtk::Main::iteration();
43 }
46 static void potraceStatusCallback(double /*progress*/, void *userData) /* callback fn */
47 {
48 updateGui();
50 if (!userData)
51 return;
53 //g_message("progress: %f\n", progress);
55 //Inkscape::Trace::Potrace::PotraceTracingEngine *engine =
56 // (Inkscape::Trace::Potrace::PotraceTracingEngine *)userData;
57 }
62 //required by potrace
63 namespace Inkscape {
65 namespace Trace {
67 namespace Potrace {
70 /**
71 *
72 */
73 PotraceTracingEngine::PotraceTracingEngine()
74 {
75 /* get default parameters */
76 potraceParams = potrace_param_default();
77 potraceParams->progress.callback = potraceStatusCallback;
78 potraceParams->progress.data = (void *)this;
80 //##### Our defaults
81 invert = false;
82 traceType = TRACE_BRIGHTNESS;
83 quantizationNrColors = 8;
84 brightnessThreshold = 0.45;
85 cannyHighThreshold = 0.65;
87 }
89 PotraceTracingEngine::~PotraceTracingEngine()
90 {
91 potrace_param_free(potraceParams);
92 }
97 typedef struct
98 {
99 double x;
100 double y;
101 } Point;
104 /**
105 * Check a point against a list of points to see if it
106 * has already occurred.
107 */
108 static bool
109 hasPoint(std::vector<Point> &points, double x, double y)
110 {
111 for (unsigned int i=0; i<points.size() ; i++)
112 {
113 Point p = points[i];
114 if (p.x == x && p.y == y)
115 return true;
116 }
117 return false;
118 }
121 /**
122 * Recursively descend the path_t node tree, writing paths in SVG
123 * format into the output stream. The Point vector is used to prevent
124 * redundant paths. Returns number of paths processed.
125 */
126 static long
127 writePaths(PotraceTracingEngine *engine, potrace_path_t *plist,
128 Inkscape::SVG::PathString& data, std::vector<Point> &points)
129 {
130 long nodeCount = 0L;
132 potrace_path_t *node;
133 for (node=plist; node ; node=node->sibling)
134 {
135 potrace_curve_t *curve = &(node->curve);
136 //g_message("node->fm:%d\n", node->fm);
137 if (!curve->n)
138 continue;
139 dpoint_t *pt = curve->c[curve->n - 1];
140 double x0 = 0.0;
141 double y0 = 0.0;
142 double x1 = 0.0;
143 double y1 = 0.0;
144 double x2 = pt[2].x;
145 double y2 = pt[2].y;
146 //Have we been here already?
147 if (hasPoint(points, x2, y2))
148 {
149 //g_message("duplicate point: (%f,%f)\n", x2, y2);
150 continue;
151 }
152 else
153 {
154 Point p;
155 p.x = x2; p.y = y2;
156 points.push_back(p);
157 }
158 data.moveTo(x2, y2);
159 nodeCount++;
161 for (int i=0 ; i<curve->n ; i++)
162 {
163 if (!engine->keepGoing)
164 return 0L;
165 pt = curve->c[i];
166 x0 = pt[0].x;
167 y0 = pt[0].y;
168 x1 = pt[1].x;
169 y1 = pt[1].y;
170 x2 = pt[2].x;
171 y2 = pt[2].y;
172 switch (curve->tag[i])
173 {
174 case POTRACE_CORNER:
175 data.lineTo(x1, y1).lineTo(x2, y2);
176 break;
177 case POTRACE_CURVETO:
178 data.curveTo(x0, y0, x1, y1, x2, y2);
179 break;
180 default:
181 break;
182 }
183 nodeCount++;
184 }
185 data.closePath();
187 for (path_t *child=node->childlist; child ; child=child->sibling)
188 {
189 nodeCount += writePaths(engine, child, data, points);
190 }
191 }
193 return nodeCount;
195 }
201 static GrayMap *
202 filter(PotraceTracingEngine &engine, GdkPixbuf * pixbuf)
203 {
204 if (!pixbuf)
205 return NULL;
207 GrayMap *newGm = NULL;
209 /*### Color quantization -- banding ###*/
210 if (engine.getTraceType() == TRACE_QUANT)
211 {
212 RgbMap *rgbmap = gdkPixbufToRgbMap(pixbuf);
213 //rgbMap->writePPM(rgbMap, "rgb.ppm");
214 newGm = quantizeBand(rgbmap,
215 engine.getQuantizationNrColors());
216 rgbmap->destroy(rgbmap);
217 //return newGm;
218 }
220 /*### Brightness threshold ###*/
221 else if ( engine.getTraceType() == TRACE_BRIGHTNESS ||
222 engine.getTraceType() == TRACE_BRIGHTNESS_MULTI )
223 {
224 GrayMap *gm = gdkPixbufToGrayMap(pixbuf);
226 newGm = GrayMapCreate(gm->width, gm->height);
227 double floor = 3.0 *
228 ( engine.getBrightnessFloor() * 256.0 );
229 double cutoff = 3.0 *
230 ( engine.getBrightnessThreshold() * 256.0 );
231 for (int y=0 ; y<gm->height ; y++)
232 {
233 for (int x=0 ; x<gm->width ; x++)
234 {
235 double brightness = (double)gm->getPixel(gm, x, y);
236 if (brightness >= floor && brightness < cutoff)
237 newGm->setPixel(newGm, x, y, GRAYMAP_BLACK); //black pixel
238 else
239 newGm->setPixel(newGm, x, y, GRAYMAP_WHITE); //white pixel
240 }
241 }
243 gm->destroy(gm);
244 //newGm->writePPM(newGm, "brightness.ppm");
245 //return newGm;
246 }
248 /*### Canny edge detection ###*/
249 else if (engine.getTraceType() == TRACE_CANNY)
250 {
251 GrayMap *gm = gdkPixbufToGrayMap(pixbuf);
252 newGm = grayMapCanny(gm, 0.1, engine.getCannyHighThreshold());
253 gm->destroy(gm);
254 //newGm->writePPM(newGm, "canny.ppm");
255 //return newGm;
256 }
258 /*### Do I invert the image? ###*/
259 if (newGm && engine.getInvert())
260 {
261 for (int y=0 ; y<newGm->height ; y++)
262 {
263 for (int x=0 ; x<newGm->width ; x++)
264 {
265 unsigned long brightness = newGm->getPixel(newGm, x, y);
266 brightness = 765 - brightness;
267 newGm->setPixel(newGm, x, y, brightness);
268 }
269 }
270 }
272 return newGm;//none of the above
273 }
276 static IndexedMap *
277 filterIndexed(PotraceTracingEngine &engine, GdkPixbuf * pixbuf)
278 {
279 if (!pixbuf)
280 return NULL;
282 IndexedMap *newGm = NULL;
284 RgbMap *gm = gdkPixbufToRgbMap(pixbuf);
285 if (engine.getMultiScanSmooth())
286 {
287 RgbMap *gaussMap = rgbMapGaussian(gm);
288 newGm = rgbMapQuantize(gaussMap, engine.getMultiScanNrColors());
289 gaussMap->destroy(gaussMap);
290 }
291 else
292 {
293 newGm = rgbMapQuantize(gm, engine.getMultiScanNrColors());
294 }
295 gm->destroy(gm);
297 if (engine.getTraceType() == TRACE_QUANT_MONO)
298 {
299 //Turn to grays
300 for (int i=0 ; i<newGm->nrColors ; i++)
301 {
302 RGB rgb = newGm->clut[i];
303 int grayVal = (rgb.r + rgb.g + rgb.b) / 3;
304 rgb.r = rgb.g = rgb.b = grayVal;
305 newGm->clut[i] = rgb;
306 }
307 }
309 return newGm;
310 }
315 Glib::RefPtr<Gdk::Pixbuf>
316 PotraceTracingEngine::preview(Glib::RefPtr<Gdk::Pixbuf> thePixbuf)
317 {
318 GdkPixbuf *pixbuf = thePixbuf->gobj();
320 if ( traceType == TRACE_QUANT_COLOR ||
321 traceType == TRACE_QUANT_MONO )
322 {
323 IndexedMap *gm = filterIndexed(*this, pixbuf);
324 if (!gm)
325 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
327 Glib::RefPtr<Gdk::Pixbuf> newBuf =
328 Glib::wrap(indexedMapToGdkPixbuf(gm), false);
330 gm->destroy(gm);
332 return newBuf;
333 }
334 else
335 {
336 GrayMap *gm = filter(*this, pixbuf);
337 if (!gm)
338 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
340 Glib::RefPtr<Gdk::Pixbuf> newBuf =
341 Glib::wrap(grayMapToGdkPixbuf(gm), false);
343 gm->destroy(gm);
345 return newBuf;
346 }
347 }
350 //*This is the core inkscape-to-potrace binding
351 std::string
352 PotraceTracingEngine::grayMapToPath(GrayMap *grayMap, long *nodeCount)
353 {
354 potrace_bitmap_t *potraceBitmap = bm_new(grayMap->width, grayMap->height);
355 bm_clear(potraceBitmap, 0);
357 //##Read the data out of the GrayMap
358 for (int y=0 ; y<grayMap->height ; y++)
359 {
360 for (int x=0 ; x<grayMap->width ; x++)
361 {
362 BM_UPUT(potraceBitmap, x, y,
363 grayMap->getPixel(grayMap, x, y) ? 0 : 1);
364 }
365 }
367 //##Debug
368 /*
369 FILE *f = fopen("poimage.pbm", "wb");
370 bm_writepbm(f, bm);
371 fclose(f);
372 */
374 if (!keepGoing)
375 {
376 g_warning("aborted");
377 return "";
378 }
380 /* trace a bitmap*/
381 potrace_state_t *potraceState = potrace_trace(potraceParams,
382 potraceBitmap);
384 //## Free the Potrace bitmap
385 bm_free(potraceBitmap);
387 if (!keepGoing)
388 {
389 g_warning("aborted");
390 potrace_state_free(potraceState);
391 return "";
392 }
394 Inkscape::SVG::PathString data;
396 //## copy the path information into our d="" attribute string
397 std::vector<Point> points;
398 long thisNodeCount = writePaths(this, potraceState->plist, data, points);
400 /* free a potrace items */
401 potrace_state_free(potraceState);
403 if (!keepGoing)
404 return "";
406 if ( nodeCount)
407 *nodeCount = thisNodeCount;
409 return data.string();
410 }
414 /**
415 * This is called for a single scan
416 */
417 std::vector<TracingEngineResult>
418 PotraceTracingEngine::traceSingle(GdkPixbuf * thePixbuf)
419 {
421 std::vector<TracingEngineResult> results;
423 if (!thePixbuf)
424 return results;
426 brightnessFloor = 0.0; //important to set this
428 GrayMap *grayMap = filter(*this, thePixbuf);
429 if (!grayMap)
430 return results;
432 long nodeCount;
433 std::string d = grayMapToPath(grayMap, &nodeCount);
435 grayMap->destroy(grayMap);
437 char const *style = "fill:#000000";
439 //g_message("### GOT '%s' \n", d);
440 TracingEngineResult result(style, d, nodeCount);
441 results.push_back(result);
443 return results;
444 }
447 /**
448 * This allow routines that already generate GrayMaps to skip image filtering,
449 * increasing performance.
450 */
451 std::vector<TracingEngineResult>
452 PotraceTracingEngine::traceGrayMap(GrayMap *grayMap)
453 {
455 std::vector<TracingEngineResult> results;
457 brightnessFloor = 0.0; //important to set this
459 long nodeCount;
460 std::string d = grayMapToPath(grayMap, &nodeCount);
462 char const *style = "fill:#000000";
464 //g_message("### GOT '%s' \n", d);
465 TracingEngineResult result(style, d, nodeCount);
466 results.push_back(result);
468 return results;
469 }
471 /**
472 * Called for multiple-scanning algorithms
473 */
474 std::vector<TracingEngineResult>
475 PotraceTracingEngine::traceBrightnessMulti(GdkPixbuf * thePixbuf)
476 {
478 std::vector<TracingEngineResult> results;
480 if (!thePixbuf)
481 return results;
483 double low = 0.2; //bottom of range
484 double high = 0.9; //top of range
485 double delta = (high - low ) / ((double)multiScanNrColors);
487 brightnessFloor = 0.0; //Set bottom to black
489 int traceCount = 0;
491 for ( brightnessThreshold = low ;
492 brightnessThreshold <= high ;
493 brightnessThreshold += delta)
495 {
497 GrayMap *grayMap = filter(*this, thePixbuf);
498 if (!grayMap)
499 return results;
501 long nodeCount;
502 std::string d = grayMapToPath(grayMap, &nodeCount);
504 grayMap->destroy(grayMap);
506 if (d.size() == 0)
507 return results;
509 int grayVal = (int)(256.0 * brightnessThreshold);
510 char style[31];
511 sprintf(style, "fill-opacity:1.0;fill:#%02x%02x%02x",
512 grayVal, grayVal, grayVal);
514 //g_message("### GOT '%s' \n", d);
515 TracingEngineResult result(style, d, nodeCount);
516 results.push_back(result);
518 if (!multiScanStack)
519 brightnessFloor = brightnessThreshold;
521 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
522 if (desktop)
523 {
524 gchar *msg = g_strdup_printf(_("Trace: %d. %ld nodes"), traceCount++, nodeCount);
525 sp_desktop_message_stack(desktop)->flash(Inkscape::NORMAL_MESSAGE, msg);
526 g_free(msg);
527 }
528 }
530 //# Remove the bottom-most scan, if requested
531 if (results.size() > 1 && multiScanRemoveBackground)
532 {
533 results.erase(results.end() - 1);
534 }
536 return results;
537 }
540 /**
541 * Quantization
542 */
543 std::vector<TracingEngineResult>
544 PotraceTracingEngine::traceQuant(GdkPixbuf * thePixbuf)
545 {
547 std::vector<TracingEngineResult> results;
549 if (!thePixbuf)
550 return results;
552 IndexedMap *iMap = filterIndexed(*this, thePixbuf);
553 if (!iMap)
554 return results;
556 //Create and clear a gray map
557 GrayMap *gm = GrayMapCreate(iMap->width, iMap->height);
558 for (int row=0 ; row<gm->height ; row++)
559 for (int col=0 ; col<gm->width ; col++)
560 gm->setPixel(gm, col, row, GRAYMAP_WHITE);
563 for (int colorIndex=0 ; colorIndex<iMap->nrColors ; colorIndex++)
564 {
566 /*Make a gray map for each color index */
567 for (int row=0 ; row<iMap->height ; row++)
568 {
569 for (int col=0 ; col<iMap->width ; col++)
570 {
571 int indx = (int) iMap->getPixel(iMap, col, row);
572 if (indx == colorIndex)
573 gm->setPixel(gm, col, row, GRAYMAP_BLACK); //black
574 else if (!multiScanStack)
575 gm->setPixel(gm, col, row, GRAYMAP_WHITE); //white
576 }
577 }
579 //## Now we have a traceable graymap
580 long nodeCount;
581 std::string d = grayMapToPath(gm, &nodeCount);
583 if (d.size() == 0)
584 return results;
586 //### get style info
587 char style[13];
588 RGB rgb = iMap->clut[colorIndex];
589 sprintf(style, "fill:#%02x%02x%02x", rgb.r, rgb.g, rgb.b);
591 //g_message("### GOT '%s' \n", d);
592 TracingEngineResult result(style, d, nodeCount);
593 results.push_back(result);
595 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
596 if (desktop)
597 {
598 gchar *msg = g_strdup_printf(_("Trace: %d. %ld nodes"), colorIndex, nodeCount);
599 sp_desktop_message_stack(desktop)->flash(Inkscape::NORMAL_MESSAGE, msg);
600 g_free(msg);
601 }
604 }// for colorIndex
606 gm->destroy(gm);
607 iMap->destroy(iMap);
609 //# Remove the bottom-most scan, if requested
610 if (results.size() > 1 && multiScanRemoveBackground)
611 {
612 results.erase(results.end() - 1);
613 }
615 return results;
616 }
619 /**
620 * This is the working method of this interface, and all
621 * implementing classes. Take a GdkPixbuf, trace it, and
622 * return the path data that is compatible with the d="" attribute
623 * of an SVG <path> element.
624 */
625 std::vector<TracingEngineResult>
626 PotraceTracingEngine::trace(Glib::RefPtr<Gdk::Pixbuf> pixbuf)
627 {
629 GdkPixbuf *thePixbuf = pixbuf->gobj();
631 //Set up for messages
632 keepGoing = 1;
634 if ( traceType == TRACE_QUANT_COLOR ||
635 traceType == TRACE_QUANT_MONO )
636 {
637 return traceQuant(thePixbuf);
638 }
639 else if ( traceType == TRACE_BRIGHTNESS_MULTI )
640 {
641 return traceBrightnessMulti(thePixbuf);
642 }
643 else
644 {
645 return traceSingle(thePixbuf);
646 }
647 }
653 /**
654 * Abort the thread that is executing getPathDataFromPixbuf()
655 */
656 void
657 PotraceTracingEngine::abort()
658 {
659 //g_message("PotraceTracingEngine::abort()\n");
660 keepGoing = 0;
661 }
666 } // namespace Potrace
667 } // namespace Trace
668 } // namespace Inkscape