Code

No more NRMatrix or NRPoint.
[inkscape.git] / src / trace / trace.cpp
index a0dbad9fdf3716454c43d98809e338f34a48b2b6..95c144e28b18b831023685a779ee699601fb8c18 100644 (file)
@@ -1,4 +1,4 @@
-/*
+/**
  * A generic interface for plugging different
  *  autotracers into Inkscape.
  *
@@ -19,6 +19,7 @@
 #include <desktop-handles.h>
 #include <document.h>
 #include <message-stack.h>
+#include <gtkmm.h>
 #include <glibmm/i18n.h>
 #include <selection.h>
 #include <xml/repr.h>
@@ -26,6 +27,8 @@
 #include <sp-item.h>
 #include <sp-shape.h>
 #include <sp-image.h>
+#include <libnr/nr-matrix-ops.h>
+#include <libnr/nr-scale-translate-ops.h>
 
 #include <display/nr-arena.h>
 #include <display/nr-arena-shape.h>
 #include "siox.h"
 #include "imagemap-gdk.h"
 
-namespace Inkscape {
-
-namespace Trace {
-
 
 
-/*
-static PackedPixelMap *
-renderToPackedPixelMap(SPDocument *doc, std::vector<SPItem *> items)
+namespace Inkscape
 {
 
-    double minX =  1.0e6;
-    double minY =  1.0e6;
-    double maxX = -1.0e6;
-    double maxY = -1.0e6;
-    for (int i=0 ; i<items.size() ; i++)
-        {
-        SPItem *item = items[i];
-        if (item->bbox.x0 < minX)
-            minX = item->bbox.x0;
-        if (item->bbox.y0 < minY)
-            minY = item->bbox.y0;
-        if (item->bbox.x1 > maxX)
-            maxX = item->bbox.x1;
-        if (item->bbox.y1 > maxY)
-            maxY = item->bbox.x1;
-        }
-
-    double dwidth  = maxX - minX;
-    double dheight = maxY - minY;
-
-    NRRectL bbox;
-    bbox.x0 = 0;
-    bbox.y0 = 0;
-    bbox.x1 = 256;
-    bbox.y1 = (int) ( 256.0 * dwidth / dheight );
-
-
-    NRArena *arena = NRArena::create();
-    unsigned dkey = sp_item_display_key_new(1);
-
-    // Create ArenaItems and set transform
-    NRArenaItem *root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)),
-            arena, dkey, SP_ITEM_SHOW_DISPLAY);
-    nr_arena_item_set_transform(root, NR::Matrix(&affine));
-
-    NRPixBlock pb;
-    nr_pixblock_setup(&pb, NR_PIXBLOCK_MODE_R8G8B8A8N,
-                      minX, minY, maxX, maxY, true);
-
-    //fill in background
-    for (int row = 0; row < bbox.y1; row++)
-        {
-        guchar *p = NR_PIXBLOCK_PX(&pb) + row * bbox.x1;
-        for (int col = 0; col < bbox.x1; col++)
-            {
-            *p++ = ebp->r;
-            *p++ = ebp->g;
-            *p++ = ebp->b;
-            *p++ = ebp->a;
-            }
-        }
-
-    // Render
-    nr_arena_item_invoke_render(root, &bbox, &pb, 0);
-
-    for (int r = 0; r < num_rows; r++) {
-        rows[r] = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
-    }
-
-    //## Make an packed pixel map
-    PackedPixelMap *ppMap = PackedPixelMapCreate(bbox.x1, bbox.y1);
-    for (int row = 0; row < bbox.y1; row++)
-        {
-        guchar *p = NR_PIXBLOCK_PX(&pb) + row * bbox.x1;
-        for (int col = 0; col < bbox.x1; col++)
-            {
-            int r = *p++;
-            int g = *p++;
-            int b = *p++;
-            int a = *p++;
-            ppMap->setPixelValue(ppMap, col, row, r, g, b);
-            }
-        }
-
-    //## Free allocated things
-    nr_pixblock_release(&pb);
-    nr_arena_item_unref(root);
-    nr_object_unref((NRObject *) arena);
-
+namespace Trace
+{
 
-    return ppMap;
-}
-*/
 
 
 
 
 /**
- *
+ * Get the selected image.  Also check for any SPItems over it, in
+ * case the user wants SIOX pre-processing.
  */
 SPImage *
 Tracer::getSelectedSPImage()
 {
+
     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (!desktop)
         {
-        g_warning("Trace: No active desktop\n");
+        g_warning("Trace: No active desktop");
         return NULL;
         }
 
-    Inkscape::Selection *sel = SP_DT_SELECTION(desktop);
+    Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
+
+    Inkscape::Selection *sel = sp_desktop_selection(desktop);
     if (!sel)
         {
         char *msg = _("Select an <b>image</b> to trace");
-        SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
+        msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
         //g_warning(msg);
         return NULL;
         }
@@ -183,7 +104,7 @@ Tracer::getSelectedSPImage()
                 if (img) //we want only one
                     {
                     char *msg = _("Select only one <b>image</b> to trace");
-                    SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
+                    msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
                     return NULL;
                     }
                 img = SP_IMAGE(item);
@@ -197,10 +118,11 @@ Tracer::getSelectedSPImage()
                     }
                 }
             }
