925e4e195de41aa2068d7f20fc56645ab0197563
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 //g_message("miss!\n");
312 //dumpMap->setPixelLong(dumpMap, col, row,
313 // simage.getPixel(col, row));
314 simage.setConfidence(col, row,
315 Siox::CERTAIN_BACKGROUND_CONFIDENCE);
316 }
317 }
318 }
320 //g_message("siox: selection done");
322 //dumpMap->writePPM(dumpMap, "siox1.ppm");
323 //dumpMap->destroy(dumpMap);
325 //## ok we have our pixel buf
326 TraceSioxObserver observer(this);
327 Siox sengine(&observer);
328 SioxImage result = sengine.extractForeground(simage, 0xffffff);
329 if (!result.isValid())
330 {
331 g_warning(_("Invalid SIOX result"));
332 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
333 }
335 //result.writePPM("siox2.ppm");
337 /* Free Arena and ArenaItem */
338 /*
339 std::vector<NRArenaItem *>::iterator aIter;
340 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
341 {
342 NRArenaItem *arenaItem = *aIter;
343 nr_arena_item_unref(arenaItem);
344 }
345 nr_object_unref((NRObject *) arena);
346 */
348 Glib::RefPtr<Gdk::Pixbuf> newPixbuf = Glib::wrap(result.getGdkPixbuf());
350 //g_message("siox: done");
352 lastSioxPixbuf = newPixbuf;
354 return newPixbuf;
355 }
358 /**
359 *
360 */
361 Glib::RefPtr<Gdk::Pixbuf>
362 Tracer::getSelectedImage()
363 {
366 SPImage *img = getSelectedSPImage();
367 if (!img)
368 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
370 if (!img->pixbuf)
371 return Glib::RefPtr<Gdk::Pixbuf>(NULL);
373 Glib::RefPtr<Gdk::Pixbuf> pixbuf =
374 Glib::wrap(img->pixbuf, true);
376 if (sioxEnabled)
377 {
378 Glib::RefPtr<Gdk::Pixbuf> sioxPixbuf =
379 sioxProcessImage(img, pixbuf);
380 if (!sioxPixbuf)
381 {
382 return pixbuf;
383 }
384 else
385 {
386 return sioxPixbuf;
387 }
388 }
389 else
390 {
391 return pixbuf;
392 }
394 }
398 //#########################################################################
399 //# T R A C E
400 //#########################################################################
402 /**
403 * Whether we want to enable SIOX subimage selection
404 */
405 void Tracer::enableSiox(bool enable)
406 {
407 sioxEnabled = enable;
408 }
411 /**
412 * Threaded method that does single bitmap--->path conversion
413 */
414 void Tracer::traceThread()
415 {
416 //## Remember. NEVER leave this method without setting
417 //## engine back to NULL
419 //## Prepare our kill flag. We will watch this later to
420 //## see if the main thread wants us to stop
421 keepGoing = true;
423 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
424 if (!desktop)
425 {
426 g_warning("Trace: No active desktop\n");
427 return;
428 }
430 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
432 Inkscape::Selection *selection = sp_desktop_selection (desktop);
434 if (!SP_ACTIVE_DOCUMENT)
435 {
436 char *msg = _("Trace: No active document");
437 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
438 //g_warning(msg);
439 engine = NULL;
440 return;
441 }
442 SPDocument *doc = SP_ACTIVE_DOCUMENT;
443 sp_document_ensure_up_to_date(doc);
446 SPImage *img = getSelectedSPImage();
447 if (!img)
448 {
449 engine = NULL;
450 return;
451 }
453 Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(img->pixbuf, true);
455 pixbuf = sioxProcessImage(img, pixbuf);
457 if (!pixbuf)
458 {
459 char *msg = _("Trace: Image has no bitmap data");
460 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
461 //g_warning(msg);
462 engine = NULL;
463 return;
464 }
466 msgStack->flash(Inkscape::NORMAL_MESSAGE, _("Trace: Starting trace..."));
467 desktop->updateCanvasNow();
469 std::vector<TracingEngineResult> results =
470 engine->trace(pixbuf);
471 //printf("nrPaths:%d\n", results.size());
472 int nrPaths = results.size();
474 //### Check if we should stop
475 if (!keepGoing || nrPaths<1)
476 {
477 engine = NULL;
478 return;
479 }
481 //### Get pointers to the <image> and its parent
482 Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->repr;
483 Inkscape::XML::Node *par = sp_repr_parent(imgRepr);
485 //### Get some information for the new transform()
486 double x = 0.0;
487 double y = 0.0;
488 double width = 0.0;
489 double height = 0.0;
490 double dval = 0.0;
492 if (sp_repr_get_double(imgRepr, "x", &dval))
493 x = dval;
494 if (sp_repr_get_double(imgRepr, "y", &dval))
495 y = dval;
497 if (sp_repr_get_double(imgRepr, "width", &dval))
498 width = dval;
499 if (sp_repr_get_double(imgRepr, "height", &dval))
500 height = dval;
502 NR::Matrix trans(NR::translate(x, y));
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::Matrix scal(NR::scale(iwscale, ihscale));
512 //# Convolve scale, translation, and the original transform
513 NR::Matrix tf(scal);
514 tf *= 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 //#########################################################################