Code

remove memory boundries on bitmap renderer, optimize memory usage
[inkscape.git] / src / extension / internal / cairo-render-context.cpp
index 5849230bf4d1cdeb3c5dff264b4b9881971dabbc..b68105b30908e275982102adc6ea90dc07208fd5 100644 (file)
@@ -1,11 +1,11 @@
-#define __SP_CAIRO_RENDER_CONTEXT_C__
-
 /** \file
  * Rendering with Cairo.
  */
 /*
  * Author:
  *   Miklos Erdelyi <erdelyim@gmail.com>
+ *   Jon A. Cruz <jon@joncruz.org>
+ *   Abhishek Sharma
  *
  * Copyright (C) 2006 Miklos Erdelyi
  *
@@ -108,7 +108,7 @@ static cairo_status_t _write_callback(void *closure, const unsigned char *data,
 
 CairoRenderContext::CairoRenderContext(CairoRenderer *parent) :
     _dpi(72),
-    _pdf_level(0),
+    _pdf_level(1),
     _ps_level(1),
     _eps(false),
     _is_texttopath(FALSE),
@@ -516,7 +516,7 @@ CairoRenderContext::getClipMode(void) const
 CairoRenderState*
 CairoRenderContext::_createState(void)
 {
-    CairoRenderState *state = (CairoRenderState*)g_malloc(sizeof(CairoRenderState));
+    CairoRenderState *state = (CairoRenderState*)g_try_malloc(sizeof(CairoRenderState));
     g_assert( state != NULL );
 
     state->has_filtereffect = FALSE;
@@ -670,6 +670,11 @@ CairoRenderContext::popLayer(void)
             }
             TRACE(("mask surface: %f x %f at %i dpi\n", surface_width, surface_height, _dpi ));
 
+            // Mask should start black, but it is created white.
+            cairo_set_source_rgba(mask_ctx->_cr, 0.0, 0.0, 0.0, 1.0);
+            cairo_rectangle(mask_ctx->_cr, 0, 0, surface_width, surface_height);
+            cairo_fill(mask_ctx->_cr);
+
             // set rendering mode to normal
             setRenderMode(RENDER_MODE_NORMAL);
 
@@ -700,19 +705,27 @@ CairoRenderContext::popLayer(void)
             int stride = cairo_image_surface_get_stride(mask_image);
             unsigned char *pixels = cairo_image_surface_get_data(mask_image);
 
-            // premultiply with opacity
             // In SVG, the rgb channels as well as the alpha channel is used in masking.
             // In Cairo, only the alpha channel is used thus requiring this conversion.
+            // SVG specifies that RGB be converted to alpha using luminance-to-alpha.
+            // Notes: This calculation assumes linear RGB values. VERIFY COLOR SPACE!
+            // The incoming pixel values already include alpha, fill-opacity, etc.,
+            // however, opacity must still be applied.
             TRACE(("premul w/ %f\n", opacity));
-            guint8 int_opacity = (guint8)(255 * opacity);
+            const float coeff_r = 0.2125 / 255.0;
+            const float coeff_g = 0.7154 / 255.0;
+            const float coeff_b = 0.0721 / 255.0;
             for (int row = 0 ; row < height; row++) {
                 unsigned char *row_data = pixels + (row * stride);
                 for (int i = 0 ; i < width; i++) {
                     guint32 *pixel = (guint32 *)row_data + i;
-                    *pixel = ((((*pixel & 0x00ff0000) >> 16) * 13817 +
-                               ((*pixel & 0x0000ff00) >>  8) * 46518 +
-                               ((*pixel & 0x000000ff)      ) * 4688) *
-                              int_opacity);
+                    float lum_alpha = (((*pixel & 0x00ff0000) >> 16) * coeff_r +
+                                       ((*pixel & 0x0000ff00) >>  8) * coeff_g +
+                                       ((*pixel & 0x000000ff)      ) * coeff_b );
+                    // lum_alpha can be slightly greater than 1 due to rounding errors...
+                    // but this should be OK since it doesn't matter what the lower
+                    // six hexadecimal numbers of *pixel are.
+                    *pixel = (guint32)(0xff000000 * lum_alpha * opacity);
                 }
             }
 
@@ -782,6 +795,9 @@ CairoRenderContext::setupSurface(double width, double height)
 #ifdef CAIRO_HAS_PDF_SURFACE
         case CAIRO_SURFACE_TYPE_PDF:
             surface = cairo_pdf_surface_create_for_stream(Inkscape::Extension::Internal::_write_callback, _stream, width, height);
+#if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 10, 0))
+            cairo_pdf_surface_restrict_to_version(surface, (cairo_pdf_version_t)_pdf_level);
+#endif
             break;
 #endif
 #ifdef CAIRO_HAS_PS_SURFACE
@@ -791,8 +807,15 @@ CairoRenderContext::setupSurface(double width, double height)
                 return FALSE;
             }
 #if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 5, 2))
-            cairo_ps_surface_restrict_to_level (surface, (cairo_ps_level_t)_ps_level);
-            cairo_ps_surface_set_eps (surface, (cairo_bool_t) _eps);
+            cairo_ps_surface_restrict_to_level(surface, (cairo_ps_level_t)_ps_level);
+            cairo_ps_surface_set_eps(surface, (cairo_bool_t) _eps);
+#endif
+            // Cairo calculates the bounding box itself, however we want to override this. See Launchpad bug #380501
+#if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 11, 2))
+            if (override_bbox) {
+                cairo_ps_dsc_comment(surface, "%%BoundingBox: 100 100 200 200");
+                cairo_ps_dsc_comment(surface, "%%PageBoundingBox: 100 100 200 200");
+            }
 #endif
             break;
 #endif
@@ -960,14 +983,15 @@ CairoRenderContext::popState(void)
     g_assert( g_slist_length(_state_stack) > 0 );
 }
 
-static bool pattern_hasItemChildren (SPPattern *pat)
+static bool pattern_hasItemChildren(SPPattern *pat)
 {
-    for (SPObject *child = sp_object_first_child(SP_OBJECT(pat)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
+    bool hasItems = false;
+    for ( SPObject *child = pat->firstChild() ; child && !hasItems; child = child->getNext() ) {
         if (SP_IS_ITEM (child)) {
-            return true;
+            hasItems = true;
         }
     }
-    return false;
+    return hasItems;
 }
 
 cairo_pattern_t*
@@ -1068,7 +1092,7 @@ CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver
     // show items and render them
     for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
         if (pat_i && SP_IS_OBJECT (pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children
-            for (SPObject *child = sp_object_first_child(SP_OBJECT(pat_i)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
+            for ( SPObject *child = pat_i->firstChild() ; child; child = child->getNext() ) {
                 if (SP_IS_ITEM (child)) {
                     SP_ITEM (child)->invoke_show (arena, dkey, SP_ITEM_REFERENCE_FLAGS);
                     _renderer->renderItem(pattern_ctx, SP_ITEM (child));
@@ -1097,7 +1121,7 @@ CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver
     // hide all items
     for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) {
         if (pat_i && SP_IS_OBJECT (pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children
-            for (SPObject *child = sp_object_first_child(SP_OBJECT(pat_i)) ; child != NULL; child = SP_OBJECT_NEXT(child) ) {
+            for ( SPObject *child = pat_i->firstChild() ; child; child = child->getNext() ) {
                 if (SP_IS_ITEM (child)) {
                     SP_ITEM (child)->invoke_hide (dkey);
                 }
@@ -1265,7 +1289,7 @@ CairoRenderContext::_setStrokeStyle(SPStyle const *style, NRRect const *pbox)
 
         cairo_set_source_rgba(_cr, rgb[0], rgb[1], rgb[2], alpha);
     } else {
-        g_assert( style->fill.isPaintserver()
+        g_assert( style->stroke.isPaintserver()
                   || SP_IS_GRADIENT(SP_STYLE_STROKE_SERVER(style))
                   || SP_IS_PATTERN(SP_STYLE_STROKE_SERVER(style)) );
 
@@ -1396,9 +1420,20 @@ CairoRenderContext::renderImage(guchar *px, unsigned int w, unsigned int h, unsi
     if (_render_mode == RENDER_MODE_CLIP)
         return true;
 
-    guchar* px_rgba = (guchar*)g_malloc(4 * w * h);
-    if (!px_rgba)
+    guchar* px_rgba = NULL;
+    guint64 size = 4L * (guint64)w * (guint64)h;
+
+    if(size < (guint64)G_MAXSIZE) {
+        px_rgba = (guchar*)g_try_malloc(4 * w * h);
+        if (!px_rgba) {
+            g_warning ("Could not allocate %lu bytes for pixel buffer!", (long unsigned) size);
+            return false;
+        }
+    } else {
+        g_warning ("the requested memory exceeds the system limit");
         return false;
+    }
+
 
     float opacity;
     if (_state->merge_opacity)
@@ -1408,15 +1443,16 @@ CairoRenderContext::renderImage(guchar *px, unsigned int w, unsigned int h, unsi
 
     // make a copy of the original pixbuf with premultiplied alpha
     // if we pass the original pixbuf it will get messed up
+    /// @todo optimize this code, it costs a lot of time
     for (unsigned i = 0; i < h; i++) {
+        guchar const *src = px + i * rs;
+        guint32 *dst = (guint32 *)(px_rgba + i * rs);
        for (unsigned j = 0; j < w; j++) {
-            guchar const *src = px + i * rs + j * 4;
-            guint32 *dst = (guint32 *)(px_rgba + i * rs + j * 4);
             guchar r, g, b, alpha_dst;
 
             // calculate opacity-modified alpha
             alpha_dst = src[3];
-            if (opacity != 1.0 && _vector_based_target)
+            if ((opacity != 1.0) && _vector_based_target)
                 alpha_dst = (guchar)ceil((float)alpha_dst * opacity);
 
             // premul alpha (needed because this will be undone by cairo-pdf)
@@ -1425,6 +1461,9 @@ CairoRenderContext::renderImage(guchar *px, unsigned int w, unsigned int h, unsi
             b = src[2]*alpha_dst/255;
 
             *dst = (((alpha_dst) << 24) | (((r)) << 16) | (((g)) << 8) | (b));
+
+            dst++;  // pointer to 4byte variables
+            src += 4;   // pointer to 1byte variables
        }
     }
 
@@ -1473,22 +1512,28 @@ CairoRenderContext::_showGlyphs(cairo_t *cr, PangoFont *font, std::vector<CairoG
     cairo_glyph_t glyph_array[GLYPH_ARRAY_SIZE];
     cairo_glyph_t *glyphs = glyph_array;
     unsigned int num_glyphs = glyphtext.size();
-    if (num_glyphs > GLYPH_ARRAY_SIZE)
-        glyphs = (cairo_glyph_t*)g_malloc(sizeof(cairo_glyph_t) * num_glyphs);
+    if (num_glyphs > GLYPH_ARRAY_SIZE) {
+        glyphs = (cairo_glyph_t*)g_try_malloc(sizeof(cairo_glyph_t) * num_glyphs);
+        if(glyphs == NULL) {
+            g_warning("CairorenderContext::_showGlyphs: can not allocate memory for %d glyphs.", num_glyphs);
+            return 0;
+        }
+    }
 
     unsigned int num_invalid_glyphs = 0;
-    unsigned int i = 0;
+    unsigned int i = 0; // is a counter for indexing the glyphs array, only counts the valid glyphs
     for (std::vector<CairoGlyphInfo>::const_iterator it_info = glyphtext.begin() ; it_info != glyphtext.end() ; it_info++) {
         // skip glyphs which are PANGO_GLYPH_EMPTY (0x0FFFFFFF)
         // or have the PANGO_GLYPH_UNKNOWN_FLAG (0x10000000) set
         if (it_info->index == 0x0FFFFFFF || it_info->index & 0x10000000) {
             TRACE(("INVALID GLYPH found\n"));
+            g_message("Invalid glyph found, continuing...");
             num_invalid_glyphs++;
             continue;
         }
-        glyphs[i - num_invalid_glyphs].index = it_info->index;
-        glyphs[i - num_invalid_glyphs].x = it_info->x;
-        glyphs[i - num_invalid_glyphs].y = it_info->y;
+        glyphs[i].index = it_info->index;
+        glyphs[i].x     = it_info->x;
+        glyphs[i].y     = it_info->y;
         i++;
     }
 
@@ -1676,4 +1721,4 @@ _write_callback(void *closure, const unsigned char *data, unsigned int length)
   fill-column:99
   End:
 */
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :