Code

Merge and cleanup of GSoC C++-ification project.
[inkscape.git] / src / helper / png-write.cpp
index 6d9f58ee3dd351ad8076b89b9202d26da926819a..d3fe2771ff8d07970153b3b1427879a0d08a0977 100644 (file)
@@ -1,11 +1,11 @@
-#define __SP_PNG_WRITE_C__
-
 /*
  * PNG file format utilities
  *
  * Authors:
  *   Lauris Kaplinski <lauris@kaplinski.com>
  *   Whoever wrote this example in libpng documentation
+ *   Jon A. Cruz <jon@joncruz.org>
+ *   Abhishek Sharma
  *
  * Copyright (C) 1999-2002 authors
  *
@@ -18,6 +18,8 @@
 
 #include <interface.h>
 #include <libnr/nr-pixops.h>
+#include <libnr/nr-translate-scale-ops.h>
+#include <2geom/rect.h>
 #include <glib/gmessages.h>
 #include <png.h>
 #include "png-write.h"
@@ -28,7 +30,8 @@
 #include <sp-item.h>
 #include <sp-root.h>
 #include <sp-defs.h>
-#include "prefs-utils.h"
+#include "preferences.h"
+#include "rdf.h"
 
 /* This is an example of how to use libpng to read and write PNG files.
  * The file libpng.txt is much more verbose then this.  If you have not
  * working PNG reader/writer, see pngtest.c, included in this distribution.
  */
 
