13885eb5bf2082a2c133a7faa897fc8d1941a5fe
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>
31 #include <display/nr-arena.h>
32 #include <display/nr-arena-shape.h>
34 #include "siox.h"
35 #include "imagemap-gdk.h"
39 namespace Inkscape
40 {
42 namespace Trace
43 {
49 /**
50 * Get the selected image. Also check for any SPItems over it, in
51 * case the user wants SIOX pre-processing.
52 */
53 SPImage *
54 Tracer::getSelectedSPImage()
55 {
57 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
58 if (!desktop)
59 {
60 g_warning("Trace: No active desktop");
61 return NULL;
62 }
64 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
66 Inkscape::Selection *sel = sp_desktop_selection(desktop);
67 if (!sel)
68 {
69 char *msg = _("Select an <b>image</b> to trace");
70 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
71 //g_warning(msg);
72 return NULL;
73 }
75 if (sioxEnabled)
76 {
77 SPImage *img = NULL;
78 GSList const *list = sel->itemList();
79 std::vector<SPItem *> items;
80 sioxShapes.clear();
82 /*
83 First, things are selected top-to-bottom, so we need to invert
84 them as bottom-to-top so that we can discover the image and any
85 SPItems above it
86 */
87 for ( ; list ; list=list->next)
88 {
89 if (!SP_IS_ITEM(list->data))
90 {
91 continue;
92 }
93 SPItem *item = SP_ITEM(list->data);
94 items.insert(items.begin(), item);
95 }
96 std::vector<SPItem *>::iterator iter;
97 for (iter = items.begin() ; iter!= items.end() ; iter++)
98 {
99 SPItem *item = *iter;
100 if (SP_IS_IMAGE(item))
101 {
102 if (img) //we want only one
103 {
104 char *msg = _("Select only one <b>image</b> to trace");
105 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
106 return NULL;
107 }
108 img = SP_IMAGE(item);
109 }
110 else // if (img) //# items -after- the image in tree (above it in Z)
111 {
112 if (SP_IS_SHAPE(item))
113 {
114 SPShape *shape = SP_SHAPE(item);
115 sioxShapes.push_back(shape);
116 }
117 }
118 }
120 if (!img || sioxShapes.size() < 1)
121 {
122 char *msg = _("Select one image and one or more shapes above it");
123 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
124 return NULL;
125 }
126 return img;
127 }
128 else
129 //### SIOX not enabled. We want exactly one image selected
130 {
131 SPItem *item = sel->singleItem();
132 if (!item)
133 {
134 char *msg = _("Select an <b>image</b> to trace"); //same as above
135 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
136 //g_warning(msg);
137 return NULL;
138 }
140 if (!SP_IS_IMAGE(item))
141 {
142 char *msg = _("Select an <b>image</b> to trace");
143 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
144 //g_warning(msg);
145 return NULL;
146 }
148 SPImage *img = SP_IMAGE(item);
150 return img;
151 }
153 }
157 typedef org::siox::SioxImage SioxImage;
158 typedef org::siox::SioxObserver SioxObserver;
159 typedef org::siox::Siox Siox;
162 class TraceSioxObserver : public SioxObserver
163 {
164 public:
166 /**
167 *
168 */
169 TraceSioxObserver (void *contextArg) :
170 SioxObserver(contextArg)
171 {}
173 /**
174 *
175 */
176 virtual ~TraceSioxObserver ()
177 { }
179 /**
180 * Informs the observer how much has been completed.
181 * Return false if the processing should be aborted.
182 */
183 virtual bool progress(float percentCompleted)
184 {
185 //Tracer *tracer = (Tracer *)context;
186 //## Allow the GUI to update
187 Gtk::Main::iteration(false); //at least once, non-blocking
188 while( Gtk::Main::events_pending() )
189 Gtk::Main::iteration();
190 return true;
191 }
193 /**
194 * Send an error string to the Observer. Processing will
195 * be halted.
196 */
197 virtual void error(const std::string &msg)
198 {
199 //Tracer *tracer = (Tracer *)context;
200 }
203 };
209 /**
210 * Process a GdkPixbuf, according to which areas have been
211 * obscured in the GUI.
212 */
213 Glib::RefPtr<Gdk::Pixbuf>
214 Tracer::sioxProcessImage(SPImage *img,
215 Glib::RefPtr<Gdk::Pixbuf>origPixbuf)
216 {
217 if (!sioxEnabled)
218 return origPixbuf;
220 if (origPixbuf == lastOrigPixbuf)
221 return lastSioxPixbuf;
223 //g_message("siox: start");
225 //Convert from gdk, so a format we know. By design, the pixel
226 //format in PackedPixelMap is identical to what is needed by SIOX
227 SioxImage simage(origPixbuf->gobj());
229 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
230 if (!desktop)
231 {
232 g_warning(_("Trace: No active desktop"));
233 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
234 }
236 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
238 Inkscape::Selection *sel = sp_desktop_selection(desktop);
239 if (!sel)
240 {
241 char *msg = _("Select an <b>image</b> to trace");
242 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
243 //g_warning(msg);
244 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
245 }
247 NRArenaItem *aImg = sp_item_get_arenaitem(img, desktop->dkey);
248 //g_message("img: %d %d %d %d\n", aImg->bbox.x0, aImg->bbox.y0,
249 // aImg->bbox.x1, aImg->bbox.y1);
251 double width = (double)(aImg->bbox.x1 - aImg->bbox.x0);
252 double height = (double)(aImg->bbox.y1 - aImg->bbox.y0);
254 double iwidth = (double)simage.getWidth();
255 double iheight = (double)simage.getHeight();
257 double iwscale = width / iwidth;
258 double ihscale = height / iheight;
260 std::vector<NRArenaItem *> arenaItems;
261 std::vector<SPShape *>::iterator iter;
262 for (iter = sioxShapes.begin() ; iter!=sioxShapes.end() ; iter++)
263 {
264 SPItem *item = *iter;
265 NRArenaItem *aItem = sp_item_get_arenaitem(item, desktop->dkey);
266 arenaItems.push_back(aItem);
267 }
269 //g_message("%d arena items\n", arenaItems.size());
271 //PackedPixelMap *dumpMap = PackedPixelMapCreate(
272 // simage.getWidth(), simage.getHeight());
274 //g_message("siox: start selection");
276 for (int row=0 ; row<iheight ; row++)
277 {
278 double ypos = ((double)aImg->bbox.y0) + ihscale * (double) row;
279 for (int col=0 ; col<simage.getWidth() ; col++)
280 {
281 //Get absolute X,Y position
282 double xpos = ((double)aImg->bbox.x0) + iwscale * (double)col;
283 NR::Point point(xpos, ypos);
284 point *= aImg->transform;
285 //point *= imgMat;
286 //point = desktop->doc2dt(point);
287 //g_message("x:%f y:%f\n", point[0], point[1]);
288 bool weHaveAHit = false;
289 std::vector<NRArenaItem *>::iterator aIter;
290 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
291 {
292 NRArenaItem *arenaItem = *aIter;
293 NRArenaItemClass *arenaClass =
294 (NRArenaItemClass *) NR_OBJECT_GET_CLASS (arenaItem);
295 if (arenaClass->pick(arenaItem, point, 1.0f, 1))
296 {
297 weHaveAHit = true;
298 break;
299 }
300 }
302 if (weHaveAHit)
303 {
304 //g_message("hit!\n");
305 //dumpMap->setPixelLong(dumpMap, col, row, 0L);
306 simage.setConfidence(col, row,
307 Siox::UNKNOWN_REGION_CONFIDENCE);
308 }
309 else
310 {
311 //dumpMap->setPixelLong(dumpMap, col, row,
312 // simage.getPixel(col, row));
313 simage.setConfidence(col, row,
314 Siox::CERTAIN_BACKGROUND_CONFIDENCE);
315 }
316 }
317 }
319 //g_message("siox: selection done");
321 //dumpMap->writePPM(dumpMap, "siox1.ppm");
322 //dumpMap->destroy(dumpMap);
324 //## ok we have our pixel buf
325 TraceSioxObserver observer(this);
326 Siox sengine(&observer);
327 SioxImage result = sengine.extractForeground(simage, 0xffffff);
328 if (!result.isValid())
329 {
330 g_warning(_("Invalid SIOX result"));
331 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
332 }
334 //result.writePPM("siox2.ppm");
336 /* Free Arena and ArenaItem */
337 /*
338 std::vector<NRArenaItem *>::iterator aIter;
339 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
340 {
341 NRArenaItem *arenaItem = *aIter;
342 nr_arena_item_unref(arenaItem);
343 }
344 nr_object_unref((NRObject *) arena);
345 */
347 Glib::RefPtr<Gdk::Pixbuf> newPixbuf = Glib::wrap(result.getGdkPixbuf());
349 //g_message("siox: done");
351 lastSioxPixbuf = newPixbuf;
353 return newPixbuf;
354 }
357 /**
358 *
359 */
360 Glib::RefPtr<Gdk::Pixbuf>
361 Tracer::getSelectedImage()
362 {
365 SPImage *img = getSelectedSPImage();
366 if (!img)
367 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
369 if (!img->pixbuf)
370 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
372 Glib::RefPtr<Gdk::Pixbuf> pixbuf =
373 Glib::wrap(img->pixbuf, true);
375 if (sioxEnabled)
376 {
377 Glib::RefPtr<Gdk::Pixbuf> sioxPixbuf =
378 sioxProcessImage(img, pixbuf);
379 if (!sioxPixbuf)
380 {
381 return pixbuf;
382 }
383 else
384 {
385 return sioxPixbuf;
386 }
387 }
388 else
389 {
390 return pixbuf;
391 }
393 }
397 //#########################################################################
398 //# T R A C E
399 //#########################################################################
401 /**
402 * Whether we want to enable SIOX subimage selection
403 */
404 void Tracer::enableSiox(bool enable)
405 {
406 sioxEnabled = enable;
407 }
410 /**
411 * Threaded method that does single bitmap--->path conversion
412 */
413 void Tracer::traceThread()
414 {
415 //## Remember. NEVER leave this method without setting
416 //## engine back to NULL
418 //## Prepare our kill flag. We will watch this later to
419 //## see if the main thread wants us to stop
420 keepGoing = true;
422 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
423 if (!desktop)
424 {
425 g_warning("Trace: No active desktop\n");
426 return;
427 }
429 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
431 Inkscape::Selection *selection = sp_desktop_selection (desktop);
433 if (!SP_ACTIVE_DOCUMENT)
434 {
435 char *msg = _("Trace: No active document");
436 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
437 //g_warning(msg);
438 engine = NULL;
439 return;
440 }
441 SPDocument *doc = SP_ACTIVE_DOCUMENT;
442 sp_document_ensure_up_to_date(doc);
445 SPImage *img = getSelectedSPImage();
446 if (!img)
447 {
448 engine = NULL;
449 return;
450 }
452 Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(img->pixbuf, true);
454 pixbuf = sioxProcessImage(img, pixbuf);
456 if (!pixbuf)
457 {
458 char *msg = _("Trace: Image has no bitmap data");
459 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
460 //g_warning(msg);
461 engine = NULL;
462 return;
463 }
465 int nrPaths;
466 TracingEngineResult *results = engine->trace(pixbuf, &nrPaths);
467 //printf("nrPaths:%d\n", nrPaths);
469 //### Check if we should stop
470 if (!keepGoing || !results || nrPaths<1)
471 {
472 engine = NULL;
473 return;
474 }
476 //### Get pointers to the <image> and its parent
477 Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->repr;
478 Inkscape::XML::Node *par = sp_repr_parent(imgRepr);
480 //### Get some information for the new transform()
481 double x = 0.0;
482 double y = 0.0;
483 double width = 0.0;
484 double height = 0.0;
485 double dval = 0.0;
487 if (sp_repr_get_double(imgRepr, "x", &dval))
488 x = dval;
489 if (sp_repr_get_double(imgRepr, "y", &dval))
490 y = dval;
492 if (sp_repr_get_double(imgRepr, "width", &dval))
493 width = dval;
494 if (sp_repr_get_double(imgRepr, "height", &dval))
495 height = dval;
497 NR::Matrix trans(NR::translate(x, y));
499 double iwidth = (double)pixbuf->get_width();
500 double iheight = (double)pixbuf->get_height();
502 double iwscale = width / iwidth;
503 double ihscale = height / iheight;
505 NR::Matrix scal(NR::scale(iwscale, ihscale));
507 //# Convolve scale, translation, and the original transform
508 NR::Matrix tf(scal);
509 tf *= trans;
510 tf *= img->transform;
513 //#OK. Now let's start making new nodes
515 Inkscape::XML::Node *groupRepr = NULL;
517 //# if more than 1, make a <g>roup of <path>s
518 if (nrPaths > 1)
519 {
520 groupRepr = sp_repr_new("svg:g");
521 par->addChild(groupRepr, imgRepr);
522 }
524 long totalNodeCount = 0L;
526 for (TracingEngineResult *result=results ;
527 result ; result=result->next)
528 {
529 totalNodeCount += result->getNodeCount();
531 Inkscape::XML::Node *pathRepr = sp_repr_new("svg:path");
532 pathRepr->setAttribute("style", result->getStyle());
533 pathRepr->setAttribute("d", result->getPathData());
535 if (nrPaths > 1)
536 groupRepr->addChild(pathRepr, NULL);
537 else
538 par->addChild(pathRepr, imgRepr);
540 //### Apply the transform from the image to the new shape
541 SPObject *reprobj = doc->getObjectByRepr(pathRepr);
542 if (reprobj)
543 {
544 SPItem *newItem = SP_ITEM(reprobj);
545 sp_item_write_transform(newItem, pathRepr, tf, NULL);
546 }
547 if (nrPaths == 1)
548 {
549 selection->clear();
550 selection->add(pathRepr);
551 }
552 Inkscape::GC::release(pathRepr);
553 }
555 delete results;
557 // If we have a group, then focus on, then forget it
558 if (nrPaths > 1)
559 {
560 selection->clear();
561 selection->add(groupRepr);
562 Inkscape::GC::release(groupRepr);
563 }
565 //## inform the document, so we can undo
566 sp_document_done(doc, SP_VERB_NONE,
567 /* TODO: annotate */ "trace.cpp:567");
569 engine = NULL;
571 char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
572 msgStack->flash(Inkscape::NORMAL_MESSAGE, msg);
573 g_free(msg);
575 }
581 /**
582 * Main tracing method
583 */
584 void Tracer::trace(TracingEngine *theEngine)
585 {
586 //Check if we are already running
587 if (engine)
588 return;
590 engine = theEngine;
592 #if HAVE_THREADS
593 //Ensure that thread support is running
594 if (!Glib::thread_supported())
595 Glib::thread_init();
597 //Create our thread and run it
598 Glib::Thread::create(
599 sigc::mem_fun(*this, &Tracer::traceThread), false);
600 #else
601 traceThread();
602 #endif
604 }
610 /**
611 * Abort the thread that is executing trace()
612 */
613 void Tracer::abort()
614 {
616 //## Inform Trace's working thread
617 keepGoing = false;
619 if (engine)
620 {
621 engine->abort();
622 }
624 }
628 } // namespace Trace
630 } // namespace Inkscape
633 //#########################################################################
634 //# E N D O F F I L E
635 //#########################################################################