+
         if (!img || sioxShapes.size() < 1)
             {
             char *msg = _("Select one image and one or more shapes above it");
-            SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
+            msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
             return NULL;
             }
         return img;
@@ -212,7 +134,7 @@ Tracer::getSelectedSPImage()
         if (!item)
             {
             char *msg = _("Select an <b>image</b> to trace");  //same as above
-            SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
+            msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
             //g_warning(msg);
             return NULL;
             }
@@ -220,7 +142,7 @@ Tracer::getSelectedSPImage()
         if (!SP_IS_IMAGE(item))
             {
             char *msg = _("Select an <b>image</b> to trace");
-            SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
+            msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
             //g_warning(msg);
             return NULL;
             }
@@ -234,126 +156,136 @@ Tracer::getSelectedSPImage()
 
 
 
-/**
- *
- */
-GdkPixbuf *
-Tracer::getSelectedImage()
+typedef org::siox::SioxImage SioxImage;
+typedef org::siox::SioxObserver SioxObserver;
+typedef org::siox::Siox Siox;
+
+
+class TraceSioxObserver : public SioxObserver
 {
+public:
+
+    /**
+     *
+     */
+    TraceSioxObserver (void *contextArg) :
+                     SioxObserver(contextArg)
+        {}
+
+    /**
+     *
+     */
+    virtual ~TraceSioxObserver ()
+        { }
+
+    /**
+     *  Informs the observer how much has been completed.
+     *  Return false if the processing should be aborted.
+     */
+    virtual bool progress(float /*percentCompleted*/)
+        {
+        //Tracer *tracer = (Tracer *)context;
+        //## Allow the GUI to update
+        Gtk::Main::iteration(false); //at least once, non-blocking
+        while( Gtk::Main::events_pending() )
+            Gtk::Main::iteration();
+        return true;
+        }
 
-    SPImage *img = getSelectedSPImage();
-    if (!img)
-        return NULL;
+    /**
+     *  Send an error string to the Observer.  Processing will
+     *  be halted.
+     */
+    virtual void error(const std::string &/*msg*/)
+        {
+        //Tracer *tracer = (Tracer *)context;
+        }
+
+
+};
 
-    GdkPixbuf *pixbuf = img->pixbuf;
 
-    return pixbuf;
 
