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 = 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 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 sp_document_ensure_up_to_date(doc);
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 Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->repr;
486 Inkscape::XML::Node *par = sp_repr_parent(imgRepr);
488 //### Get some information for the new transform()
489 double x = 0.0;
490 double y = 0.0;
491 double width = 0.0;
492 double height = 0.0;
493 double dval = 0.0;
495 if (sp_repr_get_double(imgRepr, "x", &dval))
496 x = dval;
497 if (sp_repr_get_double(imgRepr, "y", &dval))
498 y = dval;
500 if (sp_repr_get_double(imgRepr, "width", &dval))
501 width = dval;
502 if (sp_repr_get_double(imgRepr, "height", &dval))
503 height = dval;
505 double iwidth = (double)pixbuf->get_width();
506 double iheight = (double)pixbuf->get_height();
508 double iwscale = width / iwidth;
509 double ihscale = height / iheight;
511 Geom::Translate trans(x, y);
512 Geom::Scale scal(iwscale, ihscale);
514 //# Convolve scale, translation, and the original transform
515 Geom::Matrix tf(scal * trans);
516 tf *= img->transform;
519 //#OK. Now let's start making new nodes
521 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
522 Inkscape::XML::Node *groupRepr = NULL;
524 //# if more than 1, make a <g>roup of <path>s
525 if (nrPaths > 1)
526 {
527 groupRepr = xml_doc->createElement("svg:g");
528 par->addChild(groupRepr, imgRepr);
529 }
531 long totalNodeCount = 0L;
533 for (unsigned int i=0 ; i<results.size() ; i++)
534 {
535 TracingEngineResult result = results[i];
536 totalNodeCount += result.getNodeCount();
538 Inkscape::XML::Node *pathRepr = xml_doc->createElement("svg:path");
539 pathRepr->setAttribute("style", result.getStyle().c_str());
540 pathRepr->setAttribute("d", result.getPathData().c_str());
542 if (nrPaths > 1)
543 groupRepr->addChild(pathRepr, NULL);
544 else
545 par->addChild(pathRepr, imgRepr);
547 //### Apply the transform from the image to the new shape
548 SPObject *reprobj = doc->getObjectByRepr(pathRepr);
549 if (reprobj)
550 {
551 SPItem *newItem = SP_ITEM(reprobj);
552 sp_item_write_transform(newItem, pathRepr, tf, NULL);
553 }
554 if (nrPaths == 1)
555 {
556 selection->clear();
557 selection->add(pathRepr);
558 }
559 Inkscape::GC::release(pathRepr);
560 }
562 // If we have a group, then focus on, then forget it
563 if (nrPaths > 1)
564 {
565 selection->clear();
566 selection->add(groupRepr);
567 Inkscape::GC::release(groupRepr);
568 }
570 //## inform the document, so we can undo
571 sp_document_done(doc, SP_VERB_SELECTION_TRACE, _("Trace bitmap"));
573 engine = NULL;
575 char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
576 msgStack->flash(Inkscape::NORMAL_MESSAGE, msg);
577 g_free(msg);
579 }
585 /**
586 * Main tracing method
587 */
588 void Tracer::trace(TracingEngine *theEngine)
589 {
590 //Check if we are already running
591 if (engine)
592 return;
594 engine = theEngine;
596 #if HAVE_THREADS
597 //Ensure that thread support is running
598 if (!Glib::thread_supported())
599 Glib::thread_init();
601 //Create our thread and run it
602 Glib::Thread::create(
603 sigc::mem_fun(*this, &Tracer::traceThread), false);
604 #else
605 traceThread();
606 #endif
608 }
614 /**
615 * Abort the thread that is executing trace()
616 */
617 void Tracer::abort()
618 {
620 //## Inform Trace's working thread
621 keepGoing = false;
623 if (engine)
624 {
625 engine->abort();
626 }
628 }
632 } // namespace Trace
634 } // namespace Inkscape
637 //#########################################################################
638 //# E N D O F F I L E
639 //#########################################################################