04f1cbed0c9e1696e6ca527a1d8c6615a5a692f5
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 Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->repr;
198 NRArenaItem *aImg = sp_item_get_arenaitem(img, desktop->dkey);
199 //g_message("img: %d %d %d %d\n", aImg->bbox.x0, aImg->bbox.y0,
200 // aImg->bbox.x1, aImg->bbox.y1);
202 double width = (double)(aImg->bbox.x1 - aImg->bbox.x0);
203 double height = (double)(aImg->bbox.y1 - aImg->bbox.y0);
205 double iwidth = (double)ppMap->width;
206 double iheight = (double)ppMap->height;
208 double iwscale = width / iwidth;
209 double ihscale = height / iheight;
211 unsigned long cmIndex = 0;
214 std::vector<NRArenaItem *> arenaItems;
215 std::vector<SPShape *>::iterator iter;
216 for (iter = sioxShapes.begin() ; iter!=sioxShapes.end() ; iter++)
217 {
218 SPItem *item = *iter;
219 //### Create ArenaItems and set transform
220 NRArenaItem *aItem = sp_item_get_arenaitem(item, desktop->dkey);
222 //nr_arena_item_set_transform(aItem, item->transform);
223 //g_message("%d %d %d %d\n", aItem->bbox.x0, aItem->bbox.y0,
224 // aItem->bbox.x1, aItem->bbox.y1);
225 arenaItems.push_back(aItem);
226 }
227 //g_message("%d arena items\n", arenaItems.size());
229 PackedPixelMap *dumpMap = PackedPixelMapCreate(ppMap->width, ppMap->height);
231 for (int row=0 ; row<ppMap->height ; row++)
232 {
233 double ypos = ((double)aImg->bbox.y0) + ihscale * (double) row;
234 for (int col=0 ; col<ppMap->width ; col++)
235 {
236 //Get absolute X,Y position
237 double xpos = ((double)aImg->bbox.x0) + iwscale * (double)col;
238 NR::Point point(xpos, ypos);
239 point *= aImg->transform;
240 //point *= imgMat;
241 //point = desktop->doc2dt(point);
242 std::vector<SPShape *>::iterator iter;
243 //g_message("x:%f y:%f\n", point[0], point[1]);
244 bool weHaveAHit = false;
245 std::vector<NRArenaItem *>::iterator aIter;
246 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
247 {
248 NRArenaItem *arenaItem = *aIter;
249 NRArenaItemClass *arenaClass =
250 (NRArenaItemClass *) NR_OBJECT_GET_CLASS (arenaItem);
251 if (arenaClass->pick(arenaItem, point, 1.0f, 1))
252 {
253 weHaveAHit = true;
254 break;
255 }
256 }
258 if (weHaveAHit)
259 {
260 //g_message("hit!\n");
261 dumpMap->setPixelLong(dumpMap, col, row, 0L);
262 confidenceMatrix[cmIndex] =
263 org::siox::SioxSegmentator::CERTAIN_FOREGROUND_CONFIDENCE;
264 }
265 else
266 {
267 dumpMap->setPixelLong(dumpMap, col, row,
268 ppMap->getPixel(ppMap, col, row));
269 confidenceMatrix[cmIndex] =
270 org::siox::SioxSegmentator::CERTAIN_BACKGROUND_CONFIDENCE;
271 }
272 cmIndex++;
273 }
274 }
276 //## ok we have our pixel buf
277 org::siox::SioxSegmentator ss(ppMap->width, ppMap->height, NULL, 0);
278 ss.segmentate(imgBuf, confidenceMatrix, 0, 0.0);
280 dumpMap->writePPM(dumpMap, "siox.ppm");
281 dumpMap->destroy(dumpMap);
283 /* Free Arena and ArenaItem */
284 /*
285 std::vector<NRArenaItem *>::iterator aIter;
286 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
287 {
288 NRArenaItem *arenaItem = *aIter;
289 nr_arena_item_unref(arenaItem);
290 }
291 nr_object_unref((NRObject *) arena);
292 */
294 GdkPixbuf *newPixbuf = packedPixelMapToGdkPixbuf(ppMap);
295 ppMap->destroy(ppMap);
296 delete confidenceMatrix;
298 return newPixbuf;
299 }
304 //#########################################################################
305 //# T R A C E
306 //#########################################################################
308 /**
309 * Whether we want to enable SIOX subimage selection
310 */
311 void Tracer::enableSiox(bool enable)
312 {
313 sioxEnabled = enable;
314 }
317 /**
318 * Threaded method that does single bitmap--->path conversion
319 */
320 void Tracer::traceThread()
321 {
322 //## Remember. NEVER leave this method without setting
323 //## engine back to NULL
325 //## Prepare our kill flag. We will watch this later to
326 //## see if the main thread wants us to stop
327 keepGoing = true;
329 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
330 if (!desktop)
331 {
332 g_warning("Trace: No active desktop\n");
333 return;
334 }
336 Inkscape::Selection *selection = SP_DT_SELECTION (desktop);
338 if (!SP_ACTIVE_DOCUMENT)
339 {
340 char *msg = _("Trace: No active document");
341 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
342 //g_warning(msg);
343 engine = NULL;
344 return;
345 }
346 SPDocument *doc = SP_ACTIVE_DOCUMENT;
347 sp_document_ensure_up_to_date(doc);
350 SPImage *img = getSelectedSPImage();
351 if (!img)
352 {
353 engine = NULL;
354 return;
355 }
357 GdkPixbuf *pixbuf = img->pixbuf;
359 if (!pixbuf)
360 {
361 char *msg = _("Trace: Image has no bitmap data");
362 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
363 //g_warning(msg);
364 engine = NULL;
365 return;
366 }
368 //## SIOX pre-processing to get a smart subimage of the pixbuf.
369 //## This is done before any other filters
370 if (sioxEnabled)
371 {
372 /*
373 Ok, we have requested siox, and getSelectedSPImage() has found a single
374 bitmap and one or more SPItems above it. Now what we need to do is create
375 a siox-segmented subimage pixbuf. We not need alter 'img' at all, since this
376 pixbuf will be the same dimensions and at the same location.
377 Remember to free this new pixbuf later.
378 */
379 pixbuf = sioxProcessImage(img, pixbuf);
380 }
382 int nrPaths;
383 TracingEngineResult *results = engine->trace(pixbuf, &nrPaths);
384 //printf("nrPaths:%d\n", nrPaths);
386 //### Check if we should stop
387 if (!keepGoing || !results || nrPaths<1)
388 {
389 engine = NULL;
390 return;
391 }
393 //### Get pointers to the <image> and its parent
394 Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->repr;
395 Inkscape::XML::Node *par = sp_repr_parent(imgRepr);
397 //### Get some information for the new transform()
398 double x = 0.0;
399 double y = 0.0;
400 double width = 0.0;
401 double height = 0.0;
402 double dval = 0.0;
404 if (sp_repr_get_double(imgRepr, "x", &dval))
405 x = dval;
406 if (sp_repr_get_double(imgRepr, "y", &dval))
407 y = dval;
409 if (sp_repr_get_double(imgRepr, "width", &dval))
410 width = dval;
411 if (sp_repr_get_double(imgRepr, "height", &dval))
412 height = dval;
414 NR::Matrix trans(NR::translate(x, y));
416 double iwidth = (double)gdk_pixbuf_get_width(pixbuf);
417 double iheight = (double)gdk_pixbuf_get_height(pixbuf);
419 double iwscale = width / iwidth;
420 double ihscale = height / iheight;
422 NR::Matrix scal(NR::scale(iwscale, ihscale));
424 //# Convolve scale, translation, and the original transform
425 NR::Matrix tf(scal);
426 tf *= trans;
427 tf *= img->transform;
430 //#OK. Now let's start making new nodes
432 Inkscape::XML::Node *groupRepr = NULL;
434 //# if more than 1, make a <g>roup of <path>s
435 if (nrPaths > 1)
436 {
437 groupRepr = sp_repr_new("svg:g");
438 par->addChild(groupRepr, imgRepr);
439 }
441 long totalNodeCount = 0L;
443 for (TracingEngineResult *result=results ;
444 result ; result=result->next)
445 {
446 totalNodeCount += result->getNodeCount();
448 Inkscape::XML::Node *pathRepr = sp_repr_new("svg:path");
449 pathRepr->setAttribute("style", result->getStyle());
450 pathRepr->setAttribute("d", result->getPathData());
452 if (nrPaths > 1)
453 groupRepr->addChild(pathRepr, NULL);
454 else
455 par->addChild(pathRepr, imgRepr);
457 //### Apply the transform from the image to the new shape
458 SPObject *reprobj = doc->getObjectByRepr(pathRepr);
459 if (reprobj)
460 {
461 SPItem *newItem = SP_ITEM(reprobj);
462 sp_item_write_transform(newItem, pathRepr, tf, NULL);
463 }
464 if (nrPaths == 1)
465 {
466 selection->clear();
467 selection->add(pathRepr);
468 }
469 Inkscape::GC::release(pathRepr);
470 }
472 //did we allocate a pixbuf copy?
473 if (sioxEnabled)
474 {
475 g_free(pixbuf);
476 }
478 delete results;
480 // If we have a group, then focus on, then forget it
481 if (nrPaths > 1)
482 {
483 selection->clear();
484 selection->add(groupRepr);
485 Inkscape::GC::release(groupRepr);
486 }
488 //## inform the document, so we can undo
489 sp_document_done(doc);
491 engine = NULL;
493 char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
494 SP_DT_MSGSTACK(desktop)->flash(Inkscape::NORMAL_MESSAGE, msg);
495 g_free(msg);
497 }
503 /**
504 * Main tracing method
505 */
506 void Tracer::trace(TracingEngine *theEngine)
507 {
508 //Check if we are already running
509 if (engine)
510 return;
512 engine = theEngine;
514 #if HAVE_THREADS
515 //Ensure that thread support is running
516 if (!Glib::thread_supported())
517 Glib::thread_init();
519 //Create our thread and run it
520 Glib::Thread::create(
521 sigc::mem_fun(*this, &Tracer::traceThread), false);
522 #else
523 traceThread();
524 #endif
526 }
532 /**
533 * Abort the thread that is executing trace()
534 */
535 void Tracer::abort()
536 {
538 //## Inform Trace's working thread
539 keepGoing = false;
541 if (engine)
542 {
543 engine->abort();
544 }
546 }
550 } // namespace Trace
552 } // namespace Inkscape
555 //#########################################################################
556 //# E N D O F F I L E
557 //#########################################################################