-}
 
 
-GdkPixbuf *
-Tracer::sioxProcessImage(SPImage *img, GdkPixbuf *origPixbuf)
+/**
+ * Process a GdkPixbuf, according to which areas have been
+ * obscured in the GUI.
+ */
+Glib::RefPtr<Gdk::Pixbuf>
+Tracer::sioxProcessImage(SPImage *img,
+             Glib::RefPtr<Gdk::Pixbuf>origPixbuf)
 {
+    if (!sioxEnabled)
+        return origPixbuf;
+
+    if (origPixbuf == lastOrigPixbuf)
+        return lastSioxPixbuf;
+
+    //g_message("siox: start");
 
     //Convert from gdk, so a format we know.  By design, the pixel
     //format in PackedPixelMap is identical to what is needed by SIOX
-    PackedPixelMap *ppMap = gdkPixbufToPackedPixelMap(origPixbuf);
-    //We need to create two things:
-    //  1.  An array of long pixel values of ARGB
-    //  2.  A matching array of per-pixel float 'confidence' values
-    unsigned long *imgBuf = ppMap->pixels;
-    float *confidenceMatrix = new float[ppMap->width * ppMap->height];
+    SioxImage simage(origPixbuf->gobj());
 
     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
     if (!desktop)
         {
-        g_warning("Trace: No active desktop\n");
-        return NULL;
+        g_warning(_("Trace: No active desktop"));
+        return Glib::RefPtr<Gdk::Pixbuf>(NULL);
         }
 
-    Inkscape::Selection *sel = SP_DT_SELECTION(desktop);
+    Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
+
+    Inkscape::Selection *sel = sp_desktop_selection(desktop);
     if (!sel)
         {
         char *msg = _("Select an <b>image</b> to trace");
-        SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
+        msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
         //g_warning(msg);
-        return NULL;
+        return Glib::RefPtr<Gdk::Pixbuf>(NULL);
         }
 
-    Inkscape::XML::Node *imgRepr = SP_OBJECT(img)->repr;
-    /*
-    //## Make a Rect overlaying the image
-    Inkscape::XML::Node *parent  = imgRepr->parent();
-
-    Inkscape::XML::Node *rect    = sp_repr_new("svg:rect");
-    Inkscape::Util::List<Inkscape::XML::AttributeRecord const> attr =
-                   imgRepr->attributeList();
-    for (  ; attr ; attr++)
-        {
-        rect->setAttribute(g_quark_to_string(attr->key), attr->value, false);
-        }
+    NRArenaItem *aImg = sp_item_get_arenaitem(img, desktop->dkey);
+    //g_message("img: %d %d %d %d\n", aImg->bbox.x0, aImg->bbox.y0,
+    //                                aImg->bbox.x1, aImg->bbox.y1);
 
+    double width  = (double)(aImg->bbox.x1 - aImg->bbox.x0);
+    double height = (double)(aImg->bbox.y1 - aImg->bbox.y0);
 
-    //Inkscape::XML::Node *rect    = imgRepr->duplicate();
-    parent->appendChild(rect);
-    Inkscape::GC::release(rect);
-    */
-
-    //### <image> element
-    double x      = 0.0;
-    double y      = 0.0;
-    double width  = 0.0;
-    double height = 0.0;
-    double dval   = 0.0;
-
-    if (sp_repr_get_double(imgRepr, "x", &dval))
-        x = dval;
-    if (sp_repr_get_double(imgRepr, "y", &dval))
-        y = dval;
-
-    if (sp_repr_get_double(imgRepr, "width", &dval))
-        width = dval;
-    if (sp_repr_get_double(imgRepr, "height", &dval))
-        height = dval;
-
-    double iwidth  = (double)ppMap->width;
-    double iheight = (double)ppMap->height;
+    double iwidth  = (double)simage.getWidth();
+    double iheight = (double)simage.getHeight();
 
     double iwscale = width  / iwidth;
     double ihscale = height / iheight;
 
-    unsigned long cmIndex = 0;
-
-    /* Create new arena */
-    NRArena *arena = NRArena::create();
-    unsigned dkey = sp_item_display_key_new(1);
-
     std::vector<NRArenaItem *> arenaItems;
     std::vector<SPShape *>::iterator iter;
     for (iter = sioxShapes.begin() ; iter!=sioxShapes.end() ; iter++)
         {
-        /* Create ArenaItems and set transform */
-        NRArenaItem *aItem =
-            sp_item_invoke_show(*iter,
-                 arena, dkey, SP_ITEM_SHOW_DISPLAY);
-        nr_arena_item_set_transform(aItem, img->transform);
+        SPItem *item = *iter;
+        NRArenaItem *aItem = sp_item_get_arenaitem(item, desktop->dkey);
         arenaItems.push_back(aItem);
         }
 
-    PackedPixelMap *dumpMap = PackedPixelMapCreate(ppMap->width, ppMap->height);
+    //g_message("%d arena items\n", arenaItems.size());
 
-    for (int row=0 ; row<ppMap->height ; row++)
+    //PackedPixelMap *dumpMap = PackedPixelMapCreate(
+    //                simage.getWidth(), simage.getHeight());
+
+    //g_message("siox: start selection");
+
+    for (int row=0 ; row<iheight ; row++)
         {
-        double ypos = y + ihscale * (double) row;
-        for (int col=0 ; col<ppMap->width ; col++)
+        double ypos = ((double)aImg->bbox.y0) + ihscale * (double) row;
+        for (int col=0 ; col<simage.getWidth() ; col++)
             {
             //Get absolute X,Y position
-            double xpos = x + iwscale * (double)col;
+            double xpos = ((double)aImg->bbox.x0) + iwscale * (double)col;
             NR::Point point(xpos, ypos);
-            point *= img->transform;
-            point = desktop->doc2dt(point);
-            std::vector<SPShape *>::iterator iter;
+            point *= *aImg->transform;
+            //point *= imgMat;
+            //point = desktop->doc2dt(point);
             //g_message("x:%f    y:%f\n", point[0], point[1]);
             bool weHaveAHit = false;
             std::vector<NRArenaItem *>::iterator aIter;
@@ -362,42 +294,50 @@ Tracer::sioxProcessImage(SPImage *img, GdkPixbuf *origPixbuf)
                 NRArenaItem *arenaItem = *aIter;
                 NRArenaItemClass *arenaClass =
                     (NRArenaItemClass *) NR_OBJECT_GET_CLASS (arenaItem);
-                if (arenaClass && arenaClass->pick)
+                if (arenaClass->pick(arenaItem, point, 1.0f, 1))
                     {
-                    if (arenaClass->pick(arenaItem, point, 0.0f, 0))
-                        {
-                        weHaveAHit = true;
-                        break;
-                        }
+                    weHaveAHit = true;
+                    break;
                     }
                 }
 
             if (weHaveAHit)
                 {
                 //g_message("hit!\n");
-                dumpMap->setPixelLong(dumpMap, col, row, 0L);
-                confidenceMatrix[cmIndex] =
-                        org::siox::SioxSegmentator::CERTAIN_FOREGROUND_CONFIDENCE;
+                //dumpMap->setPixelLong(dumpMap, col, row, 0L);
+                simage.setConfidence(col, row,
+                        Siox::UNKNOWN_REGION_CONFIDENCE);
                 }
             else
                 {
-                dumpMap->setPixelLong(dumpMap, col, row,
-                        ppMap->getPixel(ppMap, col, row));
-                confidenceMatrix[cmIndex] =
-                        org::siox::SioxSegmentator::CERTAIN_BACKGROUND_CONFIDENCE;
+                //g_message("miss!\n");
+                //dumpMap->setPixelLong(dumpMap, col, row,
+                //        simage.getPixel(col, row));
+                simage.setConfidence(col, row,
+                        Siox::CERTAIN_BACKGROUND_CONFIDENCE);
                 }
-            cmIndex++;
             }
         }
 
