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>
30 #include <libnr/nr-matrix-ops.h>
31 #include <2geom/transforms.h>
33 #include <display/nr-arena.h>
34 #include <display/nr-arena-shape.h>
36 #include "siox.h"
37 #include "imagemap-gdk.h"
41 namespace Inkscape
42 {
44 namespace Trace
45 {
51 /**
52 * Get the selected image. Also check for any SPItems over it, in
53 * case the user wants SIOX pre-processing.
54 */
55 SPImage *
56 Tracer::getSelectedSPImage()
57 {
59 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
60 if (!desktop)
61 {
62 g_warning("Trace: No active desktop");
63 return NULL;
64 }
66 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
68 Inkscape::Selection *sel = sp_desktop_selection(desktop);
69 if (!sel)
70 {
71 char *msg = _("Select an <b>image</b> to trace");
72 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
73 //g_warning(msg);
74 return NULL;
75 }
77 if (sioxEnabled)
78 {
79 SPImage *img = NULL;
80 GSList const *list = sel->itemList();
81 std::vector<SPItem *> items;
82 sioxShapes.clear();
84 /*
85 First, things are selected top-to-bottom, so we need to invert
86 them as bottom-to-top so that we can discover the image and any
87 SPItems above it
88 */
89 for ( ; list ; list=list->next)
90 {
91 if (!SP_IS_ITEM(list->data))
92 {
93 continue;
94 }
95 SPItem *item = SP_ITEM(list->data);
96 items.insert(items.begin(), item);
97 }
98 std::vector<SPItem *>::iterator iter;
99 for (iter = items.begin() ; iter!= items.end() ; iter++)
100 {
101 SPItem *item = *iter;
102 if (SP_IS_IMAGE(item))
103 {
104 if (img) //we want only one
105 {
106 char *msg = _("Select only one <b>image</b> to trace");
107 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
108 return NULL;
109 }
110 img = SP_IMAGE(item);
111 }
112 else // if (img) //# items -after- the image in tree (above it in Z)
113 {
114 if (SP_IS_SHAPE(item))
115 {
116 SPShape *shape = SP_SHAPE(item);
117 sioxShapes.push_back(shape);
118 }
119 }
120 }
122 if (!img || sioxShapes.size() < 1)
123 {
124 char *msg = _("Select one image and one or more shapes above it");
125 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
126 return NULL;
127 }
128 return img;
129 }
130 else
131 //### SIOX not enabled. We want exactly one image selected
132 {
133 SPItem *item = sel->singleItem();
134 if (!item)
135 {
136 char *msg = _("Select an <b>image</b> to trace"); //same as above
137 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
138 //g_warning(msg);
139 return NULL;
140 }
142 if (!SP_IS_IMAGE(item))
143 {
144 char *msg = _("Select an <b>image</b> to trace");
145 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
146 //g_warning(msg);
147 return NULL;
148 }
150 SPImage *img = SP_IMAGE(item);
152 return img;
153 }
155 }
159 typedef org::siox::SioxImage SioxImage;
160 typedef org::siox::SioxObserver SioxObserver;
161 typedef org::siox::Siox Siox;
164 class TraceSioxObserver : public SioxObserver
165 {
166 public:
168 /**
169 *
170 */
171 TraceSioxObserver (void *contextArg) :
172 SioxObserver(contextArg)
173 {}
175 /**
176 *
177 */
178 virtual ~TraceSioxObserver ()
179 { }
181 /**
182 * Informs the observer how much has been completed.
183 * Return false if the processing should be aborted.
184 */
185 virtual bool progress(float /*percentCompleted*/)
186 {
187 //Tracer *tracer = (Tracer *)context;
188 //## Allow the GUI to update
189 Gtk::Main::iteration(false); //at least once, non-blocking
190 while( Gtk::Main::events_pending() )
191 Gtk::Main::iteration();
192 return true;
193 }
195 /**
196 * Send an error string to the Observer. Processing will
197 * be halted.
198 */
199 virtual void error(const std::string &/*msg*/)
200 {
201 //Tracer *tracer = (Tracer *)context;
202 }
205 };
211 /**
212 * Process a GdkPixbuf, according to which areas have been
213 * obscured in the GUI.
214 */
215 Glib::RefPtr<Gdk::Pixbuf>
216 Tracer::sioxProcessImage(SPImage *img,
217 Glib::RefPtr<Gdk::Pixbuf>origPixbuf)
218 {
219 if (!sioxEnabled)
220 return origPixbuf;
222 if (origPixbuf == lastOrigPixbuf)
223 return lastSioxPixbuf;
225 //g_message("siox: start");
227 //Convert from gdk, so a format we know. By design, the pixel
228 //format in PackedPixelMap is identical to what is needed by SIOX
229 SioxImage simage(origPixbuf->gobj());
231 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
232 if (!desktop)
233 {
234 g_warning(_("Trace: No active desktop"));
235 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
236 }
238 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
240 Inkscape::Selection *sel = sp_desktop_selection(desktop);
241 if (!sel)
242 {
243 char *msg = _("Select an <b>image</b> to trace");
244 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
245 //g_warning(msg);
246 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
247 }
249 NRArenaItem *aImg = img->get_arenaitem(desktop->dkey);
250 //g_message("img: %d %d %d %d\n", aImg->bbox.x0, aImg->bbox.y0,
251 // aImg->bbox.x1, aImg->bbox.y1);
253 double width = (double)(aImg->bbox.x1 - aImg->bbox.x0);
254 double height = (double)(aImg->bbox.y1 - aImg->bbox.y0);
256 double iwidth = (double)simage.getWidth();
257 double iheight = (double)simage.getHeight();
259 double iwscale = width / iwidth;
260 double ihscale = height / iheight;
262 std::vector<NRArenaItem *> arenaItems;
263 std::vector<SPShape *>::iterator iter;
264 for (iter = sioxShapes.begin() ; iter!=sioxShapes.end() ; iter++)
265 {
266 SPItem *item = *iter;
267 NRArenaItem *aItem = item->get_arenaitem(desktop->dkey);
268 arenaItems.push_back(aItem);
269 }
271 //g_message("%d arena items\n", arenaItems.size());
273 //PackedPixelMap *dumpMap = PackedPixelMapCreate(
274 // simage.getWidth(), simage.getHeight());
276 //g_message("siox: start selection");
278 for (int row=0 ; row<iheight ; row++)
279 {
280 double ypos = ((double)aImg->bbox.y0) + ihscale * (double) row;
281 for (int col=0 ; col<simage.getWidth() ; col++)
282 {
283 //Get absolute X,Y position
284 double xpos = ((double)aImg->bbox.x0) + iwscale * (double)col;
285 Geom::Point point(xpos, ypos);
286 if (aImg->transform)
287 point *= *aImg->transform;
288 //point *= imgMat;
289 //point = desktop->doc2dt(point);
290 //g_message("x:%f y:%f\n", point[0], point[1]);
291 bool weHaveAHit = false;
292 std::vector<NRArenaItem *>::iterator aIter;
293 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
294 {
295 NRArenaItem *arenaItem = *aIter;
296 NRArenaItemClass *arenaClass =
297 (NRArenaItemClass *) NR_OBJECT_GET_CLASS (arenaItem);
298 if (arenaClass->pick(arenaItem, point, 1.0f, 1))
299 {
300 weHaveAHit = true;
301 break;
302 }
303 }
305 if (weHaveAHit)
306 {
307 //g_message("hit!\n");
308 //dumpMap->setPixelLong(dumpMap, col, row, 0L);
309 simage.setConfidence(col, row,
310 Siox::UNKNOWN_REGION_CONFIDENCE);
311 }
312 else
313 {
314 //g_message("miss!\n");
315 //dumpMap->setPixelLong(dumpMap, col, row,
316 // simage.getPixel(col, row));
317 simage.setConfidence(col, row,
318 Siox::CERTAIN_BACKGROUND_CONFIDENCE);
319 }
320 }
321 }
323 //g_message("siox: selection done");
325 //dumpMap->writePPM(dumpMap, "siox1.ppm");
326 //dumpMap->destroy(dumpMap);
328 //## ok we have our pixel buf
329 TraceSioxObserver observer(this);
330 Siox sengine(&observer);
331 SioxImage result = sengine.extractForeground(simage, 0xffffff);
332 if (!result.isValid())
333 {
334 g_warning(_("Invalid SIOX result"));
335 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
336 }
338 //result.writePPM("siox2.ppm");
340 /* Free Arena and ArenaItem */
341 /*
342 std::vector<NRArenaItem *>::iterator aIter;
343 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
344 {
345 NRArenaItem *arenaItem = *aIter;
346 nr_arena_item_unref(arenaItem);
347 }
348 nr_object_unref((NRObject *) arena);
349 */
351 Glib::RefPtr<Gdk::Pixbuf> newPixbuf = Glib::wrap(result.getGdkPixbuf());
353 //g_message("siox: done");
355 lastSioxPixbuf = newPixbuf;
357 return newPixbuf;
358 }
361 /**
362 *
363 */
364 Glib::RefPtr<Gdk::Pixbuf>
365 Tracer::getSelectedImage()
366 {
369 SPImage *img = getSelectedSPImage();
370 if (!img)
371 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
373 if (!img->pixbuf)
374 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
376 Glib::RefPtr<Gdk::Pixbuf> pixbuf =
377 Glib::wrap(img->pixbuf, true);
379 if (sioxEnabled)
380 {
381 Glib::RefPtr<Gdk::Pixbuf> sioxPixbuf =
382 sioxProcessImage(img, pixbuf);
383 if (!sioxPixbuf)
384 {
385 return pixbuf;
386 }
387 else
388 {
389 return sioxPixbuf;
390 }
391 }
392 else
393 {
394 return pixbuf;
395 }
397 }
401 //#########################################################################
402 //# T R A C E
403 //#########################################################################
405 /**
406 * Whether we want to enable SIOX subimage selection
407 */
408 void Tracer::enableSiox(bool enable)
409 {
410 sioxEnabled = enable;
411 }
414 /**
415 * Threaded method that does single bitmap--->path conversion
416 */
417 void Tracer::traceThread()
418 {
419 //## Remember. NEVER leave this method without setting
420 //## engine back to NULL
422 //## Prepare our kill flag. We will watch this later to
423 //## see if the main thread wants us to stop
424 keepGoing = true;
426 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
427 if (!desktop)
428 {
429 g_warning("Trace: No active desktop\n");
430 return;
431 }
433 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
435 Inkscape::Selection *selection = sp_desktop_selection (desktop);
437 if (!SP_ACTIVE_DOCUMENT)
438 {
439 char *msg = _("Trace: No active document");
440 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
441 //g_warning(msg);
442 engine = NULL;
443 return;
444 }
445 SPDocument *doc = SP_ACTIVE_DOCUMENT;
446 doc->ensure_up_to_date();
449 SPImage *img = getSelectedSPImage();
450 if (!img)
451 {
452 engine = NULL;
453 return;
454 }
456 Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(img->pixbuf, true);
458 pixbuf = sioxProcessImage(img, pixbuf);
460 if (!pixbuf)
461 {
462 char *msg = _("Trace: Image has no bitmap data");
463 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
464 //g_warning(msg);
465 engine = NULL;
466 return;
467 }
469 msgStack->flash(Inkscape::NORMAL_MESSAGE, _("Trace: Starting trace..."));
470 desktop->updateCanvasNow();
472 std::vector<TracingEngineResult> results =
473 engine->trace(pixbuf);
474 //printf("nrPaths:%d\n", results.size());
475 int nrPaths = results.size();
477 //### Check if we should stop
478 if (!keepGoing || nrPaths<1)
479 {
480 engine = NULL;
481 return;
482 }
484 //### Get pointers to the <image> and its parent
485 //XML Tree being used directly here while it shouldn't be.
486 Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->getRepr();
487 Inkscape::XML::Node *par = sp_repr_parent(imgRepr);
489 //### Get some information for the new transform()
490 double x = 0.0;
491 double y = 0.0;
492 double width = 0.0;
493 double height = 0.0;
494 double dval = 0.0;
496 if (sp_repr_get_double(imgRepr, "x", &dval))
497 x = dval;
498 if (sp_repr_get_double(imgRepr, "y", &dval))
499 y = dval;
501 if (sp_repr_get_double(imgRepr, "width", &dval))
502 width = dval;
503 if (sp_repr_get_double(imgRepr, "height", &dval))
504 height = dval;
506 double iwidth = (double)pixbuf->get_width();
507 double iheight = (double)pixbuf->get_height();
509 double iwscale = width / iwidth;
510 double ihscale = height / iheight;
512 Geom::Translate trans(x, y);
513 Geom::Scale scal(iwscale, ihscale);
515 //# Convolve scale, translation, and the original transform
516 Geom::Matrix tf(scal * trans);
517 tf *= img->transform;
520 //#OK. Now let's start making new nodes
522 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
523 Inkscape::XML::Node *groupRepr = NULL;
525 //# if more than 1, make a <g>roup of <path>s
526 if (nrPaths > 1)
527 {
528 groupRepr = xml_doc->createElement("svg:g");
529 par->addChild(groupRepr, imgRepr);
530 }
532 long totalNodeCount = 0L;
534 for (unsigned int i=0 ; i<results.size() ; i++)
535 {
536 TracingEngineResult result = results[i];
537 totalNodeCount += result.getNodeCount();
539 Inkscape::XML::Node *pathRepr = xml_doc->createElement("svg:path");
540 pathRepr->setAttribute("style", result.getStyle().c_str());
541 pathRepr->setAttribute("d", result.getPathData().c_str());
543 if (nrPaths > 1)
544 groupRepr->addChild(pathRepr, NULL);
545 else
546 par->addChild(pathRepr, imgRepr);
548 //### Apply the transform from the image to the new shape
549 SPObject *reprobj = doc->getObjectByRepr(pathRepr);
550 if (reprobj)
551 {
552 SPItem *newItem = SP_ITEM(reprobj);
553 newItem->doWriteTransform(pathRepr, tf, NULL);
554 }
555 if (nrPaths == 1)
556 {
557 selection->clear();
558 selection->add(pathRepr);
559 }
560 Inkscape::GC::release(pathRepr);
561 }
563 // If we have a group, then focus on, then forget it
564 if (nrPaths > 1)
565 {
566 selection->clear();
567 selection->add(groupRepr);
568 Inkscape::GC::release(groupRepr);
569 }
571 //## inform the document, so we can undo
572 SPDocumentUndo::done(doc, SP_VERB_SELECTION_TRACE, _("Trace bitmap"));
574 engine = NULL;
576 char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
577 msgStack->flash(Inkscape::NORMAL_MESSAGE, msg);
578 g_free(msg);
580 }
586 /**
587 * Main tracing method
588 */
589 void Tracer::trace(TracingEngine *theEngine)
590 {
591 //Check if we are already running
592 if (engine)
593 return;
595 engine = theEngine;
597 #if HAVE_THREADS
598 //Ensure that thread support is running
599 if (!Glib::thread_supported())
600 Glib::thread_init();
602 //Create our thread and run it
603 Glib::Thread::create(
604 sigc::mem_fun(*this, &Tracer::traceThread), false);
605 #else
606 traceThread();
607 #endif
609 }
615 /**
616 * Abort the thread that is executing trace()
617 */
618 void Tracer::abort()
619 {
621 //## Inform Trace's working thread
622 keepGoing = false;
624 if (engine)
625 {
626 engine->abort();
627 }
629 }
633 } // namespace Trace
635 } // namespace Inkscape
638 //#########################################################################
639 //# E N D O F F I L E
640 //#########################################################################