Code

change API: separate functions creating a blur filter, one for a given item, another...
[inkscape.git] / src / trace / potrace / inkscape-potrace.cpp
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)
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;
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)
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;
198 static GrayMap *
199 filter(PotraceTracingEngine &engine, GdkPixbuf * pixbuf)
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
273 static IndexedMap *
274 filterIndexed(PotraceTracingEngine &engine, GdkPixbuf * pixbuf)
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;
330 Glib::RefPtr<Gdk::Pixbuf>
331 PotraceTracingEngine::preview(Glib::RefPtr<Gdk::Pixbuf> thePixbuf)
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         }
365 //*This is the core inkscape-to-potrace binding
366 std::string
367 PotraceTracingEngine::grayMapToPath(GrayMap *grayMap, long *nodeCount)
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;
442 /**
443  *  This is called for a single scan
444  */
445 std::vector<TracingEngineResult>
446 PotraceTracingEngine::traceSingle(GdkPixbuf * thePixbuf)
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;
475 /**
476  *  Called for multiple-scanning algorithms
477  */
478 std::vector<TracingEngineResult>
479 PotraceTracingEngine::traceBrightnessMulti(GdkPixbuf * thePixbuf)
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;
545 /**
546  *  Quantization
547  */
548 std::vector<TracingEngineResult>
549 PotraceTracingEngine::traceQuant(GdkPixbuf * thePixbuf)
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;
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)
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         }
664 /**
665  *  Abort the thread that is executing getPathDataFromPixbuf()
666  */
667 void
668 PotraceTracingEngine::abort()
670     //g_message("PotraceTracingEngine::abort()\n");
671     keepGoing = 0;
677 }  // namespace Potrace
678 }  // namespace Trace
679 }  // namespace Inkscape