Code

patch from Gustav Broberg: undo annotations and history dialog
[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                 //dumpMap->setPixelLong(dumpMap, col, row,
312                 //        simage.getPixel(col, row));
313                 simage.setConfidence(col, row,
314                         Siox::CERTAIN_BACKGROUND_CONFIDENCE);
315                 }
316             }
317         }
319     //g_message("siox: selection done");
321     //dumpMap->writePPM(dumpMap, "siox1.ppm");
322     //dumpMap->destroy(dumpMap);
324     //## ok we have our pixel buf
325     TraceSioxObserver observer(this);
326     Siox sengine(&observer);
327     SioxImage result = sengine.extractForeground(simage, 0xffffff);
328     if (!result.isValid())
329         {
330         g_warning(_("Invalid SIOX result"));
331         return Glib::RefPtr<Gdk::Pixbuf>(NULL);
332         }
334     //result.writePPM("siox2.ppm");
336     /* Free Arena and ArenaItem */
337     /*
338     std::vector<NRArenaItem *>::iterator aIter;
339     for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
340        {
341        NRArenaItem *arenaItem = *aIter;
342        nr_arena_item_unref(arenaItem);
343        }
344     nr_object_unref((NRObject *) arena);
345     */
347     Glib::RefPtr<Gdk::Pixbuf> newPixbuf = Glib::wrap(result.getGdkPixbuf());
349     //g_message("siox: done");
351     lastSioxPixbuf = newPixbuf;
353     return newPixbuf;
357 /**
358  *
359  */
360 Glib::RefPtr<Gdk::Pixbuf>
361 Tracer::getSelectedImage()
365     SPImage *img = getSelectedSPImage();
366     if (!img)
367         return Glib::RefPtr<Gdk::Pixbuf>(NULL);
369     if (!img->pixbuf)
370         return Glib::RefPtr<Gdk::Pixbuf>(NULL);
372     Glib::RefPtr<Gdk::Pixbuf> pixbuf =
373           Glib::wrap(img->pixbuf, true);
375     if (sioxEnabled)
376         {
377         Glib::RefPtr<Gdk::Pixbuf> sioxPixbuf =
378              sioxProcessImage(img, pixbuf);
379         if (!sioxPixbuf)
380             {
381             return pixbuf;
382             }
383         else
384             {
385             return sioxPixbuf;
386             }
387         }
388     else
389         {
390         return pixbuf;
391         }
397 //#########################################################################
398 //#  T R A C E
399 //#########################################################################
401 /**
402  *  Whether we want to enable SIOX subimage selection
403  */
404 void Tracer::enableSiox(bool enable)
406     sioxEnabled = enable;
410 /**
411  *  Threaded method that does single bitmap--->path conversion
412  */
413 void Tracer::traceThread()
415     //## Remember. NEVER leave this method without setting
416     //## engine back to NULL
418     //## Prepare our kill flag.  We will watch this later to
419     //## see if the main thread wants us to stop
420     keepGoing = true;
422     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
423     if (!desktop)
424         {
425         g_warning("Trace: No active desktop\n");
426         return;
427         }
429     Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
431     Inkscape::Selection *selection = sp_desktop_selection (desktop);
433     if (!SP_ACTIVE_DOCUMENT)
434         {
435         char *msg = _("Trace: No active document");
436         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
437         //g_warning(msg);
438         engine = NULL;
439         return;
440         }
441     SPDocument *doc = SP_ACTIVE_DOCUMENT;
442     sp_document_ensure_up_to_date(doc);
445     SPImage *img = getSelectedSPImage();
446     if (!img)
447         {
448         engine = NULL;
449         return;
450         }
452     Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(img->pixbuf, true);
454     pixbuf = sioxProcessImage(img, pixbuf);
456     if (!pixbuf)
457         {
458         char *msg = _("Trace: Image has no bitmap data");
459         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
460         //g_warning(msg);
461         engine = NULL;
462         return;
463         }
465     int nrPaths;
466     TracingEngineResult *results = engine->trace(pixbuf, &nrPaths);
467     //printf("nrPaths:%d\n", nrPaths);
469     //### Check if we should stop
470     if (!keepGoing || !results || nrPaths<1)
471         {
472         engine = NULL;
473         return;
474         }
476     //### Get pointers to the <image> and its parent
477     Inkscape::XML::Node *imgRepr   = SP_OBJECT(img)->repr;
478     Inkscape::XML::Node *par       = sp_repr_parent(imgRepr);
480     //### Get some information for the new transform()
481     double x      = 0.0;
482     double y      = 0.0;
483     double width  = 0.0;
484     double height = 0.0;
485     double dval   = 0.0;
487     if (sp_repr_get_double(imgRepr, "x", &dval))
488         x = dval;
489     if (sp_repr_get_double(imgRepr, "y", &dval))
490         y = dval;
492     if (sp_repr_get_double(imgRepr, "width", &dval))
493         width = dval;
494     if (sp_repr_get_double(imgRepr, "height", &dval))
495         height = dval;
497     NR::Matrix trans(NR::translate(x, y));
499     double iwidth  = (double)pixbuf->get_width();
500     double iheight = (double)pixbuf->get_height();
502     double iwscale = width  / iwidth;
503     double ihscale = height / iheight;
505     NR::Matrix scal(NR::scale(iwscale, ihscale));
507     //# Convolve scale, translation, and the original transform
508     NR::Matrix tf(scal);
509     tf *= trans;
510     tf *= img->transform;
513     //#OK.  Now let's start making new nodes
515     Inkscape::XML::Node *groupRepr = NULL;
517     //# if more than 1, make a <g>roup of <path>s
518     if (nrPaths > 1)
519         {
520         groupRepr = sp_repr_new("svg:g");
521         par->addChild(groupRepr, imgRepr);
522         }
524     long totalNodeCount = 0L;
526     for (TracingEngineResult *result=results ;
527                   result ; result=result->next)
528         {
529         totalNodeCount += result->getNodeCount();
531         Inkscape::XML::Node *pathRepr = sp_repr_new("svg:path");
532         pathRepr->setAttribute("style", result->getStyle());
533         pathRepr->setAttribute("d",     result->getPathData());
535         if (nrPaths > 1)
536             groupRepr->addChild(pathRepr, NULL);
537         else
538             par->addChild(pathRepr, imgRepr);
540         //### Apply the transform from the image to the new shape
541         SPObject *reprobj = doc->getObjectByRepr(pathRepr);
542         if (reprobj)
543             {
544             SPItem *newItem = SP_ITEM(reprobj);
545             sp_item_write_transform(newItem, pathRepr, tf, NULL);
546             }
547         if (nrPaths == 1)
548             {
549             selection->clear();
550             selection->add(pathRepr);
551             }
552         Inkscape::GC::release(pathRepr);
553         }
555     delete results;
557     // If we have a group, then focus on, then forget it
558     if (nrPaths > 1)
559         {
560         selection->clear();
561         selection->add(groupRepr);
562         Inkscape::GC::release(groupRepr);
563         }
565     //## inform the document, so we can undo
566     sp_document_done(doc, SP_VERB_NONE, 
567                      /* TODO: annotate */ "trace.cpp:567");
569     engine = NULL;
571     char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
572     msgStack->flash(Inkscape::NORMAL_MESSAGE, msg);
573     g_free(msg);
581 /**
582  *  Main tracing method
583  */
584 void Tracer::trace(TracingEngine *theEngine)
586     //Check if we are already running
587     if (engine)
588         return;
590     engine = theEngine;
592 #if HAVE_THREADS
593     //Ensure that thread support is running
594     if (!Glib::thread_supported())
595         Glib::thread_init();
597     //Create our thread and run it
598     Glib::Thread::create(
599         sigc::mem_fun(*this, &Tracer::traceThread), false);
600 #else
601     traceThread();
602 #endif
610 /**
611  *  Abort the thread that is executing trace()
612  */
613 void Tracer::abort()
616     //## Inform Trace's working thread
617     keepGoing = false;
619     if (engine)
620         {
621         engine->abort();
622         }
628 } // namespace Trace
630 } // namespace Inkscape
633 //#########################################################################
634 //# E N D   O F   F I L E
635 //#########################################################################