Code

Display message to indicate that bitmap tracing has started
[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 {
49 /**
50  * Get the selected image.  Also check for any SPItems over it, in
51  * case the user wants SIOX pre-processing.
52  */
53 SPImage *
54 Tracer::getSelectedSPImage()
55 {
57     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
58     if (!desktop)
59         {
60         g_warning("Trace: No active desktop");
61         return NULL;
62         }
64     Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
66     Inkscape::Selection *sel = sp_desktop_selection(desktop);
67     if (!sel)
68         {
69         char *msg = _("Select an <b>image</b> to trace");
70         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
71         //g_warning(msg);
72         return NULL;
73         }
75     if (sioxEnabled)
76         {
77         SPImage *img = NULL;
78         GSList const *list = sel->itemList();
79         std::vector<SPItem *> items;
80         sioxShapes.clear();
82         /*
83            First, things are selected top-to-bottom, so we need to invert
84            them as bottom-to-top so that we can discover the image and any
85            SPItems above it
86         */
87         for ( ; list ; list=list->next)
88             {
89             if (!SP_IS_ITEM(list->data))
90                 {
91                 continue;
92                 }
93             SPItem *item = SP_ITEM(list->data);
94             items.insert(items.begin(), item);
95             }
96         std::vector<SPItem *>::iterator iter;
97         for (iter = items.begin() ; iter!= items.end() ; iter++)
98             {
99             SPItem *item = *iter;
100             if (SP_IS_IMAGE(item))
101                 {
102                 if (img) //we want only one
103                     {
104                     char *msg = _("Select only one <b>image</b> to trace");
105                     msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
106                     return NULL;
107                     }
108                 img = SP_IMAGE(item);
109                 }
110             else // if (img) //# items -after- the image in tree (above it in Z)
111                 {
112                 if (SP_IS_SHAPE(item))
113                     {
114                     SPShape *shape = SP_SHAPE(item);
115                     sioxShapes.push_back(shape);
116                     }
117                 }
118             }
120         if (!img || sioxShapes.size() < 1)
121             {
122             char *msg = _("Select one image and one or more shapes above it");
123             msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
124             return NULL;
125             }
126         return img;
127         }
128     else
129         //### SIOX not enabled.  We want exactly one image selected
130         {
131         SPItem *item = sel->singleItem();
132         if (!item)
133             {
134             char *msg = _("Select an <b>image</b> to trace");  //same as above
135             msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
136             //g_warning(msg);
137             return NULL;
138             }
140         if (!SP_IS_IMAGE(item))
141             {
142             char *msg = _("Select an <b>image</b> to trace");
143             msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
144             //g_warning(msg);
145             return NULL;
146             }
148         SPImage *img = SP_IMAGE(item);
150         return img;
151         }
157 typedef org::siox::SioxImage SioxImage;
158 typedef org::siox::SioxObserver SioxObserver;
159 typedef org::siox::Siox Siox;
162 class TraceSioxObserver : public SioxObserver
164 public:
166     /**
167      *
168      */
169     TraceSioxObserver (void *contextArg) :
170                      SioxObserver(contextArg)
171         {}
173     /**
174      *
175      */
176     virtual ~TraceSioxObserver ()
177         { }
179     /**
180      *  Informs the observer how much has been completed.
181      *  Return false if the processing should be aborted.
182      */
183     virtual bool progress(float percentCompleted)
184         {
185         //Tracer *tracer = (Tracer *)context;
186         //## Allow the GUI to update
187         Gtk::Main::iteration(false); //at least once, non-blocking
188         while( Gtk::Main::events_pending() )
189             Gtk::Main::iteration();
190         return true;
191         }
193     /**
194      *  Send an error string to the Observer.  Processing will
195      *  be halted.
196      */
197     virtual void error(const std::string &msg)
198         {
199         //Tracer *tracer = (Tracer *)context;
200         }
203 };
209 /**
210  * Process a GdkPixbuf, according to which areas have been
211  * obscured in the GUI.
212  */
213 Glib::RefPtr<Gdk::Pixbuf>
214 Tracer::sioxProcessImage(SPImage *img,
215              Glib::RefPtr<Gdk::Pixbuf>origPixbuf)
217     if (!sioxEnabled)
218         return origPixbuf;
220     if (origPixbuf == lastOrigPixbuf)
221         return lastSioxPixbuf;
223     //g_message("siox: start");
225     //Convert from gdk, so a format we know.  By design, the pixel
226     //format in PackedPixelMap is identical to what is needed by SIOX
227     SioxImage simage(origPixbuf->gobj());
229     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
230     if (!desktop)
231         {
232         g_warning(_("Trace: No active desktop"));
233         return Glib::RefPtr<Gdk::Pixbuf>(NULL);
234         }
236     Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
238     Inkscape::Selection *sel = sp_desktop_selection(desktop);
239     if (!sel)
240         {
241         char *msg = _("Select an <b>image</b> to trace");
242         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
243         //g_warning(msg);
244         return Glib::RefPtr<Gdk::Pixbuf>(NULL);
245         }
247     NRArenaItem *aImg = sp_item_get_arenaitem(img, desktop->dkey);
248     //g_message("img: %d %d %d %d\n", aImg->bbox.x0, aImg->bbox.y0,
249     //                                aImg->bbox.x1, aImg->bbox.y1);
251     double width  = (double)(aImg->bbox.x1 - aImg->bbox.x0);
252     double height = (double)(aImg->bbox.y1 - aImg->bbox.y0);
254     double iwidth  = (double)simage.getWidth();
255     double iheight = (double)simage.getHeight();
257     double iwscale = width  / iwidth;
258     double ihscale = height / iheight;
260     std::vector<NRArenaItem *> arenaItems;
261     std::vector<SPShape *>::iterator iter;
262     for (iter = sioxShapes.begin() ; iter!=sioxShapes.end() ; iter++)
263         {
264         SPItem *item = *iter;
265         NRArenaItem *aItem = sp_item_get_arenaitem(item, desktop->dkey);
266         arenaItems.push_back(aItem);
267         }
269     //g_message("%d arena items\n", arenaItems.size());
271     //PackedPixelMap *dumpMap = PackedPixelMapCreate(
272     //                simage.getWidth(), simage.getHeight());
274     //g_message("siox: start selection");
276     for (int row=0 ; row<iheight ; row++)
277         {
278         double ypos = ((double)aImg->bbox.y0) + ihscale * (double) row;
279         for (int col=0 ; col<simage.getWidth() ; col++)
280             {
281             //Get absolute X,Y position
282             double xpos = ((double)aImg->bbox.x0) + iwscale * (double)col;
283             NR::Point point(xpos, ypos);
284             point *= aImg->transform;
285             //point *= imgMat;
286             //point = desktop->doc2dt(point);
287             //g_message("x:%f    y:%f\n", point[0], point[1]);
288             bool weHaveAHit = false;
289             std::vector<NRArenaItem *>::iterator aIter;
290             for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
291                 {
292                 NRArenaItem *arenaItem = *aIter;
293                 NRArenaItemClass *arenaClass =
294                     (NRArenaItemClass *) NR_OBJECT_GET_CLASS (arenaItem);
295                 if (arenaClass->pick(arenaItem, point, 1.0f, 1))
296                     {
297                     weHaveAHit = true;
298                     break;
299                     }
300                 }
302             if (weHaveAHit)
303                 {
304                 //g_message("hit!\n");
305                 //dumpMap->setPixelLong(dumpMap, col, row, 0L);
306                 simage.setConfidence(col, row, 
307                         Siox::UNKNOWN_REGION_CONFIDENCE);
308                 }
309             else
310                 {
311                 //g_message("miss!\n");
312                 //dumpMap->setPixelLong(dumpMap, col, row,
313                 //        simage.getPixel(col, row));
314                 simage.setConfidence(col, row,
315                         Siox::CERTAIN_BACKGROUND_CONFIDENCE);
316                 }
317             }
318         }
320     //g_message("siox: selection done");
322     //dumpMap->writePPM(dumpMap, "siox1.ppm");
323     //dumpMap->destroy(dumpMap);
325     //## ok we have our pixel buf
326     TraceSioxObserver observer(this);
327     Siox sengine(&observer);
328     SioxImage result = sengine.extractForeground(simage, 0xffffff);
329     if (!result.isValid())
330         {
331         g_warning(_("Invalid SIOX result"));
332         return Glib::RefPtr<Gdk::Pixbuf>(NULL);
333         }
335     //result.writePPM("siox2.ppm");
337     /* Free Arena and ArenaItem */
338     /*
339     std::vector<NRArenaItem *>::iterator aIter;
340     for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
341        {
342        NRArenaItem *arenaItem = *aIter;
343        nr_arena_item_unref(arenaItem);
344        }
345     nr_object_unref((NRObject *) arena);
346     */
348     Glib::RefPtr<Gdk::Pixbuf> newPixbuf = Glib::wrap(result.getGdkPixbuf());
350     //g_message("siox: done");
352     lastSioxPixbuf = newPixbuf;
354     return newPixbuf;
358 /**
359  *
360  */
361 Glib::RefPtr<Gdk::Pixbuf>
362 Tracer::getSelectedImage()
366     SPImage *img = getSelectedSPImage();
367     if (!img)
368         return Glib::RefPtr<Gdk::Pixbuf>(NULL);
370     if (!img->pixbuf)
371         return Glib::RefPtr<Gdk::Pixbuf>(NULL);
373     Glib::RefPtr<Gdk::Pixbuf> pixbuf =
374           Glib::wrap(img->pixbuf, true);
376     if (sioxEnabled)
377         {
378         Glib::RefPtr<Gdk::Pixbuf> sioxPixbuf =
379              sioxProcessImage(img, pixbuf);
380         if (!sioxPixbuf)
381             {
382             return pixbuf;
383             }
384         else
385             {
386             return sioxPixbuf;
387             }
388         }
389     else
390         {
391         return pixbuf;
392         }
398 //#########################################################################
399 //#  T R A C E
400 //#########################################################################
402 /**
403  *  Whether we want to enable SIOX subimage selection
404  */
405 void Tracer::enableSiox(bool enable)
407     sioxEnabled = enable;
411 /**
412  *  Threaded method that does single bitmap--->path conversion
413  */
414 void Tracer::traceThread()
416     //## Remember. NEVER leave this method without setting
417     //## engine back to NULL
419     //## Prepare our kill flag.  We will watch this later to
420     //## see if the main thread wants us to stop
421     keepGoing = true;
423     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
424     if (!desktop)
425         {
426         g_warning("Trace: No active desktop\n");
427         return;
428         }
430     Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
432     Inkscape::Selection *selection = sp_desktop_selection (desktop);
434     if (!SP_ACTIVE_DOCUMENT)
435         {
436         char *msg = _("Trace: No active document");
437         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
438         //g_warning(msg);
439         engine = NULL;
440         return;
441         }
442     SPDocument *doc = SP_ACTIVE_DOCUMENT;
443     sp_document_ensure_up_to_date(doc);
446     SPImage *img = getSelectedSPImage();
447     if (!img)
448         {
449         engine = NULL;
450         return;
451         }
453     Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(img->pixbuf, true);
455     pixbuf = sioxProcessImage(img, pixbuf);
457     if (!pixbuf)
458         {
459         char *msg = _("Trace: Image has no bitmap data");
460         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
461         //g_warning(msg);
462         engine = NULL;
463         return;
464         }
466     msgStack->flash(Inkscape::NORMAL_MESSAGE, _("Trace: Starting trace..."));
467     desktop->updateCanvasNow();
468     
469     std::vector<TracingEngineResult> results =
470                 engine->trace(pixbuf);
471     //printf("nrPaths:%d\n", results.size());
472     int nrPaths = results.size();
474     //### Check if we should stop
475     if (!keepGoing || nrPaths<1)
476         {
477         engine = NULL;
478         return;
479         }
481     //### Get pointers to the <image> and its parent
482     Inkscape::XML::Node *imgRepr   = SP_OBJECT(img)->repr;
483     Inkscape::XML::Node *par       = sp_repr_parent(imgRepr);
485     //### Get some information for the new transform()
486     double x      = 0.0;
487     double y      = 0.0;
488     double width  = 0.0;
489     double height = 0.0;
490     double dval   = 0.0;
492     if (sp_repr_get_double(imgRepr, "x", &dval))
493         x = dval;
494     if (sp_repr_get_double(imgRepr, "y", &dval))
495         y = dval;
497     if (sp_repr_get_double(imgRepr, "width", &dval))
498         width = dval;
499     if (sp_repr_get_double(imgRepr, "height", &dval))
500         height = dval;
502     NR::Matrix trans(NR::translate(x, y));
504     double iwidth  = (double)pixbuf->get_width();
505     double iheight = (double)pixbuf->get_height();
507     double iwscale = width  / iwidth;
508     double ihscale = height / iheight;
510     NR::Matrix scal(NR::scale(iwscale, ihscale));
512     //# Convolve scale, translation, and the original transform
513     NR::Matrix tf(scal);
514     tf *= trans;
515     tf *= img->transform;
518     //#OK.  Now let's start making new nodes
520     Inkscape::XML::Node *groupRepr = NULL;
522     //# if more than 1, make a <g>roup of <path>s
523     if (nrPaths > 1)
524         {
525         groupRepr = sp_repr_new("svg:g");
526         par->addChild(groupRepr, imgRepr);
527         }
529     long totalNodeCount = 0L;
531     for (unsigned int i=0 ; i<results.size() ; i++)
532         {
533         TracingEngineResult result = results[i];
534         totalNodeCount += result.getNodeCount();
536         Inkscape::XML::Node *pathRepr = sp_repr_new("svg:path");
537         pathRepr->setAttribute("style", result.getStyle().c_str());
538         pathRepr->setAttribute("d",     result.getPathData().c_str());
540         if (nrPaths > 1)
541             groupRepr->addChild(pathRepr, NULL);
542         else
543             par->addChild(pathRepr, imgRepr);
545         //### Apply the transform from the image to the new shape
546         SPObject *reprobj = doc->getObjectByRepr(pathRepr);
547         if (reprobj)
548             {
549             SPItem *newItem = SP_ITEM(reprobj);
550             sp_item_write_transform(newItem, pathRepr, tf, NULL);
551             }
552         if (nrPaths == 1)
553             {
554             selection->clear();
555             selection->add(pathRepr);
556             }
557         Inkscape::GC::release(pathRepr);
558         }
560     // If we have a group, then focus on, then forget it
561     if (nrPaths > 1)
562         {
563         selection->clear();
564         selection->add(groupRepr);
565         Inkscape::GC::release(groupRepr);
566         }
568     //## inform the document, so we can undo
569     sp_document_done(doc, SP_VERB_NONE, 
570                      /* TODO: annotate */ "trace.cpp:567");
572     engine = NULL;
574     char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
575     msgStack->flash(Inkscape::NORMAL_MESSAGE, msg);
576     g_free(msg);
584 /**
585  *  Main tracing method
586  */
587 void Tracer::trace(TracingEngine *theEngine)
589     //Check if we are already running
590     if (engine)
591         return;
593     engine = theEngine;
595 #if HAVE_THREADS
596     //Ensure that thread support is running
597     if (!Glib::thread_supported())
598         Glib::thread_init();
600     //Create our thread and run it
601     Glib::Thread::create(
602         sigc::mem_fun(*this, &Tracer::traceThread), false);
603 #else
604     traceThread();
605 #endif
613 /**
614  *  Abort the thread that is executing trace()
615  */
616 void Tracer::abort()
619     //## Inform Trace's working thread
620     keepGoing = false;
622     if (engine)
623         {
624         engine->abort();
625         }
631 } // namespace Trace
633 } // namespace Inkscape
636 //#########################################################################
637 //# E N D   O F   F I L E
638 //#########################################################################