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 <libnr/nr-scale-translate-ops.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 = sp_item_get_arenaitem(img, 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 = sp_item_get_arenaitem(item, 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 NR::Point point(xpos, ypos);
286 point *= *aImg->transform;
287 //point *= imgMat;
288 //point = desktop->doc2dt(point);
289 //g_message("x:%f y:%f\n", point[0], point[1]);
290 bool weHaveAHit = false;
291 std::vector<NRArenaItem *>::iterator aIter;
292 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
293 {
294 NRArenaItem *arenaItem = *aIter;
295 NRArenaItemClass *arenaClass =
296 (NRArenaItemClass *) NR_OBJECT_GET_CLASS (arenaItem);
297 if (arenaClass->pick(arenaItem, point, 1.0f, 1))
298 {
299 weHaveAHit = true;
300 break;
301 }
302 }
304 if (weHaveAHit)
305 {
306 //g_message("hit!\n");
307 //dumpMap->setPixelLong(dumpMap, col, row, 0L);
308 simage.setConfidence(col, row,
309 Siox::UNKNOWN_REGION_CONFIDENCE);
310 }
311 else
312 {
313 //g_message("miss!\n");
314 //dumpMap->setPixelLong(dumpMap, col, row,
315 // simage.getPixel(col, row));
316 simage.setConfidence(col, row,
317 Siox::CERTAIN_BACKGROUND_CONFIDENCE);
318 }
319 }
320 }
322 //g_message("siox: selection done");
324 //dumpMap->writePPM(dumpMap, "siox1.ppm");
325 //dumpMap->destroy(dumpMap);
327 //## ok we have our pixel buf
328 TraceSioxObserver observer(this);
329 Siox sengine(&observer);
330 SioxImage result = sengine.extractForeground(simage, 0xffffff);
331 if (!result.isValid())
332 {
333 g_warning(_("Invalid SIOX result"));
334 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
335 }
337 //result.writePPM("siox2.ppm");
339 /* Free Arena and ArenaItem */
340 /*
341 std::vector<NRArenaItem *>::iterator aIter;
342 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
343 {
344 NRArenaItem *arenaItem = *aIter;
345 nr_arena_item_unref(arenaItem);
346 }
347 nr_object_unref((NRObject *) arena);
348 */
350 Glib::RefPtr<Gdk::Pixbuf> newPixbuf = Glib::wrap(result.getGdkPixbuf());
352 //g_message("siox: done");
354 lastSioxPixbuf = newPixbuf;
356 return newPixbuf;
357 }
360 /**
361 *
362 */
363 Glib::RefPtr<Gdk::Pixbuf>
364 Tracer::getSelectedImage()
365 {
368 SPImage *img = getSelectedSPImage();
369 if (!img)
370 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
372 if (!img->pixbuf)
373 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
375 Glib::RefPtr<Gdk::Pixbuf> pixbuf =
376 Glib::wrap(img->pixbuf, true);
378 if (sioxEnabled)
379 {
380 Glib::RefPtr<Gdk::Pixbuf> sioxPixbuf =
381 sioxProcessImage(img, pixbuf);
382 if (!sioxPixbuf)
383 {
384 return pixbuf;
385 }
386 else
387 {
388 return sioxPixbuf;
389 }
390 }
391 else
392 {
393 return pixbuf;
394 }
396 }
400 //#########################################################################
401 //# T R A C E
402 //#########################################################################
404 /**
405 * Whether we want to enable SIOX subimage selection
406 */
407 void Tracer::enableSiox(bool enable)
408 {
409 sioxEnabled = enable;
410 }
413 /**
414 * Threaded method that does single bitmap--->path conversion
415 */
416 void Tracer::traceThread()
417 {
418 //## Remember. NEVER leave this method without setting
419 //## engine back to NULL
421 //## Prepare our kill flag. We will watch this later to
422 //## see if the main thread wants us to stop
423 keepGoing = true;
425 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
426 if (!desktop)
427 {
428 g_warning("Trace: No active desktop\n");
429 return;
430 }
432 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
434 Inkscape::Selection *selection = sp_desktop_selection (desktop);
436 if (!SP_ACTIVE_DOCUMENT)
437 {
438 char *msg = _("Trace: No active document");
439 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
440 //g_warning(msg);
441 engine = NULL;
442 return;
443 }
444 SPDocument *doc = SP_ACTIVE_DOCUMENT;
445 sp_document_ensure_up_to_date(doc);
448 SPImage *img = getSelectedSPImage();
449 if (!img)
450 {
451 engine = NULL;
452 return;
453 }
455 Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(img->pixbuf, true);
457 pixbuf = sioxProcessImage(img, pixbuf);
459 if (!pixbuf)
460 {
461 char *msg = _("Trace: Image has no bitmap data");
462 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
463 //g_warning(msg);
464 engine = NULL;
465 return;
466 }
468 msgStack->flash(Inkscape::NORMAL_MESSAGE, _("Trace: Starting trace..."));
469 desktop->updateCanvasNow();
471 std::vector<TracingEngineResult> results =
472 engine->trace(pixbuf);
473 //printf("nrPaths:%d\n", results.size());
474 int nrPaths = results.size();
476 //### Check if we should stop
477 if (!keepGoing || nrPaths<1)
478 {
479 engine = NULL;
480 return;
481 }
483 //### Get pointers to the <image> and its parent
484 Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->repr;
485 Inkscape::XML::Node *par = sp_repr_parent(imgRepr);
487 //### Get some information for the new transform()
488 double x = 0.0;
489 double y = 0.0;
490 double width = 0.0;
491 double height = 0.0;
492 double dval = 0.0;
494 if (sp_repr_get_double(imgRepr, "x", &dval))
495 x = dval;
496 if (sp_repr_get_double(imgRepr, "y", &dval))
497 y = dval;
499 if (sp_repr_get_double(imgRepr, "width", &dval))
500 width = dval;
501 if (sp_repr_get_double(imgRepr, "height", &dval))
502 height = dval;
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::translate trans(x, y);
511 NR::scale scal(iwscale, ihscale);
513 //# Convolve scale, translation, and the original transform
514 NR::Matrix tf(scal * trans);
515 tf *= img->transform;
518 //#OK. Now let's start making new nodes
520 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
521 Inkscape::XML::Node *groupRepr = NULL;
523 //# if more than 1, make a <g>roup of <path>s
524 if (nrPaths > 1)
525 {
526 groupRepr = xml_doc->createElement("svg:g");
527 par->addChild(groupRepr, imgRepr);
528 }
530 long totalNodeCount = 0L;
532 for (unsigned int i=0 ; i<results.size() ; i++)
533 {
534 TracingEngineResult result = results[i];
535 totalNodeCount += result.getNodeCount();
537 Inkscape::XML::Node *pathRepr = xml_doc->createElement("svg:path");
538 pathRepr->setAttribute("style", result.getStyle().c_str());
539 pathRepr->setAttribute("d", result.getPathData().c_str());
541 if (nrPaths > 1)
542 groupRepr->addChild(pathRepr, NULL);
543 else
544 par->addChild(pathRepr, imgRepr);
546 //### Apply the transform from the image to the new shape
547 SPObject *reprobj = doc->getObjectByRepr(pathRepr);
548 if (reprobj)
549 {
550 SPItem *newItem = SP_ITEM(reprobj);
551 sp_item_write_transform(newItem, pathRepr, tf, NULL);
552 }
553 if (nrPaths == 1)
554 {
555 selection->clear();
556 selection->add(pathRepr);
557 }
558 Inkscape::GC::release(pathRepr);
559 }
561 // If we have a group, then focus on, then forget it
562 if (nrPaths > 1)
563 {
564 selection->clear();
565 selection->add(groupRepr);
566 Inkscape::GC::release(groupRepr);
567 }
569 //## inform the document, so we can undo
570 sp_document_done(doc, SP_VERB_SELECTION_TRACE, _("Trace bitmap"));
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);
578 }
584 /**
585 * Main tracing method
586 */
587 void Tracer::trace(TracingEngine *theEngine)
588 {
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
607 }
613 /**
614 * Abort the thread that is executing trace()
615 */
616 void Tracer::abort()
617 {
619 //## Inform Trace's working thread
620 keepGoing = false;
622 if (engine)
623 {
624 engine->abort();
625 }
627 }
631 } // namespace Trace
633 } // namespace Inkscape
636 //#########################################################################
637 //# E N D O F F I L E
638 //#########################################################################