+    //g_message("siox: selection done");
+
+    //dumpMap->writePPM(dumpMap, "siox1.ppm");
+    //dumpMap->destroy(dumpMap);
+
     //## ok we have our pixel buf
-    org::siox::SioxSegmentator ss(ppMap->width, ppMap->height, NULL, 0);
-    ss.segmentate(imgBuf, confidenceMatrix, 0, 0.0);
+    TraceSioxObserver observer(this);
+    Siox sengine(&observer);
+    SioxImage result = sengine.extractForeground(simage, 0xffffff);
+    if (!result.isValid())
+        {
+        g_warning(_("Invalid SIOX result"));
+        return Glib::RefPtr<Gdk::Pixbuf>(NULL);
+        }
 
-    dumpMap->writePPM(dumpMap, "siox.ppm");
-    dumpMap->destroy(dumpMap);
+    //result.writePPM("siox2.ppm");
 
     /* Free Arena and ArenaItem */
+    /*
     std::vector<NRArenaItem *>::iterator aIter;
     for (aIter = arenaItems.begin() ; aIter!=arenaItems.end() ; aIter++)
        {
@@ -405,16 +345,56 @@ Tracer::sioxProcessImage(SPImage *img, GdkPixbuf *origPixbuf)
        nr_arena_item_unref(arenaItem);
        }
     nr_object_unref((NRObject *) arena);
+    */
 
