diff options
| author | Krzysztof Kosi??ski <tweenk.pl@gmail.com> | 2010-07-12 19:57:46 +0000 |
|---|---|---|
| committer | Krzysztof KosiĆski <tweenk.pl@gmail.com> | 2010-07-12 19:57:46 +0000 |
| commit | 8217b2f74c9db38d7a64ce41eeb6c9659aae1ceb (patch) | |
| tree | af4c9c0bf3e20bf79d801989512ed61d637dfe07 /src | |
| parent | Grid rendering (diff) | |
| download | inkscape-8217b2f74c9db38d7a64ce41eeb6c9659aae1ceb.tar.gz inkscape-8217b2f74c9db38d7a64ce41eeb6c9659aae1ceb.zip | |
Gaussian blur
(bzr r9508.1.15)
Diffstat (limited to 'src')
| -rw-r--r-- | src/display/cairo-utils.cpp | 72 | ||||
| -rw-r--r-- | src/display/cairo-utils.h | 7 | ||||
| -rw-r--r-- | src/display/nr-arena-item.cpp | 34 | ||||
| -rw-r--r-- | src/display/nr-filter-gaussian.cpp | 206 | ||||
| -rw-r--r-- | src/display/nr-filter-gaussian.h | 1 | ||||
| -rw-r--r-- | src/display/nr-filter-primitive.cpp | 7 | ||||
| -rw-r--r-- | src/display/nr-filter-primitive.h | 1 | ||||
| -rw-r--r-- | src/display/nr-filter-slot.cpp | 237 | ||||
| -rw-r--r-- | src/display/nr-filter-slot.h | 63 | ||||
| -rw-r--r-- | src/display/nr-filter.cpp | 72 | ||||
| -rw-r--r-- | src/display/nr-filter.h | 7 |
11 files changed, 574 insertions, 133 deletions
diff --git a/src/display/cairo-utils.cpp b/src/display/cairo-utils.cpp index bb401dc87..a063a62bb 100644 --- a/src/display/cairo-utils.cpp +++ b/src/display/cairo-utils.cpp @@ -329,6 +329,78 @@ ink_cairo_set_source_argb32_pixbuf(cairo_t *ct, GdkPixbuf *pb, double x, double cairo_surface_destroy(pbs); } +/** @brief Create an exact copy of a surface. + * Creates a surface that has the same type, content type, dimensions and contents + * as the specified surface. */ +cairo_surface_t * +ink_cairo_surface_copy(cairo_surface_t *s) +{ + cairo_surface_t *ns = ink_cairo_surface_create_identical(s); + + cairo_t *ct = cairo_create(ns); + cairo_set_source_surface(ct, s, 0, 0); + cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE); + cairo_paint(ct); + cairo_destroy(ct); + + return ns; +} + +/** @brief Create a surface that differs only in pixel content. + * Creates a surface that has the same type, content type and dimensions + * as the specified surface. Pixel contents are not copied. */ +cairo_surface_t * +ink_cairo_surface_create_identical(cairo_surface_t *s) +{ + cairo_surface_t *ns = cairo_surface_create_similar(s, cairo_surface_get_content(s), + ink_cairo_surface_get_width(s), ink_cairo_surface_get_height(s)); + return ns; +} + +/** @brief Extract the alpha channel into a new surface. + * Creates a surface with a content type of CAIRO_CONTENT_ALPHA that contains + * the alpha values of pixels from @a s. */ +cairo_surface_t * +ink_cairo_extract_alpha(cairo_surface_t *s) +{ + cairo_surface_t *alpha = cairo_surface_create_similar(s, CAIRO_CONTENT_ALPHA, + ink_cairo_surface_get_width(s), ink_cairo_surface_get_height(s)); + + cairo_t *ct = cairo_create(alpha); + cairo_set_source_surface(ct, s, 0, 0); + cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE); + cairo_paint(ct); + cairo_destroy(ct); + + return alpha; +} + +cairo_surface_t * +ink_cairo_surface_unshare(cairo_surface_t *s) +{ + if (cairo_surface_get_reference_count(s) > 1) { + return ink_cairo_surface_copy(s); + } else { + cairo_surface_reference(s); + return s; + } +} + +int +ink_cairo_surface_get_width(cairo_surface_t *surface) +{ + // For now only image surface is handled. + // Later add others, e.g. cairo-gl + assert(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE); + return cairo_image_surface_get_width(surface); +} +int +ink_cairo_surface_get_height(cairo_surface_t *surface) +{ + assert(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE); + return cairo_image_surface_get_height(surface); +} + // taken from Cairo sources static inline guint32 premul_alpha(guint32 color, guint32 alpha) { diff --git a/src/display/cairo-utils.h b/src/display/cairo-utils.h index 096dc6046..cfd33330b 100644 --- a/src/display/cairo-utils.h +++ b/src/display/cairo-utils.h @@ -84,6 +84,13 @@ void ink_cairo_transform(cairo_t *ct, Geom::Matrix const &m); void ink_cairo_pattern_set_matrix(cairo_pattern_t *cp, Geom::Matrix const &m); void ink_cairo_set_source_argb32_pixbuf(cairo_t *ct, GdkPixbuf *pb, double x, double y); +cairo_surface_t *ink_cairo_surface_copy(cairo_surface_t *s); +cairo_surface_t *ink_cairo_surface_create_identical(cairo_surface_t *s); +cairo_surface_t *ink_cairo_extract_alpha(cairo_surface_t *s); +cairo_surface_t *ink_cairo_surface_unshare(cairo_surface_t *s); +int ink_cairo_surface_get_width(cairo_surface_t *surface); +int ink_cairo_surface_get_height(cairo_surface_t *surface); + void convert_pixels_pixbuf_to_argb32(guchar *data, int w, int h, int rs); void convert_pixels_argb32_to_pixbuf(guchar *data, int w, int h, int rs); void convert_pixbuf_normal_to_argb32(GdkPixbuf *); diff --git a/src/display/nr-arena-item.cpp b/src/display/nr-arena-item.cpp index f5731c0e3..e7cf08722 100644 --- a/src/display/nr-arena-item.cpp +++ b/src/display/nr-arena-item.cpp @@ -336,6 +336,7 @@ nr_arena_item_invoke_render (cairo_t *ct, NRArenaItem *item, NRRectL const *area return item->state | NR_ARENA_ITEM_STATE_RENDER; // carea is the bounding box for intermediate rendering. + // NOTE: carea might be larger than area, because of filter effects. NRRectL carea; nr_rect_l_intersect (&carea, area, &item->drawbox); if (nr_rect_l_test_empty(carea)) @@ -587,25 +588,39 @@ nr_arena_item_invoke_render (cairo_t *ct, NRArenaItem *item, NRRectL const *area this_ct = cairo_create(intermediate); this_area = &carea; cairo_surface_destroy(intermediate); // the surface will be held in memory by this_ct + } else { + cairo_reference(this_ct); } - Cairo::Context cct(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(this_ct); // RAII for save / restore + 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); - if (item->clip && !(item->filter && filter)) { + // 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(this_ct, item->clip, this_area); + state = nr_arena_item_invoke_clip(ct, item->clip, const_cast<NRRectL*>(area)); if (state & NR_ARENA_ITEM_STATE_INVALID) { item->state |= NR_ARENA_ITEM_STATE_INVALID; return item->state; } - cct.clip(); + base_ct.clip(); } + // render mask on the intermediate context and store it if (item->mask) { maskgroup.push_with_content(CAIRO_CONTENT_ALPHA); // handle opacity of a masked object by composing it with the mask @@ -628,12 +643,20 @@ nr_arena_item_invoke_render (cairo_t *ct, NRArenaItem *item, NRRectL const *area /*if (mask) { drawgroup.push(); }*/ + + // render the object (possibly to the intermediate surface) state = NR_ARENA_ITEM_VIRTUAL (item, render) (this_ct, item, this_area, pb, flags); if (state & NR_ARENA_ITEM_STATE_INVALID) { /* Clean up and return error */ item->state |= NR_ARENA_ITEM_STATE_INVALID; return item->state; } + + // apply filter + if (item->filter && filter) { + item->filter->render(item, ct, area, this_ct, &carea); + } + if (needs_intermediate_rendering) { cairo_surface_t *intermediate = cairo_get_target(this_ct); cairo_set_source_surface(ct, intermediate, carea.x0 - area->x0, carea.y0 - area->y0); @@ -655,7 +678,6 @@ nr_arena_item_invoke_render (cairo_t *ct, NRArenaItem *item, NRRectL const *area } } cairo_set_source_rgba(ct,0,0,0,0); - cairo_destroy(this_ct); } return item->state | NR_ARENA_ITEM_STATE_RENDER; diff --git a/src/display/nr-filter-gaussian.cpp b/src/display/nr-filter-gaussian.cpp index 9509eaef7..2e6bed070 100644 --- a/src/display/nr-filter-gaussian.cpp +++ b/src/display/nr-filter-gaussian.cpp @@ -27,6 +27,7 @@ #include "2geom/isnan.h" +#include "display/cairo-utils.h" #include "display/nr-filter-primitive.h" #include "display/nr-filter-gaussian.h" #include "display/nr-filter-types.h" @@ -284,6 +285,12 @@ filter2D_IIR(PT *const dest, int const dstr1, int const dstr2, int const n1, int const n2, IIRValue const b[N+1], double const M[N*N], IIRValue *const tmpdata[], int const num_threads) { +#if G_BYTE_ORDER == G_LITTLE_ENDIAN + static unsigned int const alpha_PC = PC-1; +#else + static unsigned int const alpha_PC = 0; +#endif + #if HAVE_OPENMP #pragma omp parallel for num_threads(num_threads) #else @@ -319,8 +326,8 @@ filter2D_IIR(PT *const dest, int const dstr1, int const dstr2, calcTriggsSdikaInitialization<PC>(M, u, iplus, iplus, b[0], v); dstimg -= dstr1; if ( PREMULTIPLIED_ALPHA ) { - dstimg[PC-1] = clip_round_cast<PT>(v[0][PC-1]); - for(unsigned int c=0; c<PC-1; c++) dstimg[c] = clip_round_cast<PT>(v[0][c], std::numeric_limits<PT>::min(), dstimg[PC-1]); + dstimg[alpha_PC] = clip_round_cast<PT>(v[0][alpha_PC]); + for(unsigned int c=0; c<alpha_PC; c++) dstimg[c] = clip_round_cast<PT>(v[0][c], std::numeric_limits<PT>::min(), dstimg[alpha_PC]); } else { for(unsigned int c=0; c<PC; c++) dstimg[c] = clip_round_cast<PT>(v[0][c]); } @@ -334,8 +341,8 @@ filter2D_IIR(PT *const dest, int const dstr1, int const dstr2, } dstimg -= dstr1; if ( PREMULTIPLIED_ALPHA ) { - dstimg[PC-1] = clip_round_cast<PT>(v[0][PC-1]); - for(unsigned int c=0; c<PC-1; c++) dstimg[c] = clip_round_cast<PT>(v[0][c], std::numeric_limits<PT>::min(), dstimg[PC-1]); + dstimg[alpha_PC] = clip_round_cast<PT>(v[0][alpha_PC]); + for(unsigned int c=0; c<alpha_PC; c++) dstimg[c] = clip_round_cast<PT>(v[0][c], std::numeric_limits<PT>::min(), dstimg[alpha_PC]); } else { for(unsigned int c=0; c<PC; c++) dstimg[c] = clip_round_cast<PT>(v[0][c]); } @@ -537,6 +544,197 @@ upsample(PT *const dst, int const dstr1, int const dstr2, unsigned int const dn1 } } +static void +gaussian_pass_IIR(Geom::Dim2 d, double deviation, cairo_surface_t *src, cairo_surface_t *dest, + IIRValue **tmpdata, int num_threads) +{ + // Filter variables + IIRValue b[N+1]; // scaling coefficient + filter coefficients (can be 10.21 fixed point) + double bf[N]; // computed filter coefficients + double M[N*N]; // matrix used for initialization procedure (has to be double) + + // Compute filter + calcFilter(deviation, bf); + for(size_t i=0; i<N; i++) bf[i] = -bf[i]; + b[0] = 1; // b[0] == alpha (scaling coefficient) + for(size_t i=0; i<N; i++) { + b[i+1] = bf[i]; + b[0] -= b[i+1]; + } + + // Compute initialization matrix + calcTriggsSdikaM(bf, M); + + int stride = cairo_image_surface_get_stride(src); + int w = cairo_image_surface_get_width(src); + int h = cairo_image_surface_get_height(src); + if (d != Geom::X) std::swap(w, h); + + // Filter + switch (cairo_image_surface_get_format(src)) { + case CAIRO_FORMAT_A8: ///< Grayscale + filter2D_IIR<unsigned char,1,false>( + cairo_image_surface_get_data(dest), d == Geom::X ? 1 : stride, d == Geom::X ? stride : 1, + cairo_image_surface_get_data(src), d == Geom::X ? 1 : stride, d == Geom::X ? stride : 1, + w, h, b, M, tmpdata, num_threads); + break; + case CAIRO_FORMAT_ARGB32: ///< Premultiplied 8 bit RGBA + filter2D_IIR<unsigned char,4,true>( + cairo_image_surface_get_data(dest), d == Geom::X ? 4 : stride, d == Geom::X ? stride : 4, + cairo_image_surface_get_data(src), d == Geom::X ? 4 : stride, d == Geom::X ? stride : 4, + w, h, b, M, tmpdata, num_threads); + break; + default: + assert(false); + }; +} + +static void +gaussian_pass_FIR(Geom::Dim2 d, double deviation, cairo_surface_t *src, cairo_surface_t *dest, + int num_threads) +{ + int scr_len = _effect_area_scr(deviation); + // Filter kernel for x direction + FIRValue kernel[scr_len+1]; + _make_kernel(kernel, deviation); + + int stride = cairo_image_surface_get_stride(src); + int w = cairo_image_surface_get_width(src); + int h = cairo_image_surface_get_height(src); + if (d != Geom::X) std::swap(w, h); + + // Filter (x) + switch (cairo_image_surface_get_format(src)) { + case CAIRO_FORMAT_A8: ///< Grayscale + filter2D_FIR<unsigned char,1>( + cairo_image_surface_get_data(dest), d == Geom::X ? 1 : stride, d == Geom::X ? stride : 1, + cairo_image_surface_get_data(src), d == Geom::X ? 1 : stride, d == Geom::X ? stride : 1, + w, h, kernel, scr_len, num_threads); + break; + case CAIRO_FORMAT_ARGB32: ///< Premultiplied 8 bit RGBA + filter2D_FIR<unsigned char,4>( + cairo_image_surface_get_data(dest), d == Geom::X ? 4 : stride, d == Geom::X ? stride : 4, + cairo_image_surface_get_data(src), d == Geom::X ? 4 : stride, d == Geom::X ? stride : 4, + w, h, kernel, scr_len, num_threads); + break; + default: + assert(false); + }; +} + +void FilterGaussian::render_cairo(FilterSlot &slot) +{ + cairo_surface_t *in = slot.getcairo(_input); + if (!in) return; + + // zero deviation = transparent black as output + if (_deviation_x <= 0 || _deviation_y <= 0) { + cairo_surface_t *blank = ink_cairo_surface_create_identical(in); + slot.set(_output, blank); + cairo_surface_destroy(blank); + return; + } + + Geom::Matrix trans = slot.get_units().get_matrix_primitiveunits2pb(); + + int w_orig = ink_cairo_surface_get_width(in); + int h_orig = ink_cairo_surface_get_height(in); + double deviation_x_orig = _deviation_x * trans.expansionX(); + double deviation_y_orig = _deviation_y * trans.expansionY(); + cairo_format_t fmt = cairo_image_surface_get_format(in); + int bytes_per_pixel = 0; + switch (fmt) { + case CAIRO_FORMAT_A8: + bytes_per_pixel = 1; break; + case CAIRO_FORMAT_ARGB32: + default: + bytes_per_pixel = 4; break; + } + +#if HAVE_OPENMP + int threads = Inkscape::Preferences::get()->getIntLimited("/options/threading/numthreads", omp_get_num_procs(), 1, 256); +#else + int threads = 1; +#endif + + int quality = slot.get_blurquality(); + int x_step = 1 << _effect_subsample_step_log2(deviation_x_orig, quality); + int y_step = 1 << _effect_subsample_step_log2(deviation_y_orig, quality); + bool resampling = x_step > 1 || y_step > 1; + int w_downsampled = resampling ? static_cast<int>(ceil(static_cast<double>(w_orig)/x_step))+1 : w_orig; + int h_downsampled = resampling ? static_cast<int>(ceil(static_cast<double>(h_orig)/y_step))+1 : h_orig; + double deviation_x = deviation_x_orig / x_step; + double deviation_y = deviation_y_orig / y_step; + int scr_len_x = _effect_area_scr(deviation_x); + int scr_len_y = _effect_area_scr(deviation_y); + + // Decide which filter to use for X and Y + // This threshold was determined by trial-and-error for one specific machine, + // so there's a good chance that it's not optimal. + // Whatever you do, don't go below 1 (and preferrably not even below 2), as + // the IIR filter gets unstable there. + bool use_IIR_x = deviation_x > 3; + bool use_IIR_y = deviation_y > 3; + + // Temporary storage for IIR filter + // NOTE: This can be eliminated, but it reduces the precision a bit + IIRValue * tmpdata[threads]; + std::fill_n(tmpdata, threads, (IIRValue*)0); + if ( use_IIR_x || use_IIR_y ) { + for(int i = 0; i < threads; ++i) { + tmpdata[i] = new IIRValue[std::max(w_downsampled,h_downsampled)*bytes_per_pixel]; + } + } + + cairo_surface_t *downsampled = NULL; + if (resampling) { + downsampled = cairo_surface_create_similar(in, cairo_surface_get_content(in), + w_downsampled, h_downsampled); + cairo_t *ct = cairo_create(downsampled); + cairo_scale(ct, static_cast<double>(w_downsampled)/w_orig, static_cast<double>(h_downsampled)/h_orig); + cairo_set_source_surface(ct, in, 0, 0); + cairo_paint(ct); + cairo_destroy(ct); + } else { + downsampled = ink_cairo_surface_copy(in); + } + cairo_surface_flush(downsampled); + + if (scr_len_x > 0) { + if (use_IIR_x) { + gaussian_pass_IIR(Geom::X, deviation_x, downsampled, downsampled, tmpdata, threads); + } else { + gaussian_pass_FIR(Geom::X, deviation_x, downsampled, downsampled, threads); + } + } + + if (scr_len_y > 0) { + if (use_IIR_y) { + gaussian_pass_IIR(Geom::Y, deviation_y, downsampled, downsampled, tmpdata, threads); + } else { + gaussian_pass_FIR(Geom::Y, deviation_y, downsampled, downsampled, threads); + } + } + + cairo_surface_mark_dirty(downsampled); + if (resampling) { + cairo_surface_t *upsampled = cairo_surface_create_similar(downsampled, cairo_surface_get_content(downsampled), + w_orig, h_orig); + cairo_t *ct = cairo_create(upsampled); + cairo_scale(ct, static_cast<double>(w_orig)/w_downsampled, static_cast<double>(h_orig)/h_downsampled); + cairo_set_source_surface(ct, downsampled, 0, 0); + cairo_paint(ct); + cairo_destroy(ct); + + slot.set(_output, upsampled); + cairo_surface_destroy(upsampled); + cairo_surface_destroy(downsampled); + } else { + slot.set(_output, downsampled); + cairo_surface_destroy(downsampled); + } +} + int FilterGaussian::render(FilterSlot &slot, FilterUnits const &units) { // TODO: Meaningful return values? (If they're checked at all.) diff --git a/src/display/nr-filter-gaussian.h b/src/display/nr-filter-gaussian.h index 763e42de2..7bcabdba9 100644 --- a/src/display/nr-filter-gaussian.h +++ b/src/display/nr-filter-gaussian.h @@ -38,6 +38,7 @@ public: static FilterPrimitive *create(); virtual ~FilterGaussian(); + virtual void render_cairo(FilterSlot &slot); virtual int render(FilterSlot &slot, FilterUnits const &units); virtual void area_enlarge(NRRectL &area, Geom::Matrix const &m); virtual FilterTraits get_input_traits(); diff --git a/src/display/nr-filter-primitive.cpp b/src/display/nr-filter-primitive.cpp index b70ae57fe..31e314055 100644 --- a/src/display/nr-filter-primitive.cpp +++ b/src/display/nr-filter-primitive.cpp @@ -37,6 +37,13 @@ FilterPrimitive::~FilterPrimitive() // Nothing to do here } +void FilterPrimitive::render_cairo(FilterSlot &slot) +{ + // passthrough + cairo_surface_t *in = slot.getcairo(_input); + slot.set(_output, in); +} + void FilterPrimitive::area_enlarge(NRRectL &/*area*/, Geom::Matrix const &/*m*/) { // This doesn't need to do anything by default diff --git a/src/display/nr-filter-primitive.h b/src/display/nr-filter-primitive.h index 74b41211b..a7ae0125e 100644 --- a/src/display/nr-filter-primitive.h +++ b/src/display/nr-filter-primitive.h @@ -43,6 +43,7 @@ public: FilterPrimitive(); virtual ~FilterPrimitive(); + virtual void render_cairo(FilterSlot &slot); virtual int render(FilterSlot &slot, FilterUnits const &units) = 0; virtual void area_enlarge(NRRectL &area, Geom::Matrix const &m); diff --git a/src/display/nr-filter-slot.cpp b/src/display/nr-filter-slot.cpp index 7df9ab979..d700cd433 100644 --- a/src/display/nr-filter-slot.cpp +++ b/src/display/nr-filter-slot.cpp @@ -14,6 +14,8 @@ #include <assert.h> #include <string.h> +#include <2geom/transforms.h> +#include "display/cairo-utils.h" #include "display/nr-arena-item.h" #include "display/nr-filter-types.h" #include "display/nr-filter-gaussian.h" @@ -64,95 +66,153 @@ inline static int _min2(const double a, const double b) { namespace Inkscape { namespace Filters { -FilterSlot::FilterSlot(int slots, NRArenaItem const *item) - : _last_out(-1), - filterquality(FILTER_QUALITY_BEST), - blurquality(BLUR_QUALITY_BEST), - _arena_item(item) +FilterSlot::FilterSlot(NRArenaItem *item, cairo_t *bgct, NRRectL const *bgarea, + cairo_surface_t *graphic, NRRectL const *graphicarea, FilterUnits const &u) + : _item(item) + , _source_graphic(graphic) + , _background_ct(bgct) + , _source_graphic_area(graphicarea) + , _background_area(bgarea) + , _units(u) + , _last_out(NR_FILTER_SOURCEGRAPHIC) + , filterquality(FILTER_QUALITY_BEST) + , blurquality(BLUR_QUALITY_BEST) { - _slot_count = ((slots > 0) ? slots : 2); - _slot = new NRPixBlock*[_slot_count]; - _slot_number = new int[_slot_count]; - - for (int i = 0 ; i < _slot_count ; i++) { - _slot[i] = NULL; - _slot_number[i] = NR_FILTER_SLOT_NOT_SET; - } + using Geom::X; + using Geom::Y; + + // compute slot bbox + Geom::Rect bbox( + Geom::Point(_source_graphic_area->x0, _source_graphic_area->y0), + Geom::Point(_source_graphic_area->x1, _source_graphic_area->y1)); + + Geom::Matrix trans = _units.get_matrix_display2pb(); + + Geom::Rect bbox_trans = bbox * trans; + Geom::Point min = bbox_trans.min(); + Geom::Point max = bbox_trans.max(); + _slot_area.x0 = floor(min[X]); + _slot_area.y0 = floor(min[Y]); + _slot_area.x1 = ceil(max[X]); + _slot_area.y1 = ceil(max[Y]); } FilterSlot::~FilterSlot() { - for (int i = 0 ; i < _slot_count ; i++) { - if (_slot[i]) { - nr_pixblock_release(_slot[i]); - delete _slot[i]; - } + for (SlotMap::iterator i = _slots.begin(); i != _slots.end(); ++i) { + cairo_surface_destroy(i->second); } - delete[] _slot; - delete[] _slot_number; } -NRPixBlock *FilterSlot::get(int slot_nr) +cairo_surface_t *FilterSlot::getcairo(int slot_nr) { - int index = _get_index(slot_nr); - assert(index >= 0); + //int index = _get_index(slot_nr); + //assert(index >= 0); + + if (slot_nr == NR_FILTER_SLOT_NOT_SET) + slot_nr = _last_out; + + SlotMap::iterator s = _slots.find(slot_nr); /* If we didn't have the specified image, but we could create it * from the other information we have, let's do that */ - if (_slot[index] == NULL - && (slot_nr == NR_FILTER_SOURCEALPHA + if (s == _slots.end() + && (slot_nr == NR_FILTER_SOURCEGRAPHIC + || slot_nr == NR_FILTER_SOURCEALPHA || slot_nr == NR_FILTER_BACKGROUNDIMAGE || slot_nr == NR_FILTER_BACKGROUNDALPHA || slot_nr == NR_FILTER_FILLPAINT || slot_nr == NR_FILTER_STROKEPAINT)) { - /* If needed, fetch background */ - if (slot_nr == NR_FILTER_BACKGROUNDIMAGE) { - NRPixBlock *pb; - pb = nr_arena_item_get_background(_arena_item); - if (pb) { - pb->empty = false; - this->set(NR_FILTER_BACKGROUNDIMAGE, pb); - } else { - NRPixBlock *source = this->get(NR_FILTER_SOURCEGRAPHIC); - pb = new NRPixBlock(); - if (!pb) return NULL; // Allocation failed - nr_pixblock_setup_fast(pb, source->mode, - source->area.x0, source->area.y0, - source->area.x1, source->area.y1, true); - if (pb->size != NR_PIXBLOCK_SIZE_TINY && pb->data.px == NULL) { - // allocation failed - delete pb; - return NULL; - } - pb->empty = FALSE; - this->set(NR_FILTER_BACKGROUNDIMAGE, pb); - } - } else if (slot_nr == NR_FILTER_SOURCEALPHA) { - /* If only a alpha channel is needed, strip it from full image */ - NRPixBlock *src = get(NR_FILTER_SOURCEGRAPHIC); - NRPixBlock *sa = filter_get_alpha(src); - set(NR_FILTER_SOURCEALPHA, sa); - } else if (slot_nr == NR_FILTER_BACKGROUNDALPHA) { - NRPixBlock *src = get(NR_FILTER_BACKGROUNDIMAGE); - NRPixBlock *ba = filter_get_alpha(src); - set(NR_FILTER_BACKGROUNDALPHA, ba); - } else if (slot_nr == NR_FILTER_FILLPAINT) { - /* When a paint is needed, fetch it from arena item */ - // TODO - } else if (slot_nr == NR_FILTER_STROKEPAINT) { - // TODO + switch (slot_nr) { + case NR_FILTER_SOURCEGRAPHIC: { + cairo_surface_t *tr = _get_transformed_source_graphic(); + _set_internal(NR_FILTER_SOURCEGRAPHIC, tr); + cairo_surface_destroy(tr); + } break; + case NR_FILTER_BACKGROUNDIMAGE: { + // TODO + //cairo_surface_t *bg = _get_transformed_background(); + //_set_internal(NR_FILTER_BACKGROUNDIMAGE, bg); + //cairo_surface_destroy(bg); + } break; + case NR_FILTER_SOURCEALPHA: { + cairo_surface_t *src = getcairo(NR_FILTER_SOURCEGRAPHIC); + cairo_surface_t *alpha = ink_cairo_extract_alpha(src); + _set_internal(NR_FILTER_SOURCEALPHA, alpha); + cairo_surface_destroy(alpha); + } break; + case NR_FILTER_BACKGROUNDALPHA: { + cairo_surface_t *src = getcairo(NR_FILTER_BACKGROUNDIMAGE); + cairo_surface_t *ba = ink_cairo_extract_alpha(src); + _set_internal(NR_FILTER_BACKGROUNDALPHA, ba); + cairo_surface_destroy(ba); + } break; + case NR_FILTER_FILLPAINT: //TODO + case NR_FILTER_STROKEPAINT: //TODO + default: + break; } + s = _slots.find(slot_nr); } - if (_slot[index]) { - _slot[index]->empty = false; + if (s == _slots.end()) { + // create empty surface + // TODO + return NULL; } + return s->second; - assert(slot_nr == NR_FILTER_SLOT_NOT_SET ||_slot_number[index] == slot_nr); - return _slot[index]; + //assert(slot_nr == NR_FILTER_SLOT_NOT_SET ||_slot_number[index] == slot_nr); + //return _slot[index]; +} + +cairo_surface_t *FilterSlot::_get_transformed_source_graphic() +{ + Geom::Matrix trans = _units.get_matrix_display2pb(); + + cairo_surface_t *tsg = cairo_surface_create_similar( + _source_graphic, cairo_surface_get_content(_source_graphic), + _slot_area.x1 - _slot_area.x0, _slot_area.y1 - _slot_area.y0); + cairo_t *tsg_ct = cairo_create(tsg); + + cairo_translate(tsg_ct, -_slot_area.x0, -_slot_area.y0); + ink_cairo_transform(tsg_ct, trans); + cairo_translate(tsg_ct, _source_graphic_area->x0, _source_graphic_area->y0); + cairo_set_source_surface(tsg_ct, _source_graphic, 0, 0); + cairo_set_operator(tsg_ct, CAIRO_OPERATOR_SOURCE); + cairo_paint(tsg_ct); + cairo_destroy(tsg_ct); + + return tsg; +} + +cairo_surface_t *FilterSlot::_get_transformed_background() +{ + return NULL; } +cairo_surface_t *FilterSlot::get_result(int res) +{ + Geom::Matrix trans = _units.get_matrix_pb2display(); + + cairo_surface_t *r = cairo_surface_create_similar(_source_graphic, + cairo_surface_get_content(_source_graphic), + _source_graphic_area->x1 - _source_graphic_area->x0, + _source_graphic_area->y1 - _source_graphic_area->y0); + cairo_t *r_ct = cairo_create(r); + + cairo_translate(r_ct, -_source_graphic_area->x0, -_source_graphic_area->y0); + ink_cairo_transform(r_ct, trans); + cairo_translate(r_ct, _slot_area.x0, _slot_area.y0); + cairo_set_source_surface(r_ct, getcairo(res), 0, 0); + cairo_set_operator(r_ct, CAIRO_OPERATOR_SOURCE); + cairo_paint(r_ct); + cairo_destroy(r_ct); + + return r; +} +/* void FilterSlot::get_final(int slot_nr, NRPixBlock *result) { NRPixBlock *final_usr = get(slot_nr); Geom::Matrix trans = units.get_matrix_pb2display(); @@ -173,10 +233,33 @@ void FilterSlot::get_final(int slot_nr, NRPixBlock *result) { } else { nr_blit_pixblock_pixblock(result, final_usr); } +}*/ + +void FilterSlot::_set_internal(int slot_nr, cairo_surface_t *surface) +{ + // destroy after referencing + // this way assigning a surface to a slot it already occupies will not cause errors + cairo_surface_reference(surface); + + SlotMap::iterator s = _slots.find(slot_nr); + if (s != _slots.end()) { + cairo_surface_destroy(s->second); + } + + _slots[slot_nr] = surface; } -void FilterSlot::set(int slot_nr, NRPixBlock *pb) +void FilterSlot::set(int slot_nr, cairo_surface_t *surface) { + g_return_if_fail(surface != NULL); + + if (slot_nr == NR_FILTER_SLOT_NOT_SET) + slot_nr = NR_FILTER_UNNAMED_SLOT; + + _set_internal(slot_nr, surface); + _last_out = slot_nr; + +#if 0 /* Unnamed slot is for saving filter primitive results, when parameter * 'result' is not set. Only the filter immediately after this one * can access unnamed results, so we don't have to worry about overwriting @@ -216,6 +299,11 @@ void FilterSlot::set(int slot_nr, NRPixBlock *pb) trans[1] * x1 + trans[3] * y0 + trans[5], trans[1] * x1 + trans[3] * y1 + trans[5]); + cairo_surface_t *trans_s = cairo_surface_create_similar(s, + CAIRO_CONTENT_COLOR, max_x - min_x, max_y - min_y); + cairo_t *ct = cairo_create(trans_s); + + nr_pixblock_setup_fast(trans_pb, pb->mode, min_x, min_y, max_x, max_y, true); @@ -270,25 +358,24 @@ void FilterSlot::set(int slot_nr, NRPixBlock *pb) } _slot[index] = pb; _last_out = index; +#endif } int FilterSlot::get_slot_count() { + return _slots.size(); + /* int seek = _slot_count; do { seek--; } while (!_slot[seek] && _slot_number[seek] == NR_FILTER_SLOT_NOT_SET); - return seek + 1; -} - -NRArenaItem const* FilterSlot::get_arenaitem() -{ - return _arena_item; + return seek + 1;*/ } int FilterSlot::_get_index(int slot_nr) { +#if 0 assert(slot_nr >= 0 || slot_nr == NR_FILTER_SLOT_NOT_SET || slot_nr == NR_FILTER_SOURCEGRAPHIC || @@ -340,10 +427,8 @@ int FilterSlot::_get_index(int slot_nr) index = seek + 1; } return index; -} - -void FilterSlot::set_units(FilterUnits const &units) { - this->units = units; +#endif + return 0; } void FilterSlot::set_quality(FilterQuality const q) { diff --git a/src/display/nr-filter-slot.h b/src/display/nr-filter-slot.h index 8d7a82d2d..92f66ddf8 100644 --- a/src/display/nr-filter-slot.h +++ b/src/display/nr-filter-slot.h @@ -14,6 +14,8 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ +#include <map> +#include <cairo.h> #include "libnr/nr-pixblock.h" #include "display/nr-filter-types.h" #include "display/nr-filter-units.h" @@ -26,13 +28,11 @@ namespace Filters { class FilterSlot { public: /** Creates a new FilterSlot object. - * First parameter specifies the amount of slots this SilterSlot - * should reserve beforehand. If a negative number is given, - * two slots will be reserved. - * Second parameter specifies the arena item, which should be used + * Parameter specifies the surface which should be used * for background accesses from filters. */ - FilterSlot(int slots, NRArenaItem const *item); + FilterSlot(NRArenaItem *item, cairo_t *bgct, NRRectL const *bgarea, + cairo_surface_t *graphic, NRRectL const *graphicarea, FilterUnits const &u); /** Destroys the FilterSlot object and all its contents */ virtual ~FilterSlot(); @@ -45,15 +45,8 @@ public: * If the defined filter slot is not set before, this function * returns NULL. Also, that filter slot is created in process. */ - NRPixBlock *get(int slot); - - /** Gets the final result from this filter. - * The result is fetched from the specified slot, see description of - * method get for valid values. The pixblock 'result' will be modified - * to contain the result image, ready to be used in the rest of rendering - * pipeline - */ - void get_final(int slot, NRPixBlock *result); + cairo_surface_t *getcairo(int slot); + NRPixBlock *get(int slot) { return NULL; } /** Sets or re-sets the pixblock associated with given slot. * If there was a pixblock already assigned with this slot, @@ -63,16 +56,19 @@ public: * Pixblocks passed to this function should be reserved with * c++ -style new-operator. */ - void set(int slot, NRPixBlock *pb); + void set(int slot, cairo_surface_t *s); + + void set(int, NRPixBlock*){} + + cairo_surface_t *get_result(int slot_nr); + + NRRectL const *get_slot_area(); /** Returns the number of slots in use. */ int get_slot_count(); - /** arenaitem getter method*/ - NRArenaItem const* get_arenaitem(); - /** Sets the unit system to be used for the internal images. */ - void set_units(FilterUnits const &units); + //void set_units(FilterUnits const &units); /** Sets the filtering quality. Affects used interpolation methods */ void set_quality(FilterQuality const q); @@ -83,25 +79,36 @@ public: /** Gets the gaussian filtering quality. Affects used interpolation methods */ int get_blurquality(void); -private: - NRPixBlock **_slot; - int *_slot_number; - int _slot_count; + FilterUnits const &get_units() const { return _units; } +private: + typedef std::map<int, cairo_surface_t *> SlotMap; + SlotMap _slots; + NRArenaItem *_item; + + //Geom::Rect _source_bbox; ///< bounding box of source graphic surface + //Geom::Rect _intermediate_bbox; ///< bounding box of intermediate surfaces + + NRRectL _slot_area; + cairo_surface_t *_source_graphic; + cairo_t *_background_ct; + NRRectL const *_source_graphic_area; + NRRectL const *_background_area; ///< needed to extract background + FilterUnits const &_units; int _last_out; - FilterQuality filterquality; - int blurquality; - NRArenaItem const *_arena_item; - - FilterUnits units; + cairo_surface_t *_get_transformed_source_graphic(); + cairo_surface_t *_get_transformed_background(); + cairo_surface_t *_get_fill_paint(); + cairo_surface_t *_get_stroke_paint(); /** Returns the table index of given slot. If that slot does not exist, * it is created. Table index can be used to read the correct * pixblock from _slot */ int _get_index(int slot); + void _set_internal(int slot, cairo_surface_t *s); }; } /* namespace Filters */ diff --git a/src/display/nr-filter.cpp b/src/display/nr-filter.cpp index 24079be4e..a5b5801b1 100644 --- a/src/display/nr-filter.cpp +++ b/src/display/nr-filter.cpp @@ -15,6 +15,7 @@ #include <cmath> #include <cstring> #include <string> +#include <cairo.h> #include "display/nr-filter.h" #include "display/nr-filter-primitive.h" @@ -129,20 +130,21 @@ Filter::~Filter() } -int Filter::render(NRArenaItem const *item, NRPixBlock *pb, cairo_t *ct) +int Filter::render(NRArenaItem const *item, cairo_t *bgct, NRRectL const *bgarea, cairo_t *graphic, NRRectL const *area) { if (!_primitive[0]) { - // TODO: Should clear the input buffer instead of just returning - return 1; + // when no primitives are defined, clear source graphic + cairo_set_source_rgba(graphic, 0,0,0,0); + cairo_set_operator(graphic, CAIRO_OPERATOR_SOURCE); + cairo_paint(graphic); + cairo_set_operator(graphic, CAIRO_OPERATOR_OVER); + return 1; } FilterQuality const filterquality = (FilterQuality)item->arena->filterquality; int const blurquality = item->arena->blurquality; Geom::Matrix trans = item->ctm; - FilterSlot slot(_slot_count, item); - slot.set_quality(filterquality); - slot.set_blurquality(blurquality); Geom::Rect item_bbox; { @@ -165,11 +167,17 @@ int Filter::render(NRArenaItem const *item, NRPixBlock *pb, cairo_t *ct) units.set_item_bbox(item_bbox); units.set_filter_area(filter_area); - // TODO: with filterRes of 0x0 should return an empty image std::pair<double,double> resolution = _filter_resolution(filter_area, trans, filterquality); - if(!(resolution.first > 0 && resolution.second > 0)) - return 1; + if (!(resolution.first > 0 && resolution.second > 0)) { + // zero resolution - clear source graphic and return + cairo_set_source_rgba(graphic, 0,0,0,0); + cairo_set_operator(graphic, CAIRO_OPERATOR_SOURCE); + cairo_paint(graphic); + cairo_set_operator(graphic, CAIRO_OPERATOR_OVER); + return 1; + } + units.set_resolution(resolution.first, resolution.second); if (_x_pixels > 0) { units.set_automatic_resolution(false); @@ -178,17 +186,44 @@ int Filter::render(NRArenaItem const *item, NRPixBlock *pb, cairo_t *ct) units.set_automatic_resolution(true); } - units.set_paraller(false); + /*units.set_paraller(false); for (int i = 0 ; i < _primitive_count ; i++) { if (_primitive[i]->get_input_traits() & TRAIT_PARALLER) { units.set_paraller(true); break; } + }*/ + units.set_paraller(true); + + FilterSlot slot(const_cast<NRArenaItem*>(item), bgct, bgarea, cairo_get_target(graphic), area, units); + slot.set_quality(filterquality); + slot.set_blurquality(blurquality); + + for (int i = 0 ; i < _primitive_count ; i++) { + _primitive[i]->render_cairo(slot); } - slot.set_units(units); + cairo_surface_t *result = slot.get_result(_output_slot); + cairo_set_source_surface(graphic, result, 0, 0); + cairo_set_operator(graphic, CAIRO_OPERATOR_SOURCE); + cairo_paint(graphic); + cairo_set_operator(graphic, CAIRO_OPERATOR_OVER); + cairo_surface_destroy(result); + + //slot.set_units(units); + + /*cairo_surface_t *in = cairo_surface_create_similar( + cairo_get_target(ct), CAIRO_CONTENT_COLOR_ALPHA, + area->x1 - area->x0, area->y1 - area->y0); + cairo_t *inct = cairo_create(in); + cairo_translate(inct, -area->x0, -area->y0); + cairo_set_source_surface(inct, cairo_get_target(ct), 0, 0); + cairo_paint(inct); + slot.set(NR_FILTER_SOURCEGRAPHIC, in); + cairo_destroy(inct); + cairo_surface_destroy(in);*/ - NRPixBlock *in = new NRPixBlock; + /*NRPixBlock *in = new NRPixBlock; nr_pixblock_setup_fast(in, pb->mode, pb->area.x0, pb->area.y0, pb->area.x1, pb->area.y1, true); if (in->size != NR_PIXBLOCK_SIZE_TINY && in->data.px == NULL) { @@ -197,10 +232,10 @@ int Filter::render(NRArenaItem const *item, NRPixBlock *pb, cairo_t *ct) } nr_blit_pixblock_pixblock(in, pb); in->empty = FALSE; - slot.set(NR_FILTER_SOURCEGRAPHIC, in); + slot.set(NR_FILTER_SOURCEGRAPHIC, in);*/ // Check that we are rendering a non-empty area - in = slot.get(NR_FILTER_SOURCEGRAPHIC); + /*in = slot.get(NR_FILTER_SOURCEGRAPHIC); if (in->area.x1 - in->area.x0 <= 0 || in->area.y1 - in->area.y0 <= 0) { if (in->area.x1 - in->area.x0 < 0 || in->area.y1 - in->area.y0 < 0) { g_warning("Inkscape::Filters::Filter::render: negative area! (%d, %d) (%d, %d)", @@ -209,17 +244,18 @@ int Filter::render(NRArenaItem const *item, NRPixBlock *pb, cairo_t *ct) return 0; } in = NULL; // in is now handled by FilterSlot, we should not touch it + */ - for (int i = 0 ; i < _primitive_count ; i++) { + /*for (int i = 0 ; i < _primitive_count ; i++) { _primitive[i]->render(slot, units); - } + }*/ - slot.get_final(_output_slot, pb); + //slot.get_final(_output_slot, ct, area); // Take note of the amount of used image slots // -> next time this filter is rendered, we can reserve enough slots // immediately - _slot_count = slot.get_slot_count(); + //_slot_count = slot.get_slot_count(); return 0; } diff --git a/src/display/nr-filter.h b/src/display/nr-filter.h index 08d0254d1..cd805043c 100644 --- a/src/display/nr-filter.h +++ b/src/display/nr-filter.h @@ -30,7 +30,12 @@ namespace Filters { class Filter : public Inkscape::GC::Managed<> { public: - int render(NRArenaItem const *item, NRPixBlock *pb, cairo_t *ct); + /** Given background state from @a bgct and an intermediate rendering from the surface + * backing @a graphic, modify the contents of the surface backing @a graphic to represent + * the results of filter rendering. @a bgarea and @a area specify bounding boxes + * of both surfaces in world coordinates; Cairo contexts are assumed to be in default state + * (0,0 = surface origin, no path, OVER operator) */ + int render(NRArenaItem const *item, cairo_t *bgct, NRRectL const *bgarea, cairo_t *graphic, NRRectL const *area); /** * Creates a new filter primitive under this filter object. |
