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 <glibmm/i18n.h>
23 #include <selection.h>
24 #include <xml/repr.h>
25 #include <xml/attribute-record.h>
26 #include <sp-item.h>
27 #include <sp-shape.h>
28 #include <sp-image.h>
30 #include <display/nr-arena.h>
31 #include <display/nr-arena-shape.h>
33 #include "siox.h"
34 #include "imagemap-gdk.h"
38 namespace Inkscape
39 {
41 namespace Trace
42 {
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\n");
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::Siox Siox;
160 GdkPixbuf *
161 Tracer::sioxProcessImage(SPImage *img, GdkPixbuf *origPixbuf)
162 {
163 if (!sioxEnabled)
164 return origPixbuf;
166 //Convert from gdk, so a format we know. By design, the pixel
167 //format in PackedPixelMap is identical to what is needed by SIOX
168 SioxImage simage(origPixbuf);
170 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
171 if (!desktop)
172 {
173 g_warning(_("Trace: No active desktop"));
174 return NULL;
175 }
177 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
179 Inkscape::Selection *sel = sp_desktop_selection(desktop);
180 if (!sel)
181 {
182 char *msg = _("Select an <b>image</b> to trace");
183 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
184 //g_warning(msg);
185 return NULL;
186 }
188 NRArenaItem *aImg = sp_item_get_arenaitem(img, desktop->dkey);
189 //g_message("img: %d %d %d %d\n", aImg->bbox.x0, aImg->bbox.y0,
190 // aImg->bbox.x1, aImg->bbox.y1);
192 double width = (double)(aImg->bbox.x1 - aImg->bbox.x0);
193 double height = (double)(aImg->bbox.y1 - aImg->bbox.y0);
195 double iwidth = (double)simage.getWidth();
196 double iheight = (double)simage.getHeight();
198 double iwscale = width / iwidth;
199 double ihscale = height / iheight;
201 std::vector<NRArenaItem *> arenaItems;
202 std::vector<SPShape *>::iterator iter;
203 for (iter = sioxShapes.begin() ; iter!=sioxShapes.end() ; iter++)
204 {
205 SPItem *item = *iter;
206 //### Create ArenaItems and set transform
207 NRArenaItem *aItem = sp_item_get_arenaitem(item, desktop->dkey);
209 //nr_arena_item_set_transform(aItem, item->transform);
210 //g_message("%d %d %d %d\n", aItem->bbox.x0, aItem->bbox.y0,
211 // aItem->bbox.x1, aItem->bbox.y1);
212 arenaItems.push_back(aItem);
213 }
214 //g_message("%d arena items\n", arenaItems.size());
216 PackedPixelMap *dumpMap = PackedPixelMapCreate(
217 simage.getWidth(), simage.getHeight());
219 for (int row=0 ; row<simage.getHeight() ; row++)
220 {
221 double ypos = ((double)aImg->bbox.y0) + ihscale * (double) row;
222 for (int col=0 ; col<simage.getWidth() ; col++)
223 {
224 //Get absolute X,Y position
225 double xpos = ((double)aImg->bbox.x0) + iwscale * (double)col;
226 NR::Point point(xpos, ypos);
227 point *= aImg->transform;
228 //point *= imgMat;
229 //point = desktop->doc2dt(point);
230 std::vector<SPShape *>::iterator iter;
231 //g_message("x:%f y:%f\n", point[0], point[1]);
232 bool weHaveAHit = false;
233 std::vector<NRArenaItem *>::iterator aIter;
234 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
235 {
236 NRArenaItem *arenaItem = *aIter;
237 NRArenaItemClass *arenaClass =
238 (NRArenaItemClass *) NR_OBJECT_GET_CLASS (arenaItem);
239 if (arenaClass->pick(arenaItem, point, 1.0f, 1))
240 {
241 weHaveAHit = true;
242 break;
243 }
244 }
246 if (weHaveAHit)
247 {
248 //g_message("hit!\n");
249 dumpMap->setPixelLong(dumpMap, col, row, 0L);
250 simage.setConfidence(col, row,
251 Siox::UNKNOWN_REGION_CONFIDENCE);
252 }
253 else
254 {
255 dumpMap->setPixelLong(dumpMap, col, row,
256 simage.getPixel(col, row));
257 simage.setConfidence(col, row,
258 Siox::CERTAIN_BACKGROUND_CONFIDENCE);
259 }
260 }
261 }
263 //dumpMap->writePPM(dumpMap, "siox1.ppm");
264 dumpMap->destroy(dumpMap);
266 //## ok we have our pixel buf
267 org::siox::Siox sengine;
268 org::siox::SioxImage result =
269 sengine.extractForeground(simage, 0xffffff);
270 if (!result.isValid())
271 {
272 g_warning(_("Invalid SIOX result"));
273 return NULL;
274 }
276 //result.writePPM("siox2.ppm");
278 /* Free Arena and ArenaItem */
279 /*
280 std::vector<NRArenaItem *>::iterator aIter;
281 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
282 {
283 NRArenaItem *arenaItem = *aIter;
284 nr_arena_item_unref(arenaItem);
285 }
286 nr_object_unref((NRObject *) arena);
287 */
289 GdkPixbuf *newPixbuf = result.getGdkPixbuf();
291 return newPixbuf;
292 }
295 /**
296 *
297 */
298 GdkPixbuf *
299 Tracer::getSelectedImage()
300 {
302 SPImage *img = getSelectedSPImage();
303 if (!img)
304 return NULL;
306 GdkPixbuf *pixbuf = img->pixbuf;
307 if (!pixbuf)
308 return NULL;
310 if (sioxEnabled)
311 {
312 GdkPixbuf *sioxPixbuf = sioxProcessImage(img, pixbuf);
313 if (!sioxPixbuf)
314 {
315 g_object_ref(pixbuf);
316 return pixbuf;
317 }
318 else
319 {
320 return sioxPixbuf;
321 }
322 }
323 else
324 {
325 g_object_ref(pixbuf);
326 return pixbuf;
327 }
329 }
333 //#########################################################################
334 //# T R A C E
335 //#########################################################################
337 /**
338 * Whether we want to enable SIOX subimage selection
339 */
340 void Tracer::enableSiox(bool enable)
341 {
342 sioxEnabled = enable;
343 }
346 /**
347 * Threaded method that does single bitmap--->path conversion
348 */
349 void Tracer::traceThread()
350 {
351 //## Remember. NEVER leave this method without setting
352 //## engine back to NULL
354 //## Prepare our kill flag. We will watch this later to
355 //## see if the main thread wants us to stop
356 keepGoing = true;
358 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
359 if (!desktop)
360 {
361 g_warning("Trace: No active desktop\n");
362 return;
363 }
365 Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
367 Inkscape::Selection *selection = sp_desktop_selection (desktop);
369 if (!SP_ACTIVE_DOCUMENT)
370 {
371 char *msg = _("Trace: No active document");
372 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
373 //g_warning(msg);
374 engine = NULL;
375 return;
376 }
377 SPDocument *doc = SP_ACTIVE_DOCUMENT;
378 sp_document_ensure_up_to_date(doc);
381 SPImage *img = getSelectedSPImage();
382 if (!img)
383 {
384 engine = NULL;
385 return;
386 }
388 GdkPixbuf *pixbuf = img->pixbuf;
389 g_object_ref(pixbuf);
391 pixbuf = sioxProcessImage(img, pixbuf);
393 if (!pixbuf)
394 {
395 char *msg = _("Trace: Image has no bitmap data");
396 msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
397 //g_warning(msg);
398 engine = NULL;
399 return;
400 }
402 int nrPaths;
403 TracingEngineResult *results = engine->trace(pixbuf, &nrPaths);
404 //printf("nrPaths:%d\n", nrPaths);
406 //### Check if we should stop
407 if (!keepGoing || !results || nrPaths<1)
408 {
409 engine = NULL;
410 return;
411 }
413 //### Get pointers to the <image> and its parent
414 Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->repr;
415 Inkscape::XML::Node *par = sp_repr_parent(imgRepr);
417 //### Get some information for the new transform()
418 double x = 0.0;
419 double y = 0.0;
420 double width = 0.0;
421 double height = 0.0;
422 double dval = 0.0;
424 if (sp_repr_get_double(imgRepr, "x", &dval))
425 x = dval;
426 if (sp_repr_get_double(imgRepr, "y", &dval))
427 y = dval;
429 if (sp_repr_get_double(imgRepr, "width", &dval))
430 width = dval;
431 if (sp_repr_get_double(imgRepr, "height", &dval))
432 height = dval;
434 NR::Matrix trans(NR::translate(x, y));
436 double iwidth = (double)gdk_pixbuf_get_width(pixbuf);
437 double iheight = (double)gdk_pixbuf_get_height(pixbuf);
439 double iwscale = width / iwidth;
440 double ihscale = height / iheight;
442 NR::Matrix scal(NR::scale(iwscale, ihscale));
444 //# Convolve scale, translation, and the original transform
445 NR::Matrix tf(scal);
446 tf *= trans;
447 tf *= img->transform;
450 //#OK. Now let's start making new nodes
452 Inkscape::XML::Node *groupRepr = NULL;
454 //# if more than 1, make a <g>roup of <path>s
455 if (nrPaths > 1)
456 {
457 groupRepr = sp_repr_new("svg:g");
458 par->addChild(groupRepr, imgRepr);
459 }
461 long totalNodeCount = 0L;
463 for (TracingEngineResult *result=results ;
464 result ; result=result->next)
465 {
466 totalNodeCount += result->getNodeCount();
468 Inkscape::XML::Node *pathRepr = sp_repr_new("svg:path");
469 pathRepr->setAttribute("style", result->getStyle());
470 pathRepr->setAttribute("d", result->getPathData());
472 if (nrPaths > 1)
473 groupRepr->addChild(pathRepr, NULL);
474 else
475 par->addChild(pathRepr, imgRepr);
477 //### Apply the transform from the image to the new shape
478 SPObject *reprobj = doc->getObjectByRepr(pathRepr);
479 if (reprobj)
480 {
481 SPItem *newItem = SP_ITEM(reprobj);
482 sp_item_write_transform(newItem, pathRepr, tf, NULL);
483 }
484 if (nrPaths == 1)
485 {
486 selection->clear();
487 selection->add(pathRepr);
488 }
489 Inkscape::GC::release(pathRepr);
490 }
492 //release our pixbuf
493 g_object_unref(pixbuf);
495 delete results;
497 // If we have a group, then focus on, then forget it
498 if (nrPaths > 1)
499 {
500 selection->clear();
501 selection->add(groupRepr);
502 Inkscape::GC::release(groupRepr);
503 }
505 //## inform the document, so we can undo
506 sp_document_done(doc);
508 engine = NULL;
510 char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
511 msgStack->flash(Inkscape::NORMAL_MESSAGE, msg);
512 g_free(msg);
514 }
520 /**
521 * Main tracing method
522 */
523 void Tracer::trace(TracingEngine *theEngine)
524 {
525 //Check if we are already running
526 if (engine)
527 return;
529 engine = theEngine;
531 #if HAVE_THREADS
532 //Ensure that thread support is running
533 if (!Glib::thread_supported())
534 Glib::thread_init();
536 //Create our thread and run it
537 Glib::Thread::create(
538 sigc::mem_fun(*this, &Tracer::traceThread), false);
539 #else
540 traceThread();
541 #endif
543 }
549 /**
550 * Abort the thread that is executing trace()
551 */
552 void Tracer::abort()
553 {
555 //## Inform Trace's working thread
556 keepGoing = false;
558 if (engine)
559 {
560 engine->abort();
561 }
563 }
567 } // namespace Trace
569 } // namespace Inkscape
572 //#########################################################################
573 //# E N D O F F I L E
574 //#########################################################################