Code

Merge and cleanup of GSoC C++-ification project.
[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  *   Jon A. Cruz <jon@joncruz.org>
8  *   Abhishek Sharma
9  *
10  * Copyright (C) 2004-2006 Bob Jamison
11  *
12  * Released under GNU GPL, read the file 'COPYING' for more information
13  */
17 #include "trace/potrace/inkscape-potrace.h"
19 #include <inkscape.h>
20 #include <desktop.h>
21 #include <desktop-handles.h>
22 #include <document.h>
23 #include <message-stack.h>
24 #include <gtkmm.h>
25 #include <glibmm/i18n.h>
26 #include <selection.h>
27 #include <xml/repr.h>
28 #include <xml/attribute-record.h>
29 #include <sp-item.h>
30 #include <sp-shape.h>
31 #include <sp-image.h>
32 #include <libnr/nr-matrix-ops.h>
33 #include <2geom/transforms.h>
35 #include <display/nr-arena.h>
36 #include <display/nr-arena-shape.h>
38 #include "siox.h"
39 #include "imagemap-gdk.h"
43 namespace Inkscape
44 {
46 namespace Trace
47 {
53 /**
54  * Get the selected image.  Also check for any SPItems over it, in
55  * case the user wants SIOX pre-processing.
56  */
57 SPImage *
58 Tracer::getSelectedSPImage()
59 {
61     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
62     if (!desktop)
63         {
64         g_warning("Trace: No active desktop");
65         return NULL;
66         }
68     Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
70     Inkscape::Selection *sel = sp_desktop_selection(desktop);
71     if (!sel)
72         {
73         char *msg = _("Select an <b>image</b> to trace");
74         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
75         //g_warning(msg);
76         return NULL;
77         }
79     if (sioxEnabled)
80         {
81         SPImage *img = NULL;
82         GSList const *list = sel->itemList();
83         std::vector<SPItem *> items;
84         sioxShapes.clear();
86         /*
87            First, things are selected top-to-bottom, so we need to invert
88            them as bottom-to-top so that we can discover the image and any
89            SPItems above it
90         */
91         for ( ; list ; list=list->next)
92             {
93             if (!SP_IS_ITEM(list->data))
94                 {
95                 continue;
96                 }
97             SPItem *item = SP_ITEM(list->data);
98             items.insert(items.begin(), item);
99             }
100         std::vector<SPItem *>::iterator iter;
101         for (iter = items.begin() ; iter!= items.end() ; iter++)
102             {
103             SPItem *item = *iter;
104             if (SP_IS_IMAGE(item))
105                 {
106                 if (img) //we want only one
107                     {
108                     char *msg = _("Select only one <b>image</b> to trace");
109                     msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
110                     return NULL;
111                     }
112                 img = SP_IMAGE(item);
113                 }
114             else // if (img) //# items -after- the image in tree (above it in Z)
115                 {
116                 if (SP_IS_SHAPE(item))
117                     {
118                     SPShape *shape = SP_SHAPE(item);
119                     sioxShapes.push_back(shape);
120                     }
121                 }
122             }
124         if (!img || sioxShapes.size() < 1)
125             {
126             char *msg = _("Select one image and one or more shapes above it");
127             msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
128             return NULL;
129             }
130         return img;
131         }
132     else
133         //### SIOX not enabled.  We want exactly one image selected
134         {
135         SPItem *item = sel->singleItem();
136         if (!item)
137             {
138             char *msg = _("Select an <b>image</b> to trace");  //same as above
139             msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
140             //g_warning(msg);
141             return NULL;
142             }
144         if (!SP_IS_IMAGE(item))
145             {
146             char *msg = _("Select an <b>image</b> to trace");
147             msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
148             //g_warning(msg);
149             return NULL;
150             }
152         SPImage *img = SP_IMAGE(item);
154         return img;
155         }
161 typedef org::siox::SioxImage SioxImage;
162 typedef org::siox::SioxObserver SioxObserver;
163 typedef org::siox::Siox Siox;
166 class TraceSioxObserver : public SioxObserver
168 public:
170     /**
171      *
172      */
173     TraceSioxObserver (void *contextArg) :
174                      SioxObserver(contextArg)
175         {}
177     /**
178      *
179      */
180     virtual ~TraceSioxObserver ()
181         { }
183     /**
184      *  Informs the observer how much has been completed.
185      *  Return false if the processing should be aborted.
186      */
187     virtual bool progress(float /*percentCompleted*/)
188         {
189         //Tracer *tracer = (Tracer *)context;
190         //## Allow the GUI to update
191         Gtk::Main::iteration(false); //at least once, non-blocking
192         while( Gtk::Main::events_pending() )
193             Gtk::Main::iteration();
194         return true;
195         }
197     /**
198      *  Send an error string to the Observer.  Processing will
199      *  be halted.
200      */
201     virtual void error(const std::string &/*msg*/)
202         {
203         //Tracer *tracer = (Tracer *)context;
204         }
207 };
213 /**
214  * Process a GdkPixbuf, according to which areas have been
215  * obscured in the GUI.
216  */
217 Glib::RefPtr<Gdk::Pixbuf>
218 Tracer::sioxProcessImage(SPImage *img,
219              Glib::RefPtr<Gdk::Pixbuf>origPixbuf)
221     if (!sioxEnabled)
222         return origPixbuf;
224     if (origPixbuf == lastOrigPixbuf)
225         return lastSioxPixbuf;
227     //g_message("siox: start");
229     //Convert from gdk, so a format we know.  By design, the pixel
230     //format in PackedPixelMap is identical to what is needed by SIOX
231     SioxImage simage(origPixbuf->gobj());
233     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
234     if (!desktop)
235         {
236         g_warning(_("Trace: No active desktop"));
237         return Glib::RefPtr<Gdk::Pixbuf>(NULL);
238         }
240     Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
242     Inkscape::Selection *sel = sp_desktop_selection(desktop);
243     if (!sel)
244         {
245         char *msg = _("Select an <b>image</b> to trace");
246         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
247         //g_warning(msg);
248         return Glib::RefPtr<Gdk::Pixbuf>(NULL);
249         }
251     NRArenaItem *aImg = img->get_arenaitem(desktop->dkey);
252     //g_message("img: %d %d %d %d\n", aImg->bbox.x0, aImg->bbox.y0,
253     //                                aImg->bbox.x1, aImg->bbox.y1);
255     double width  = (double)(aImg->bbox.x1 - aImg->bbox.x0);
256     double height = (double)(aImg->bbox.y1 - aImg->bbox.y0);
258     double iwidth  = (double)simage.getWidth();
259     double iheight = (double)simage.getHeight();
261     double iwscale = width  / iwidth;
262     double ihscale = height / iheight;
264     std::vector<NRArenaItem *> arenaItems;
265     std::vector<SPShape *>::iterator iter;
266     for (iter = sioxShapes.begin() ; iter!=sioxShapes.end() ; iter++)
267         {
268         SPItem *item = *iter;
269         NRArenaItem *aItem = item->get_arenaitem(desktop->dkey);
270         arenaItems.push_back(aItem);
271         }
273     //g_message("%d arena items\n", arenaItems.size());
275     //PackedPixelMap *dumpMap = PackedPixelMapCreate(
276     //                simage.getWidth(), simage.getHeight());
278     //g_message("siox: start selection");
280     for (int row=0 ; row<iheight ; row++)
281         {
282         double ypos = ((double)aImg->bbox.y0) + ihscale * (double) row;
283         for (int col=0 ; col<simage.getWidth() ; col++)
284             {
285             //Get absolute X,Y position
286             double xpos = ((double)aImg->bbox.x0) + iwscale * (double)col;
287             Geom::Point point(xpos, ypos);
288             if (aImg->transform)
289                 point *= *aImg->transform;
290             //point *= imgMat;
291             //point = desktop->doc2dt(point);
292             //g_message("x:%f    y:%f\n", point[0], point[1]);
293             bool weHaveAHit = false;
294             std::vector<NRArenaItem *>::iterator aIter;
295             for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
296                 {
297                 NRArenaItem *arenaItem = *aIter;
298                 NRArenaItemClass *arenaClass =
299                     (NRArenaItemClass *) NR_OBJECT_GET_CLASS (arenaItem);
300                 if (arenaClass->pick(arenaItem, point, 1.0f, 1))
301                     {
302                     weHaveAHit = true;
303                     break;
304                     }
305                 }
307             if (weHaveAHit)
308                 {
309                 //g_message("hit!\n");
310                 //dumpMap->setPixelLong(dumpMap, col, row, 0L);
311                 simage.setConfidence(col, row,
312                         Siox::UNKNOWN_REGION_CONFIDENCE);
313                 }
314             else
315                 {
316                 //g_message("miss!\n");
317                 //dumpMap->setPixelLong(dumpMap, col, row,
318                 //        simage.getPixel(col, row));
319                 simage.setConfidence(col, row,
320                         Siox::CERTAIN_BACKGROUND_CONFIDENCE);
321                 }
322             }
323         }
325     //g_message("siox: selection done");
327     //dumpMap->writePPM(dumpMap, "siox1.ppm");
328     //dumpMap->destroy(dumpMap);
330     //## ok we have our pixel buf
331     TraceSioxObserver observer(this);
332     Siox sengine(&observer);
333     SioxImage result = sengine.extractForeground(simage, 0xffffff);
334     if (!result.isValid())
335         {
336         g_warning(_("Invalid SIOX result"));
337         return Glib::RefPtr<Gdk::Pixbuf>(NULL);
338         }
340     //result.writePPM("siox2.ppm");
342     /* Free Arena and ArenaItem */
343     /*
344     std::vector<NRArenaItem *>::iterator aIter;
345     for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
346        {
347        NRArenaItem *arenaItem = *aIter;
348        nr_arena_item_unref(arenaItem);
349        }
350     nr_object_unref((NRObject *) arena);
351     */
353     Glib::RefPtr<Gdk::Pixbuf> newPixbuf = Glib::wrap(result.getGdkPixbuf());
355     //g_message("siox: done");
357     lastSioxPixbuf = newPixbuf;
359     return newPixbuf;
363 /**
364  *
365  */
366 Glib::RefPtr<Gdk::Pixbuf>
367 Tracer::getSelectedImage()
371     SPImage *img = getSelectedSPImage();
372     if (!img)
373         return Glib::RefPtr<Gdk::Pixbuf>(NULL);
375     if (!img->pixbuf)
376         return Glib::RefPtr<Gdk::Pixbuf>(NULL);
378     Glib::RefPtr<Gdk::Pixbuf> pixbuf =
379           Glib::wrap(img->pixbuf, true);
381     if (sioxEnabled)
382         {
383         Glib::RefPtr<Gdk::Pixbuf> sioxPixbuf =
384              sioxProcessImage(img, pixbuf);
385         if (!sioxPixbuf)
386             {
387             return pixbuf;
388             }
389         else
390             {
391             return sioxPixbuf;
392             }
393         }
394     else
395         {
396         return pixbuf;
397         }
403 //#########################################################################
404 //#  T R A C E
405 //#########################################################################
407 /**
408  *  Whether we want to enable SIOX subimage selection
409  */
410 void Tracer::enableSiox(bool enable)
412     sioxEnabled = enable;
416 /**
417  *  Threaded method that does single bitmap--->path conversion
418  */
419 void Tracer::traceThread()
421     //## Remember. NEVER leave this method without setting
422     //## engine back to NULL
424     //## Prepare our kill flag.  We will watch this later to
425     //## see if the main thread wants us to stop
426     keepGoing = true;
428     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
429     if (!desktop)
430         {
431         g_warning("Trace: No active desktop\n");
432         return;
433         }
435     Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
437     Inkscape::Selection *selection = sp_desktop_selection (desktop);
439     if (!SP_ACTIVE_DOCUMENT)
440         {
441         char *msg = _("Trace: No active document");
442         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
443         //g_warning(msg);
444         engine = NULL;
445         return;
446         }
447     SPDocument *doc = SP_ACTIVE_DOCUMENT;
448     doc->ensureUpToDate();
451     SPImage *img = getSelectedSPImage();
452     if (!img)
453         {
454         engine = NULL;
455         return;
456         }
458     Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(img->pixbuf, true);
460     pixbuf = sioxProcessImage(img, pixbuf);
462     if (!pixbuf)
463         {
464         char *msg = _("Trace: Image has no bitmap data");
465         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
466         //g_warning(msg);
467         engine = NULL;
468         return;
469         }
471     msgStack->flash(Inkscape::NORMAL_MESSAGE, _("Trace: Starting trace..."));
472     desktop->updateCanvasNow();
474     std::vector<TracingEngineResult> results =
475                 engine->trace(pixbuf);
476     //printf("nrPaths:%d\n", results.size());
477     int nrPaths = results.size();
479     //### Check if we should stop
480     if (!keepGoing || nrPaths<1)
481         {
482         engine = NULL;
483         return;
484         }
486     //### Get pointers to the <image> and its parent
487     //XML Tree being used directly here while it shouldn't be.
488     Inkscape::XML::Node *imgRepr   = SP_OBJECT(img)->getRepr();
489     Inkscape::XML::Node *par       = sp_repr_parent(imgRepr);
491     //### Get some information for the new transform()
492     double x      = 0.0;
493     double y      = 0.0;
494     double width  = 0.0;
495     double height = 0.0;
496     double dval   = 0.0;
498     if (sp_repr_get_double(imgRepr, "x", &dval))
499         x = dval;
500     if (sp_repr_get_double(imgRepr, "y", &dval))
501         y = dval;
503     if (sp_repr_get_double(imgRepr, "width", &dval))
504         width = dval;
505     if (sp_repr_get_double(imgRepr, "height", &dval))
506         height = dval;
508     double iwidth  = (double)pixbuf->get_width();
509     double iheight = (double)pixbuf->get_height();
511     double iwscale = width  / iwidth;
512     double ihscale = height / iheight;
514     Geom::Translate trans(x, y);
515     Geom::Scale scal(iwscale, ihscale);
517     //# Convolve scale, translation, and the original transform
518     Geom::Matrix tf(scal * trans);
519     tf *= img->transform;
522     //#OK.  Now let's start making new nodes
524     Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
525     Inkscape::XML::Node *groupRepr = NULL;
527     //# if more than 1, make a <g>roup of <path>s
528     if (nrPaths > 1)
529         {
530         groupRepr = xml_doc->createElement("svg:g");
531         par->addChild(groupRepr, imgRepr);
532         }
534     long totalNodeCount = 0L;
536     for (unsigned int i=0 ; i<results.size() ; i++)
537         {
538         TracingEngineResult result = results[i];
539         totalNodeCount += result.getNodeCount();
541         Inkscape::XML::Node *pathRepr = xml_doc->createElement("svg:path");
542         pathRepr->setAttribute("style", result.getStyle().c_str());
543         pathRepr->setAttribute("d",     result.getPathData().c_str());
545         if (nrPaths > 1)
546             groupRepr->addChild(pathRepr, NULL);
547         else
548             par->addChild(pathRepr, imgRepr);
550         //### Apply the transform from the image to the new shape
551         SPObject *reprobj = doc->getObjectByRepr(pathRepr);
552         if (reprobj)
553             {
554             SPItem *newItem = SP_ITEM(reprobj);
555             newItem->doWriteTransform(pathRepr, tf, NULL);
556             }
557         if (nrPaths == 1)
558             {
559             selection->clear();
560             selection->add(pathRepr);
561             }
562         Inkscape::GC::release(pathRepr);
563         }
565     // If we have a group, then focus on, then forget it
566     if (nrPaths > 1)
567         {
568         selection->clear();
569         selection->add(groupRepr);
570         Inkscape::GC::release(groupRepr);
571         }
573     //## inform the document, so we can undo
574     DocumentUndo::done(doc, SP_VERB_SELECTION_TRACE, _("Trace bitmap"));
576     engine = NULL;
578     char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
579     msgStack->flash(Inkscape::NORMAL_MESSAGE, msg);
580     g_free(msg);
588 /**
589  *  Main tracing method
590  */
591 void Tracer::trace(TracingEngine *theEngine)
593     //Check if we are already running
594     if (engine)
595         return;
597     engine = theEngine;
599 #if HAVE_THREADS
600     //Ensure that thread support is running
601     if (!Glib::thread_supported())
602         Glib::thread_init();
604     //Create our thread and run it
605     Glib::Thread::create(
606         sigc::mem_fun(*this, &Tracer::traceThread), false);
607 #else
608     traceThread();
609 #endif
617 /**
618  *  Abort the thread that is executing trace()
619  */
620 void Tracer::abort()
623     //## Inform Trace's working thread
624     keepGoing = false;
626     if (engine)
627         {
628         engine->abort();
629         }
635 } // namespace Trace
637 } // namespace Inkscape
640 //#########################################################################
641 //# E N D   O F   F I L E
642 //#########################################################################