1 /**
2 * A generic interface for plugging different
3 * autotracers into Inkscape.
4 *
5 * Authors:
6 * Bob Jamison <rjamison@earthlink.net>
7 * Jon A. Cruz <jon@joncruz.org>
8 * Abhishek Sharma
9 *
10 * Copyright (C) 2004-2006 Bob Jamison
11 *
12 * Released under GNU GPL, read the file 'COPYING' for more information
13 */
17 #include "trace/potrace/inkscape-potrace.h"
19 #include <inkscape.h>
20 #include <desktop.h>
21 #include <desktop-handles.h>
22 #include <document.h>
23 #include <message-stack.h>
24 #include <gtkmm.h>
25 #include <glibmm/i18n.h>
26 #include <selection.h>
27 #include <xml/repr.h>
28 #include <xml/attribute-record.h>
29 #include <sp-item.h>
30 #include <sp-shape.h>
31 #include <sp-image.h>
32 #include <libnr/nr-matrix-ops.h>
33 #include <2geom/transforms.h>
35 #include <display/nr-arena.h>
36 #include <display/nr-arena-shape.h>
38 #include "siox.h"
39 #include "imagemap-gdk.h"
43 namespace Inkscape
44 {
46 namespace Trace
47 {
53 /**
54 * Get the selected image. Also check for any SPItems over it, in
55 * case the user wants SIOX pre-processing.
56 */
57 SPImage *
58 Tracer::getSelectedSPImage()
59 {
61 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
62 if (!desktop)
63 {
64 g_warning("Trace: No active desktop");
65 return NULL;
66 }
68 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
70 Inkscape::Selection *sel = sp_desktop_selection(desktop);
71 if (!sel)
72 {
73 char *msg = _("Select an <b>image</b> to trace");
74 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
75 //g_warning(msg);
76 return NULL;
77 }
79 if (sioxEnabled)
80 {
81 SPImage *img = NULL;
82 GSList const *list = sel->itemList();
83 std::vector<SPItem *> items;
84 sioxShapes.clear();
86 /*
87 First, things are selected top-to-bottom, so we need to invert
88 them as bottom-to-top so that we can discover the image and any
89 SPItems above it
90 */
91 for ( ; list ; list=list->next)
92 {
93 if (!SP_IS_ITEM(list->data))
94 {
95 continue;
96 }
97 SPItem *item = SP_ITEM(list->data);
98 items.insert(items.begin(), item);
99 }
100 std::vector<SPItem *>::iterator iter;
101 for (iter = items.begin() ; iter!= items.end() ; iter++)
102 {
103 SPItem *item = *iter;
104 if (SP_IS_IMAGE(item))
105 {
106 if (img) //we want only one
107 {
108 char *msg = _("Select only one <b>image</b> to trace");
109 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
110 return NULL;
111 }
112 img = SP_IMAGE(item);
113 }
114 else // if (img) //# items -after- the image in tree (above it in Z)
115 {
116 if (SP_IS_SHAPE(item))
117 {
118 SPShape *shape = SP_SHAPE(item);
119 sioxShapes.push_back(shape);
120 }
121 }
122 }
124 if (!img || sioxShapes.size() < 1)
125 {
126 char *msg = _("Select one image and one or more shapes above it");
127 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
128 return NULL;
129 }
130 return img;
131 }
132 else
133 //### SIOX not enabled. We want exactly one image selected
134 {
135 SPItem *item = sel->singleItem();
136 if (!item)
137 {
138 char *msg = _("Select an <b>image</b> to trace"); //same as above
139 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
140 //g_warning(msg);
141 return NULL;
142 }
144 if (!SP_IS_IMAGE(item))
145 {
146 char *msg = _("Select an <b>image</b> to trace");
147 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
148 //g_warning(msg);
149 return NULL;
150 }
152 SPImage *img = SP_IMAGE(item);
154 return img;
155 }
157 }
161 typedef org::siox::SioxImage SioxImage;
162 typedef org::siox::SioxObserver SioxObserver;
163 typedef org::siox::Siox Siox;
166 class TraceSioxObserver : public SioxObserver
167 {
168 public:
170 /**
171 *
172 */
173 TraceSioxObserver (void *contextArg) :
174 SioxObserver(contextArg)
175 {}
177 /**
178 *
179 */
180 virtual ~TraceSioxObserver ()
181 { }
183 /**
184 * Informs the observer how much has been completed.
185 * Return false if the processing should be aborted.
186 */
187 virtual bool progress(float /*percentCompleted*/)
188 {
189 //Tracer *tracer = (Tracer *)context;
190 //## Allow the GUI to update
191 Gtk::Main::iteration(false); //at least once, non-blocking
192 while( Gtk::Main::events_pending() )
193 Gtk::Main::iteration();
194 return true;
195 }
197 /**
198 * Send an error string to the Observer. Processing will
199 * be halted.
200 */
201 virtual void error(const std::string &/*msg*/)
202 {
203 //Tracer *tracer = (Tracer *)context;
204 }
207 };
213 /**
214 * Process a GdkPixbuf, according to which areas have been
215 * obscured in the GUI.
216 */
217 Glib::RefPtr<Gdk::Pixbuf>
218 Tracer::sioxProcessImage(SPImage *img,
219 Glib::RefPtr<Gdk::Pixbuf>origPixbuf)
220 {
221 if (!sioxEnabled)
222 return origPixbuf;
224 if (origPixbuf == lastOrigPixbuf)
225 return lastSioxPixbuf;
227 //g_message("siox: start");
229 //Convert from gdk, so a format we know. By design, the pixel
230 //format in PackedPixelMap is identical to what is needed by SIOX
231 SioxImage simage(origPixbuf->gobj());
233 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
234 if (!desktop)
235 {
236 g_warning(_("Trace: No active desktop"));
237 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
238 }
240 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
242 Inkscape::Selection *sel = sp_desktop_selection(desktop);
243 if (!sel)
244 {
245 char *msg = _("Select an <b>image</b> to trace");
246 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
247 //g_warning(msg);
248 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
249 }
251 NRArenaItem *aImg = img->get_arenaitem(desktop->dkey);
252 //g_message("img: %d %d %d %d\n", aImg->bbox.x0, aImg->bbox.y0,
253 // aImg->bbox.x1, aImg->bbox.y1);
255 double width = (double)(aImg->bbox.x1 - aImg->bbox.x0);
256 double height = (double)(aImg->bbox.y1 - aImg->bbox.y0);
258 double iwidth = (double)simage.getWidth();
259 double iheight = (double)simage.getHeight();
261 double iwscale = width / iwidth;
262 double ihscale = height / iheight;
264 std::vector<NRArenaItem *> arenaItems;
265 std::vector<SPShape *>::iterator iter;
266 for (iter = sioxShapes.begin() ; iter!=sioxShapes.end() ; iter++)
267 {
268 SPItem *item = *iter;
269 NRArenaItem *aItem = item->get_arenaitem(desktop->dkey);
270 arenaItems.push_back(aItem);
271 }
273 //g_message("%d arena items\n", arenaItems.size());
275 //PackedPixelMap *dumpMap = PackedPixelMapCreate(
276 // simage.getWidth(), simage.getHeight());
278 //g_message("siox: start selection");
280 for (int row=0 ; row<iheight ; row++)
281 {
282 double ypos = ((double)aImg->bbox.y0) + ihscale * (double) row;
283 for (int col=0 ; col<simage.getWidth() ; col++)
284 {
285 //Get absolute X,Y position
286 double xpos = ((double)aImg->bbox.x0) + iwscale * (double)col;
287 Geom::Point point(xpos, ypos);
288 if (aImg->transform)
289 point *= *aImg->transform;
290 //point *= imgMat;
291 //point = desktop->doc2dt(point);
292 //g_message("x:%f y:%f\n", point[0], point[1]);
293 bool weHaveAHit = false;
294 std::vector<NRArenaItem *>::iterator aIter;
295 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
296 {
297 NRArenaItem *arenaItem = *aIter;
298 NRArenaItemClass *arenaClass =
299 (NRArenaItemClass *) NR_OBJECT_GET_CLASS (arenaItem);
300 if (arenaClass->pick(arenaItem, point, 1.0f, 1))
301 {
302 weHaveAHit = true;
303 break;
304 }
305 }
307 if (weHaveAHit)
308 {
309 //g_message("hit!\n");
310 //dumpMap->setPixelLong(dumpMap, col, row, 0L);
311 simage.setConfidence(col, row,
312 Siox::UNKNOWN_REGION_CONFIDENCE);
313 }
314 else
315 {
316 //g_message("miss!\n");
317 //dumpMap->setPixelLong(dumpMap, col, row,
318 // simage.getPixel(col, row));
319 simage.setConfidence(col, row,
320 Siox::CERTAIN_BACKGROUND_CONFIDENCE);
321 }
322 }
323 }
325 //g_message("siox: selection done");
327 //dumpMap->writePPM(dumpMap, "siox1.ppm");
328 //dumpMap->destroy(dumpMap);
330 //## ok we have our pixel buf
331 TraceSioxObserver observer(this);
332 Siox sengine(&observer);
333 SioxImage result = sengine.extractForeground(simage, 0xffffff);
334 if (!result.isValid())
335 {
336 g_warning(_("Invalid SIOX result"));
337 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
338 }
340 //result.writePPM("siox2.ppm");
342 /* Free Arena and ArenaItem */
343 /*
344 std::vector<NRArenaItem *>::iterator aIter;
345 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
346 {
347 NRArenaItem *arenaItem = *aIter;
348 nr_arena_item_unref(arenaItem);
349 }
350 nr_object_unref((NRObject *) arena);
351 */
353 Glib::RefPtr<Gdk::Pixbuf> newPixbuf = Glib::wrap(result.getGdkPixbuf());
355 //g_message("siox: done");
357 lastSioxPixbuf = newPixbuf;
359 return newPixbuf;
360 }
363 /**
364 *
365 */
366 Glib::RefPtr<Gdk::Pixbuf>
367 Tracer::getSelectedImage()
368 {
371 SPImage *img = getSelectedSPImage();
372 if (!img)
373 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
375 if (!img->pixbuf)
376 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
378 Glib::RefPtr<Gdk::Pixbuf> pixbuf =
379 Glib::wrap(img->pixbuf, true);
381 if (sioxEnabled)
382 {
383 Glib::RefPtr<Gdk::Pixbuf> sioxPixbuf =
384 sioxProcessImage(img, pixbuf);
385 if (!sioxPixbuf)
386 {
387 return pixbuf;
388 }
389 else
390 {
391 return sioxPixbuf;
392 }
393 }
394 else
395 {
396 return pixbuf;
397 }
399 }
403 //#########################################################################
404 //# T R A C E
405 //#########################################################################
407 /**
408 * Whether we want to enable SIOX subimage selection
409 */
410 void Tracer::enableSiox(bool enable)
411 {
412 sioxEnabled = enable;
413 }
416 /**
417 * Threaded method that does single bitmap--->path conversion
418 */
419 void Tracer::traceThread()
420 {
421 //## Remember. NEVER leave this method without setting
422 //## engine back to NULL
424 //## Prepare our kill flag. We will watch this later to
425 //## see if the main thread wants us to stop
426 keepGoing = true;
428 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
429 if (!desktop)
430 {
431 g_warning("Trace: No active desktop\n");
432 return;
433 }
435 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
437 Inkscape::Selection *selection = sp_desktop_selection (desktop);
439 if (!SP_ACTIVE_DOCUMENT)
440 {
441 char *msg = _("Trace: No active document");
442 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
443 //g_warning(msg);
444 engine = NULL;
445 return;
446 }
447 SPDocument *doc = SP_ACTIVE_DOCUMENT;
448 doc->ensureUpToDate();
451 SPImage *img = getSelectedSPImage();
452 if (!img)
453 {
454 engine = NULL;
455 return;
456 }
458 Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(img->pixbuf, true);
460 pixbuf = sioxProcessImage(img, pixbuf);
462 if (!pixbuf)
463 {
464 char *msg = _("Trace: Image has no bitmap data");
465 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
466 //g_warning(msg);
467 engine = NULL;
468 return;
469 }
471 msgStack->flash(Inkscape::NORMAL_MESSAGE, _("Trace: Starting trace..."));
472 desktop->updateCanvasNow();
474 std::vector<TracingEngineResult> results =
475 engine->trace(pixbuf);
476 //printf("nrPaths:%d\n", results.size());
477 int nrPaths = results.size();
479 //### Check if we should stop
480 if (!keepGoing || nrPaths<1)
481 {
482 engine = NULL;
483 return;
484 }
486 //### Get pointers to the <image> and its parent
487 //XML Tree being used directly here while it shouldn't be.
488 Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->getRepr();
489 Inkscape::XML::Node *par = sp_repr_parent(imgRepr);
491 //### Get some information for the new transform()
492 double x = 0.0;
493 double y = 0.0;
494 double width = 0.0;
495 double height = 0.0;
496 double dval = 0.0;
498 if (sp_repr_get_double(imgRepr, "x", &dval))
499 x = dval;
500 if (sp_repr_get_double(imgRepr, "y", &dval))
501 y = dval;
503 if (sp_repr_get_double(imgRepr, "width", &dval))
504 width = dval;
505 if (sp_repr_get_double(imgRepr, "height", &dval))
506 height = dval;
508 double iwidth = (double)pixbuf->get_width();
509 double iheight = (double)pixbuf->get_height();
511 double iwscale = width / iwidth;
512 double ihscale = height / iheight;
514 Geom::Translate trans(x, y);
515 Geom::Scale scal(iwscale, ihscale);
517 //# Convolve scale, translation, and the original transform
518 Geom::Matrix tf(scal * trans);
519 tf *= img->transform;
522 //#OK. Now let's start making new nodes
524 Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
525 Inkscape::XML::Node *groupRepr = NULL;
527 //# if more than 1, make a <g>roup of <path>s
528 if (nrPaths > 1)
529 {
530 groupRepr = xml_doc->createElement("svg:g");
531 par->addChild(groupRepr, imgRepr);
532 }
534 long totalNodeCount = 0L;
536 for (unsigned int i=0 ; i<results.size() ; i++)
537 {
538 TracingEngineResult result = results[i];
539 totalNodeCount += result.getNodeCount();
541 Inkscape::XML::Node *pathRepr = xml_doc->createElement("svg:path");
542 pathRepr->setAttribute("style", result.getStyle().c_str());
543 pathRepr->setAttribute("d", result.getPathData().c_str());
545 if (nrPaths > 1)
546 groupRepr->addChild(pathRepr, NULL);
547 else
548 par->addChild(pathRepr, imgRepr);
550 //### Apply the transform from the image to the new shape
551 SPObject *reprobj = doc->getObjectByRepr(pathRepr);
552 if (reprobj)
553 {
554 SPItem *newItem = SP_ITEM(reprobj);
555 newItem->doWriteTransform(pathRepr, tf, NULL);
556 }
557 if (nrPaths == 1)
558 {
559 selection->clear();
560 selection->add(pathRepr);
561 }
562 Inkscape::GC::release(pathRepr);
563 }
565 // If we have a group, then focus on, then forget it
566 if (nrPaths > 1)
567 {
568 selection->clear();
569 selection->add(groupRepr);
570 Inkscape::GC::release(groupRepr);
571 }
573 //## inform the document, so we can undo
574 DocumentUndo::done(doc, SP_VERB_SELECTION_TRACE, _("Trace bitmap"));
576 engine = NULL;
578 char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
579 msgStack->flash(Inkscape::NORMAL_MESSAGE, msg);
580 g_free(msg);
582 }
588 /**
589 * Main tracing method
590 */
591 void Tracer::trace(TracingEngine *theEngine)
592 {
593 //Check if we are already running
594 if (engine)
595 return;
597 engine = theEngine;
599 #if HAVE_THREADS
600 //Ensure that thread support is running
601 if (!Glib::thread_supported())
602 Glib::thread_init();
604 //Create our thread and run it
605 Glib::Thread::create(
606 sigc::mem_fun(*this, &Tracer::traceThread), false);
607 #else
608 traceThread();
609 #endif
611 }
617 /**
618 * Abort the thread that is executing trace()
619 */
620 void Tracer::abort()
621 {
623 //## Inform Trace's working thread
624 keepGoing = false;
626 if (engine)
627 {
628 engine->abort();
629 }
631 }
635 } // namespace Trace
637 } // namespace Inkscape
640 //#########################################################################
641 //# E N D O F F I L E
642 //#########################################################################