Code

Applied patch #1501375
[inkscape.git] / src / trace / trace.cpp
1 /*
2  * A generic interface for plugging different
3  *  autotracers into Inkscape.
4  *
5  * Authors:
6  *   Bob Jamison <rjamison@earthlink.net>
7  *
8  * Copyright (C) 2004-2006 Bob Jamison
9  *
10  * Released under GNU GPL, read the file 'COPYING' for more information
11  */
15 #include "trace/potrace/inkscape-potrace.h"
17 #include <inkscape.h>
18 #include <desktop.h>
19 #include <desktop-handles.h>
20 #include <document.h>
21 #include <message-stack.h>
22 #include <gtkmm.h>
23 #include <glibmm/i18n.h>
24 #include <selection.h>
25 #include <xml/repr.h>
26 #include <xml/attribute-record.h>
27 #include <sp-item.h>
28 #include <sp-shape.h>
29 #include <sp-image.h>
31 #include <display/nr-arena.h>
32 #include <display/nr-arena-shape.h>
34 #include "siox.h"
35 #include "imagemap-gdk.h"
39 namespace Inkscape
40 {
42 namespace Trace
43 {
50 /**
51  * Get the selected image.  Also check for any SPItems over it, in
52  * case the user wants SIOX pre-processing.
53  */
54 SPImage *
55 Tracer::getSelectedSPImage()
56 {
58     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
59     if (!desktop)
60         {
61         g_warning("Trace: No active desktop");
62         return NULL;
63         }
65     Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
67     Inkscape::Selection *sel = sp_desktop_selection(desktop);
68     if (!sel)
69         {
70         char *msg = _("Select an <b>image</b> to trace");
71         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
72         //g_warning(msg);
73         return NULL;
74         }
76     if (sioxEnabled)
77         {
78         SPImage *img = NULL;
79         GSList const *list = sel->itemList();
80         std::vector<SPItem *> items;
81         sioxShapes.clear();
83         /*
84            First, things are selected top-to-bottom, so we need to invert
85            them as bottom-to-top so that we can discover the image and any
86            SPItems above it
87         */
88         for ( ; list ; list=list->next)
89             {
90             if (!SP_IS_ITEM(list->data))
91                 {
92                 continue;
93                 }
94             SPItem *item = SP_ITEM(list->data);
95             items.insert(items.begin(), item);
96             }
97         std::vector<SPItem *>::iterator iter;
98         for (iter = items.begin() ; iter!= items.end() ; iter++)
99             {
100             SPItem *item = *iter;
101             if (SP_IS_IMAGE(item))
102                 {
103                 if (img) //we want only one
104                     {
105                     char *msg = _("Select only one <b>image</b> to trace");
106                     msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
107                     return NULL;
108                     }
109                 img = SP_IMAGE(item);
110                 }
111             else // if (img) //# items -after- the image in tree (above it in Z)
112                 {
113                 if (SP_IS_SHAPE(item))
114                     {
115                     SPShape *shape = SP_SHAPE(item);
116                     sioxShapes.push_back(shape);
117                     }
118                 }
119             }
121         if (!img || sioxShapes.size() < 1)
122             {
123             char *msg = _("Select one image and one or more shapes above it");
124             msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
125             return NULL;
126             }
127         return img;
128         }
129     else
130         //### SIOX not enabled.  We want exactly one image selected
131         {
132         SPItem *item = sel->singleItem();
133         if (!item)
134             {
135             char *msg = _("Select an <b>image</b> to trace");  //same as above
136             msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
137             //g_warning(msg);
138             return NULL;
139             }
141         if (!SP_IS_IMAGE(item))
142             {
143             char *msg = _("Select an <b>image</b> to trace");
144             msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
145             //g_warning(msg);
146             return NULL;
147             }
149         SPImage *img = SP_IMAGE(item);
151         return img;
152         }
158 typedef org::siox::SioxImage SioxImage;
159 typedef org::siox::SioxObserver SioxObserver;
160 typedef org::siox::Siox Siox;
163 class TraceSioxObserver : public SioxObserver
165 public:
167     /**
168      *
169      */
170     TraceSioxObserver (void *contextArg) :
171                      SioxObserver(contextArg)
172         {}
174     /**
175      *
176      */
177     virtual ~TraceSioxObserver ()
178         { }
180     /**
181      *  Informs the observer how much has been completed.
182      *  Return false if the processing should be aborted.
183      */
184     virtual bool progress(float percentCompleted)
185         {
186         //Tracer *tracer = (Tracer *)context;
187         //## Allow the GUI to update
188         Gtk::Main::iteration(false); //at least once, non-blocking
189         while( Gtk::Main::events_pending() )
190             Gtk::Main::iteration();
191         return true;
192         }
194     /**
195      *  Send an error string to the Observer.  Processing will
196      *  be halted.
197      */
198     virtual void error(const std::string &msg)
199         {
200         //Tracer *tracer = (Tracer *)context;
201         }
204 };
210 /**
211  * Process a GdkPixbuf, according to which areas have been
212  * obscured in the GUI.
213  */
214 GdkPixbuf *
215 Tracer::sioxProcessImage(SPImage *img, GdkPixbuf *origPixbuf)
217     if (!sioxEnabled)
218         return origPixbuf;
220     //Convert from gdk, so a format we know.  By design, the pixel
221     //format in PackedPixelMap is identical to what is needed by SIOX
222     SioxImage simage(origPixbuf);
224     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
225     if (!desktop)
226         {
227         g_warning(_("Trace: No active desktop"));
228         return NULL;
229         }
231     Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
233     Inkscape::Selection *sel = sp_desktop_selection(desktop);
234     if (!sel)
235         {
236         char *msg = _("Select an <b>image</b> to trace");
237         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
238         //g_warning(msg);
239         return NULL;
240         }
242     NRArenaItem *aImg = sp_item_get_arenaitem(img, desktop->dkey);
243     //g_message("img: %d %d %d %d\n", aImg->bbox.x0, aImg->bbox.y0,
244     //                                aImg->bbox.x1, aImg->bbox.y1);
246     double width  = (double)(aImg->bbox.x1 - aImg->bbox.x0);
247     double height = (double)(aImg->bbox.y1 - aImg->bbox.y0);
249     double iwidth  = (double)simage.getWidth();
250     double iheight = (double)simage.getHeight();
252     double iwscale = width  / iwidth;
253     double ihscale = height / iheight;
255     std::vector<NRArenaItem *> arenaItems;
256     std::vector<SPShape *>::iterator iter;
257     for (iter = sioxShapes.begin() ; iter!=sioxShapes.end() ; iter++)
258         {
259         SPItem *item = *iter;
260         //### Create ArenaItems and set transform
261         NRArenaItem *aItem = sp_item_get_arenaitem(item, desktop->dkey);
263         //nr_arena_item_set_transform(aItem, item->transform);
264         //g_message("%d %d %d %d\n", aItem->bbox.x0, aItem->bbox.y0,
265         //                           aItem->bbox.x1, aItem->bbox.y1);
266         arenaItems.push_back(aItem);
267         }
268     //g_message("%d arena items\n", arenaItems.size());
270     //PackedPixelMap *dumpMap = PackedPixelMapCreate(
271     //                simage.getWidth(), simage.getHeight());
273     for (int row=0 ; row<simage.getHeight() ; row++)
274         {
275         double ypos = ((double)aImg->bbox.y0) + ihscale * (double) row;
276         for (int col=0 ; col<simage.getWidth() ; col++)
277             {
278             //Get absolute X,Y position
279             double xpos = ((double)aImg->bbox.x0) + iwscale * (double)col;
280             NR::Point point(xpos, ypos);
281             point *= aImg->transform;
282             //point *= imgMat;
283             //point = desktop->doc2dt(point);
284             std::vector<SPShape *>::iterator iter;
285             //g_message("x:%f    y:%f\n", point[0], point[1]);
286             bool weHaveAHit = false;
287             std::vector<NRArenaItem *>::iterator aIter;
288             for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
289                 {
290                 NRArenaItem *arenaItem = *aIter;
291                 NRArenaItemClass *arenaClass =
292                     (NRArenaItemClass *) NR_OBJECT_GET_CLASS (arenaItem);
293                 if (arenaClass->pick(arenaItem, point, 1.0f, 1))
294                     {
295                     weHaveAHit = true;
296                     break;
297                     }
298                 }
300             if (weHaveAHit)
301                 {
302                 //g_message("hit!\n");
303                 //dumpMap->setPixelLong(dumpMap, col, row, 0L);
304                 simage.setConfidence(col, row, 
305                         Siox::UNKNOWN_REGION_CONFIDENCE);
306                 }
307             else
308                 {
309                 //dumpMap->setPixelLong(dumpMap, col, row,
310                 //        simage.getPixel(col, row));
311                 simage.setConfidence(col, row,
312                         Siox::CERTAIN_BACKGROUND_CONFIDENCE);
313                 }
314             }
315         }
317     //dumpMap->writePPM(dumpMap, "siox1.ppm");
318     //dumpMap->destroy(dumpMap);
320     //## ok we have our pixel buf
321     org::siox::Siox sengine;
322     org::siox::SioxImage result =
323             sengine.extractForeground(simage, 0xffffff);
324     if (!result.isValid())
325         {
326         g_warning(_("Invalid SIOX result"));
327         return NULL;
328         }
330     //result.writePPM("siox2.ppm");
332     /* Free Arena and ArenaItem */
333     /*
334     std::vector<NRArenaItem *>::iterator aIter;
335     for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
336        {
337        NRArenaItem *arenaItem = *aIter;
338        nr_arena_item_unref(arenaItem);
339        }
340     nr_object_unref((NRObject *) arena);
341     */
343     GdkPixbuf *newPixbuf = result.getGdkPixbuf();
345     return newPixbuf;
349 /**
350  *
351  */
352 GdkPixbuf *
353 Tracer::getSelectedImage()
356     SPImage *img = getSelectedSPImage();
357     if (!img)
358         return NULL;
360     GdkPixbuf *pixbuf = img->pixbuf;
361     if (!pixbuf)
362         return NULL;
364     if (sioxEnabled)
365         {
366         GdkPixbuf *sioxPixbuf = sioxProcessImage(img, pixbuf);
367         if (!sioxPixbuf)
368             {
369             g_object_ref(pixbuf);
370             return pixbuf;
371             }
372         else
373             {
374             return sioxPixbuf;
375             }
376         }
377     else
378         {
379         g_object_ref(pixbuf);
380         return pixbuf;
381         }
387 //#########################################################################
388 //#  T R A C E
389 //#########################################################################
391 /**
392  *  Whether we want to enable SIOX subimage selection
393  */
394 void Tracer::enableSiox(bool enable)
396     sioxEnabled = enable;
400 /**
401  *  Threaded method that does single bitmap--->path conversion
402  */
403 void Tracer::traceThread()
405     //## Remember. NEVER leave this method without setting
406     //## engine back to NULL
408     //## Prepare our kill flag.  We will watch this later to
409     //## see if the main thread wants us to stop
410     keepGoing = true;
412     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
413     if (!desktop)
414         {
415         g_warning("Trace: No active desktop\n");
416         return;
417         }
419     Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
421     Inkscape::Selection *selection = sp_desktop_selection (desktop);
423     if (!SP_ACTIVE_DOCUMENT)
424         {
425         char *msg = _("Trace: No active document");
426         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
427         //g_warning(msg);
428         engine = NULL;
429         return;
430         }
431     SPDocument *doc = SP_ACTIVE_DOCUMENT;
432     sp_document_ensure_up_to_date(doc);
435     SPImage *img = getSelectedSPImage();
436     if (!img)
437         {
438         engine = NULL;
439         return;
440         }
442     GdkPixbuf *pixbuf = img->pixbuf;
443     g_object_ref(pixbuf);
445     pixbuf = sioxProcessImage(img, pixbuf);
447     if (!pixbuf)
448         {
449         char *msg = _("Trace: Image has no bitmap data");
450         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
451         //g_warning(msg);
452         engine = NULL;
453         return;
454         }
456     int nrPaths;
457     TracingEngineResult *results = engine->trace(pixbuf, &nrPaths);
458     //printf("nrPaths:%d\n", nrPaths);
460     //### Check if we should stop
461     if (!keepGoing || !results || nrPaths<1)
462         {
463         engine = NULL;
464         return;
465         }
467     //### Get pointers to the <image> and its parent
468     Inkscape::XML::Node *imgRepr   = SP_OBJECT(img)->repr;
469     Inkscape::XML::Node *par       = sp_repr_parent(imgRepr);
471     //### Get some information for the new transform()
472     double x      = 0.0;
473     double y      = 0.0;
474     double width  = 0.0;
475     double height = 0.0;
476     double dval   = 0.0;
478     if (sp_repr_get_double(imgRepr, "x", &dval))
479         x = dval;
480     if (sp_repr_get_double(imgRepr, "y", &dval))
481         y = dval;
483     if (sp_repr_get_double(imgRepr, "width", &dval))
484         width = dval;
485     if (sp_repr_get_double(imgRepr, "height", &dval))
486         height = dval;
488     NR::Matrix trans(NR::translate(x, y));
490     double iwidth  = (double)gdk_pixbuf_get_width(pixbuf);
491     double iheight = (double)gdk_pixbuf_get_height(pixbuf);
493     double iwscale = width  / iwidth;
494     double ihscale = height / iheight;
496     NR::Matrix scal(NR::scale(iwscale, ihscale));
498     //# Convolve scale, translation, and the original transform
499     NR::Matrix tf(scal);
500     tf *= trans;
501     tf *= img->transform;
504     //#OK.  Now let's start making new nodes
506     Inkscape::XML::Node *groupRepr = NULL;
508     //# if more than 1, make a <g>roup of <path>s
509     if (nrPaths > 1)
510         {
511         groupRepr = sp_repr_new("svg:g");
512         par->addChild(groupRepr, imgRepr);
513         }
515     long totalNodeCount = 0L;
517     for (TracingEngineResult *result=results ;
518                   result ; result=result->next)
519         {
520         totalNodeCount += result->getNodeCount();
522         Inkscape::XML::Node *pathRepr = sp_repr_new("svg:path");
523         pathRepr->setAttribute("style", result->getStyle());
524         pathRepr->setAttribute("d",     result->getPathData());
526         if (nrPaths > 1)
527             groupRepr->addChild(pathRepr, NULL);
528         else
529             par->addChild(pathRepr, imgRepr);
531         //### Apply the transform from the image to the new shape
532         SPObject *reprobj = doc->getObjectByRepr(pathRepr);
533         if (reprobj)
534             {
535             SPItem *newItem = SP_ITEM(reprobj);
536             sp_item_write_transform(newItem, pathRepr, tf, NULL);
537             }
538         if (nrPaths == 1)
539             {
540             selection->clear();
541             selection->add(pathRepr);
542             }
543         Inkscape::GC::release(pathRepr);
544         }
546     //release our pixbuf
547     g_object_unref(pixbuf);
549     delete results;
551     // If we have a group, then focus on, then forget it
552     if (nrPaths > 1)
553         {
554         selection->clear();
555         selection->add(groupRepr);
556         Inkscape::GC::release(groupRepr);
557         }
559     //## inform the document, so we can undo
560     sp_document_done(doc);
562     engine = NULL;
564     char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
565     msgStack->flash(Inkscape::NORMAL_MESSAGE, msg);
566     g_free(msg);
574 /**
575  *  Main tracing method
576  */
577 void Tracer::trace(TracingEngine *theEngine)
579     //Check if we are already running
580     if (engine)
581         return;
583     engine = theEngine;
585 #if HAVE_THREADS
586     //Ensure that thread support is running
587     if (!Glib::thread_supported())
588         Glib::thread_init();
590     //Create our thread and run it
591     Glib::Thread::create(
592         sigc::mem_fun(*this, &Tracer::traceThread), false);
593 #else
594     traceThread();
595 #endif
603 /**
604  *  Abort the thread that is executing trace()
605  */
606 void Tracer::abort()
609     //## Inform Trace's working thread
610     keepGoing = false;
612     if (engine)
613         {
614         engine->abort();
615         }
621 } // namespace Trace
623 } // namespace Inkscape
626 //#########################################################################
627 //# E N D   O F   F I L E
628 //#########################################################################