Code

WIP. siox election progress. fix api a bit.
[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"
36 namespace Inkscape {
38 namespace Trace {
42 /*
43 static PackedPixelMap *
44 renderToPackedPixelMap(SPDocument *doc, std::vector<SPItem *> items)
45 {
47     double minX =  1.0e6;
48     double minY =  1.0e6;
49     double maxX = -1.0e6;
50     double maxY = -1.0e6;
51     for (int i=0 ; i<items.size() ; i++)
52         {
53         SPItem *item = items[i];
54         if (item->bbox.x0 < minX)
55             minX = item->bbox.x0;
56         if (item->bbox.y0 < minY)
57             minY = item->bbox.y0;
58         if (item->bbox.x1 > maxX)
59             maxX = item->bbox.x1;
60         if (item->bbox.y1 > maxY)
61             maxY = item->bbox.x1;
62         }
64     double dwidth  = maxX - minX;
65     double dheight = maxY - minY;
67     NRRectL bbox;
68     bbox.x0 = 0;
69     bbox.y0 = 0;
70     bbox.x1 = 256;
71     bbox.y1 = (int) ( 256.0 * dwidth / dheight );
74     NRArena *arena = NRArena::create();
75     unsigned dkey = sp_item_display_key_new(1);
77     // Create ArenaItems and set transform
78     NRArenaItem *root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)),
79             arena, dkey, SP_ITEM_SHOW_DISPLAY);
80     nr_arena_item_set_transform(root, NR::Matrix(&affine));
82     NRPixBlock pb;
83     nr_pixblock_setup(&pb, NR_PIXBLOCK_MODE_R8G8B8A8N,
84                       minX, minY, maxX, maxY, true);
86     //fill in background
87     for (int row = 0; row < bbox.y1; row++)
88         {
89         guchar *p = NR_PIXBLOCK_PX(&pb) + row * bbox.x1;
90         for (int col = 0; col < bbox.x1; col++)
91             {
92             *p++ = ebp->r;
93             *p++ = ebp->g;
94             *p++ = ebp->b;
95             *p++ = ebp->a;
96             }
97         }
99     // Render
100     nr_arena_item_invoke_render(root, &bbox, &pb, 0);
102     for (int r = 0; r < num_rows; r++) {
103         rows[r] = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
104     }
106     //## Make an packed pixel map
107     PackedPixelMap *ppMap = PackedPixelMapCreate(bbox.x1, bbox.y1);
108     for (int row = 0; row < bbox.y1; row++)
109         {
110         guchar *p = NR_PIXBLOCK_PX(&pb) + row * bbox.x1;
111         for (int col = 0; col < bbox.x1; col++)
112             {
113             int r = *p++;
114             int g = *p++;
115             int b = *p++;
116             int a = *p++;
117             ppMap->setPixelValue(ppMap, col, row, r, g, b);
118             }
119         }
121     //## Free allocated things
122     nr_pixblock_release(&pb);
123     nr_arena_item_unref(root);
124     nr_object_unref((NRObject *) arena);
127     return ppMap;
129 */
134 /**
135  *
136  */
137 SPImage *
138 Tracer::getSelectedSPImage()
140     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
141     if (!desktop)
142         {
143         g_warning("Trace: No active desktop\n");
144         return NULL;
145         }
147     Inkscape::Selection *sel = SP_DT_SELECTION(desktop);
148     if (!sel)
149         {
150         char *msg = _("Select an <b>image</b> to trace");
151         SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
152         //g_warning(msg);
153         return NULL;
154         }
156     if (sioxEnabled)
157         {
158         SPImage *img = NULL;
159         GSList const *list = sel->itemList();
160         std::vector<SPItem *> items;
161         sioxShapes.clear();
163         /*
164            First, things are selected top-to-bottom, so we need to invert
165            them as bottom-to-top so that we can discover the image and any
166            SPItems above it
167         */
168         for ( ; list ; list=list->next)
169             {
170             if (!SP_IS_ITEM(list->data))
171                 {
172                 continue;
173                 }
174             SPItem *item = SP_ITEM(list->data);
175             items.insert(items.begin(), item);
176             }
177         std::vector<SPItem *>::iterator iter;
178         for (iter = items.begin() ; iter!= items.end() ; iter++)
179             {
180             SPItem *item = *iter;
181             if (SP_IS_IMAGE(item))
182                 {
183                 if (img) //we want only one
184                     {
185                     char *msg = _("Select only one <b>image</b> to trace");
186                     SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
187                     return NULL;
188                     }
189                 img = SP_IMAGE(item);
190                 }
191             else // if (img) //# items -after- the image in tree (above it in Z)
192                 {
193                 if (SP_IS_SHAPE(item))
194                     {
195                     SPShape *shape = SP_SHAPE(item);
196                     sioxShapes.push_back(shape);
197                     }
198                 }
199             }
200         if (!img || sioxShapes.size() < 1)
201             {
202             char *msg = _("Select one image and one or more shapes above it");
203             SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
204             return NULL;
205             }
206         return img;
207         }
208     else
209         //### SIOX not enabled.  We want exactly one image selected
210         {
211         SPItem *item = sel->singleItem();
212         if (!item)
213             {
214             char *msg = _("Select an <b>image</b> to trace");  //same as above
215             SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
216             //g_warning(msg);
217             return NULL;
218             }
220         if (!SP_IS_IMAGE(item))
221             {
222             char *msg = _("Select an <b>image</b> to trace");
223             SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
224             //g_warning(msg);
225             return NULL;
226             }
228         SPImage *img = SP_IMAGE(item);
230         return img;
231         }
237 /**
238  *
239  */
240 GdkPixbuf *
241 Tracer::getSelectedImage()
244     SPImage *img = getSelectedSPImage();
245     if (!img)
246         return NULL;
248     GdkPixbuf *pixbuf = img->pixbuf;
250     return pixbuf;
255 GdkPixbuf *
256 Tracer::sioxProcessImage(SPImage *img, GdkPixbuf *origPixbuf)
259     //Convert from gdk, so a format we know.  By design, the pixel
260     //format in PackedPixelMap is identical to what is needed by SIOX
261     PackedPixelMap *ppMap = gdkPixbufToPackedPixelMap(origPixbuf);
262     //We need to create two things:
263     //  1.  An array of long pixel values of ARGB
264     //  2.  A matching array of per-pixel float 'confidence' values
265     unsigned long *imgBuf = ppMap->pixels;
266     float *confidenceMatrix = new float[ppMap->width * ppMap->height];
268     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
269     if (!desktop)
270         {
271         g_warning("Trace: No active desktop\n");
272         return NULL;
273         }
275     Inkscape::Selection *sel = SP_DT_SELECTION(desktop);
276     if (!sel)
277         {
278         char *msg = _("Select an <b>image</b> to trace");
279         SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
280         //g_warning(msg);
281         return NULL;
282         }
284     Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->repr;
285     /*
286     //## Make a Rect overlaying the image
287     Inkscape::XML::Node *parent  = imgRepr->parent();
289     Inkscape::XML::Node *rect    = sp_repr_new("svg:rect");
290     Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attr =
291                    imgRepr->attributeList();
292     for (  ; attr ; attr++)
293         {
294         rect->setAttribute(g_quark_to_string(attr->key), attr->value, false);
295         }
298     //Inkscape::XML::Node *rect    = imgRepr->duplicate();
299     parent->appendChild(rect);
300     Inkscape::GC::release(rect);
301     */
303     //### <image> element
304     double x      = 0.0;
305     double y      = 0.0;
306     double width  = 0.0;
307     double height = 0.0;
308     double dval   = 0.0;
310     if (sp_repr_get_double(imgRepr, "x", &dval))
311         x = dval;
312     if (sp_repr_get_double(imgRepr, "y", &dval))
313         y = dval;
315     if (sp_repr_get_double(imgRepr, "width", &dval))
316         width = dval;
317     if (sp_repr_get_double(imgRepr, "height", &dval))
318         height = dval;
320     double iwidth  = (double)ppMap->width;
321     double iheight = (double)ppMap->height;
323     double iwscale = width  / iwidth;
324     double ihscale = height / iheight;
326     unsigned long cmIndex = 0;
328     /* Create new arena */
329     NRArena *arena = NRArena::create();
330     unsigned dkey = sp_item_display_key_new(1);
332     std::vector<NRArenaItem *> arenaItems;
333     std::vector<SPShape *>::iterator iter;
334     for (iter = sioxShapes.begin() ; iter!=sioxShapes.end() ; iter++)
335         {
336         /* Create ArenaItems and set transform */
337         NRArenaItem *aItem =
338             sp_item_invoke_show(*iter,
339                  arena, dkey, SP_ITEM_SHOW_DISPLAY);
340         nr_arena_item_set_transform(aItem, img->transform);
341         arenaItems.push_back(aItem);
342         }
344     PackedPixelMap *dumpMap = PackedPixelMapCreate(ppMap->width, ppMap->height);
346     for (int row=0 ; row<ppMap->height ; row++)
347         {
348         double ypos = y + ihscale * (double) row;
349         for (int col=0 ; col<ppMap->width ; col++)
350             {
351             //Get absolute X,Y position
352             double xpos = x + iwscale * (double)col;
353             NR::Point point(xpos, ypos);
354             point *= img->transform;
355             point = desktop->doc2dt(point);
356             std::vector<SPShape *>::iterator iter;
357             //g_message("x:%f    y:%f\n", point[0], point[1]);
358             bool weHaveAHit = false;
359             std::vector<NRArenaItem *>::iterator aIter;
360             for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
361                 {
362                 NRArenaItem *arenaItem = *aIter;
363                 NRArenaItemClass *arenaClass =
364                     (NRArenaItemClass *) NR_OBJECT_GET_CLASS (arenaItem);
365                 if (arenaClass && arenaClass->pick)
366                     {
367                     if (arenaClass->pick(arenaItem, point, 0.0f, 0))
368                         {
369                         weHaveAHit = true;
370                         break;
371                         }
372                     }
373                 }
375             if (weHaveAHit)
376                 {
377                 //g_message("hit!\n");
378                 dumpMap->setPixelLong(dumpMap, col, row, 0L);
379                 confidenceMatrix[cmIndex] =
380                         org::siox::SioxSegmentator::CERTAIN_FOREGROUND_CONFIDENCE;
381                 }
382             else
383                 {
384                 dumpMap->setPixelLong(dumpMap, col, row,
385                         ppMap->getPixel(ppMap, col, row));
386                 confidenceMatrix[cmIndex] =
387                         org::siox::SioxSegmentator::CERTAIN_BACKGROUND_CONFIDENCE;
388                 }
389             cmIndex++;
390             }
391         }
393     //## ok we have our pixel buf
394     org::siox::SioxSegmentator ss(ppMap->width, ppMap->height, NULL, 0);
395     ss.segmentate(imgBuf, confidenceMatrix, 0, 0.0);
397     dumpMap->writePPM(dumpMap, "siox.ppm");
398     dumpMap->destroy(dumpMap);
400     /* Free Arena and ArenaItem */
401     std::vector<NRArenaItem *>::iterator aIter;
402     for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
403        {
404        NRArenaItem *arenaItem = *aIter;
405        nr_arena_item_unref(arenaItem);
406        }
407     nr_object_unref((NRObject *) arena);
410     GdkPixbuf *newPixbuf = packedPixelMapToGdkPixbuf(ppMap);
411     ppMap->destroy(ppMap);
412     delete confidenceMatrix;
414     return newPixbuf;
420 //#########################################################################
421 //#  T R A C E
422 //#########################################################################
424 /**
425  *  Whether we want to enable SIOX subimage selection
426  */
427 void Tracer::enableSiox(bool enable)
429     sioxEnabled = enable;
433 /**
434  *  Threaded method that does single bitmap--->path conversion
435  */
436 void Tracer::traceThread()
438     //## Remember. NEVER leave this method without setting
439     //## engine back to NULL
441     //## Prepare our kill flag.  We will watch this later to
442     //## see if the main thread wants us to stop
443     keepGoing = true;
445     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
446     if (!desktop)
447         {
448         g_warning("Trace: No active desktop\n");
449         return;
450         }
452     Inkscape::Selection *selection = SP_DT_SELECTION (desktop);
454     if (!SP_ACTIVE_DOCUMENT)
455         {
456         char *msg = _("Trace: No active document");
457         SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
458         //g_warning(msg);
459         engine = NULL;
460         return;
461         }
462     SPDocument *doc = SP_ACTIVE_DOCUMENT;
463     sp_document_ensure_up_to_date(doc);
466     SPImage *img = getSelectedSPImage();
467     if (!img)
468         {
469         engine = NULL;
470         return;
471         }
473     GdkPixbuf *pixbuf = img->pixbuf;
475     if (!pixbuf)
476         {
477         char *msg = _("Trace: Image has no bitmap data");
478         SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
479         //g_warning(msg);
480         engine = NULL;
481         return;
482         }
484     //## SIOX pre-processing to get a smart subimage of the pixbuf.
485     //## This is done before any other filters
486     if (sioxEnabled)
487         {
488         /*
489            Ok, we have requested siox, and getSelectedSPImage() has found a single
490            bitmap and one or more SPItems above it.  Now what we need to do is create
491            a siox-segmented subimage pixbuf.  We not need alter 'img' at all, since this
492            pixbuf will be the same dimensions and at the same location.
493            Remember to free this new pixbuf later.
494         */
495         pixbuf = sioxProcessImage(img, pixbuf);
496         }
498     int nrPaths;
499     TracingEngineResult *results = engine->trace(pixbuf, &nrPaths);
500     //printf("nrPaths:%d\n", nrPaths);
502     //### Check if we should stop
503     if (!keepGoing || !results || nrPaths<1)
504         {
505         engine = NULL;
506         return;
507         }
509     //### Get pointers to the <image> and its parent
510     Inkscape::XML::Node *imgRepr   = SP_OBJECT(img)->repr;
511     Inkscape::XML::Node *par       = sp_repr_parent(imgRepr);
513     //### Get some information for the new transform()
514     double x      = 0.0;
515     double y      = 0.0;
516     double width  = 0.0;
517     double height = 0.0;
518     double dval   = 0.0;
520     if (sp_repr_get_double(imgRepr, "x", &dval))
521         x = dval;
522     if (sp_repr_get_double(imgRepr, "y", &dval))
523         y = dval;
525     if (sp_repr_get_double(imgRepr, "width", &dval))
526         width = dval;
527     if (sp_repr_get_double(imgRepr, "height", &dval))
528         height = dval;
530     NR::Matrix trans(NR::translate(x, y));
532     double iwidth  = (double)gdk_pixbuf_get_width(pixbuf);
533     double iheight = (double)gdk_pixbuf_get_height(pixbuf);
535     double iwscale = width  / iwidth;
536     double ihscale = height / iheight;
538     NR::Matrix scal(NR::scale(iwscale, ihscale));
540     //# Convolve scale, translation, and the original transform
541     NR::Matrix tf(scal);
542     tf *= trans;
543     tf *= img->transform;
546     //#OK.  Now let's start making new nodes
548     Inkscape::XML::Node *groupRepr = NULL;
550     //# if more than 1, make a <g>roup of <path>s
551     if (nrPaths > 1)
552         {
553         groupRepr = sp_repr_new("svg:g");
554         par->addChild(groupRepr, imgRepr);
555         }
557     long totalNodeCount = 0L;
559     for (TracingEngineResult *result=results ;
560                   result ; result=result->next)
561         {
562         totalNodeCount += result->getNodeCount();
564         Inkscape::XML::Node *pathRepr = sp_repr_new("svg:path");
565         pathRepr->setAttribute("style", result->getStyle());
566         pathRepr->setAttribute("d",     result->getPathData());
568         if (nrPaths > 1)
569             groupRepr->addChild(pathRepr, NULL);
570         else
571             par->addChild(pathRepr, imgRepr);
573         //### Apply the transform from the image to the new shape
574         SPObject *reprobj = doc->getObjectByRepr(pathRepr);
575         if (reprobj)
576             {
577             SPItem *newItem = SP_ITEM(reprobj);
578             sp_item_write_transform(newItem, pathRepr, tf, NULL);
579             }
580         if (nrPaths == 1)
581             {
582             selection->clear();
583             selection->add(pathRepr);
584             }
585         Inkscape::GC::release(pathRepr);
586         }
588     //did we allocate a pixbuf copy?
589     if (sioxEnabled)
590         {
591         g_free(pixbuf);
592         }
594     delete results;
596     // If we have a group, then focus on, then forget it
597     if (nrPaths > 1)
598         {
599         selection->clear();
600         selection->add(groupRepr);
601         Inkscape::GC::release(groupRepr);
602         }
604     //## inform the document, so we can undo
605     sp_document_done(doc);
607     engine = NULL;
609     char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
610     SP_DT_MSGSTACK(desktop)->flash(Inkscape::NORMAL_MESSAGE, msg);
611     g_free(msg);
619 /**
620  *  Main tracing method
621  */
622 void Tracer::trace(TracingEngine *theEngine)
624     //Check if we are already running
625     if (engine)
626         return;
628     engine = theEngine;
630 #if HAVE_THREADS
631     //Ensure that thread support is running
632     if (!Glib::thread_supported())
633         Glib::thread_init();
635     //Create our thread and run it
636     Glib::Thread::create(
637         sigc::mem_fun(*this, &Tracer::traceThread), false);
638 #else
639     traceThread();
640 #endif
648 /**
649  *  Abort the thread that is executing trace()
650  */
651 void Tracer::abort()
654     //## Inform Trace's working thread
655     keepGoing = false;
657     if (engine)
658         {
659         engine->abort();
660         }
666 } // namespace Trace
668 } // namespace Inkscape
671 //#########################################################################
672 //# E N D   O F   F I L E
673 //#########################################################################