+    Glib::RefPtr<Gdk::Pixbuf> newPixbuf = Glib::wrap(result.getGdkPixbuf());
 
-    GdkPixbuf *newPixbuf = packedPixelMapToGdkPixbuf(ppMap);
-    ppMap->destroy(ppMap);
-    delete confidenceMatrix;
+    //g_message("siox: done");
+
+    lastSioxPixbuf = newPixbuf;
 
     return newPixbuf;
 }
 
 
+/**
+ *
+ */
+Glib::RefPtr<Gdk::Pixbuf>
+Tracer::getSelectedImage()
+{
+
+
+    SPImage *img = getSelectedSPImage();
+    if (!img)
+        return Glib::RefPtr<Gdk::Pixbuf>(NULL);
+
+    if (!img->pixbuf)
+        return Glib::RefPtr<Gdk::Pixbuf>(NULL);
+
+    Glib::RefPtr<Gdk::Pixbuf> pixbuf =
+          Glib::wrap(img->pixbuf, true);
+
+    if (sioxEnabled)
+        {
+        Glib::RefPtr<Gdk::Pixbuf> sioxPixbuf =
+             sioxProcessImage(img, pixbuf);
+        if (!sioxPixbuf)
+            {
+            return pixbuf;
+            }
+        else
+            {
+            return sioxPixbuf;
+            }
+        }
+    else
+        {
+        return pixbuf;
+        }
+
+}
+
 
 
 //#########################################################################
@@ -449,12 +429,14 @@ void Tracer::traceThread()
         return;
         }
 
-    Inkscape::Selection *selection = SP_DT_SELECTION (desktop);
+    Inkscape::MessageStack *msgStack = sp_desktop_message_stack(desktop);
+
+    Inkscape::Selection *selection = sp_desktop_selection (desktop);
 
     if (!SP_ACTIVE_DOCUMENT)
         {
         char *msg = _("Trace: No active document");
-        SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
+        msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
         //g_warning(msg);
         engine = NULL;
         return;
@@ -470,37 +452,29 @@ void Tracer::traceThread()
         return;
         }
 
-    GdkPixbuf *pixbuf = img->pixbuf;
+    Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(img->pixbuf, true);
+
+    pixbuf = sioxProcessImage(img, pixbuf);
 
     if (!pixbuf)
         {
         char *msg = _("Trace: Image has no bitmap data");
-        SP_DT_MSGSTACK(desktop)->flash(Inkscape::ERROR_MESSAGE, msg);
+        msgStack->flash(Inkscape::ERROR_MESSAGE, msg);
         //g_warning(msg);
         engine = NULL;
         return;
         }
 
-    //## SIOX pre-processing to get a smart subimage of the pixbuf.
-    //## This is done before any other filters
-    if (sioxEnabled)
-        {
-        /*
-           Ok, we have requested siox, and getSelectedSPImage() has found a single
-           bitmap and one or more SPItems above it.  Now what we need to do is create
-           a siox-segmented subimage pixbuf.  We not need alter 'img' at all, since this
-           pixbuf will be the same dimensions and at the same location.
-           Remember to free this new pixbuf later.
-        */
-        pixbuf = sioxProcessImage(img, pixbuf);
-        }
+    msgStack->flash(Inkscape::NORMAL_MESSAGE, _("Trace: Starting trace..."));
+    desktop->updateCanvasNow();
 
