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 {
50 /**
51 * Get the selected image. Also check for any SPItems over it, in
52 * case the user wants SIOX pre-processing.
53 */
54 SPImage *
55 Tracer::getSelectedSPImage()
56 {
58 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
59 if (!desktop)
60 {
61 g_warning("Trace: No active desktop");
62 return NULL;
63 }
65 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
67 Inkscape::Selection *sel = sp_desktop_selection(desktop);
68 if (!sel)
69 {
70 char *msg = _("Select an <b>image</b> to trace");
71 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
72 //g_warning(msg);
73 return NULL;
74 }
76 if (sioxEnabled)
77 {
78 SPImage *img = NULL;
79 GSList const *list = sel->itemList();
80 std::vector<SPItem *> items;
81 sioxShapes.clear();
83 /*
84 First, things are selected top-to-bottom, so we need to invert
85 them as bottom-to-top so that we can discover the image and any
86 SPItems above it
87 */
88 for ( ; list ; list=list->next)
89 {
90 if (!SP_IS_ITEM(list->data))
91 {
92 continue;
93 }
94 SPItem *item = SP_ITEM(list->data);
95 items.insert(items.begin(), item);
96 }
97 std::vector<SPItem *>::iterator iter;
98 for (iter = items.begin() ; iter!= items.end() ; iter++)
99 {
100 SPItem *item = *iter;
101 if (SP_IS_IMAGE(item))
102 {
103 if (img) //we want only one
104 {
105 char *msg = _("Select only one <b>image</b> to trace");
106 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
107 return NULL;
108 }
109 img = SP_IMAGE(item);
110 }
111 else // if (img) //# items -after- the image in tree (above it in Z)
112 {
113 if (SP_IS_SHAPE(item))
114 {
115 SPShape *shape = SP_SHAPE(item);
116 sioxShapes.push_back(shape);
117 }
118 }
119 }
121 if (!img || sioxShapes.size() < 1)
122 {
123 char *msg = _("Select one image and one or more shapes above it");
124 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
125 return NULL;
126 }
127 return img;
128 }
129 else
130 //### SIOX not enabled. We want exactly one image selected
131 {
132 SPItem *item = sel->singleItem();
133 if (!item)
134 {
135 char *msg = _("Select an <b>image</b> to trace"); //same as above
136 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
137 //g_warning(msg);
138 return NULL;
139 }
141 if (!SP_IS_IMAGE(item))
142 {
143 char *msg = _("Select an <b>image</b> to trace");
144 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
145 //g_warning(msg);
146 return NULL;
147 }
149 SPImage *img = SP_IMAGE(item);
151 return img;
152 }
154 }
158 typedef org::siox::SioxImage SioxImage;
159 typedef org::siox::SioxObserver SioxObserver;
160 typedef org::siox::Siox Siox;
163 class TraceSioxObserver : public SioxObserver
164 {
165 public:
167 /**
168 *
169 */
170 TraceSioxObserver (void *contextArg) :
171 SioxObserver(contextArg)
172 {}
174 /**
175 *
176 */
177 virtual ~TraceSioxObserver ()
178 { }
180 /**
181 * Informs the observer how much has been completed.
182 * Return false if the processing should be aborted.
183 */
184 virtual bool progress(float percentCompleted)
185 {
186 //Tracer *tracer = (Tracer *)context;
187 //## Allow the GUI to update
188 Gtk::Main::iteration(false); //at least once, non-blocking
189 while( Gtk::Main::events_pending() )
190 Gtk::Main::iteration();
191 return true;
192 }
194 /**
195 * Send an error string to the Observer. Processing will
196 * be halted.
197 */
198 virtual void error(const std::string &msg)
199 {
200 //Tracer *tracer = (Tracer *)context;
201 }
204 };
210 /**
211 * Process a GdkPixbuf, according to which areas have been
212 * obscured in the GUI.
213 */
214 GdkPixbuf *
215 Tracer::sioxProcessImage(SPImage *img, GdkPixbuf *origPixbuf)
216 {
217 if (!sioxEnabled)
218 return origPixbuf;
220 //Convert from gdk, so a format we know. By design, the pixel
221 //format in PackedPixelMap is identical to what is needed by SIOX
222 SioxImage simage(origPixbuf);
224 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
225 if (!desktop)
226 {
227 g_warning(_("Trace: No active desktop"));
228 return NULL;
229 }
231 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
233 Inkscape::Selection *sel = sp_desktop_selection(desktop);
234 if (!sel)
235 {
236 char *msg = _("Select an <b>image</b> to trace");
237 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
238 //g_warning(msg);
239 return NULL;
240 }
242 NRArenaItem *aImg = sp_item_get_arenaitem(img, desktop->dkey);
243 //g_message("img: %d %d %d %d\n", aImg->bbox.x0, aImg->bbox.y0,
244 // aImg->bbox.x1, aImg->bbox.y1);
246 double width = (double)(aImg->bbox.x1 - aImg->bbox.x0);
247 double height = (double)(aImg->bbox.y1 - aImg->bbox.y0);
249 double iwidth = (double)simage.getWidth();
250 double iheight = (double)simage.getHeight();
252 double iwscale = width / iwidth;
253 double ihscale = height / iheight;
255 std::vector<NRArenaItem *> arenaItems;
256 std::vector<SPShape *>::iterator iter;
257 for (iter = sioxShapes.begin() ; iter!=sioxShapes.end() ; iter++)
258 {
259 SPItem *item = *iter;
260 //### Create ArenaItems and set transform
261 NRArenaItem *aItem = sp_item_get_arenaitem(item, desktop->dkey);
263 //nr_arena_item_set_transform(aItem, item->transform);
264 //g_message("%d %d %d %d\n", aItem->bbox.x0, aItem->bbox.y0,
265 // aItem->bbox.x1, aItem->bbox.y1);
266 arenaItems.push_back(aItem);
267 }
268 //g_message("%d arena items\n", arenaItems.size());
270 //PackedPixelMap *dumpMap = PackedPixelMapCreate(
271 // simage.getWidth(), simage.getHeight());
273 for (int row=0 ; row<simage.getHeight() ; row++)
274 {
275 double ypos = ((double)aImg->bbox.y0) + ihscale * (double) row;
276 for (int col=0 ; col<simage.getWidth() ; col++)
277 {
278 //Get absolute X,Y position
279 double xpos = ((double)aImg->bbox.x0) + iwscale * (double)col;
280 NR::Point point(xpos, ypos);
281 point *= aImg->transform;
282 //point *= imgMat;
283 //point = desktop->doc2dt(point);
284 std::vector<SPShape *>::iterator iter;
285 //g_message("x:%f y:%f\n", point[0], point[1]);
286 bool weHaveAHit = false;
287 std::vector<NRArenaItem *>::iterator aIter;
288 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
289 {
290 NRArenaItem *arenaItem = *aIter;
291 NRArenaItemClass *arenaClass =
292 (NRArenaItemClass *) NR_OBJECT_GET_CLASS (arenaItem);
293 if (arenaClass->pick(arenaItem, point, 1.0f, 1))
294 {
295 weHaveAHit = true;
296 break;
297 }
298 }
300 if (weHaveAHit)
301 {
302 //g_message("hit!\n");
303 //dumpMap->setPixelLong(dumpMap, col, row, 0L);
304 simage.setConfidence(col, row,
305 Siox::UNKNOWN_REGION_CONFIDENCE);
306 }
307 else
308 {
309 //dumpMap->setPixelLong(dumpMap, col, row,
310 // simage.getPixel(col, row));
311 simage.setConfidence(col, row,
312 Siox::CERTAIN_BACKGROUND_CONFIDENCE);
313 }
314 }
315 }
317 //dumpMap->writePPM(dumpMap, "siox1.ppm");
318 //dumpMap->destroy(dumpMap);
320 //## ok we have our pixel buf
321 org::siox::Siox sengine;
322 org::siox::SioxImage result =
323 sengine.extractForeground(simage, 0xffffff);
324 if (!result.isValid())
325 {
326 g_warning(_("Invalid SIOX result"));
327 return NULL;
328 }
330 //result.writePPM("siox2.ppm");
332 /* Free Arena and ArenaItem */
333 /*
334 std::vector<NRArenaItem *>::iterator aIter;
335 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
336 {
337 NRArenaItem *arenaItem = *aIter;
338 nr_arena_item_unref(arenaItem);
339 }
340 nr_object_unref((NRObject *) arena);
341 */
343 GdkPixbuf *newPixbuf = result.getGdkPixbuf();
345 return newPixbuf;
346 }
349 /**
350 *
351 */
352 GdkPixbuf *
353 Tracer::getSelectedImage()
354 {
356 SPImage *img = getSelectedSPImage();
357 if (!img)
358 return NULL;
360 GdkPixbuf *pixbuf = img->pixbuf;
361 if (!pixbuf)
362 return NULL;
364 if (sioxEnabled)
365 {
366 GdkPixbuf *sioxPixbuf = sioxProcessImage(img, pixbuf);
367 if (!sioxPixbuf)
368 {
369 g_object_ref(pixbuf);
370 return pixbuf;
371 }
372 else
373 {
374 return sioxPixbuf;
375 }
376 }
377 else
378 {
379 g_object_ref(pixbuf);
380 return pixbuf;
381 }
383 }
387 //#########################################################################
388 //# T R A C E
389 //#########################################################################
391 /**
392 * Whether we want to enable SIOX subimage selection
393 */
394 void Tracer::enableSiox(bool enable)
395 {
396 sioxEnabled = enable;
397 }
400 /**
401 * Threaded method that does single bitmap--->path conversion
402 */
403 void Tracer::traceThread()
404 {
405 //## Remember. NEVER leave this method without setting
406 //## engine back to NULL
408 //## Prepare our kill flag. We will watch this later to
409 //## see if the main thread wants us to stop
410 keepGoing = true;
412 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
413 if (!desktop)
414 {
415 g_warning("Trace: No active desktop\n");
416 return;
417 }
419 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
421 Inkscape::Selection *selection = sp_desktop_selection (desktop);
423 if (!SP_ACTIVE_DOCUMENT)
424 {
425 char *msg = _("Trace: No active document");
426 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
427 //g_warning(msg);
428 engine = NULL;
429 return;
430 }
431 SPDocument *doc = SP_ACTIVE_DOCUMENT;
432 sp_document_ensure_up_to_date(doc);
435 SPImage *img = getSelectedSPImage();
436 if (!img)
437 {
438 engine = NULL;
439 return;
440 }
442 GdkPixbuf *pixbuf = img->pixbuf;
443 g_object_ref(pixbuf);
445 pixbuf = sioxProcessImage(img, pixbuf);
447 if (!pixbuf)
448 {
449 char *msg = _("Trace: Image has no bitmap data");
450 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
451 //g_warning(msg);
452 engine = NULL;
453 return;
454 }
456 int nrPaths;
457 TracingEngineResult *results = engine->trace(pixbuf, &nrPaths);
458 //printf("nrPaths:%d\n", nrPaths);
460 //### Check if we should stop
461 if (!keepGoing || !results || nrPaths<1)
462 {
463 engine = NULL;
464 return;
465 }
467 //### Get pointers to the <image> and its parent
468 Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->repr;
469 Inkscape::XML::Node *par = sp_repr_parent(imgRepr);
471 //### Get some information for the new transform()
472 double x = 0.0;
473 double y = 0.0;
474 double width = 0.0;
475 double height = 0.0;
476 double dval = 0.0;
478 if (sp_repr_get_double(imgRepr, "x", &dval))
479 x = dval;
480 if (sp_repr_get_double(imgRepr, "y", &dval))
481 y = dval;
483 if (sp_repr_get_double(imgRepr, "width", &dval))
484 width = dval;
485 if (sp_repr_get_double(imgRepr, "height", &dval))
486 height = dval;
488 NR::Matrix trans(NR::translate(x, y));
490 double iwidth = (double)gdk_pixbuf_get_width(pixbuf);
491 double iheight = (double)gdk_pixbuf_get_height(pixbuf);
493 double iwscale = width / iwidth;
494 double ihscale = height / iheight;
496 NR::Matrix scal(NR::scale(iwscale, ihscale));
498 //# Convolve scale, translation, and the original transform
499 NR::Matrix tf(scal);
500 tf *= trans;
501 tf *= img->transform;
504 //#OK. Now let's start making new nodes
506 Inkscape::XML::Node *groupRepr = NULL;
508 //# if more than 1, make a <g>roup of <path>s
509 if (nrPaths > 1)
510 {
511 groupRepr = sp_repr_new("svg:g");
512 par->addChild(groupRepr, imgRepr);
513 }
515 long totalNodeCount = 0L;
517 for (TracingEngineResult *result=results ;
518 result ; result=result->next)
519 {
520 totalNodeCount += result->getNodeCount();
522 Inkscape::XML::Node *pathRepr = sp_repr_new("svg:path");
523 pathRepr->setAttribute("style", result->getStyle());
524 pathRepr->setAttribute("d", result->getPathData());
526 if (nrPaths > 1)
527 groupRepr->addChild(pathRepr, NULL);
528 else
529 par->addChild(pathRepr, imgRepr);
531 //### Apply the transform from the image to the new shape
532 SPObject *reprobj = doc->getObjectByRepr(pathRepr);
533 if (reprobj)
534 {
535 SPItem *newItem = SP_ITEM(reprobj);
536 sp_item_write_transform(newItem, pathRepr, tf, NULL);
537 }
538 if (nrPaths == 1)
539 {
540 selection->clear();
541 selection->add(pathRepr);
542 }
543 Inkscape::GC::release(pathRepr);
544 }
546 //release our pixbuf
547 g_object_unref(pixbuf);
549 delete results;
551 // If we have a group, then focus on, then forget it
552 if (nrPaths > 1)
553 {
554 selection->clear();
555 selection->add(groupRepr);
556 Inkscape::GC::release(groupRepr);
557 }
559 //## inform the document, so we can undo
560 sp_document_done(doc);
562 engine = NULL;
564 char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
565 msgStack->flash(Inkscape::NORMAL_MESSAGE, msg);
566 g_free(msg);
568 }
574 /**
575 * Main tracing method
576 */
577 void Tracer::trace(TracingEngine *theEngine)
578 {
579 //Check if we are already running
580 if (engine)
581 return;
583 engine = theEngine;
585 #if HAVE_THREADS
586 //Ensure that thread support is running
587 if (!Glib::thread_supported())
588 Glib::thread_init();
590 //Create our thread and run it
591 Glib::Thread::create(
592 sigc::mem_fun(*this, &Tracer::traceThread), false);
593 #else
594 traceThread();
595 #endif
597 }
603 /**
604 * Abort the thread that is executing trace()
605 */
606 void Tracer::abort()
607 {
609 //## Inform Trace's working thread
610 keepGoing = false;
612 if (engine)
613 {
614 engine->abort();
615 }
617 }
621 } // namespace Trace
623 } // namespace Inkscape
626 //#########################################################################
627 //# E N D O F F I L E
628 //#########################################################################