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 {
42 /*
43 static PackedPixelMap *
44 renderToPackedPixelMap(SPDocument *doc, std::vector<SPItem *> items)
45 {
47 double minX = 1.0e6;
48 double minY = 1.0e6;
49 double maxX = -1.0e6;
50 double maxY = -1.0e6;
51 for (int i=0 ; i<items.size() ; i++)
52 {
53 SPItem *item = items[i];
54 if (item->bbox.x0 < minX)
55 minX = item->bbox.x0;
56 if (item->bbox.y0 < minY)
57 minY = item->bbox.y0;
58 if (item->bbox.x1 > maxX)
59 maxX = item->bbox.x1;
60 if (item->bbox.y1 > maxY)
61 maxY = item->bbox.x1;
62 }
64 double dwidth = maxX - minX;
65 double dheight = maxY - minY;
67 NRRectL bbox;
68 bbox.x0 = 0;
69 bbox.y0 = 0;
70 bbox.x1 = 256;
71 bbox.y1 = (int) ( 256.0 * dwidth / dheight );
74 NRArena *arena = NRArena::create();
75 unsigned dkey = sp_item_display_key_new(1);
77 // Create ArenaItems and set transform
78 NRArenaItem *root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)),
79 arena, dkey, SP_ITEM_SHOW_DISPLAY);
80 nr_arena_item_set_transform(root, NR::Matrix(&affine));
82 NRPixBlock pb;
83 nr_pixblock_setup(&pb, NR_PIXBLOCK_MODE_R8G8B8A8N,
84 minX, minY, maxX, maxY, true);
86 //fill in background
87 for (int row = 0; row < bbox.y1; row++)
88 {
89 guchar *p = NR_PIXBLOCK_PX(&pb) + row * bbox.x1;
90 for (int col = 0; col < bbox.x1; col++)
91 {
92 *p++ = ebp->r;
93 *p++ = ebp->g;
94 *p++ = ebp->b;
95 *p++ = ebp->a;
96 }
97 }
99 // Render
100 nr_arena_item_invoke_render(root, &bbox, &pb, 0);
102 for (int r = 0; r < num_rows; r++) {
103 rows[r] = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
104 }
106 //## Make an packed pixel map
107 PackedPixelMap *ppMap = PackedPixelMapCreate(bbox.x1, bbox.y1);
108 for (int row = 0; row < bbox.y1; row++)
109 {
110 guchar *p = NR_PIXBLOCK_PX(&pb) + row * bbox.x1;
111 for (int col = 0; col < bbox.x1; col++)
112 {
113 int r = *p++;
114 int g = *p++;
115 int b = *p++;
116 int a = *p++;
117 ppMap->setPixelValue(ppMap, col, row, r, g, b);
118 }
119 }
121 //## Free allocated things
122 nr_pixblock_release(&pb);
123 nr_arena_item_unref(root);
124 nr_object_unref((NRObject *) arena);
127 return ppMap;
128 }
129 */
134 /**
135 *
136 */
137 SPImage *
138 Tracer::getSelectedSPImage()
139 {
140 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
141 if (!desktop)
142 {
143 g_warning("Trace: No active desktop\n");
144 return NULL;
145 }
147 Inkscape::Selection *sel = SP_DT_SELECTION(desktop);
148 if (!sel)
149 {
150 char *msg = _("Select an <b>image</b> to trace");
151 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
152 //g_warning(msg);
153 return NULL;
154 }
156 if (sioxEnabled)
157 {
158 SPImage *img = NULL;
159 GSList const *list = sel->itemList();
160 std::vector<SPItem *> items;
161 sioxShapes.clear();
163 /*
164 First, things are selected top-to-bottom, so we need to invert
165 them as bottom-to-top so that we can discover the image and any
166 SPItems above it
167 */
168 for ( ; list ; list=list->next)
169 {
170 if (!SP_IS_ITEM(list->data))
171 {
172 continue;
173 }
174 SPItem *item = SP_ITEM(list->data);
175 items.insert(items.begin(), item);
176 }
177 std::vector<SPItem *>::iterator iter;
178 for (iter = items.begin() ; iter!= items.end() ; iter++)
179 {
180 SPItem *item = *iter;
181 if (SP_IS_IMAGE(item))
182 {
183 if (img) //we want only one
184 {
185 char *msg = _("Select only one <b>image</b> to trace");
186 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
187 return NULL;
188 }
189 img = SP_IMAGE(item);
190 }
191 else // if (img) //# items -after- the image in tree (above it in Z)
192 {
193 if (SP_IS_SHAPE(item))
194 {
195 SPShape *shape = SP_SHAPE(item);
196 sioxShapes.push_back(shape);
197 }
198 }
199 }
200 if (!img || sioxShapes.size() < 1)
201 {
202 char *msg = _("Select one image and one or more shapes above it");
203 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
204 return NULL;
205 }
206 return img;
207 }
208 else
209 //### SIOX not enabled. We want exactly one image selected
210 {
211 SPItem *item = sel->singleItem();
212 if (!item)
213 {
214 char *msg = _("Select an <b>image</b> to trace"); //same as above
215 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
216 //g_warning(msg);
217 return NULL;
218 }
220 if (!SP_IS_IMAGE(item))
221 {
222 char *msg = _("Select an <b>image</b> to trace");
223 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
224 //g_warning(msg);
225 return NULL;
226 }
228 SPImage *img = SP_IMAGE(item);
230 return img;
231 }
233 }
237 /**
238 *
239 */
240 GdkPixbuf *
241 Tracer::getSelectedImage()
242 {
244 SPImage *img = getSelectedSPImage();
245 if (!img)
246 return NULL;
248 GdkPixbuf *pixbuf = img->pixbuf;
250 return pixbuf;
252 }
255 GdkPixbuf *
256 Tracer::sioxProcessImage(SPImage *img, GdkPixbuf *origPixbuf)
257 {
259 //Convert from gdk, so a format we know. By design, the pixel
260 //format in PackedPixelMap is identical to what is needed by SIOX
261 PackedPixelMap *ppMap = gdkPixbufToPackedPixelMap(origPixbuf);
262 //We need to create two things:
263 // 1. An array of long pixel values of ARGB
264 // 2. A matching array of per-pixel float 'confidence' values
265 unsigned long *imgBuf = ppMap->pixels;
266 float *confidenceMatrix = new float[ppMap->width * ppMap->height];
268 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
269 if (!desktop)
270 {
271 g_warning("Trace: No active desktop\n");
272 return NULL;
273 }
275 Inkscape::Selection *sel = SP_DT_SELECTION(desktop);
276 if (!sel)
277 {
278 char *msg = _("Select an <b>image</b> to trace");
279 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
280 //g_warning(msg);
281 return NULL;
282 }
284 Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->repr;
285 /*
286 //## Make a Rect overlaying the image
287 Inkscape::XML::Node *parent = imgRepr->parent();
289 Inkscape::XML::Node *rect = sp_repr_new("svg:rect");
290 Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attr =
291 imgRepr->attributeList();
292 for ( ; attr ; attr++)
293 {
294 rect->setAttribute(g_quark_to_string(attr->key), attr->value, false);
295 }
298 //Inkscape::XML::Node *rect = imgRepr->duplicate();
299 parent->appendChild(rect);
300 Inkscape::GC::release(rect);
301 */
303 //### <image> element
304 double x = 0.0;
305 double y = 0.0;
306 double width = 0.0;
307 double height = 0.0;
308 double dval = 0.0;
310 if (sp_repr_get_double(imgRepr, "x", &dval))
311 x = dval;
312 if (sp_repr_get_double(imgRepr, "y", &dval))
313 y = dval;
315 if (sp_repr_get_double(imgRepr, "width", &dval))
316 width = dval;
317 if (sp_repr_get_double(imgRepr, "height", &dval))
318 height = dval;
320 double iwidth = (double)ppMap->width;
321 double iheight = (double)ppMap->height;
323 double iwscale = width / iwidth;
324 double ihscale = height / iheight;
326 unsigned long cmIndex = 0;
328 /* Create new arena */
329 NRArena *arena = NRArena::create();
330 unsigned dkey = sp_item_display_key_new(1);
332 std::vector<NRArenaItem *> arenaItems;
333 std::vector<SPShape *>::iterator iter;
334 for (iter = sioxShapes.begin() ; iter!=sioxShapes.end() ; iter++)
335 {
336 /* Create ArenaItems and set transform */
337 NRArenaItem *aItem =
338 sp_item_invoke_show(*iter,
339 arena, dkey, SP_ITEM_SHOW_DISPLAY);
340 nr_arena_item_set_transform(aItem, img->transform);
341 arenaItems.push_back(aItem);
342 }
344 PackedPixelMap *dumpMap = PackedPixelMapCreate(ppMap->width, ppMap->height);
346 for (int row=0 ; row<ppMap->height ; row++)
347 {
348 double ypos = y + ihscale * (double) row;
349 for (int col=0 ; col<ppMap->width ; col++)
350 {
351 //Get absolute X,Y position
352 double xpos = x + iwscale * (double)col;
353 NR::Point point(xpos, ypos);
354 point *= img->transform;
355 point = desktop->doc2dt(point);
356 std::vector<SPShape *>::iterator iter;
357 //g_message("x:%f y:%f\n", point[0], point[1]);
358 bool weHaveAHit = false;
359 std::vector<NRArenaItem *>::iterator aIter;
360 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
361 {
362 NRArenaItem *arenaItem = *aIter;
363 NRArenaItemClass *arenaClass =
364 (NRArenaItemClass *) NR_OBJECT_GET_CLASS (arenaItem);
365 if (arenaClass && arenaClass->pick)
366 {
367 if (arenaClass->pick(arenaItem, point, 0.0f, 0))
368 {
369 weHaveAHit = true;
370 break;
371 }
372 }
373 }
375 if (weHaveAHit)
376 {
377 //g_message("hit!\n");
378 dumpMap->setPixelLong(dumpMap, col, row, 0L);
379 confidenceMatrix[cmIndex] =
380 org::siox::SioxSegmentator::CERTAIN_FOREGROUND_CONFIDENCE;
381 }
382 else
383 {
384 dumpMap->setPixelLong(dumpMap, col, row,
385 ppMap->getPixel(ppMap, col, row));
386 confidenceMatrix[cmIndex] =
387 org::siox::SioxSegmentator::CERTAIN_BACKGROUND_CONFIDENCE;
388 }
389 cmIndex++;
390 }
391 }
393 //## ok we have our pixel buf
394 org::siox::SioxSegmentator ss(ppMap->width, ppMap->height, NULL, 0);
395 ss.segmentate(imgBuf, confidenceMatrix, 0, 0.0);
397 dumpMap->writePPM(dumpMap, "siox.ppm");
398 dumpMap->destroy(dumpMap);
400 /* Free Arena and ArenaItem */
401 std::vector<NRArenaItem *>::iterator aIter;
402 for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
403 {
404 NRArenaItem *arenaItem = *aIter;
405 nr_arena_item_unref(arenaItem);
406 }
407 nr_object_unref((NRObject *) arena);
410 GdkPixbuf *newPixbuf = packedPixelMapToGdkPixbuf(ppMap);
411 ppMap->destroy(ppMap);
412 delete confidenceMatrix;
414 return newPixbuf;
415 }
420 //#########################################################################
421 //# T R A C E
422 //#########################################################################
424 /**
425 * Whether we want to enable SIOX subimage selection
426 */
427 void Tracer::enableSiox(bool enable)
428 {
429 sioxEnabled = enable;
430 }
433 /**
434 * Threaded method that does single bitmap--->path conversion
435 */
436 void Tracer::traceThread()
437 {
438 //## Remember. NEVER leave this method without setting
439 //## engine back to NULL
441 //## Prepare our kill flag. We will watch this later to
442 //## see if the main thread wants us to stop
443 keepGoing = true;
445 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
446 if (!desktop)
447 {
448 g_warning("Trace: No active desktop\n");
449 return;
450 }
452 Inkscape::Selection *selection = SP_DT_SELECTION (desktop);
454 if (!SP_ACTIVE_DOCUMENT)
455 {
456 char *msg = _("Trace: No active document");
457 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
458 //g_warning(msg);
459 engine = NULL;
460 return;
461 }
462 SPDocument *doc = SP_ACTIVE_DOCUMENT;
463 sp_document_ensure_up_to_date(doc);
466 SPImage *img = getSelectedSPImage();
467 if (!img)
468 {
469 engine = NULL;
470 return;
471 }
473 GdkPixbuf *pixbuf = img->pixbuf;
475 if (!pixbuf)
476 {
477 char *msg = _("Trace: Image has no bitmap data");
478 SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
479 //g_warning(msg);
480 engine = NULL;
481 return;
482 }
484 //## SIOX pre-processing to get a smart subimage of the pixbuf.
485 //## This is done before any other filters
486 if (sioxEnabled)
487 {
488 /*
489 Ok, we have requested siox, and getSelectedSPImage() has found a single
490 bitmap and one or more SPItems above it. Now what we need to do is create
491 a siox-segmented subimage pixbuf. We not need alter 'img' at all, since this
492 pixbuf will be the same dimensions and at the same location.
493 Remember to free this new pixbuf later.
494 */
495 pixbuf = sioxProcessImage(img, pixbuf);
496 }
498 int nrPaths;
499 TracingEngineResult *results = engine->trace(pixbuf, &nrPaths);
500 //printf("nrPaths:%d\n", nrPaths);
502 //### Check if we should stop
503 if (!keepGoing || !results || nrPaths<1)
504 {
505 engine = NULL;
506 return;
507 }
509 //### Get pointers to the <image> and its parent
510 Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->repr;
511 Inkscape::XML::Node *par = sp_repr_parent(imgRepr);
513 //### Get some information for the new transform()
514 double x = 0.0;
515 double y = 0.0;
516 double width = 0.0;
517 double height = 0.0;
518 double dval = 0.0;
520 if (sp_repr_get_double(imgRepr, "x", &dval))
521 x = dval;
522 if (sp_repr_get_double(imgRepr, "y", &dval))
523 y = dval;
525 if (sp_repr_get_double(imgRepr, "width", &dval))
526 width = dval;
527 if (sp_repr_get_double(imgRepr, "height", &dval))
528 height = dval;
530 NR::Matrix trans(NR::translate(x, y));
532 double iwidth = (double)gdk_pixbuf_get_width(pixbuf);
533 double iheight = (double)gdk_pixbuf_get_height(pixbuf);
535 double iwscale = width / iwidth;
536 double ihscale = height / iheight;
538 NR::Matrix scal(NR::scale(iwscale, ihscale));
540 //# Convolve scale, translation, and the original transform
541 NR::Matrix tf(scal);
542 tf *= trans;
543 tf *= img->transform;
546 //#OK. Now let's start making new nodes
548 Inkscape::XML::Node *groupRepr = NULL;
550 //# if more than 1, make a <g>roup of <path>s
551 if (nrPaths > 1)
552 {
553 groupRepr = sp_repr_new("svg:g");
554 par->addChild(groupRepr, imgRepr);
555 }
557 long totalNodeCount = 0L;
559 for (TracingEngineResult *result=results ;
560 result ; result=result->next)
561 {
562 totalNodeCount += result->getNodeCount();
564 Inkscape::XML::Node *pathRepr = sp_repr_new("svg:path");
565 pathRepr->setAttribute("style", result->getStyle());
566 pathRepr->setAttribute("d", result->getPathData());
568 if (nrPaths > 1)
569 groupRepr->addChild(pathRepr, NULL);
570 else
571 par->addChild(pathRepr, imgRepr);
573 //### Apply the transform from the image to the new shape
574 SPObject *reprobj = doc->getObjectByRepr(pathRepr);
575 if (reprobj)
576 {
577 SPItem *newItem = SP_ITEM(reprobj);
578 sp_item_write_transform(newItem, pathRepr, tf, NULL);
579 }
580 if (nrPaths == 1)
581 {
582 selection->clear();
583 selection->add(pathRepr);
584 }
585 Inkscape::GC::release(pathRepr);
586 }
588 //did we allocate a pixbuf copy?
589 if (sioxEnabled)
590 {
591 g_free(pixbuf);
592 }
594 delete results;
596 // If we have a group, then focus on, then forget it
597 if (nrPaths > 1)
598 {
599 selection->clear();
600 selection->add(groupRepr);
601 Inkscape::GC::release(groupRepr);
602 }
604 //## inform the document, so we can undo
605 sp_document_done(doc);
607 engine = NULL;
609 char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
610 SP_DT_MSGSTACK(desktop)->flash(Inkscape::NORMAL_MESSAGE, msg);
611 g_free(msg);
613 }
619 /**
620 * Main tracing method
621 */
622 void Tracer::trace(TracingEngine *theEngine)
623 {
624 //Check if we are already running
625 if (engine)
626 return;
628 engine = theEngine;
630 #if HAVE_THREADS
631 //Ensure that thread support is running
632 if (!Glib::thread_supported())
633 Glib::thread_init();
635 //Create our thread and run it
636 Glib::Thread::create(
637 sigc::mem_fun(*this, &Tracer::traceThread), false);
638 #else
639 traceThread();
640 #endif
642 }
648 /**
649 * Abort the thread that is executing trace()
650 */
651 void Tracer::abort()
652 {
654 //## Inform Trace's working thread
655 keepGoing = false;
657 if (engine)
658 {
659 engine->abort();
660 }
662 }
666 } // namespace Trace
668 } // namespace Inkscape
671 //#########################################################################
672 //# E N D O F F I L E
673 //#########################################################################