1 /*
2 * This is the C++ glue between Inkscape and Potrace
3 *
4 * Authors:
5 * Bob Jamison <rjamison@titan.com>
6 *
7 * Copyright (C) 2004 Bob Jamison
8 *
9 * Released under GNU GPL, read the file 'COPYING' for more information
10 *
11 * Potrace, the wonderful tracer located at http://potrace.sourceforge.net,
12 * is provided by the generosity of Peter Selinger, to whom we are grateful.
13 *
14 */
16 #include "inkscape-potrace.h"
18 #include <glibmm/i18n.h>
19 #include <gtkmm.h>
21 #include "trace/filterset.h"
22 #include "trace/imagemap-gdk.h"
24 #include <inkscape.h>
25 #include <desktop-handles.h>
26 #include "message-stack.h"
27 #include <sp-path.h>
28 #include <svg/stringstream.h>
29 #include "curve.h"
30 #include "bitmap.h"
34 static void updateGui()
35 {
36 //## Allow the GUI to update
37 Gtk::Main::iteration(false); //at least once, non-blocking
38 while( Gtk::Main::events_pending() )
39 Gtk::Main::iteration();
41 }
44 static void potraceStatusCallback(double progress, void *userData) /* callback fn */
45 {
46 updateGui();
48 if (!userData)
49 return;
51 //g_message("progress: %f\n", progress);
53 //Inkscape::Trace::Potrace::PotraceTracingEngine *engine =
54 // (Inkscape::Trace::Potrace::PotraceTracingEngine *)userData;
55 }
60 //required by potrace
61 namespace Inkscape {
63 namespace Trace {
65 namespace Potrace {
68 /**
69 *
70 */
71 PotraceTracingEngine::PotraceTracingEngine()
72 {
74 //##### Our defaults
75 invert = false;
76 traceType = TRACE_BRIGHTNESS;
78 quantizationNrColors = 8;
80 brightnessThreshold = 0.45;
82 cannyHighThreshold = 0.65;
85 }
90 typedef struct
91 {
92 double x;
93 double y;
94 } Point;
97 /**
98 * Check a point against a list of points to see if it
99 * has already occurred.
100 */
101 static bool
102 hasPoint(std::vector<Point> &points, double x, double y)
103 {
104 for (unsigned int i=0; i<points.size() ; i++)
105 {
106 Point p = points[i];
107 if (p.x == x && p.y == y)
108 return true;
109 }
110 return false;
111 }
114 /**
115 * Recursively descend the path_t node tree, writing paths in SVG
116 * format into the output stream. The Point vector is used to prevent
117 * redundant paths. Returns number of paths processed.
118 */
119 static long
120 writePaths(PotraceTracingEngine *engine, potrace_path_t *plist,
121 Inkscape::SVGOStringStream& data, std::vector<Point> &points)
122 {
123 long nodeCount = 0L;
125 potrace_path_t *node;
126 for (node=plist; node ; node=node->sibling)
127 {
128 potrace_curve_t *curve = &(node->curve);
129 //g_message("node->fm:%d\n", node->fm);
130 if (!curve->n)
131 continue;
132 dpoint_t *pt = curve->c[curve->n - 1];
133 double x0 = 0.0;
134 double y0 = 0.0;
135 double x1 = 0.0;
136 double y1 = 0.0;
137 double x2 = pt[2].x;
138 double y2 = pt[2].y;
139 //Have we been here already?
140 if (hasPoint(points, x2, y2))
141 {
142 //g_message("duplicate point: (%f,%f)\n", x2, y2);
143 continue;
144 }
145 else
146 {
147 Point p;
148 p.x = x2; p.y = y2;
149 points.push_back(p);
150 }
151 data << "M " << x2 << " " << y2 << " ";
152 nodeCount++;
154 for (int i=0 ; i<curve->n ; i++)
155 {
156 if (!engine->keepGoing)
157 return 0L;
158 pt = curve->c[i];
159 x0 = pt[0].x;
160 y0 = pt[0].y;
161 x1 = pt[1].x;
162 y1 = pt[1].y;
163 x2 = pt[2].x;
164 y2 = pt[2].y;
165 switch (curve->tag[i])
166 {
167 case POTRACE_CORNER:
168 data << "L " << x1 << " " << y1 << " " ;
169 data << "L " << x2 << " " << y2 << " " ;
170 break;
171 case POTRACE_CURVETO:
172 data << "C " << x0 << " " << y0 << " "
173 << x1 << " " << y1 << " "
174 << x2 << " " << y2 << " ";
176 break;
177 default:
178 break;
179 }
180 nodeCount++;
181 }
182 data << "z";
184 for (path_t *child=node->childlist; child ; child=child->sibling)
185 {
186 nodeCount += writePaths(engine, child, data, points);
187 }
188 }
190 return nodeCount;
192 }
198 static GrayMap *
199 filter(PotraceTracingEngine &engine, GdkPixbuf * pixbuf)
200 {
201 if (!pixbuf)
202 return NULL;
204 GrayMap *newGm = NULL;
206 /*### Color quantization -- banding ###*/
207 if (engine.getTraceType() == TRACE_QUANT)
208 {
209 RgbMap *rgbmap = gdkPixbufToRgbMap(pixbuf);
210 //rgbMap->writePPM(rgbMap, "rgb.ppm");
211 newGm = quantizeBand(rgbmap,
212 engine.getQuantizationNrColors());
213 rgbmap->destroy(rgbmap);
214 //return newGm;
215 }
217 /*### Brightness threshold ###*/
218 else if ( engine.getTraceType() == TRACE_BRIGHTNESS ||
219 engine.getTraceType() == TRACE_BRIGHTNESS_MULTI )
220 {
221 GrayMap *gm = gdkPixbufToGrayMap(pixbuf);
223 newGm = GrayMapCreate(gm->width, gm->height);
224 double floor = 3.0 *
225 ( engine.getBrightnessFloor() * 256.0 );
226 double cutoff = 3.0 *
227 ( engine.getBrightnessThreshold() * 256.0 );
228 for (int y=0 ; y<gm->height ; y++)
229 {
230 for (int x=0 ; x<gm->width ; x++)
231 {
232 double brightness = (double)gm->getPixel(gm, x, y);
233 if (brightness >= floor && brightness < cutoff)
234 newGm->setPixel(newGm, x, y, GRAYMAP_BLACK); //black pixel
235 else
236 newGm->setPixel(newGm, x, y, GRAYMAP_WHITE); //white pixel
237 }
238 }
240 gm->destroy(gm);
241 //newGm->writePPM(newGm, "brightness.ppm");
242 //return newGm;
243 }
245 /*### Canny edge detection ###*/
246 else if (engine.getTraceType() == TRACE_CANNY)
247 {
248 GrayMap *gm = gdkPixbufToGrayMap(pixbuf);
249 newGm = grayMapCanny(gm, 0.1, engine.getCannyHighThreshold());
250 gm->destroy(gm);
251 //newGm->writePPM(newGm, "canny.ppm");
252 //return newGm;
253 }
255 /*### Do I invert the image? ###*/
256 if (newGm && engine.getInvert())
257 {
258 for (int y=0 ; y<newGm->height ; y++)
259 {
260 for (int x=0 ; x<newGm->width ; x++)
261 {
262 unsigned long brightness = newGm->getPixel(newGm, x, y);
263 brightness = 765 - brightness;
264 newGm->setPixel(newGm, x, y, brightness);
265 }
266 }
267 }
269 return newGm;//none of the above
270 }
273 static IndexedMap *
274 filterIndexed(PotraceTracingEngine &engine, GdkPixbuf * pixbuf)
275 {
276 if (!pixbuf)
277 return NULL;
279 IndexedMap *newGm = NULL;
281 /*### Color quant multiscan ###*/
282 if (engine.getTraceType() == TRACE_QUANT_COLOR)
283 {
284 RgbMap *gm = gdkPixbufToRgbMap(pixbuf);
285 if (engine.getMultiScanSmooth())
286 {
287 RgbMap *gaussMap = rgbMapGaussian(gm);
288 newGm = rgbMapQuantize(gaussMap, (int)log2(engine.getMultiScanNrColors())+2, engine.getMultiScanNrColors());
289 gaussMap->destroy(gaussMap);
290 }
291 else
292 {
293 newGm = rgbMapQuantize(gm, (int)log2(engine.getMultiScanNrColors())+2, engine.getMultiScanNrColors());
294 }
295 gm->destroy(gm);
296 }
298 /*### Quant multiscan ###*/
299 else if (engine.getTraceType() == TRACE_QUANT_MONO)
300 {
301 RgbMap *gm = gdkPixbufToRgbMap(pixbuf);
302 if (engine.getMultiScanSmooth())
303 {
304 RgbMap *gaussMap = rgbMapGaussian(gm);
305 newGm = rgbMapQuantize(gaussMap, (int)log2(engine.getMultiScanNrColors())+2, engine.getMultiScanNrColors());
306 gaussMap->destroy(gaussMap);
307 }
308 else
309 {
310 newGm = rgbMapQuantize(gm, (int)log2(engine.getMultiScanNrColors())+2, engine.getMultiScanNrColors());
311 }
312 gm->destroy(gm);
314 //Turn to grays
315 for (int i=0 ; i<newGm->nrColors ; i++)
316 {
317 RGB rgb = newGm->clut[i];
318 int grayVal = (rgb.r + rgb.g + rgb.b) / 3;
319 rgb.r = rgb.g = rgb.b = grayVal;
320 newGm->clut[i] = rgb;
321 }
322 }
324 return newGm;
325 }
330 Glib::RefPtr<Gdk::Pixbuf>
331 PotraceTracingEngine::preview(Glib::RefPtr<Gdk::Pixbuf> thePixbuf)
332 {
333 GdkPixbuf *pixbuf = thePixbuf->gobj();
335 if ( traceType == TRACE_QUANT_COLOR ||
336 traceType == TRACE_QUANT_MONO )
337 {
338 IndexedMap *gm = filterIndexed(*this, pixbuf);
339 if (!gm)
340 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
342 Glib::RefPtr<Gdk::Pixbuf> newBuf =
343 Glib::wrap(indexedMapToGdkPixbuf(gm), false);
345 gm->destroy(gm);
347 return newBuf;
348 }
349 else
350 {
351 GrayMap *gm = filter(*this, pixbuf);
352 if (!gm)
353 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
355 Glib::RefPtr<Gdk::Pixbuf> newBuf =
356 Glib::wrap(grayMapToGdkPixbuf(gm), false);
358 gm->destroy(gm);
360 return newBuf;
361 }
362 }
365 //*This is the core inkscape-to-potrace binding
366 std::string
367 PotraceTracingEngine::grayMapToPath(GrayMap *grayMap, long *nodeCount)
368 {
370 /* get default parameters */
371 potrace_param_t *potraceParams = potrace_param_default();
373 potraceParams->progress.callback = potraceStatusCallback;
374 potraceParams->progress.data = (void *)this;
376 potrace_bitmap_t *potraceBitmap = bm_new(grayMap->width, grayMap->height);
377 bm_clear(potraceBitmap, 0);
379 //##Read the data out of the GrayMap
380 for (int y=0 ; y<grayMap->height ; y++)
381 {
382 for (int x=0 ; x<grayMap->width ; x++)
383 {
384 BM_UPUT(potraceBitmap, x, y,
385 grayMap->getPixel(grayMap, x, y) ? 0 : 1);
386 }
387 }
389 //##Debug
390 /*
391 FILE *f = fopen("poimage.pbm", "wb");
392 bm_writepbm(f, bm);
393 fclose(f);
394 */
396 if (!keepGoing)
397 {
398 g_warning("aborted");
399 return "";
400 }
402 /* trace a bitmap*/
403 potrace_state_t *potraceState = potrace_trace(potraceParams,
404 potraceBitmap);
406 //## Free the Potrace bitmap
407 bm_free(potraceBitmap);
409 if (!keepGoing)
410 {
411 g_warning("aborted");
412 potrace_state_free(potraceState);
413 potrace_param_free(potraceParams);
414 return "";
415 }
417 Inkscape::SVGOStringStream data;
419 data << "";
421 //## copy the path information into our d="" attribute string
422 std::vector<Point> points;
423 long thisNodeCount = writePaths(this, potraceState->plist, data, points);
425 /* free a potrace items */
426 potrace_state_free(potraceState);
427 potrace_param_free(potraceParams);
429 if (!keepGoing)
430 return "";
432 std::string d = data.str();
433 if ( nodeCount)
434 *nodeCount = thisNodeCount;
436 return d;
438 }
442 /**
443 * This is called for a single scan
444 */
445 std::vector<TracingEngineResult>
446 PotraceTracingEngine::traceSingle(GdkPixbuf * thePixbuf)
447 {
449 std::vector<TracingEngineResult> results;
451 if (!thePixbuf)
452 return results;
454 brightnessFloor = 0.0; //important to set this
456 GrayMap *grayMap = filter(*this, thePixbuf);
457 if (!grayMap)
458 return results;
460 long nodeCount;
461 std::string d = grayMapToPath(grayMap, &nodeCount);
463 grayMap->destroy(grayMap);
465 char *style = "fill:#000000";
467 //g_message("### GOT '%s' \n", d);
468 TracingEngineResult result(style, d, nodeCount);
469 results.push_back(result);
471 return results;
472 }
475 /**
476 * Called for multiple-scanning algorithms
477 */
478 std::vector<TracingEngineResult>
479 PotraceTracingEngine::traceBrightnessMulti(GdkPixbuf * thePixbuf)
480 {
482 std::vector<TracingEngineResult> results;
484 if (!thePixbuf)
485 return results;
487 double low = 0.2; //bottom of range
488 double high = 0.9; //top of range
489 double delta = (high - low ) / ((double)multiScanNrColors);
491 brightnessFloor = 0.0; //Set bottom to black
493 int traceCount = 0;
495 for ( brightnessThreshold = low ;
496 brightnessThreshold <= high ;
497 brightnessThreshold += delta)
499 {
501 GrayMap *grayMap = filter(*this, thePixbuf);
502 if (!grayMap)
503 return results;
505 long nodeCount;
506 std::string d = grayMapToPath(grayMap, &nodeCount);
508 grayMap->destroy(grayMap);
510 if (d.size() == 0)
511 return results;
513 int grayVal = (int)(256.0 * brightnessThreshold);
514 char style[31];
515 sprintf(style, "fill-opacity:1.0;fill:#%02x%02x%02x",
516 grayVal, grayVal, grayVal);
518 //g_message("### GOT '%s' \n", d);
519 TracingEngineResult result(style, d, nodeCount);
520 results.push_back(result);
522 if (!multiScanStack)
523 brightnessFloor = brightnessThreshold;
525 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
526 if (desktop)
527 {
528 gchar *msg = g_strdup_printf(_("Trace: %d. %ld nodes"), traceCount++, nodeCount);
529 sp_desktop_message_stack(desktop)->flash(Inkscape::NORMAL_MESSAGE, msg);
530 g_free(msg);
531 }
532 }
534 //# Remove the bottom-most scan, if requested
535 if (results.size() > 1 && multiScanRemoveBackground)
536 {
537 g_message("remove background");
538 results.erase(results.end() - 1);
539 }
541 return results;
542 }
545 /**
546 * Quantization
547 */
548 std::vector<TracingEngineResult>
549 PotraceTracingEngine::traceQuant(GdkPixbuf * thePixbuf)
550 {
552 std::vector<TracingEngineResult> results;
554 if (!thePixbuf)
555 return results;
557 IndexedMap *iMap = filterIndexed(*this, thePixbuf);
558 if (!iMap)
559 return results;
561 //Create and clear a gray map
562 GrayMap *gm = GrayMapCreate(iMap->width, iMap->height);
563 for (int row=0 ; row<gm->height ; row++)
564 for (int col=0 ; col<gm->width ; col++)
565 gm->setPixel(gm, col, row, GRAYMAP_WHITE);
568 for (int colorIndex=0 ; colorIndex<iMap->nrColors ; colorIndex++)
569 {
571 /*Make a gray map for each color index */
572 for (int row=0 ; row<iMap->height ; row++)
573 {
574 for (int col=0 ; col<iMap->width ; col++)
575 {
576 int indx = (int) iMap->getPixel(iMap, col, row);
577 if (indx == colorIndex)
578 {
579 gm->setPixel(gm, col, row, GRAYMAP_BLACK); //black
580 }
581 else
582 {
583 if (!multiScanStack)
584 gm->setPixel(gm, col, row, GRAYMAP_WHITE);//white
585 }
586 }
587 }
589 //## Now we have a traceable graymap
590 long nodeCount;
591 std::string d = grayMapToPath(gm, &nodeCount);
593 if (d.size() == 0)
594 return results;
596 //### get style info
597 char style[13];
598 RGB rgb = iMap->clut[colorIndex];
599 sprintf(style, "fill:#%02x%02x%02x", rgb.r, rgb.g, rgb.b);
601 //g_message("### GOT '%s' \n", d);
602 TracingEngineResult result(style, d, nodeCount);
603 results.push_back(result);
605 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
606 if (desktop)
607 {
608 gchar *msg = g_strdup_printf(_("Trace: %d. %ld nodes"), colorIndex, nodeCount);
609 sp_desktop_message_stack(desktop)->flash(Inkscape::NORMAL_MESSAGE, msg);
610 g_free(msg);
611 }
614 }// for colorIndex
616 gm->destroy(gm);
617 iMap->destroy(iMap);
619 //# Remove the bottom-most scan, if requested
620 if (results.size() > 1 && multiScanRemoveBackground)
621 {
622 g_message("remove background");
623 results.erase(results.end() - 1);
624 }
626 return results;
627 }
630 /**
631 * This is the working method of this interface, and all
632 * implementing classes. Take a GdkPixbuf, trace it, and
633 * return the path data that is compatible with the d="" attribute
634 * of an SVG <path> element.
635 */
636 std::vector<TracingEngineResult>
637 PotraceTracingEngine::trace(Glib::RefPtr<Gdk::Pixbuf> pixbuf)
638 {
640 GdkPixbuf *thePixbuf = pixbuf->gobj();
642 //Set up for messages
643 keepGoing = 1;
645 if ( traceType == TRACE_QUANT_COLOR ||
646 traceType == TRACE_QUANT_MONO )
647 {
648 return traceQuant(thePixbuf);
649 }
650 else if ( traceType == TRACE_BRIGHTNESS_MULTI )
651 {
652 return traceBrightnessMulti(thePixbuf);
653 }
654 else
655 {
656 return traceSingle(thePixbuf);
657 }
658 }
664 /**
665 * Abort the thread that is executing getPathDataFromPixbuf()
666 */
667 void
668 PotraceTracingEngine::abort()
669 {
670 //g_message("PotraceTracingEngine::abort()\n");
671 keepGoing = 0;
672 }
677 } // namespace Potrace
678 } // namespace Trace
679 } // namespace Inkscape