-const unsigned int MAX_STRIPE_SIZE = 1024*1024;
+static unsigned int const MAX_STRIPE_SIZE = 1024*1024;
 
 struct SPEBP {
-    int width, height, sheight;
+    unsigned long int width, height, sheight;
     guchar r, g, b, a;
     NRArenaItem *root; // the root arena item to show; it is assumed that all unneeded items are hidden
     guchar *px;
@@ -56,173 +59,245 @@ struct SPEBP {
 /* write a png file */
 
 typedef struct SPPNGBD {
-       const guchar *px;
-       int rowstride;
+    guchar const *px;
+    int rowstride;
 } SPPNGBD;
 
-static int
-sp_png_get_block_stripe (const guchar **rows, int row, int num_rows, void *data)
-{
-       SPPNGBD *bd = (SPPNGBD *) data;
-
-       for (int r = 0; r < num_rows; r++) {
-               rows[r] = bd->px + (row + r) * bd->rowstride;
-       }
+/**
+ * A simple wrapper to list png_text.
+ */
+class PngTextList {
+public:
+    PngTextList() : count(0), textItems(0) {}
+    ~PngTextList();
+
+    void add(gchar const* key, gchar const* text);
+    gint getCount() {return count;}
+    png_text* getPtext() {return textItems;}
+
+private:
+    gint count;
+    png_text* textItems;
+};
 
-       return num_rows;
+PngTextList::~PngTextList() {
+    for (gint i = 0; i < count; i++) {
+        if (textItems[i].key) {
+            g_free(textItems[i].key);
+        }
+        if (textItems[i].text) {
+            g_free(textItems[i].text);
+        }
+    }
 }
 
-int
-sp_png_write_rgba (const gchar *filename, const guchar *px, int width, int height, double xdpi, double ydpi, int rowstride)
+void PngTextList::add(gchar const* key, gchar const* text)
 {
-       SPPNGBD bd;
-
-       bd.px = px;
-       bd.rowstride = rowstride;
-
-       return sp_png_write_rgba_striped (filename, width, height, xdpi, ydpi, sp_png_get_block_stripe, &bd);
+    if (count < 0) {
+        count = 0;
+        textItems = 0;
+    }
+    png_text* tmp = (count > 0) ? g_try_renew(png_text, textItems, count + 1): g_try_new(png_text, 1);
+    if (tmp) {
+        textItems = tmp;
+        count++;
+
+        png_text* item = &(textItems[count - 1]);
+        item->compression = PNG_TEXT_COMPRESSION_NONE;
+        item->key = g_strdup(key);
+        item->text = g_strdup(text);
+        item->text_length = 0;
+#ifdef PNG_iTXt_SUPPORTED
+        item->itxt_length = 0;
+        item->lang = 0;
+        item->lang_key = 0;
+#endif // PNG_iTXt_SUPPORTED
+    } else {
+        g_warning("Unable to allocate arrary for %d PNG text data.", count);
+        textItems = 0;
+        count = 0;
+    }
 }
 
-int
-sp_png_write_rgba_striped (const gchar *filename, int width, int height, double xdpi, double ydpi,
-                          int (* get_rows) (const guchar **rows, int row, int num_rows, void *data),
-                          void *data)
+static bool
+sp_png_write_rgba_striped(SPDocument *doc,
+                          gchar const *filename, unsigned long int width, unsigned long int height, double xdpi, double ydpi,
+                          int (* get_rows)(guchar const **rows, int row, int num_rows, void *data),
+                          void *data)
 {
     struct SPEBP *ebp = (struct SPEBP *) data;
-       FILE *fp;
-       png_structp png_ptr;
-       png_infop info_ptr;
-       png_color_8 sig_bit;
-       png_text text_ptr[3];
-       png_uint_32 r;
-
-       g_return_val_if_fail (filename != NULL, FALSE);
-
-       /* open the file */
-
-       Inkscape::IO::dump_fopen_call(filename, "M");
-       fp = Inkscape::IO::fopen_utf8name(filename, "wb");
-       g_return_val_if_fail (fp != NULL, FALSE);
-
-       /* Create and initialize the png_struct with the desired error handler
-        * functions.  If you want to use the default stderr and longjump method,
-        * you can supply NULL for the last three parameters.  We also check that
-        * the library version is compatible with the one used at compile time,
-        * in case we are using dynamically linked libraries.  REQUIRED.
-        */
-       png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
-
-       if (png_ptr == NULL) {
-               fclose(fp);
-               return FALSE;
-       }
-
-       /* Allocate/initialize the image information data.  REQUIRED */
-       info_ptr = png_create_info_struct(png_ptr);
-       if (info_ptr == NULL) {
-               fclose(fp);
-               png_destroy_write_struct(&png_ptr, NULL);
-               return FALSE;
-       }
-
-       /* Set error handling.  REQUIRED if you aren't supplying your own
-        * error hadnling functions in the png_create_write_struct() call.
-        */
-       if (setjmp(png_ptr->jmpbuf)) {
-               /* If we get here, we had a problem reading the file */
-               fclose(fp);
-               png_destroy_write_struct(&png_ptr, &info_ptr);
-               return FALSE;
-       }
-
-       /* set up the output control if you are using standard C streams */
-       png_init_io(png_ptr, fp);
-
-       /* Set the image information here.  Width and height are up to 2^31,
-        * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
-        * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
-        * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
-        * or PNG_COLOR_TYPE_RGB_ALPHA.  interlace is either PNG_INTERLACE_NONE or
-        * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
-        * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
-        */
-       png_set_IHDR(png_ptr, info_ptr,
-                    width,
-                    height,
-                    8, /* bit_depth */
-                    PNG_COLOR_TYPE_RGB_ALPHA,
-                    PNG_INTERLACE_NONE,
-                    PNG_COMPRESSION_TYPE_BASE,
-                    PNG_FILTER_TYPE_BASE);
-
-       /* otherwise, if we are dealing with a color image then */
-       sig_bit.red = 8;
-       sig_bit.green = 8;
-       sig_bit.blue = 8;
-       /* if the image has an alpha channel then */
-       sig_bit.alpha = 8;
-       png_set_sBIT(png_ptr, info_ptr, &sig_bit);
-
-       /* Made by Inkscape comment */
-       text_ptr[0].key = "Software";
-       text_ptr[0].text = "www.inkscape.org";
-       text_ptr[0].compression = PNG_TEXT_COMPRESSION_NONE;
-       png_set_text(png_ptr, info_ptr, text_ptr, 1);
-
-       /* other optional chunks like cHRM, bKGD, tRNS, tIME, oFFs, pHYs, */
-       /* note that if sRGB is present the cHRM chunk must be ignored
-        * on read and must be written in accordance with the sRGB profile */
-        png_set_pHYs(png_ptr, info_ptr, unsigned(xdpi / 0.0254 + 0.5), unsigned(ydpi / 0.0254 + 0.5), PNG_RESOLUTION_METER); 
-
-       /* Write the file header information.  REQUIRED */
-       png_write_info(png_ptr, info_ptr);
-
-       /* Once we write out the header, the compression type on the text
-        * chunks gets changed to PNG_TEXT_COMPRESSION_NONE_WR or
-        * PNG_TEXT_COMPRESSION_zTXt_WR, so it doesn't get written out again
-        * at the end.
-        */
-
-       /* set up the transformations you want.  Note that these are
-        * all optional.  Only call them if you want them.
-        */
-
-       /* --- CUT --- */
-
-       /* The easiest way to write the image (you may have a different memory
-        * layout, however, so choose what fits your needs best).  You need to
-        * use the first method if you aren't handling interlacing yourself.
-        */
-
-       png_bytep* row_pointers = new png_bytep[ebp->sheight];
-
-       r = 0;
-       while (r < static_cast< png_uint_32 > (height) ) {
-               int n = get_rows ((const unsigned char **) row_pointers, r, height-r, data);
-               if (!n) break;
-               png_write_rows (png_ptr, row_pointers, n);
-               r += n;
-       }
+    FILE *fp;
+    png_structp png_ptr;
+    png_infop info_ptr;
+    png_color_8 sig_bit;
+    png_uint_32 r;
+
+    g_return_val_if_fail(filename != NULL, false);
+    g_return_val_if_fail(data != NULL, false);
+
+    /* open the file */
+
+    Inkscape::IO::dump_fopen_call(filename, "M");
+    fp = Inkscape::IO::fopen_utf8name(filename, "wb");
+    g_return_val_if_fail(fp != NULL, false);
+
+    /* Create and initialize the png_struct with the desired error handler
+     * functions.  If you want to use the default stderr and longjump method,
+     * you can supply NULL for the last three parameters.  We also check that
+     * the library version is compatible with the one used at compile time,
+     * in case we are using dynamically linked libraries.  REQUIRED.
+     */
+    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+
+    if (png_ptr == NULL) {
+        fclose(fp);
+        return false;
+    }
+
+    /* Allocate/initialize the image information data.  REQUIRED */
+    info_ptr = png_create_info_struct(png_ptr);
+    if (info_ptr == NULL) {
+        fclose(fp);
+        png_destroy_write_struct(&png_ptr, NULL);
+        return false;
+    }
+
+    /* Set error handling.  REQUIRED if you aren't supplying your own
+     * error hadnling functions in the png_create_write_struct() call.
+     */
+    if (setjmp(png_ptr->jmpbuf)) {
+        /* If we get here, we had a problem reading the file */
+        fclose(fp);
+        png_destroy_write_struct(&png_ptr, &info_ptr);
+        return false;
+    }
+
+    /* set up the output control if you are using standard C streams */
+    png_init_io(png_ptr, fp);
+
+    /* Set the image information here.  Width and height are up to 2^31,
+     * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
+     * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
+     * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
+     * or PNG_COLOR_TYPE_RGB_ALPHA.  interlace is either PNG_INTERLACE_NONE or
+     * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
+     * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
+     */
+    png_set_IHDR(png_ptr, info_ptr,
+                 width,
+                 height,
+                 8, /* bit_depth */
+                 PNG_COLOR_TYPE_RGB_ALPHA,
+                 PNG_INTERLACE_NONE,
+                 PNG_COMPRESSION_TYPE_BASE,
+                 PNG_FILTER_TYPE_BASE);
+
+    /* otherwise, if we are dealing with a color image then */
+    sig_bit.red = 8;
+    sig_bit.green = 8;
+    sig_bit.blue = 8;
+    /* if the image has an alpha channel then */
+    sig_bit.alpha = 8;
+    png_set_sBIT(png_ptr, info_ptr, &sig_bit);
+
+    PngTextList textList;
+
+    textList.add("Software", "www.inkscape.org"); // Made by Inkscape comment
+    {
+        const gchar* pngToDc[] = {"Title", "title",
+                               "Author", "creator",
+                               "Description", "description",
+                               //"Copyright", "",
+                               "Creation Time", "date",
+                               //"Disclaimer", "",
+                               //"Warning", "",
+                               "Source", "source"
+                               //"Comment", ""
+        };
+        for (size_t i = 0; i < G_N_ELEMENTS(pngToDc); i += 2) {
+            struct rdf_work_entity_t * entity = rdf_find_entity ( pngToDc[i + 1] );
+            if (entity) {
+                gchar const* data = rdf_get_work_entity(doc, entity);
+                if (data && *data) {
+                    textList.add(pngToDc[i], data);
+                }
+            } else {
+                g_warning("Unable to find entity [%s]", pngToDc[i + 1]);
+            }
+        }
+
+
+        struct rdf_license_t *license =  rdf_get_license(doc);
+        if (license) {
+            if (license->name && license->uri) {
+                gchar* tmp = g_strdup_printf("%s %s", license->name, license->uri);
+                textList.add("Copyright", tmp);
+                g_free(tmp);
+            } else if (license->name) {
+                textList.add("Copyright", license->name);
+            } else if (license->uri) {
+                textList.add("Copyright", license->uri);
+            }
+        }
+    }
+    if (textList.getCount() > 0) {
+        png_set_text(png_ptr, info_ptr, textList.getPtext(), textList.getCount());
+    }
+
+    /* other optional chunks like cHRM, bKGD, tRNS, tIME, oFFs, pHYs, */
+    /* note that if sRGB is present the cHRM chunk must be ignored
+     * on read and must be written in accordance with the sRGB profile */
+    png_set_pHYs(png_ptr, info_ptr, unsigned(xdpi / 0.0254 + 0.5), unsigned(ydpi / 0.0254 + 0.5), PNG_RESOLUTION_METER);
+
+    /* Write the file header information.  REQUIRED */
+    png_write_info(png_ptr, info_ptr);
+
+    /* Once we write out the header, the compression type on the text
+     * chunks gets changed to PNG_TEXT_COMPRESSION_NONE_WR or
+     * PNG_TEXT_COMPRESSION_zTXt_WR, so it doesn't get written out again
+     * at the end.
+     */
+
+    /* set up the transformations you want.  Note that these are
+     * all optional.  Only call them if you want them.
+     */
+
+    /* --- CUT --- */
+
+    /* The easiest way to write the image (you may have a different memory
+     * layout, however, so choose what fits your needs best).  You need to
+     * use the first method if you aren't handling interlacing yourself.
+     */
+
+    png_bytep* row_pointers = new png_bytep[ebp->sheight];
+
+    r = 0;
+    while (r < static_cast< png_uint_32 > (height) ) {
+        int n = get_rows((unsigned char const **) row_pointers, r, height-r, data);
+        if (!n) break;
+        png_write_rows(png_ptr, row_pointers, n);
+        r += n;
+    }
 
     delete[] row_pointers;
 
-       /* You can write optional chunks like tEXt, zTXt, and tIME at the end
-        * as well.
-        */
+    /* You can write optional chunks like tEXt, zTXt, and tIME at the end
+     * as well.
+     */
 
-       /* It is REQUIRED to call this to finish writing the rest of the file */
-       png_write_end(png_ptr, info_ptr);
+    /* It is REQUIRED to call this to finish writing the rest of the file */
+    png_write_end(png_ptr, info_ptr);
 
-       /* if you allocated any text comments, free them here */
+    /* if you allocated any text comments, free them here */
 
-       /* clean up after the write, and free any memory allocated */
-       png_destroy_write_struct(&png_ptr, &info_ptr);
+    /* clean up after the write, and free any memory allocated */
+    png_destroy_write_struct(&png_ptr, &info_ptr);
 
-       /* close the file */
-       fclose(fp);
+    /* close the file */
+    fclose(fp);
 
-       /* that's it */
-       return TRUE;
+    /* that's it */
+    return true;
 }
 
 
@@ -238,8 +313,8 @@ sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
         if (!ebp->status((float) row / ebp->height, ebp->data)) return 0;
     }
 
-    num_rows = MIN(num_rows, ebp->sheight);
-    num_rows = MIN(num_rows, ebp->height - row);
+    num_rows = MIN(num_rows, static_cast<int>(ebp->sheight));
+    num_rows = MIN(num_rows, static_cast<int>(ebp->height - row));
 
     /* Set area of interest */
     // bbox is now set to the entire image to prevent discontinuities
@@ -247,24 +322,24 @@ sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
     // off, but that's less noticeable).
     NRRectL bbox;
     bbox.x0 = 0;
-    bbox.y0 = 0;//row;
+    bbox.y0 = row;
     bbox.x1 = ebp->width;
-    bbox.y1 = ebp->height;//row + num_rows;
+    bbox.y1 = row + num_rows;
     /* Update to renderable state */
     NRGC gc(NULL);
-    nr_matrix_set_identity(&gc.transform);
+    gc.transform.setIdentity();
 
     nr_arena_item_invoke_update(ebp->root, &bbox, &gc,
            NR_ARENA_ITEM_STATE_ALL, NR_ARENA_ITEM_STATE_NONE);
 
     NRPixBlock pb;
     nr_pixblock_setup_extern(&pb, NR_PIXBLOCK_MODE_R8G8B8A8N,
-                             bbox.x0, row/*bbox.y0*/, bbox.x1, row + num_rows/*bbox.y1*/,
+                             bbox.x0, bbox.y0, bbox.x1, bbox.y1,
                              ebp->px, 4 * ebp->width, FALSE, FALSE);
 
     for (int r = 0; r < num_rows; r++) {
         guchar *p = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
-        for (int c = 0; c < ebp->width; c++) {
+        for (int c = 0; c < static_cast<int>(ebp->width); c++) {
             *p++ = ebp->r;
             *p++ = ebp->g;
             *p++ = ebp->b;
@@ -273,7 +348,7 @@ sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
     }
 
     /* Render */
-    nr_arena_item_invoke_render(ebp->root, &bbox, &pb, 0);
+    nr_arena_item_invoke_render(NULL, ebp->root, &bbox, &pb, 0);
 
     for (int r = 0; r < num_rows; r++) {
         rows[r] = NR_PIXBLOCK_PX(&pb) + r * pb.rs;
@@ -285,23 +360,22 @@ sp_export_get_rows(guchar const **rows, int row, int num_rows, void *data)
 }
 
 /**
-Hide all items which are not listed in list, recursively, skipping groups and defs
-*/
-static void
-hide_other_items_recursively(SPObject *o, GSList *list, unsigned dkey)
+ * Hide all items that are not listed in list, recursively, skipping groups and defs.
+ */
+static void hide_other_items_recursively(SPObject *o, GSList *list, unsigned dkey)
 {
-    if (SP_IS_ITEM(o)
-        && !SP_IS_DEFS(o)
-        && !SP_IS_ROOT(o)
-        && !SP_IS_GROUP(o)
-        && !g_slist_find(list, o))
+    if ( SP_IS_ITEM(o)
+         && !SP_IS_DEFS(o)
+         && !SP_IS_ROOT(o)
+         && !SP_IS_GROUP(o)
+         && !g_slist_find(list, o) )
     {
-        sp_item_invoke_hide(SP_ITEM(o), dkey);
+        SP_ITEM(o)->invoke_hide(dkey);
     }
 
-     // recurse
+    // recurse
     if (!g_slist_find(list, o)) {
-        for (SPObject *child = sp_object_first_child(o) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
+        for ( SPObject *child = o->firstChild() ; child; child = child->getNext() ) {
             hide_other_items_recursively(child, list, dkey);
         }
     }
@@ -309,41 +383,50 @@ hide_other_items_recursively(SPObject *o, GSList *list, unsigned dkey)
 
 
 /**
- *  Render the SVG drawing onto a PNG raster image, then save to
- *  a file.  Returns TRUE if succeeded in writing the file,
- *  FALSE otherwise.
+ * Export the given document as a Portable Network Graphics (PNG) file.
+ *
+ * \return true if succeeded (or if no action was taken), false if an error occurred.
  */
-int
-sp_export_png_file(SPDocument *doc, gchar const *filename,
+bool sp_export_png_file (SPDocument *doc, gchar const *filename,
                    double x0, double y0, double x1, double y1,
-                   unsigned width, unsigned height, double xdpi, double ydpi,
+                   unsigned long int width, unsigned long int height, double xdpi, double ydpi,
+                   unsigned long bgcolor,
+                   unsigned int (*status) (float, void *),
+                   void *data, bool force_overwrite,
+                   GSList *items_only)
+{
+    return sp_export_png_file(doc, filename, Geom::Rect(Geom::Point(x0,y0),Geom::Point(x1,y1)),
+                              width, height, xdpi, ydpi, bgcolor, status, data, force_overwrite, items_only);
+}
+bool
+sp_export_png_file(SPDocument *doc, gchar const *filename,
+                   Geom::Rect const &area,
+                   unsigned long width, unsigned long height, double xdpi, double ydpi,
                    unsigned long bgcolor,
                    unsigned (*status)(float, void *),
                    void *data, bool force_overwrite,
                    GSList *items_only)
 {
-    int write_status = TRUE;
-    g_return_val_if_fail(doc != NULL, FALSE);
-    g_return_val_if_fail(filename != NULL, FALSE);
-    g_return_val_if_fail(width >= 1, FALSE);
-    g_return_val_if_fail(height >= 1, FALSE);
+    g_return_val_if_fail(doc != NULL, false);
+    g_return_val_if_fail(filename != NULL, false);
+    g_return_val_if_fail(width >= 1, false);
+    g_return_val_if_fail(height >= 1, false);
+    g_return_val_if_fail(!area.hasZeroArea(), false);
 
     if (!force_overwrite && !sp_ui_overwrite_file(filename)) {
-        return FALSE;
+        /* Remark: We return true so as not to invoke an error dialog in case export is cancelled
+           by the user; currently this is safe because the callers only act when false is returned.
+           If this changes in the future we need better distinction of return types (e.g., use int)
+        */
+        return true;
     }
 
-    // export with maximum blur rendering quality
-    int saved_quality = prefs_get_int_attribute ("options.blurquality", "value", 0);
-    prefs_set_int_attribute ("options.blurquality", "value", 2);
-
-    sp_document_ensure_up_to_date(doc);
+    doc->ensureUpToDate();
 
-    /* Go to document coordinates */
-    gdouble t = y0;
-    y0 = sp_document_height(doc) - y1;
-    y1 = sp_document_height(doc) - t;
+    /* Calculate translation by transforming to document coordinates (flipping Y)*/
+    Geom::Point translation = Geom::Point(-area[Geom::X][0], area[Geom::Y][1] - doc->getHeight());
 
-    /*
+    /*  This calculation is only valid when assumed that (x0,y0)= area.corner(0) and (x1,y1) = area.corner(2)
      * 1) a[0] * x0 + a[2] * y1 + a[4] = 0.0
      * 2) a[1] * x0 + a[3] * y1 + a[5] = 0.0
      * 3) a[0] * x1 + a[2] * y1 + a[4] = width
@@ -359,13 +442,9 @@ sp_export_png_file(SPDocument *doc, gchar const *filename,
      * (2) a[5] = -a[3] * y1
      */
 
-    NRMatrix affine;
-    affine.c[0] = width / (x1 - x0);
-    affine.c[1] = 0.0;
-    affine.c[2] = 0.0;
-    affine.c[3] = height / (y1 - y0);
-    affine.c[4] = -affine.c[0] * x0;
-    affine.c[5] = -affine.c[3] * y0;
+    Geom::Matrix const affine(Geom::Translate(translation)
+                            * Geom::Scale(width / area.width(),
+                                        height / area.height()));
 
     //SP_PRINT_MATRIX("SVG2PNG", &affine);
 
@@ -378,44 +457,54 @@ sp_export_png_file(SPDocument *doc, gchar const *filename,
     ebp.a      = NR_RGBA32_A(bgcolor);
 
     /* Create new arena */
-    NRArena *arena = NRArena::create();
-    unsigned dkey = sp_item_display_key_new(1);
+    NRArena *const arena = NRArena::create();
+    // export with maximum blur rendering quality
+    nr_arena_set_renderoffscreen(arena);
+    unsigned const dkey = SPItem::display_key_new(1);
 
     /* Create ArenaItems and set transform */
-    ebp.root = sp_item_invoke_show(SP_ITEM(sp_document_root(doc)), arena, dkey, SP_ITEM_SHOW_DISPLAY);
-    nr_arena_item_set_transform(NR_ARENA_ITEM(ebp.root), NR::Matrix(&affine));
+    ebp.root = SP_ITEM(doc->getRoot())->invoke_show(arena, dkey, SP_ITEM_SHOW_DISPLAY);
+    nr_arena_item_set_transform(NR_ARENA_ITEM(ebp.root), affine);
 
     // We show all and then hide all items we don't want, instead of showing only requested items,
     // because that would not work if the shown item references something in defs
     if (items_only) {
-        hide_other_items_recursively(sp_document_root(doc), items_only, dkey);
+        hide_other_items_recursively(doc->getRoot(), items_only, dkey);
     }
 
     ebp.status = status;
     ebp.data   = data;
 
-    if (4 * width * height < 65536) {
+    bool write_status;
+    if ((width < 256) || ((width * height) < 32768)) {
         ebp.px = nr_pixelstore_64K_new(FALSE, 0);
-        ebp.sheight = height;
-        write_status = sp_png_write_rgba_striped(filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
+        ebp.sheight = 65536 / (4 * width);
+        write_status = sp_png_write_rgba_striped(doc, filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
         nr_pixelstore_64K_free(ebp.px);
     } else {
-        ebp.sheight = MAX(1,MIN(MAX_STRIPE_SIZE / (4 * width),height));
-        ebp.px = g_new(guchar, 4 * width * ebp.sheight);
-        write_status = sp_png_write_rgba_striped(filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
+        ebp.sheight = 64;
+        ebp.px = g_try_new(guchar, 4 * ebp.sheight * width);
+        write_status = sp_png_write_rgba_striped(doc, filename, width, height, xdpi, ydpi, sp_export_get_rows, &ebp);
         g_free(ebp.px);
     }
 
-    // Hide items
-    sp_item_invoke_hide(SP_ITEM(sp_document_root(doc)), dkey);
+    // Hide items, this releases arenaitem
+    SP_ITEM(doc->getRoot())->invoke_hide(dkey);
 
-    /* Free Arena and ArenaItem */
-    nr_arena_item_unref(ebp.root);
+    /* Free arena */
     nr_object_unref((NRObject *) arena);
 
-    // restore saved blur quality
-    prefs_set_int_attribute ("options.blurquality", "value", saved_quality);
-
     return write_status;
 }
 
+
+/*
+  Local Variables:
+  mode:c++
+  c-file-style:"stroustrup"
+  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+  indent-tabs-mode:nil
+  fill-column:99
+  End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :