Code

fix by Preben S for LP bug 389780, flatness
[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  *   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)
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;
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)
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;
201 static GrayMap *
202 filter(PotraceTracingEngine &engine, GdkPixbuf * pixbuf)
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
276 static IndexedMap *
277 filterIndexed(PotraceTracingEngine &engine, GdkPixbuf * pixbuf)
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;
315 Glib::RefPtr<Gdk::Pixbuf>
316 PotraceTracingEngine::preview(Glib::RefPtr<Gdk::Pixbuf> thePixbuf)
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         }
350 //*This is the core inkscape-to-potrace binding
351 std::string
352 PotraceTracingEngine::grayMapToPath(GrayMap *grayMap, long *nodeCount)
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();
414 /**
415  *  This is called for a single scan
416  */
417 std::vector<TracingEngineResult>
418 PotraceTracingEngine::traceSingle(GdkPixbuf * thePixbuf)
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;
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)
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;
471 /**
472  *  Called for multiple-scanning algorithms
473  */
474 std::vector<TracingEngineResult>
475 PotraceTracingEngine::traceBrightnessMulti(GdkPixbuf * thePixbuf)
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;
540 /**
541  *  Quantization
542  */
543 std::vector<TracingEngineResult>
544 PotraceTracingEngine::traceQuant(GdkPixbuf * thePixbuf)
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;
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)
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         }
653 /**
654  *  Abort the thread that is executing getPathDataFromPixbuf()
655  */
656 void
657 PotraceTracingEngine::abort()
659     //g_message("PotraceTracingEngine::abort()\n");
660     keepGoing = 0;
666 }  // namespace Potrace
667 }  // namespace Trace
668 }  // namespace Inkscape