diff options
| author | Krzysztof Kosi??ski <tweenk.pl@gmail.com> | 2011-07-09 23:03:55 +0000 |
|---|---|---|
| committer | Krzysztof KosiĆski <tweenk.pl@gmail.com> | 2011-07-09 23:03:55 +0000 |
| commit | f8a34926de0258e7b82ec5336aa394834f42b55b (patch) | |
| tree | eee9e532b6a4e85e2c6b9412a4c39ff8ef0ed764 /src | |
| parent | Add SPCanvasArena caching layer. Currently breaks for clipped groups (diff) | |
| download | inkscape-f8a34926de0258e7b82ec5336aa394834f42b55b.tar.gz inkscape-f8a34926de0258e7b82ec5336aa394834f42b55b.zip | |
Redesign the rendering pipeline. Clipping paths are now rasterized.
This fixes breakage related to clipped groups and correctly
handles nested clipping paths. Also add the ability to use text
objects as clipping paths.
(bzr r10347.1.7)
Diffstat (limited to 'src')
| -rw-r--r-- | src/display/nr-arena-glyphs.cpp | 31 | ||||
| -rw-r--r-- | src/display/nr-arena-group.cpp | 1 | ||||
| -rw-r--r-- | src/display/nr-arena-item.cpp | 206 | ||||
| -rw-r--r-- | src/display/nr-arena-shape.cpp | 25 |
4 files changed, 154 insertions, 109 deletions
diff --git a/src/display/nr-arena-glyphs.cpp b/src/display/nr-arena-glyphs.cpp index 0e20f0ddb..185551d31 100644 --- a/src/display/nr-arena-glyphs.cpp +++ b/src/display/nr-arena-glyphs.cpp @@ -282,7 +282,8 @@ nr_arena_glyphs_group_update(NRArenaItem *item, NRRectL *area, NRGC *gc, guint s } -static unsigned int nr_arena_glyphs_group_render(cairo_t *ct, NRArenaItem *item, NRRectL *area, NRPixBlock * /*pb*/, unsigned int /*flags*/) +static unsigned int +nr_arena_glyphs_group_render(cairo_t *ct, NRArenaItem *item, NRRectL *area, NRPixBlock * /*pb*/, unsigned int /*flags*/) { NRArenaItem *child = 0; @@ -309,9 +310,11 @@ static unsigned int nr_arena_glyphs_group_render(cairo_t *ct, NRArenaItem *item, Geom::Affine transform = g->g_transform * group->ctm; cairo_new_path(ct); + cairo_save(ct); ink_cairo_transform(ct, transform); feed_pathvector_to_cairo (ct, *pathv); cairo_fill(ct); + cairo_restore(ct); } return item->state; @@ -352,20 +355,26 @@ static unsigned int nr_arena_glyphs_group_render(cairo_t *ct, NRArenaItem *item, return item->state; } -static unsigned int nr_arena_glyphs_group_clip(cairo_t * /*ct*/, NRArenaItem *item, NRRectL * /*area*/) +static unsigned int nr_arena_glyphs_group_clip(cairo_t *ct, NRArenaItem *item, NRRectL * /*area*/) { - //NRArenaGroup *group = NR_ARENA_GROUP(item); + NRArenaGroup *ggroup = NR_ARENA_GLYPHS_GROUP(item); + + cairo_save(ct); + ink_cairo_transform(ct, ggroup->ctm); - guint ret = item->state; + for (NRArenaItem *child = ggroup->children; child != NULL; child = child->next) { + NRArenaGlyphs *g = NR_ARENA_GLYPHS(child); + Geom::PathVector const &pathv = *g->font->PathVector(g->glyph); - // Render children fill mask - /* - for (NRArenaItem *child = group->children; child != NULL; child = child->next) { - ret = nr_arena_glyphs_fill_mask(NR_ARENA_GLYPHS(child), area, pb); - if (!(ret & NR_ARENA_ITEM_STATE_RENDER)) return ret; - }*/ + cairo_save(ct); + ink_cairo_transform(ct, g->g_transform); + feed_pathvector_to_cairo(ct, pathv); + cairo_fill(ct); + cairo_restore(ct); + } + cairo_restore(ct); - return ret; + return item->state; } static NRArenaItem * diff --git a/src/display/nr-arena-group.cpp b/src/display/nr-arena-group.cpp index d1e6869aa..5f11c1a6b 100644 --- a/src/display/nr-arena-group.cpp +++ b/src/display/nr-arena-group.cpp @@ -237,7 +237,6 @@ nr_arena_group_clip (cairo_t *ct, NRArenaItem *item, NRRectL *area) unsigned int ret = item->state; - /* Just compose children into parent buffer */ for (NRArenaItem *child = group->children; child != NULL; child = child->next) { ret = nr_arena_item_invoke_clip (ct, child, area); if (ret & NR_ARENA_ITEM_STATE_INVALID) break; diff --git a/src/display/nr-arena-item.cpp b/src/display/nr-arena-item.cpp index 9c7af1077..534591f82 100644 --- a/src/display/nr-arena-item.cpp +++ b/src/display/nr-arena-item.cpp @@ -392,119 +392,138 @@ nr_arena_item_invoke_render (cairo_t *ct, NRArenaItem *item, NRRectL const *area using namespace Inkscape; - // clipping and masks - unsigned int state; - - cairo_t *this_ct = ct; - NRRectL *this_area = const_cast<NRRectL*>(area); + unsigned state; + unsigned retstate; + // determine whether this shape needs intermediate rendering. bool needs_intermediate_rendering = false; bool &nir = needs_intermediate_rendering; bool needs_opacity = (item->opacity != 255 && !item->render_opacity); // this item needs an intermediate rendering if: - nir |= (item->mask != NULL); // 1. it has a mask - nir |= (item->filter != NULL && filter); // 2. it has a filter - nir |= needs_opacity; // 3. it is non-opaque + nir |= (item->clip != NULL); // 1. it has a clipping path + nir |= (item->mask != NULL); // 2. it has a mask + nir |= (item->filter != NULL && filter); // 3. it has a filter + nir |= needs_opacity; // 4. it is non-opaque double opacity = static_cast<double>(item->opacity) / 255.0; - if (needs_intermediate_rendering) { - cairo_surface_t *intermediate = cairo_surface_create_similar( - cairo_get_target(ct), CAIRO_CONTENT_COLOR_ALPHA, - carea.x1 - carea.x0, carea.y1 - carea.y0); - this_ct = cairo_create(intermediate); - cairo_translate(this_ct, -carea.x0, -carea.y0); - this_area = &carea; - cairo_surface_destroy(intermediate); // the surface will be held in memory by this_ct - } else { - cairo_reference(this_ct); - } - - // The pipeline needs to be different for filters. - // First we render the item into an intermediate surface. Then the filter rotates - // the surface to user coordinates (if necessary) and runs the rendering. - // Once that's done we retrieve the result, rotating it back to screen coords. - // Clipping and masking happens after the filter result is ready. - if (item->filter && filter) { - } - - Cairo::Context cct(this_ct, true); - Cairo::Context base_ct(ct); - Cairo::RefPtr<Cairo::Pattern> mask; - CairoSave clipsave(ct); // RAII for save / restore - CairoGroup maskgroup(this_ct); // RAII for push_group / pop_group - CairoGroup drawgroup(this_ct); - CairoGroup maskopacitygroup(this_ct); + /* How the rendering is done. + * + * There is one intermediate surface onto which the object is rendered. + * Clipping, masking and opacity are done with a mask. + * Here are the algorithms: + * a) no clip, no mask, no opacity: direct rendering. + * b) clip, no mask, no opacity: clipping path is rendered and used as a mask. + * c) no clip, mask, no opacity: mask is rendered, luminance is converted to alpha, + * then it is used as a mask. + * d) no clip, no mask, opacity: paint_with_alpha is used. + * e) clip, mask, no opacity: mask is rendered and its luminance is converted to alpha, + * then the clip is composited with it using the IN operator, the result is used + * as a mask. + * f) clip, no mask, opacity: clipping path is rendered with alpha corresponding + * to the opacity value and used as a mask. + * g) no clip, mask, opacity: like e), but the converted mask is composited with + * an uniform fill + * h) clip, mask, opacity: converted mask is composited with the clipping path + * rendered with alpha corresponding to the opacity using the IN operator + */ - // always clip the base context, not the one on the intermediate surface - // this is because filters must be done before clipping - if (item->clip) { - clipsave.save(); - state = nr_arena_item_invoke_clip(ct, item->clip, const_cast<NRRectL*>(area)); + // handle case a). + if (!needs_intermediate_rendering) { + state = NR_ARENA_ITEM_VIRTUAL (item, render) (ct, item, &carea, pb, flags); if (state & NR_ARENA_ITEM_STATE_INVALID) { item->state |= NR_ARENA_ITEM_STATE_INVALID; return item->state; } - base_ct.clip(); + return item->state | NR_ARENA_ITEM_STATE_RENDER; } - // render mask on the intermediate context and store it + cairo_surface_t *intermediate = cairo_surface_create_similar( + cairo_get_target(ct), CAIRO_CONTENT_COLOR_ALPHA, + carea.x1 - carea.x0, carea.y1 - carea.y0); + cairo_t *ict = cairo_create(intermediate); + cairo_translate(ict, -carea.x0, -carea.y0); + + // now ict draws on the intermediate surface and carea is its area. + // 1. Render the mask if present. Otherwise initialize the intermediate surface to opaque. if (item->mask) { - maskgroup.push_with_content(CAIRO_CONTENT_COLOR_ALPHA); - // handle opacity of a masked object by composing it with the mask - if (needs_opacity) { - maskopacitygroup.push(); - } - state = NR_ARENA_ITEM_VIRTUAL (item->mask, render) (this_ct, item->mask, this_area, pb, flags); + state = NR_ARENA_ITEM_VIRTUAL (item->mask, render) (ict, item->mask, &carea, NULL, flags); if (state & NR_ARENA_ITEM_STATE_INVALID) { - item->state |= NR_ARENA_ITEM_STATE_INVALID; - return item->state; + retstate = (item->state |= NR_ARENA_ITEM_STATE_INVALID); + goto cleanup; } - if (needs_opacity) { - maskopacitygroup.pop_to_source(); - cct.paint_with_alpha(opacity); + ink_cairo_surface_filter(intermediate, intermediate, MaskLuminanceToAlpha()); + } else { + cairo_set_source_rgba(ict, 0,0,0,1); + cairo_paint(ict); + } + + // 2. Render clipping path and composite it with mask + if (item->clip) { + cairo_push_group_with_content(ict, CAIRO_CONTENT_ALPHA); + cairo_set_source_rgba(ict, 0,0,0,opacity); + // Since clip can be combined with opacity, the result could be incorrect + // for overlapping children. To fix this we use the SOURCE operator + // instead of the default OVER + cairo_set_operator(ict, CAIRO_OPERATOR_SOURCE); + state = nr_arena_item_invoke_clip(ict, item->clip, const_cast<NRRectL*>(area)); + cairo_pop_group_to_source(ict); + if (state & NR_ARENA_ITEM_STATE_INVALID) { + retstate = (item->state |= NR_ARENA_ITEM_STATE_INVALID); + goto cleanup; } - mask = maskgroup.popmm(); - // convert luminance to alpha - cairo_pattern_t *p = mask->cobj(); - cairo_surface_t *s; - cairo_pattern_get_surface(p, &s); - ink_cairo_surface_filter(s, s, MaskLuminanceToAlpha()); + cairo_set_operator(ict, CAIRO_OPERATOR_IN); + cairo_paint(ict); + cairo_set_operator(ict, CAIRO_OPERATOR_OVER); } - // render the object (possibly to the intermediate surface) - state = NR_ARENA_ITEM_VIRTUAL (item, render) (this_ct, item, this_area, pb, flags); + // 3. Render object itself + cairo_push_group_with_content(ict, CAIRO_CONTENT_COLOR_ALPHA); + state = NR_ARENA_ITEM_VIRTUAL (item, render) (ict, item, &carea, pb, flags); + cairo_pop_group_to_source(ict); if (state & NR_ARENA_ITEM_STATE_INVALID) { - /* Clean up and return error */ - item->state |= NR_ARENA_ITEM_STATE_INVALID; - return item->state; + retstate = (item->state |= NR_ARENA_ITEM_STATE_INVALID); + goto cleanup; } - // apply filter + // 4. Apply filter if (item->filter && filter) { + // TODO: creating the Cairo context here only to pass it to the filter renderer, + // which calls cairo_get_target almost immediately, is rather silly. + // See whether creating the context can be avoided. + // Could also be fixed in Cairo by fixing cairo_get_target() to return + // the intermediate surface when a group is pushed. + cairo_pattern_t *obj = cairo_get_source(ict); + cairo_surface_t *objs; + cairo_pattern_get_surface(obj, &objs); + cairo_t *tct = cairo_create(objs); + cairo_translate(tct, -carea.x0, -carea.y0); NRRectL bgarea(item->arena->canvasarena->cache_area); - item->filter->render(item, ct, &bgarea, this_ct, &carea); + item->filter->render(item, ct, &bgarea, tct, &carea); + cairo_destroy(tct); } - if (needs_intermediate_rendering) { - cairo_surface_t *intermediate = cairo_get_target(this_ct); - cairo_set_source_surface(ct, intermediate, carea.x0, carea.y0); - if (mask) { - cairo_mask(ct, mask->cobj()); - // opacity of masked objects is handled by premultiplying the mask - } else { - // opacity of non-masked objects must be rendered explicitly - if (needs_opacity) { - cairo_paint_with_alpha(ct, opacity); - } else { - cairo_paint(ct); - } - } - cairo_set_source_rgba(ct,0,0,0,0); + // 5. Render object inside the composited mask + clip + cairo_set_operator(ict, CAIRO_OPERATOR_IN); + if (needs_opacity && !item->clip) { + cairo_paint_with_alpha(ict, opacity); + } else { + cairo_paint(ict); } - return item->state | NR_ARENA_ITEM_STATE_RENDER; + // 6. Paint the completed rendering onto the base context + cairo_set_source_surface(ct, intermediate, carea.x0, carea.y0); + cairo_paint(ct); + cairo_set_source_rgba(ct, 0,0,0,0); + + retstate = item->state | NR_ARENA_ITEM_STATE_RENDER; + + cleanup: + cairo_destroy(ict); + cairo_surface_destroy(intermediate); + + return retstate; } unsigned int @@ -530,15 +549,34 @@ nr_arena_item_invoke_clip (cairo_t *ct, NRArenaItem *item, NRRectL *area) (&item->bbox)->y0, (&item->bbox)->x1, (&item->bbox)->y1); #endif + unsigned retstate = 0; + + // The item itself has a clipping path + // Render the clipping path onto a temporary surface, then composite it with the item + // using the IN operator + if (item->clip) { + cairo_push_group_with_content(ct, CAIRO_CONTENT_ALPHA); + // The source could have had opacity set, but push_group implicitly saves state + cairo_set_source_rgba(ct, 0,0,0,1); + nr_arena_item_invoke_clip(ct, item->clip, area); + } + if (item->visible && nr_rect_l_test_intersect_ptr(area, &item->bbox)) { /* Need render that item */ if (((NRArenaItemClass *) NR_OBJECT_GET_CLASS (item))->clip) { - return ((NRArenaItemClass *) NR_OBJECT_GET_CLASS (item))-> + retstate = ((NRArenaItemClass *) NR_OBJECT_GET_CLASS (item))-> clip (ct, item, area); } } + + if (item->clip) { + cairo_pop_group_to_source(ct); + cairo_set_operator(ct, CAIRO_OPERATOR_IN); + cairo_paint(ct); + cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE); + } - return item->state; + return retstate; } NRArenaItem * diff --git a/src/display/nr-arena-shape.cpp b/src/display/nr-arena-shape.cpp index ff87b5134..9bece05b5 100644 --- a/src/display/nr-arena-shape.cpp +++ b/src/display/nr-arena-shape.cpp @@ -396,22 +396,21 @@ nr_arena_shape_render(cairo_t *ct, NRArenaItem *item, NRRectL *area, NRPixBlock static guint nr_arena_shape_clip(cairo_t *ct, NRArenaItem *item, NRRectL * /*area*/) { - guint result = 0; - - // NOTE: for now this is incorrect, because it doesn't honor clip-rule, - // and will be incorrect for nested clipping paths. NRArenaShape *shape = NR_ARENA_SHAPE(item); if (!shape->curve) { - result = item->state; - } else { - cairo_save(ct); - ink_cairo_transform(ct, shape->ctm); - feed_pathvector_to_cairo(ct, shape->curve->get_pathvector()); - cairo_restore(ct); - - result = item->state; + return item->state; } - return result; + + // TODO: Handling of the clip-rule property / CSS attribute. + // Once the required bits are in SPStyle, this is as trivial as adding a single + // call to cairo_set_fill_rule() before cairo_fill(). + cairo_save(ct); + ink_cairo_transform(ct, shape->ctm); + feed_pathvector_to_cairo(ct, shape->curve->get_pathvector()); + cairo_fill(ct); + cairo_restore(ct); + + return item->state; } static NRArenaItem * |
