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"
36 namespace Inkscape {
38 namespace Trace {
45 /**
46 *
47 */
48 SPImage *
49 Tracer::getSelectedSPImage()
50 {
51 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
52 if (!desktop)
53 {
54 g_warning("Trace: No active desktop\n");
55 return NULL;
56 }
58 Inkscape::Selection *sel = SP_DT_SELECTION(desktop);
59 if (!sel)
60 {
61 char *msg = _("Select an <b>image</b> to trace");
62 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
63 //g_warning(msg);
64 return NULL;
65 }
67 if (sioxEnabled)
68 {
69 SPImage *img = NULL;
70 GSList const *list = sel->itemList();
71 std::vector<SPItem *> items;
72 sioxShapes.clear();
74 /*
75 First, things are selected top-to-bottom, so we need to invert
76 them as bottom-to-top so that we can discover the image and any
77 SPItems above it
78 */
79 for ( ; list ; list=list->next)
80 {
81 if (!SP_IS_ITEM(list->data))
82 {
83 continue;
84 }
85 SPItem *item = SP_ITEM(list->data);
86 items.insert(items.begin(), item);
87 }
88 std::vector<SPItem *>::iterator iter;
89 for (iter = items.begin() ; iter!= items.end() ; iter++)
90 {
91 SPItem *item = *iter;
92 if (SP_IS_IMAGE(item))
93 {
94 if (img) //we want only one
95 {
96 char *msg = _("Select only one <b>image</b> to trace");
97 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
98 return NULL;
99 }
100 img = SP_IMAGE(item);
101 }
102 else // if (img) //# items -after- the image in tree (above it in Z)
103 {
104 if (SP_IS_SHAPE(item))
105 {
106 SPShape *shape = SP_SHAPE(item);
107 sioxShapes.push_back(shape);
108 }
109 }
110 }
112 if (!img || sioxShapes.size() < 1)
113 {
114 char *msg = _("Select one image and one or more shapes above it");
115 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
116 return NULL;
117 }
118 return img;
119 }
120 else
121 //### SIOX not enabled. We want exactly one image selected
122 {
123 SPItem *item = sel->singleItem();
124 if (!item)
125 {
126 char *msg = _("Select an <b>image</b> to trace"); //same as above
127 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
128 //g_warning(msg);
129 return NULL;
130 }
132 if (!SP_IS_IMAGE(item))
133 {
134 char *msg = _("Select an <b>image</b> to trace");
135 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
136 //g_warning(msg);
137 return NULL;
138 }
140 SPImage *img = SP_IMAGE(item);
142 return img;
143 }
145 }
149 /**
150 *
151 */
152 GdkPixbuf *
153 Tracer::getSelectedImage()
154 {
156 SPImage *img = getSelectedSPImage();
157 if (!img)
158 return NULL;
160 GdkPixbuf *pixbuf = img->pixbuf;
162 return pixbuf;
164 }
167 GdkPixbuf *
168 Tracer::sioxProcessImage(SPImage *img, GdkPixbuf *origPixbuf)
169 {
171 //Convert from gdk, so a format we know. By design, the pixel
172 //format in PackedPixelMap is identical to what is needed by SIOX
173 PackedPixelMap *ppMap = gdkPixbufToPackedPixelMap(origPixbuf);
174 //We need to create two things:
175 // 1. An array of long pixel values of ARGB
176 // 2. A matching array of per-pixel float 'confidence' values
177 unsigned long *imgBuf = ppMap->pixels;
178 float *confidenceMatrix = new float[ppMap->width * ppMap->height];
180 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
181 if (!desktop)
182 {
183 g_warning("Trace: No active desktop\n");
184 return NULL;
185 }
187 Inkscape::Selection *sel = SP_DT_SELECTION(desktop);
188 if (!sel)
189 {
190 char *msg = _("Select an <b>image</b> to trace");
191 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
192 //g_warning(msg);
193 return NULL;
194 }
196 NRArenaItem *aImg = sp_item_get_arenaitem(img, desktop->dkey);
197 //g_message("img: %d %d %d %d\n", aImg->bbox.x0, aImg->bbox.y0,
198 // aImg->bbox.x1, aImg->bbox.y1);
200 double width = (double)(aImg->bbox.x1 - aImg->bbox.x0);
201 double height = (double)(aImg->bbox.y1 - aImg->bbox.y0);
203 double iwidth = (double)ppMap->width;
204 double iheight = (double)ppMap->height;
206 double iwscale = width / iwidth;
207 double ihscale = height / iheight;
209 unsigned long cmIndex = 0;
212 std::vector<NRArenaItem *> arenaItems;
213 std::vector<SPShape *>::iterator iter;
214 for (iter = sioxShapes.begin() ; iter!=sioxShapes.end() ; iter++)
215 {
216 SPItem *item = *iter;
217 //### Create ArenaItems and set transform
218 NRArenaItem *aItem = sp_item_get_arenaitem(item, desktop->dkey);
220 //nr_arena_item_set_transform(aItem, item->transform);
221 //g_message("%d %d %d %d\n", aItem->bbox.x0, aItem->bbox.y0,
222 // aItem->bbox.x1, aItem->bbox.y1);
223 arenaItems.push_back(aItem);
224 }
225 //g_message("%d arena items\n", arenaItems.size());
227 PackedPixelMap *dumpMap = PackedPixelMapCreate(ppMap->width, ppMap->height);
229 for (int row=0 ; row<ppMap->height ; row++)
230 {
231 double ypos = ((double)aImg->bbox.y0) + ihscale * (double) row;
232 for (int col=0 ; col<ppMap->width ; col++)
233 {
234 //Get absolute X,Y position
235 double xpos = ((double)aImg->bbox.x0) + iwscale * (double)col;
236 NR::Point point(xpos, ypos);
237 point *= aImg->transform;
238 //point *= imgMat;
239 //point = desktop->doc2dt(point);
240 std::vector<SPShape *>::iterator iter;
241 //g_message("x:%f y:%f\n", point[0], point[1]);
242 bool weHaveAHit = false;
243 std::vector<NRArenaItem *>::iterator aIter;
244 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
245 {
246 NRArenaItem *arenaItem = *aIter;
247 NRArenaItemClass *arenaClass =
248 (NRArenaItemClass *) NR_OBJECT_GET_CLASS (arenaItem);
249 if (arenaClass->pick(arenaItem, point, 1.0f, 1))
250 {
251 weHaveAHit = true;
252 break;
253 }
254 }
256 if (weHaveAHit)
257 {
258 //g_message("hit!\n");
259 dumpMap->setPixelLong(dumpMap, col, row, 0L);
260 confidenceMatrix[cmIndex] =
261 org::siox::SioxSegmentator::CERTAIN_FOREGROUND_CONFIDENCE;
262 }
263 else
264 {
265 dumpMap->setPixelLong(dumpMap, col, row,
266 ppMap->getPixel(ppMap, col, row));
267 confidenceMatrix[cmIndex] =
268 org::siox::SioxSegmentator::CERTAIN_BACKGROUND_CONFIDENCE;
269 }
270 cmIndex++;
271 }
272 }
274 //## ok we have our pixel buf
275 org::siox::SioxSegmentator ss(ppMap->width, ppMap->height, NULL, 0);
276 ss.segmentate(imgBuf, confidenceMatrix, 0, 0.0);
278 dumpMap->writePPM(dumpMap, "siox.ppm");
279 dumpMap->destroy(dumpMap);
281 /* Free Arena and ArenaItem */
282 /*
283 std::vector<NRArenaItem *>::iterator aIter;
284 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
285 {
286 NRArenaItem *arenaItem = *aIter;
287 nr_arena_item_unref(arenaItem);
288 }
289 nr_object_unref((NRObject *) arena);
290 */
292 GdkPixbuf *newPixbuf = packedPixelMapToGdkPixbuf(ppMap);
293 ppMap->destroy(ppMap);
294 delete confidenceMatrix;
296 return newPixbuf;
297 }
302 //#########################################################################
303 //# T R A C E
304 //#########################################################################
306 /**
307 * Whether we want to enable SIOX subimage selection
308 */
309 void Tracer::enableSiox(bool enable)
310 {
311 sioxEnabled = enable;
312 }
315 /**
316 * Threaded method that does single bitmap--->path conversion
317 */
318 void Tracer::traceThread()
319 {
320 //## Remember. NEVER leave this method without setting
321 //## engine back to NULL
323 //## Prepare our kill flag. We will watch this later to
324 //## see if the main thread wants us to stop
325 keepGoing = true;
327 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
328 if (!desktop)
329 {
330 g_warning("Trace: No active desktop\n");
331 return;
332 }
334 Inkscape::Selection *selection = SP_DT_SELECTION (desktop);
336 if (!SP_ACTIVE_DOCUMENT)
337 {
338 char *msg = _("Trace: No active document");
339 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
340 //g_warning(msg);
341 engine = NULL;
342 return;
343 }
344 SPDocument *doc = SP_ACTIVE_DOCUMENT;
345 sp_document_ensure_up_to_date(doc);
348 SPImage *img = getSelectedSPImage();
349 if (!img)
350 {
351 engine = NULL;
352 return;
353 }
355 GdkPixbuf *pixbuf = img->pixbuf;
357 if (!pixbuf)
358 {
359 char *msg = _("Trace: Image has no bitmap data");
360 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
361 //g_warning(msg);
362 engine = NULL;
363 return;
364 }
366 //## SIOX pre-processing to get a smart subimage of the pixbuf.
367 //## This is done before any other filters
368 if (sioxEnabled)
369 {
370 /*
371 Ok, we have requested siox, and getSelectedSPImage() has found a single
372 bitmap and one or more SPItems above it. Now what we need to do is create
373 a siox-segmented subimage pixbuf. We not need alter 'img' at all, since this
374 pixbuf will be the same dimensions and at the same location.
375 Remember to free this new pixbuf later.
376 */
377 pixbuf = sioxProcessImage(img, pixbuf);
378 }
380 int nrPaths;
381 TracingEngineResult *results = engine->trace(pixbuf, &nrPaths);
382 //printf("nrPaths:%d\n", nrPaths);
384 //### Check if we should stop
385 if (!keepGoing || !results || nrPaths<1)
386 {
387 engine = NULL;
388 return;
389 }
391 //### Get pointers to the <image> and its parent
392 Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->repr;
393 Inkscape::XML::Node *par = sp_repr_parent(imgRepr);
395 //### Get some information for the new transform()
396 double x = 0.0;
397 double y = 0.0;
398 double width = 0.0;
399 double height = 0.0;
400 double dval = 0.0;
402 if (sp_repr_get_double(imgRepr, "x", &dval))
403 x = dval;
404 if (sp_repr_get_double(imgRepr, "y", &dval))
405 y = dval;
407 if (sp_repr_get_double(imgRepr, "width", &dval))
408 width = dval;
409 if (sp_repr_get_double(imgRepr, "height", &dval))
410 height = dval;
412 NR::Matrix trans(NR::translate(x, y));
414 double iwidth = (double)gdk_pixbuf_get_width(pixbuf);
415 double iheight = (double)gdk_pixbuf_get_height(pixbuf);
417 double iwscale = width / iwidth;
418 double ihscale = height / iheight;
420 NR::Matrix scal(NR::scale(iwscale, ihscale));
422 //# Convolve scale, translation, and the original transform
423 NR::Matrix tf(scal);
424 tf *= trans;
425 tf *= img->transform;
428 //#OK. Now let's start making new nodes
430 Inkscape::XML::Node *groupRepr = NULL;
432 //# if more than 1, make a <g>roup of <path>s
433 if (nrPaths > 1)
434 {
435 groupRepr = sp_repr_new("svg:g");
436 par->addChild(groupRepr, imgRepr);
437 }
439 long totalNodeCount = 0L;
441 for (TracingEngineResult *result=results ;
442 result ; result=result->next)
443 {
444 totalNodeCount += result->getNodeCount();
446 Inkscape::XML::Node *pathRepr = sp_repr_new("svg:path");
447 pathRepr->setAttribute("style", result->getStyle());
448 pathRepr->setAttribute("d", result->getPathData());
450 if (nrPaths > 1)
451 groupRepr->addChild(pathRepr, NULL);
452 else
453 par->addChild(pathRepr, imgRepr);
455 //### Apply the transform from the image to the new shape
456 SPObject *reprobj = doc->getObjectByRepr(pathRepr);
457 if (reprobj)
458 {
459 SPItem *newItem = SP_ITEM(reprobj);
460 sp_item_write_transform(newItem, pathRepr, tf, NULL);
461 }
462 if (nrPaths == 1)
463 {
464 selection->clear();
465 selection->add(pathRepr);
466 }
467 Inkscape::GC::release(pathRepr);
468 }
470 //did we allocate a pixbuf copy?
471 if (sioxEnabled)
472 {
473 g_free(pixbuf);
474 }
476 delete results;
478 // If we have a group, then focus on, then forget it
479 if (nrPaths > 1)
480 {
481 selection->clear();
482 selection->add(groupRepr);
483 Inkscape::GC::release(groupRepr);
484 }
486 //## inform the document, so we can undo
487 sp_document_done(doc);
489 engine = NULL;
491 char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
492 SP_DT_MSGSTACK(desktop)->flash(Inkscape::NORMAL_MESSAGE, msg);
493 g_free(msg);
495 }
501 /**
502 * Main tracing method
503 */
504 void Tracer::trace(TracingEngine *theEngine)
505 {
506 //Check if we are already running
507 if (engine)
508 return;
510 engine = theEngine;
512 #if HAVE_THREADS
513 //Ensure that thread support is running
514 if (!Glib::thread_supported())
515 Glib::thread_init();
517 //Create our thread and run it
518 Glib::Thread::create(
519 sigc::mem_fun(*this, &Tracer::traceThread), false);
520 #else
521 traceThread();
522 #endif
524 }
530 /**
531 * Abort the thread that is executing trace()
532 */
533 void Tracer::abort()
534 {
536 //## Inform Trace's working thread
537 keepGoing = false;
539 if (engine)
540 {
541 engine->abort();
542 }
544 }
548 } // namespace Trace
550 } // namespace Inkscape
553 //#########################################################################
554 //# E N D O F F I L E
555 //#########################################################################