-    int nrPaths;
-    TracingEngineResult *results = engine->trace(pixbuf, &nrPaths);
-    //printf("nrPaths:%d\n", nrPaths);
+    std::vector<TracingEngineResult> results =
+                engine->trace(pixbuf);
+    //printf("nrPaths:%d\n", results.size());
+    int nrPaths = results.size();
 
     //### Check if we should stop
-    if (!keepGoing || !results || nrPaths<1)
+    if (!keepGoing || nrPaths<1)
         {
         engine = NULL;
         return;
@@ -527,43 +501,42 @@ void Tracer::traceThread()
     if (sp_repr_get_double(imgRepr, "height", &dval))
         height = dval;
 
-    NR::Matrix trans(NR::translate(x, y));
-
-    double iwidth  = (double)gdk_pixbuf_get_width(pixbuf);
-    double iheight = (double)gdk_pixbuf_get_height(pixbuf);
+    double iwidth  = (double)pixbuf->get_width();
+    double iheight = (double)pixbuf->get_height();
 
     double iwscale = width  / iwidth;
     double ihscale = height / iheight;
 
-    NR::Matrix scal(NR::scale(iwscale, ihscale));
+    NR::translate trans(x, y);
+    NR::scale scal(iwscale, ihscale);
 
     //# Convolve scale, translation, and the original transform
-    NR::Matrix tf(scal);
-    tf *= trans;
+    NR::Matrix tf(scal * trans);
     tf *= img->transform;
 
 
     //#OK.  Now let's start making new nodes
 
+    Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
     Inkscape::XML::Node *groupRepr = NULL;
 
     //# if more than 1, make a <g>roup of <path>s
     if (nrPaths > 1)
         {
-        groupRepr = sp_repr_new("svg:g");
+        groupRepr = xml_doc->createElement("svg:g");
         par->addChild(groupRepr, imgRepr);
         }
 
     long totalNodeCount = 0L;
 
-    for (TracingEngineResult *result=results ;
-                  result ; result=result->next)
+    for (unsigned int i=0 ; i<results.size() ; i++)
         {
-        totalNodeCount += result->getNodeCount();
+        TracingEngineResult result = results[i];
+        totalNodeCount += result.getNodeCount();
 
-        Inkscape::XML::Node *pathRepr = sp_repr_new("svg:path");
-        pathRepr->setAttribute("style", result->getStyle());
-        pathRepr->setAttribute("d",     result->getPathData());
+        Inkscape::XML::Node *pathRepr = xml_doc->createElement("svg:path");
+        pathRepr->setAttribute("style", result.getStyle().c_str());
+        pathRepr->setAttribute("d",     result.getPathData().c_str());
 
         if (nrPaths > 1)
             groupRepr->addChild(pathRepr, NULL);
@@ -585,14 +558,6 @@ void Tracer::traceThread()
         Inkscape::GC::release(pathRepr);
         }
 
-    //did we allocate a pixbuf copy?
-    if (sioxEnabled)
-        {
-        g_free(pixbuf);
-        }
-
-    delete results;
-
     // If we have a group, then focus on, then forget it
     if (nrPaths > 1)
         {
@@ -602,12 +567,12 @@ void Tracer::traceThread()
         }
 
     //## inform the document, so we can undo
-    sp_document_done(doc);
+    sp_document_done(doc, SP_VERB_SELECTION_TRACE, _("Trace bitmap"));
 
     engine = NULL;
 
     char *msg = g_strdup_printf(_("Trace: Done. %ld nodes created"), totalNodeCount);
-    SP_DT_MSGSTACK(desktop)->flash(Inkscape::NORMAL_MESSAGE, msg);
+    msgStack->flash(Inkscape::NORMAL_MESSAGE, msg);
     g_free(msg);
 
 }