Code

Applied patch #1501134
[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 <glibmm/i18n.h>
23 #include <selection.h>
24 #include <xml/repr.h>
25 #include <xml/attribute-record.h>
26 #include <sp-item.h>
27 #include <sp-shape.h>
28 #include <sp-image.h>
30 #include <display/nr-arena.h>
31 #include <display/nr-arena-shape.h>
33 #include "siox.h"
34 #include "imagemap-gdk.h"
38 namespace Inkscape
39 {
41 namespace Trace
42 {
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\n");
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::Siox Siox;
160 GdkPixbuf *
161 Tracer::sioxProcessImage(SPImage *img, GdkPixbuf *origPixbuf)
163     if (!sioxEnabled)
164         return origPixbuf;
166     //Convert from gdk, so a format we know.  By design, the pixel
167     //format in PackedPixelMap is identical to what is needed by SIOX
168     SioxImage simage(origPixbuf);
170     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
171     if (!desktop)
172         {
173         g_warning(_("Trace: No active desktop"));
174         return NULL;
175         }
177     Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
179     Inkscape::Selection *sel = sp_desktop_selection(desktop);
180     if (!sel)
181         {
182         char *msg = _("Select an <b>image</b> to trace");
183         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
184         //g_warning(msg);
185         return NULL;
186         }
188     NRArenaItem *aImg = sp_item_get_arenaitem(img, desktop->dkey);
189     //g_message("img: %d %d %d %d\n", aImg->bbox.x0, aImg->bbox.y0,
190     //                                aImg->bbox.x1, aImg->bbox.y1);
192     double width  = (double)(aImg->bbox.x1 - aImg->bbox.x0);
193     double height = (double)(aImg->bbox.y1 - aImg->bbox.y0);
195     double iwidth  = (double)simage.getWidth();
196     double iheight = (double)simage.getHeight();
198     double iwscale = width  / iwidth;
199     double ihscale = height / iheight;
201     std::vector<NRArenaItem *> arenaItems;
202     std::vector<SPShape *>::iterator iter;
203     for (iter = sioxShapes.begin() ; iter!=sioxShapes.end() ; iter++)
204         {
205         SPItem *item = *iter;
206         //### Create ArenaItems and set transform
207         NRArenaItem *aItem = sp_item_get_arenaitem(item, desktop->dkey);
209         //nr_arena_item_set_transform(aItem, item->transform);
210         //g_message("%d %d %d %d\n", aItem->bbox.x0, aItem->bbox.y0,
211         //                           aItem->bbox.x1, aItem->bbox.y1);
212         arenaItems.push_back(aItem);
213         }
214     //g_message("%d arena items\n", arenaItems.size());
216     PackedPixelMap *dumpMap = PackedPixelMapCreate(
217                     simage.getWidth(), simage.getHeight());
219     for (int row=0 ; row<simage.getHeight() ; row++)
220         {
221         double ypos = ((double)aImg->bbox.y0) + ihscale * (double) row;
222         for (int col=0 ; col<simage.getWidth() ; col++)
223             {
224             //Get absolute X,Y position
225             double xpos = ((double)aImg->bbox.x0) + iwscale * (double)col;
226             NR::Point point(xpos, ypos);
227             point *= aImg->transform;
228             //point *= imgMat;
229             //point = desktop->doc2dt(point);
230             std::vector<SPShape *>::iterator iter;
231             //g_message("x:%f    y:%f\n", point[0], point[1]);
232             bool weHaveAHit = false;
233             std::vector<NRArenaItem *>::iterator aIter;
234             for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
235                 {
236                 NRArenaItem *arenaItem = *aIter;
237                 NRArenaItemClass *arenaClass =
238                     (NRArenaItemClass *) NR_OBJECT_GET_CLASS (arenaItem);
239                 if (arenaClass->pick(arenaItem, point, 1.0f, 1))
240                     {
241                     weHaveAHit = true;
242                     break;
243                     }
244                 }
246             if (weHaveAHit)
247                 {
248                 //g_message("hit!\n");
249                 dumpMap->setPixelLong(dumpMap, col, row, 0L);
250                 simage.setConfidence(col, row, 
251                         Siox::UNKNOWN_REGION_CONFIDENCE);
252                 }
253             else
254                 {
255                 dumpMap->setPixelLong(dumpMap, col, row,
256                         simage.getPixel(col, row));
257                 simage.setConfidence(col, row,
258                         Siox::CERTAIN_BACKGROUND_CONFIDENCE);
259                 }
260             }
261         }
263     //dumpMap->writePPM(dumpMap, "siox1.ppm");
264     dumpMap->destroy(dumpMap);
266     //## ok we have our pixel buf
267     org::siox::Siox sengine;
268     org::siox::SioxImage result =
269             sengine.extractForeground(simage, 0xffffff);
270     if (!result.isValid())
271         {
272         g_warning(_("Invalid SIOX result"));
273         return NULL;
274         }
276     //result.writePPM("siox2.ppm");
278     /* Free Arena and ArenaItem */
279     /*
280     std::vector<NRArenaItem *>::iterator aIter;
281     for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
282        {
283        NRArenaItem *arenaItem = *aIter;
284        nr_arena_item_unref(arenaItem);
285        }
286     nr_object_unref((NRObject *) arena);
287     */
289     GdkPixbuf *newPixbuf = result.getGdkPixbuf();
291     return newPixbuf;
295 /**
296  *
297  */
298 GdkPixbuf *
299 Tracer::getSelectedImage()
302     SPImage *img = getSelectedSPImage();
303     if (!img)
304         return NULL;
306     GdkPixbuf *pixbuf = img->pixbuf;
307     if (!pixbuf)
308         return NULL;
310     if (sioxEnabled)
311         {
312         GdkPixbuf *sioxPixbuf = sioxProcessImage(img, pixbuf);
313         if (!sioxPixbuf)
314             {
315             g_object_ref(pixbuf);
316             return pixbuf;
317             }
318         else
319             {
320             return sioxPixbuf;
321             }
322         }
323     else
324         {
325         g_object_ref(pixbuf);
326         return pixbuf;
327         }
333 //#########################################################################
334 //#  T R A C E
335 //#########################################################################
337 /**
338  *  Whether we want to enable SIOX subimage selection
339  */
340 void Tracer::enableSiox(bool enable)
342     sioxEnabled = enable;
346 /**
347  *  Threaded method that does single bitmap--->path conversion
348  */
349 void Tracer::traceThread()
351     //## Remember. NEVER leave this method without setting
352     //## engine back to NULL
354     //## Prepare our kill flag.  We will watch this later to
355     //## see if the main thread wants us to stop
356     keepGoing = true;
358     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
359     if (!desktop)
360         {
361         g_warning("Trace: No active desktop\n");
362         return;
363         }
365     Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
367     Inkscape::Selection *selection = sp_desktop_selection (desktop);
369     if (!SP_ACTIVE_DOCUMENT)
370         {
371         char *msg = _("Trace: No active document");
372         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
373         //g_warning(msg);
374         engine = NULL;
375         return;
376         }
377     SPDocument *doc = SP_ACTIVE_DOCUMENT;
378     sp_document_ensure_up_to_date(doc);
381     SPImage *img = getSelectedSPImage();
382     if (!img)
383         {
384         engine = NULL;
385         return;
386         }
388     GdkPixbuf *pixbuf = img->pixbuf;
389     g_object_ref(pixbuf);
391     pixbuf = sioxProcessImage(img, pixbuf);
393     if (!pixbuf)
394         {
395         char *msg = _("Trace: Image has no bitmap data");
396         msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
397         //g_warning(msg);
398         engine = NULL;
399         return;
400         }
402     int nrPaths;
403     TracingEngineResult *results = engine->trace(pixbuf, &nrPaths);
404     //printf("nrPaths:%d\n", nrPaths);
406     //### Check if we should stop
407     if (!keepGoing || !results || nrPaths<1)
408         {
409         engine = NULL;
410         return;
411         }
413     //### Get pointers to the <image> and its parent
414     Inkscape::XML::Node *imgRepr   = SP_OBJECT(img)->repr;
415     Inkscape::XML::Node *par       = sp_repr_parent(imgRepr);
417     //### Get some information for the new transform()
418     double x      = 0.0;
419     double y      = 0.0;
420     double width  = 0.0;
421     double height = 0.0;
422     double dval   = 0.0;
424     if (sp_repr_get_double(imgRepr, "x", &dval))
425         x = dval;
426     if (sp_repr_get_double(imgRepr, "y", &dval))
427         y = dval;
429     if (sp_repr_get_double(imgRepr, "width", &dval))
430         width = dval;
431     if (sp_repr_get_double(imgRepr, "height", &dval))
432         height = dval;
434     NR::Matrix trans(NR::translate(x, y));
436     double iwidth  = (double)gdk_pixbuf_get_width(pixbuf);
437     double iheight = (double)gdk_pixbuf_get_height(pixbuf);
439     double iwscale = width  / iwidth;
440     double ihscale = height / iheight;
442     NR::Matrix scal(NR::scale(iwscale, ihscale));
444     //# Convolve scale, translation, and the original transform
445     NR::Matrix tf(scal);
446     tf *= trans;
447     tf *= img->transform;
450     //#OK.  Now let's start making new nodes
452     Inkscape::XML::Node *groupRepr = NULL;
454     //# if more than 1, make a <g>roup of <path>s
455     if (nrPaths > 1)
456         {
457         groupRepr = sp_repr_new("svg:g");
458         par->addChild(groupRepr, imgRepr);
459         }
461     long totalNodeCount = 0L;
463     for (TracingEngineResult *result=results ;
464                   result ; result=result->next)
465         {
466         totalNodeCount += result->getNodeCount();
468         Inkscape::XML::Node *pathRepr = sp_repr_new("svg:path");
469         pathRepr->setAttribute("style", result->getStyle());
470         pathRepr->setAttribute("d",     result->getPathData());
472         if (nrPaths > 1)
473             groupRepr->addChild(pathRepr, NULL);
474         else
475             par->addChild(pathRepr, imgRepr);
477         //### Apply the transform from the image to the new shape
478         SPObject *reprobj = doc->getObjectByRepr(pathRepr);
479         if (reprobj)
480             {
481             SPItem *newItem = SP_ITEM(reprobj);
482             sp_item_write_transform(newItem, pathRepr, tf, NULL);
483             }
484         if (nrPaths == 1)
485             {
486             selection->clear();
487             selection->add(pathRepr);
488             }
489         Inkscape::GC::release(pathRepr);
490         }
492     //release our pixbuf
493     g_object_unref(pixbuf);
495     delete results;
497     // If we have a group, then focus on, then forget it
498     if (nrPaths > 1)
499         {
500         selection->clear();
501         selection->add(groupRepr);
502         Inkscape::GC::release(groupRepr);
503         }
505     //## inform the document, so we can undo
506     sp_document_done(doc);
508     engine = NULL;
510     char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
511     msgStack->flash(Inkscape::NORMAL_MESSAGE, msg);
512     g_free(msg);
520 /**
521  *  Main tracing method
522  */
523 void Tracer::trace(TracingEngine *theEngine)
525     //Check if we are already running
526     if (engine)
527         return;
529     engine = theEngine;
531 #if HAVE_THREADS
532     //Ensure that thread support is running
533     if (!Glib::thread_supported())
534         Glib::thread_init();
536     //Create our thread and run it
537     Glib::Thread::create(
538         sigc::mem_fun(*this, &Tracer::traceThread), false);
539 #else
540     traceThread();
541 #endif
549 /**
550  *  Abort the thread that is executing trace()
551  */
552 void Tracer::abort()
555     //## Inform Trace's working thread
556     keepGoing = false;
558     if (engine)
559         {
560         engine->abort();
561         }
567 } // namespace Trace
569 } // namespace Inkscape
572 //#########################################################################
573 //# E N D   O F   F I L E
574 //#########################################################################