From bc44e67acb1457725817238120bcef0d1f1ada04 Mon Sep 17 00:00:00 2001 From: kiirala Date: Sat, 18 Aug 2007 11:00:22 +0000 Subject: [PATCH] Improved handling bounding boxes and rotated/skewed coordinates in filters --- src/display/Makefile_insert | 2 + src/display/nr-arena-item.cpp | 9 + src/display/nr-arena-item.h | 4 + src/display/nr-filter-gaussian.cpp | 4 + src/display/nr-filter-gaussian.h | 1 + src/display/nr-filter-primitive.cpp | 4 + src/display/nr-filter-primitive.h | 26 +++ src/display/nr-filter-slot.cpp | 129 ++++++++++++++- src/display/nr-filter-slot.h | 16 +- src/display/nr-filter-units.cpp | 154 ++++++++++++++++++ src/display/nr-filter-units.h | 116 +++++++++++++ src/display/nr-filter.cpp | 244 +++++++++++----------------- src/display/nr-filter.h | 8 +- src/display/pixblock-scaler.cpp | 87 +++++++++- src/sp-item.cpp | 11 ++ 15 files changed, 662 insertions(+), 153 deletions(-) create mode 100644 src/display/nr-filter-units.cpp create mode 100644 src/display/nr-filter-units.h diff --git a/src/display/Makefile_insert b/src/display/Makefile_insert index de6e64c4a..b43fcb826 100644 --- a/src/display/Makefile_insert +++ b/src/display/Makefile_insert @@ -107,6 +107,8 @@ display_libspdisplay_a_SOURCES = \ display/nr-filter-turbulence.h \ display/nr-filter-pixops.h \ display/nr-filter-types.h \ + display/nr-filter-units.h \ + display/nr-filter-units.cpp \ display/nr-filter-utils.h \ display/nr-filter-utils.cpp \ display/pixblock-scaler.cpp \ diff --git a/src/display/nr-arena-item.cpp b/src/display/nr-arena-item.cpp index 5ebd94a9b..ddde4102e 100644 --- a/src/display/nr-arena-item.cpp +++ b/src/display/nr-arena-item.cpp @@ -830,6 +830,15 @@ nr_arena_item_set_order (NRArenaItem *item, int order) nr_arena_item_set_child_position (item->parent, item, ref); } +void +nr_arena_item_set_item_bbox (NRArenaItem *item, NR::Maybe &bbox) +{ + nr_return_if_fail(item != NULL); + nr_return_if_fail(NR_IS_ARENA_ITEM(item)); + + item->item_bbox = bbox; +} + /** Returns a background image for use with filter effects. */ NRPixBlock * nr_arena_item_get_background (NRArenaItem const *item, int depth) diff --git a/src/display/nr-arena-item.h b/src/display/nr-arena-item.h index 103315c5e..801657cf3 100644 --- a/src/display/nr-arena-item.h +++ b/src/display/nr-arena-item.h @@ -89,6 +89,9 @@ struct NRArenaItem : public NRObject { /* BBox in grid coordinates */ NRRectL bbox; + /* BBox in item coordinates - this should be a bounding box as + * specified in SVG standard. Required by filters. */ + NR::Maybe item_bbox; /* Our affine */ NRMatrix *transform; /* Clip item */ @@ -174,6 +177,7 @@ void nr_arena_item_set_visible (NRArenaItem *item, unsigned int visible); void nr_arena_item_set_clip (NRArenaItem *item, NRArenaItem *clip); void nr_arena_item_set_mask (NRArenaItem *item, NRArenaItem *mask); void nr_arena_item_set_order (NRArenaItem *item, int order); +void nr_arena_item_set_item_bbox (NRArenaItem *item, NR::Maybe &bbox); NRPixBlock *nr_arena_item_get_background (NRArenaItem const *item, int depth = 0); diff --git a/src/display/nr-filter-gaussian.cpp b/src/display/nr-filter-gaussian.cpp index da8d9f71c..5d79b1c3e 100644 --- a/src/display/nr-filter-gaussian.cpp +++ b/src/display/nr-filter-gaussian.cpp @@ -777,6 +777,10 @@ void FilterGaussian::area_enlarge(NRRectL &area, Matrix const &trans) area.y1 += area_max; } +FilterTraits FilterGaussian::get_input_traits() { + return TRAIT_PARALLER; +} + void FilterGaussian::set_deviation(double deviation) { if(isFinite(deviation) && deviation >= 0) { diff --git a/src/display/nr-filter-gaussian.h b/src/display/nr-filter-gaussian.h index d4f6c942c..7b2a96ca5 100644 --- a/src/display/nr-filter-gaussian.h +++ b/src/display/nr-filter-gaussian.h @@ -38,6 +38,7 @@ public: virtual int render(FilterSlot &slot, Matrix const &trans); virtual void area_enlarge(NRRectL &area, Matrix const &m); + virtual FilterTraits get_input_traits(); /** * Set the standard deviation value for gaussian blur. Deviation along diff --git a/src/display/nr-filter-primitive.cpp b/src/display/nr-filter-primitive.cpp index ec8364542..14f5c0078 100644 --- a/src/display/nr-filter-primitive.cpp +++ b/src/display/nr-filter-primitive.cpp @@ -64,6 +64,10 @@ void FilterPrimitive::set_output(int slot) { if (slot >= 0) _output = slot; } +FilterTraits FilterPrimitive::get_input_traits() { + return TRAIT_ANYTHING; +} + } /* namespace NR */ /* diff --git a/src/display/nr-filter-primitive.h b/src/display/nr-filter-primitive.h index ac747cb55..e1e5984e6 100644 --- a/src/display/nr-filter-primitive.h +++ b/src/display/nr-filter-primitive.h @@ -20,6 +20,24 @@ namespace NR { +/* + * Different filter effects need different types of inputs. This is what + * traits are used for: one can specify, what special restrictions + * there are for inputs. + * + * Example: gaussian blur requires that x- and y-axis of input image + * are paraller to blurred object's x- and y-axis, respectively. + * Otherwise blur wouldn't rotate with the object. + * + * Values here should be powers of two, so these can be used as bitfield. + * That is: any combination ef existing traits can be specified. (excluding + * TRAIT_ANYTHING, which is alias for no traits defined) + */ +enum FilterTraits { + TRAIT_ANYTHING = 0, + TRAIT_PARALLER = 1 +}; + class FilterPrimitive { public: FilterPrimitive(); @@ -83,6 +101,14 @@ public: */ void reset_region(); + /** + * Queries the filter, which traits it needs from its input buffers. + * At the time of writing this, only one trait was needed, having + * user coordinate system and input pixelblock coordinates paraller to + * each other. + */ + virtual FilterTraits get_input_traits(); + protected: int _input; int _output; diff --git a/src/display/nr-filter-slot.cpp b/src/display/nr-filter-slot.cpp index 9775a99a7..17ea373ae 100644 --- a/src/display/nr-filter-slot.cpp +++ b/src/display/nr-filter-slot.cpp @@ -8,7 +8,7 @@ * Author: * Niko Kiirala * - * Copyright (C) 2006 Niko Kiirala + * Copyright (C) 2006,2007 Niko Kiirala * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -19,9 +19,48 @@ #include "display/nr-filter-types.h" #include "display/nr-filter-slot.h" #include "display/nr-filter-getalpha.h" +#include "display/nr-filter-units.h" +#include "display/pixblock-scaler.h" +#include "display/pixblock-transform.h" #include "libnr/nr-pixblock.h" #include "libnr/nr-blit.h" +__attribute__ ((const)) +inline static int _max4(const double a, const double b, + const double c, const double d) { + double ret = a; + if (b > ret) ret = b; + if (c > ret) ret = c; + if (d > ret) ret = d; + return (int)round(ret); +} + +__attribute__ ((const)) +inline static int _min4(const double a, const double b, + const double c, const double d) { + double ret = a; + if (b < ret) ret = b; + if (c < ret) ret = c; + if (d < ret) ret = d; + return (int)round(ret); +} + +__attribute__ ((const)) +inline static int _max2(const double a, const double b) { + if (a > b) + return (int)round(a); + else + return (int)round(b); +} + +__attribute__ ((const)) +inline static int _min2(const double a, const double b) { + if (a > b) + return (int)round(b); + else + return (int)round(a); +} + namespace NR { FilterSlot::FilterSlot(int slots, NRArenaItem const *item) @@ -105,16 +144,100 @@ NRPixBlock *FilterSlot::get(int slot_nr) } } + _slot[index]->empty = false; assert(slot_nr == NR_FILTER_SLOT_NOT_SET ||_slot_number[index] == slot_nr); return _slot[index]; } +void FilterSlot::get_final(int slot_nr, NRPixBlock *result) { + NRPixBlock *final_usr = get(slot_nr); + Matrix trans = units.get_matrix_pb2display(); + + int size = (result->area.x1 - result->area.x0) + * (result->area.y1 - result->area.y0) + * NR_PIXBLOCK_BPP(result); + memset(NR_PIXBLOCK_PX(result), 0, size); + + if (fabs(trans[1]) > 1e-6 || fabs(trans[2]) > 1e-6) { + transform_nearest(result, final_usr, trans); + } else if (fabs(trans[0] - 1) > 1e-6 || fabs(trans[3] - 1) > 1e-6) { + scale_bicubic(result, final_usr); + } else { + nr_blit_pixblock_pixblock(result, final_usr); + } +} + void FilterSlot::set(int slot_nr, NRPixBlock *pb) { int index = _get_index(slot_nr); assert(index >= 0); assert(slot_nr == NR_FILTER_SLOT_NOT_SET ||_slot_number[index] == slot_nr); + if (slot_nr == NR_FILTER_SOURCEGRAPHIC || slot_nr == NR_FILTER_BACKGROUNDIMAGE) { + Matrix trans = units.get_matrix_display2pb(); + if (fabs(trans[1]) > 1e-6 || fabs(trans[2]) > 1e-6) { + NRPixBlock *trans_pb = new NRPixBlock; + int x0 = pb->area.x0; + int y0 = pb->area.y0; + int x1 = pb->area.x1; + int y1 = pb->area.y1; + int min_x = _min4(trans[0] * x0 + trans[2] * y0 + trans[4], + trans[0] * x0 + trans[2] * y1 + trans[4], + trans[0] * x1 + trans[2] * y0 + trans[4], + trans[0] * x1 + trans[2] * y1 + trans[4]); + int max_x = _max4(trans[0] * x0 + trans[2] * y0 + trans[4], + trans[0] * x0 + trans[2] * y1 + trans[4], + trans[0] * x1 + trans[2] * y0 + trans[4], + trans[0] * x1 + trans[2] * y1 + trans[4]); + int min_y = _min4(trans[1] * x0 + trans[3] * y0 + trans[5], + trans[1] * x0 + trans[3] * y1 + trans[5], + trans[1] * x1 + trans[3] * y0 + trans[5], + trans[1] * x1 + trans[3] * y1 + trans[5]); + int max_y = _max4(trans[1] * x0 + trans[3] * y0 + trans[5], + trans[1] * x0 + trans[3] * y1 + trans[5], + trans[1] * x1 + trans[3] * y0 + trans[5], + trans[1] * x1 + trans[3] * y1 + trans[5]); + + nr_pixblock_setup_fast(trans_pb, pb->mode, + min_x, min_y, + max_x, max_y, true); + if (trans_pb->size != NR_PIXBLOCK_SIZE_TINY && trans_pb->data.px == NULL) { + // memory allocation failed + return; + } + transform_nearest(trans_pb, pb, trans); + nr_pixblock_release(pb); + delete pb; + pb = trans_pb; + } else if (fabs(trans[0] - 1) > 1e-6 || fabs(trans[3] - 1) > 1e-6) { + NRPixBlock *trans_pb = new NRPixBlock; + + int x0 = pb->area.x0; + int y0 = pb->area.y0; + int x1 = pb->area.x1; + int y1 = pb->area.y1; + int min_x = _min2(trans[0] * x0 + trans[4], + trans[0] * x1 + trans[4]); + int max_x = _max2(trans[0] * x0 + trans[4], + trans[0] * x1 + trans[4]); + int min_y = _min2(trans[3] * y0 + trans[5], + trans[3] * y1 + trans[5]); + int max_y = _max2(trans[3] * y0 + trans[5], + trans[3] * y1 + trans[5]); + + nr_pixblock_setup_fast(trans_pb, pb->mode, + min_x, min_y, max_x, max_y, true); + if (trans_pb->size != NR_PIXBLOCK_SIZE_TINY && trans_pb->data.px == NULL) { + //memory allocation failed + return; + } + scale_bicubic(trans_pb, pb); + nr_pixblock_release(pb); + delete pb; + pb = trans_pb; + } + } + if(_slot[index]) { nr_pixblock_release(_slot[index]); delete _slot[index]; @@ -192,6 +315,10 @@ int FilterSlot::_get_index(int slot_nr) return index; } +void FilterSlot::set_units(FilterUnits const &units) { + this->units = units; +} + } /* diff --git a/src/display/nr-filter-slot.h b/src/display/nr-filter-slot.h index 8c24a3641..2272c3ae7 100644 --- a/src/display/nr-filter-slot.h +++ b/src/display/nr-filter-slot.h @@ -9,12 +9,13 @@ * Author: * Niko Kiirala * - * Copyright (C) 2006 Niko Kiirala + * Copyright (C) 2006,2007 Niko Kiirala * * Released under GNU GPL, read the file 'COPYING' for more information */ #include "libnr/nr-pixblock.h" +#include "display/nr-filter-units.h" struct NRArenaItem; @@ -44,6 +45,14 @@ public: */ 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); + /** Sets or re-sets the pixblock associated with given slot. * If there was a pixblock already assigned with this slot, * that pixblock is destroyed. @@ -60,6 +69,9 @@ public: /** arenaitem getter method*/ NRArenaItem const* get_arenaitem(); + /** Sets the unit system to be used for the internal images. */ + void set_units(FilterUnits const &units); + private: NRPixBlock **_slot; int *_slot_number; @@ -69,6 +81,8 @@ private: NRArenaItem const *_arena_item; + FilterUnits units; + /** 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 */ diff --git a/src/display/nr-filter-units.cpp b/src/display/nr-filter-units.cpp new file mode 100644 index 000000000..2c84800d6 --- /dev/null +++ b/src/display/nr-filter-units.cpp @@ -0,0 +1,154 @@ +/* + * Utilities for handling coordinate system transformations in filters + * + * Author: + * Niko Kiirala + * + * Copyright (C) 2007 Niko Kiirala + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "display/nr-filter-units.h" +#include "libnr/nr-matrix.h" +#include "libnr/nr-rect.h" +#include "libnr/nr-scale.h" +#include "sp-filter-units.h" + +namespace NR { + +FilterUnits::FilterUnits() : + filterUnits(SP_FILTER_UNITS_OBJECTBOUNDINGBOX), + primitiveUnits(SP_FILTER_UNITS_USERSPACEONUSE), + resolution_x(-1), resolution_y(-1), + paraller_axis(false), automatic_resolution(true) +{} + +FilterUnits::FilterUnits(SPFilterUnits const filterUnits, SPFilterUnits const primitiveUnits) : + filterUnits(filterUnits), primitiveUnits(primitiveUnits), + resolution_x(-1), resolution_y(-1), + paraller_axis(false), automatic_resolution(true) +{} + +void FilterUnits::set_ctm(Matrix const &ctm) { + this->ctm = ctm; +} + +void FilterUnits::set_resolution(double const x_res, double const y_res) { + g_assert(x_res > 0); + g_assert(y_res > 0); + + resolution_x = x_res; + resolution_y = y_res; +} + +void FilterUnits::set_item_bbox(Rect const &bbox) { + item_bbox = bbox; +} + +void FilterUnits::set_filter_area(Rect const &area) { + filter_area = area; +} + +void FilterUnits::set_paraller(bool const paraller) { + paraller_axis = paraller; +} + +void FilterUnits::set_automatic_resolution(bool const automatic) { + automatic_resolution = automatic; +} + +Matrix FilterUnits::get_matrix_user2pb() const { + g_assert(resolution_x > 0); + g_assert(resolution_y > 0); + + Matrix u2pb = ctm; + + if (paraller_axis || !automatic_resolution) { + u2pb[0] = resolution_x / (filter_area.max()[X] - filter_area.min()[X]); + u2pb[1] = 0; + u2pb[2] = 0; + u2pb[3] = resolution_y / (filter_area.max()[Y] - filter_area.min()[Y]); + u2pb[4] = 0; + u2pb[5] = 0; + } + + return u2pb; +} + +Matrix FilterUnits::get_matrix_units2pb(SPFilterUnits units) const { + if (units == SP_FILTER_UNITS_OBJECTBOUNDINGBOX) { + Matrix u2pb = get_matrix_user2pb(); + Point origo(item_bbox.min()); + origo *= u2pb; + Point i_end(item_bbox.max()[X], item_bbox.min()[Y]); + i_end *= u2pb; + Point j_end(item_bbox.min()[X], item_bbox.max()[Y]); + j_end *= u2pb; + + double len_i = sqrt((origo[X] - i_end[X]) * (origo[X] - i_end[X]) + + (origo[Y] - i_end[Y]) * (origo[Y] - i_end[Y])); + double len_j = sqrt((origo[X] - j_end[X]) * (origo[X] - j_end[X]) + + (origo[Y] - j_end[Y]) * (origo[Y] - j_end[Y])); + + /* TODO: make sure that user coordinate system (0,0) is in correct + * place in pixblock coordinates */ + scale scaling(1.0 / len_i, 1.0 / len_j); + u2pb *= scaling; + return u2pb; + } else if (units == SP_FILTER_UNITS_USERSPACEONUSE) { + return get_matrix_user2pb(); + } else { + g_warning("Error in NR::FilterUnits::get_matrix_units2pb: unrecognized value of filterUnits"); + return Matrix(); + } +} + +Matrix FilterUnits::get_matrix_filterunits2pb() const { + return get_matrix_units2pb(filterUnits); +} + +Matrix FilterUnits::get_matrix_primitiveunits2pb() const { + return get_matrix_units2pb(primitiveUnits); +} + +Matrix FilterUnits::get_matrix_display2pb() const { + Matrix d2pb = ctm.inverse(); + d2pb *= get_matrix_user2pb(); + return d2pb; +} + +Matrix FilterUnits::get_matrix_pb2display() const { + Matrix pb2d = get_matrix_user2pb().inverse(); + pb2d *= ctm; + return pb2d; +} + +FilterUnits& FilterUnits::operator=(FilterUnits const &other) { + filterUnits = other.filterUnits; + primitiveUnits = other.primitiveUnits; + resolution_x = other.resolution_x; + resolution_y = other.resolution_y; + paraller_axis = other.paraller_axis; + automatic_resolution = other.automatic_resolution; + ctm = other.ctm; + item_bbox = other.item_bbox; + filter_area = other.filter_area; + return *this; +} + +} // namespace NR + + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/display/nr-filter-units.h b/src/display/nr-filter-units.h new file mode 100644 index 000000000..8ebb50327 --- /dev/null +++ b/src/display/nr-filter-units.h @@ -0,0 +1,116 @@ +#ifndef __NR_FILTER_UNITS_H__ +#define __NR_FILTER_UNITS_H__ + +/* + * Utilities for handling coordinate system transformations in filters + * + * Author: + * Niko Kiirala + * + * Copyright (C) 2007 Niko Kiirala + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "sp-filter-units.h" +#include "libnr/nr-matrix.h" +#include "libnr/nr-rect.h" + +namespace NR { + +class FilterUnits { +public: + FilterUnits(); + FilterUnits(SPFilterUnits const filterUnits, SPFilterUnits const primitiveUnits); + + /** + * Sets the current transformation matrix, i.e. transformation matrix + * from object's user coordinates to screen coordinates + */ + void set_ctm(Matrix const &ctm); + + /** + * Sets the resolution, the filter should be rendered with. + */ + void set_resolution(double const x_res, double const y_res); + + /** + * Sets the item bounding box in user coordinates + */ + void set_item_bbox(Rect const &bbox); + + /** + * Sets the filter effects area in user coordinates + */ + void set_filter_area(Rect const &area); + + /** + * Sets, if x and y axis in pixblock coordinates should be paraller + * to x and y of user coordinates. + */ + void set_paraller(bool const paraller); + + /** + * Sets, if filter resolution is automatic. + * NOTE: even if resolution is automatic, it must be set with + * set_resolution. This only tells, if the set value is automatic. + */ + void set_automatic_resolution(bool const automatic); + + /** + * Gets the user coordinates to pixblock coordinates transformation matrix. + */ + Matrix get_matrix_user2pb() const; + + /** + * Gets the filterUnits to pixblock coordinates transformation matrix. + */ + Matrix get_matrix_filterunits2pb() const; + + /** + * Gets the primitiveUnits to pixblock coordinates transformation matrix. + */ + Matrix get_matrix_primitiveunits2pb() const; + + /** + * Gets the display coordinates to pixblock coordinates transformation + * matrix. + */ + Matrix get_matrix_display2pb() const; + + /** + * Gets the pixblock coordinates to display coordinates transformation + * matrix + */ + Matrix get_matrix_pb2display() const; + + FilterUnits& operator=(FilterUnits const &other); + +private: + Matrix get_matrix_units2pb(SPFilterUnits units) const; + + SPFilterUnits filterUnits, primitiveUnits; + double resolution_x, resolution_y; + bool paraller_axis; + bool automatic_resolution; + Matrix ctm; + Rect item_bbox; + Rect filter_area; + +}; + + +} // namespace NR + + +#endif /* __NR_FILTER_UNITS_H__ */ +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/display/nr-filter.cpp b/src/display/nr-filter.cpp index b97784ec4..2e973b4ec 100644 --- a/src/display/nr-filter.cpp +++ b/src/display/nr-filter.cpp @@ -6,7 +6,7 @@ * Author: * Niko Kiirala * - * Copyright (C) 2006 Niko Kiirala + * Copyright (C) 2006,2007 Niko Kiirala * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -18,8 +18,7 @@ #include "display/nr-filter-primitive.h" #include "display/nr-filter-slot.h" #include "display/nr-filter-types.h" -#include "display/pixblock-scaler.h" -#include "display/pixblock-transform.h" +#include "display/nr-filter-units.h" #include "display/nr-filter-blend.h" #include "display/nr-filter-composite.h" @@ -50,26 +49,6 @@ using Inkscape::round; #endif -__attribute__ ((const)) -inline static int _max4(const double a, const double b, - const double c, const double d) { - double ret = a; - if (b > ret) ret = b; - if (c > ret) ret = c; - if (d > ret) ret = d; - return (int)round(ret); -} - -__attribute__ ((const)) -inline static int _min4(const double a, const double b, - const double c, const double d) { - double ret = a; - if (b < ret) ret = b; - if (c < ret) ret = c; - if (d < ret) ret = d; - return (int)round(ret); -} - namespace NR { Filter::Filter() @@ -125,131 +104,88 @@ Filter::~Filter() int Filter::render(NRArenaItem const *item, NRPixBlock *pb) { - if(!_primitive[0]) { // if there are no primitives, do nothing + if(!_primitive[0]) { + // TODO: Should clear the input buffer instead of just returning return 0; } Matrix trans = *item->ctm; - Matrix paraller_trans = trans; - bool notparaller = false; FilterSlot slot(_slot_count, item); - NRPixBlock *in = new NRPixBlock; - // If filter effects region is not paraller to viewport, - // we must first undo the rotation / shear. - // It will be redone after filtering. - // If there is only scaling, let's skip this, as it will not make - // a difference with gaussian blur. - // TODO: This should be done in FilterSlot and for all input images - if (fabs(trans[1]) > 1e-6 || fabs(trans[2]) > 1e-6) { - notparaller = true; - - // TODO: if filter resolution is specified, scaling should be set - // according to that - double scaling_factor = sqrt(trans.expansionX() * trans.expansionX() + - trans.expansionY() * trans.expansionY()); - scale scaling(scaling_factor, scaling_factor); - scale scaling_inv(1.0 / scaling_factor, 1.0 / scaling_factor); - trans *= scaling_inv; - paraller_trans.set_identity(); - paraller_trans *= scaling; - - Matrix itrans = trans.inverse(); - int x0 = pb->area.x0; - int y0 = pb->area.y0; - int x1 = pb->area.x1; - int y1 = pb->area.y1; - int min_x = _min4(itrans[0] * x0 + itrans[2] * y0 + itrans[4], - itrans[0] * x0 + itrans[2] * y1 + itrans[4], - itrans[0] * x1 + itrans[2] * y0 + itrans[4], - itrans[0] * x1 + itrans[2] * y1 + itrans[4]); - int max_x = _max4(itrans[0] * x0 + itrans[2] * y0 + itrans[4], - itrans[0] * x0 + itrans[2] * y1 + itrans[4], - itrans[0] * x1 + itrans[2] * y0 + itrans[4], - itrans[0] * x1 + itrans[2] * y1 + itrans[4]); - int min_y = _min4(itrans[1] * x0 + itrans[3] * y0 + itrans[5], - itrans[1] * x0 + itrans[3] * y1 + itrans[5], - itrans[1] * x1 + itrans[3] * y0 + itrans[5], - itrans[1] * x1 + itrans[3] * y1 + itrans[5]); - int max_y = _max4(itrans[1] * x0 + itrans[3] * y0 + itrans[5], - itrans[1] * x0 + itrans[3] * y1 + itrans[5], - itrans[1] * x1 + itrans[3] * y0 + itrans[5], - itrans[1] * x1 + itrans[3] * y1 + itrans[5]); - - nr_pixblock_setup_fast(in, pb->mode, - min_x, min_y, - max_x, max_y, true); - if (in->size != NR_PIXBLOCK_SIZE_TINY && in->data.px == NULL) // memory allocation failed - return 0; - transform_nearest(in, pb, itrans); - } else if (_x_pixels >= 0) { - // If filter resolution is not set to automatic, we should - // scale the input image to correct resolution - /* If filter resolution is zero, the object should not be rendered */ - if (_x_pixels == 0 || _y_pixels == 0) { - int size = (pb->area.x1 - pb->area.x0) - * (pb->area.y1 - pb->area.y0) - * NR_PIXBLOCK_BPP(pb); - memset(NR_PIXBLOCK_PX(pb), 0, size); - return 0; - } - // Resolution is specified as pixel length of our internal buffer. - // Though, we might not be rendering the whole object at time, - // so we need to calculate the correct pixel size - int x_len = (int)round(((pb->area.x1 - pb->area.x0) * _x_pixels) / (item->bbox.x1 - item->bbox.x0)); - if (x_len < 1) x_len = 1; - // If y-resolution is also set, count y-area in the same way as x-area - // Otherwise, make y-area so, that aspect ratio of input pixblock and - // internal pixblock are the same. - int y_len; + Rect item_bbox = *item->item_bbox; + Rect filter_area = filter_effect_area(item_bbox); + FilterUnits units(_filter_units, _primitive_units); + units.set_ctm(trans); + units.set_item_bbox(item_bbox); + units.set_filter_area(filter_area); + + // TODO: with filterRes of 0x0 should return an empty image + if (_x_pixels > 0) { + double y_len; if (_y_pixels > 0) { - y_len = (int)round(((pb->area.y1 - pb->area.y0) * _y_pixels) / (item->bbox.y1 - item->bbox.y0)); + y_len = _y_pixels; } else { - y_len = (int)round((x_len * (pb->area.y1 - pb->area.y0)) / (double)(pb->area.x1 - pb->area.x0)); + y_len = (_x_pixels * (filter_area.max()[Y] - filter_area.min()[Y])) + / (filter_area.max()[X] - filter_area.min()[X]); } - if (y_len < 1) y_len = 1; - nr_pixblock_setup_fast(in, pb->mode, 0, 0, x_len, y_len, true); - if (in->size != NR_PIXBLOCK_SIZE_TINY && in->data.px == NULL) // memory allocation failed - return 0; - scale_bicubic(in, pb); - scale res_scaling(x_len / (double)(pb->area.x1 - pb->area.x0), - y_len / (double)(pb->area.y1 - pb->area.y0)); - paraller_trans *= res_scaling; + units.set_automatic_resolution(false); + units.set_resolution(_x_pixels, y_len); } else { - // If filter resolution is automatic, just make copy of input image - 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) // memory allocation failed - return 0; - nr_blit_pixblock_pixblock(in, pb); + Point origo = filter_area.min(); + origo *= trans; + Point max_i(filter_area.max()[X], filter_area.min()[Y]); + max_i *= trans; + Point max_j(filter_area.min()[X], filter_area.max()[Y]); + max_j *= trans; + double i_len = sqrt((origo[X] - max_i[X]) * (origo[X] - max_i[X]) + + (origo[Y] - max_i[Y]) * (origo[Y] - max_i[Y])); + double j_len = sqrt((origo[X] - max_j[X]) * (origo[X] - max_j[X]) + + (origo[Y] - max_j[Y]) * (origo[Y] - max_j[Y])); + units.set_automatic_resolution(true); + units.set_resolution(i_len, j_len); } + + 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; + } + } + + slot.set_units(units); + + NRPixBlock *in = new NRPixBlock; + nr_pixblock_setup_fast(in, pb->mode, pb->area.x0, pb->area.y0, + pb->area.x1, pb->area.y1, false); + if (in->size != NR_PIXBLOCK_SIZE_TINY && in->data.px == NULL) { + g_warning("NR::Filter::render: failed to reserve temporary buffer"); + return 0; + } + nr_blit_pixblock_pixblock(in, pb); in->empty = FALSE; slot.set(NR_FILTER_SOURCEGRAPHIC, in); + + // Check that we are rendering a non-empty area + 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("NR::Filter::render: negative area! (%d, %d) (%d, %d)", + in->area.x0, in->area.y0, in->area.x1, in->area.y1); + } + return 0; + } in = NULL; // in is now handled by FilterSlot, we should not touch it + // TODO: filters may need both filterUnits and primitiveUnits, + // so we should pass FilterUnits to render method, not just one Matrix + Matrix primitiveunits2pixblock = units.get_matrix_primitiveunits2pb(); for (int i = 0 ; i < _primitive_count ; i++) { - _primitive[i]->render(slot, paraller_trans); - } - NRPixBlock *out = slot.get(_output_slot); - - // Clear the pixblock, where the output will be put - // -> the original image does not show through - int size = (pb->area.x1 - pb->area.x0) - * (pb->area.y1 - pb->area.y0) - * NR_PIXBLOCK_BPP(pb); - memset(NR_PIXBLOCK_PX(pb), 0, size); - - if (notparaller) { - transform_nearest(pb, out, trans); - } else if (_x_pixels < 0) { - // If the filter resolution is automatic, just copy our final image - // to output pixblock, otherwise use bicubic scaling - nr_blit_pixblock_pixblock(pb, out); - } else { - scale_bicubic(pb, out); + _primitive[i]->render(slot, primitiveunits2pixblock); } + slot.get_final(_output_slot, pb); + // Take note of the amount of used image slots // -> next time this filter is rendered, we can reserve enough slots // immediately @@ -263,10 +199,26 @@ void Filter::area_enlarge(NRRectL &bbox, Matrix const &m) { } } -void Filter::bbox_enlarge(NRRectL &bbox) +void Filter::bbox_enlarge(NRRectL &bbox) { + /* TODO: this is wrong. Should use bounding box in user coordinates + * and find its extents in display coordinates. */ + Point min(bbox.x0, bbox.y0); + Point max(bbox.x1, bbox.y1); + Rect tmp_bbox(min, max); + + Rect enlarged = filter_effect_area(tmp_bbox); + + bbox.x0 = (ICoord)enlarged.min()[X]; + bbox.y0 = (ICoord)enlarged.min()[Y]; + bbox.x1 = (ICoord)enlarged.max()[X]; + bbox.y1 = (ICoord)enlarged.max()[Y]; +} + +Rect Filter::filter_effect_area(Rect const &bbox) { - int len_x = bbox.x1 - bbox.x0; - int len_y = bbox.y1 - bbox.y0; + Point minp, maxp; + double len_x = bbox.max()[X] - bbox.min()[X]; + double len_y = bbox.max()[Y] - bbox.min()[Y]; /* TODO: fetch somehow the object ex and em lengths */ _region_x.update(12, 6, len_x); _region_y.update(12, 6, len_y); @@ -274,35 +226,37 @@ void Filter::bbox_enlarge(NRRectL &bbox) _region_height.update(12, 6, len_y); if (_filter_units == SP_FILTER_UNITS_OBJECTBOUNDINGBOX) { if (_region_x.unit == SVGLength::PERCENT) { - bbox.x0 += (ICoord)_region_x.computed; + minp[X] = bbox.min()[X] + _region_x.computed; } else { - bbox.x0 += (ICoord)(_region_x.computed * len_x); + minp[X] = bbox.min()[X] + _region_x.computed * len_x; } if (_region_width.unit == SVGLength::PERCENT) { - bbox.x1 = bbox.x0 + (ICoord)_region_width.computed; + maxp[X] = minp[X] + _region_width.computed; } else { - bbox.x1 = bbox.x0 + (ICoord)(_region_width.computed * len_x); + maxp[X] = minp[X] + _region_width.computed * len_x; } if (_region_y.unit == SVGLength::PERCENT) { - bbox.y0 += (ICoord)_region_y.computed; + minp[Y] = bbox.min()[Y] + _region_y.computed; } else { - bbox.y0 += (ICoord)(_region_y.computed * len_y); + minp[Y] = bbox.min()[Y] + _region_y.computed * len_y; } if (_region_height.unit == SVGLength::PERCENT) { - bbox.y1 = bbox.y0 + (ICoord)_region_height.computed; + maxp[Y] = minp[Y] + _region_height.computed; } else { - bbox.y1 = bbox.y0 + (ICoord)(_region_height.computed * len_y); + maxp[Y] = minp[Y] + _region_height.computed * len_y; } } else if (_filter_units == SP_FILTER_UNITS_USERSPACEONUSE) { /* TODO: make sure bbox and fe region are in same coordinate system */ - bbox.x0 = (ICoord) _region_x.computed; - bbox.x1 = bbox.x0 + (ICoord) _region_width.computed; - bbox.y0 = (ICoord) _region_y.computed; - bbox.y1 = bbox.y0 + (ICoord) _region_height.computed; + minp[X] = _region_x.computed; + maxp[X] = minp[X] + _region_width.computed; + minp[Y] = _region_y.computed; + maxp[Y] = minp[Y] + _region_height.computed; } else { g_warning("Error in NR::Filter::bbox_enlarge: unrecognized value of _filter_units"); } + Rect area(minp, maxp); + return area; } /* Constructor table holds pointers to static methods returning filter diff --git a/src/display/nr-filter.h b/src/display/nr-filter.h index 9f26a1f3b..4d33c3d70 100644 --- a/src/display/nr-filter.h +++ b/src/display/nr-filter.h @@ -131,7 +131,7 @@ public: /** * Set the primitiveUnits-properterty. If not set, the default value of - * userSpaceOnUseis used. If the parameter value is not a valid + * userSpaceOnUse is used. If the parameter value is not a valid * enumeration value from SPFilterUnits, no changes to filter state * are made. */ @@ -150,6 +150,12 @@ public: * it contains the filter effect area. */ void bbox_enlarge(NRRectL &bbox); + /** + * Returns the filter effects area in user coordinate system. + * The given bounding box should be a bounding box as specified in + * SVG standard and in user coordinate system. + */ + Rect filter_effect_area(Rect const &bbox); /** Creates a new filter with space for one filter element */ Filter(); diff --git a/src/display/pixblock-scaler.cpp b/src/display/pixblock-scaler.cpp index d65cfd4e0..ddb4c4ee2 100644 --- a/src/display/pixblock-scaler.cpp +++ b/src/display/pixblock-scaler.cpp @@ -34,7 +34,7 @@ struct RGBA { * decimal part. (24.8 assuming 32-bit int) */ __attribute__ ((const)) -inline int sampley(unsigned const char a, unsigned const char b, +inline static int sampley(unsigned const char a, unsigned const char b, unsigned const char c, unsigned const char d, const double len) { @@ -62,7 +62,7 @@ inline int sampley(unsigned const char a, unsigned const char b, * Returns the interpolated value in 8-bit format, ready to be written * to output buffer. */ -inline int samplex(const int a, const int b, const int c, const int d, const double len) { +inline static int samplex(const int a, const int b, const int c, const int d, const double len) { double lenf = len - floor(len); int sum = 0; sum += (int)(a * (((-1.0 / 3.0) * lenf + 4.0 / 5.0) * lenf - 7.0 / 15.0) * lenf); @@ -79,7 +79,7 @@ inline int samplex(const int a, const int b, const int c, const int d, const dou * Catches reading and writing outside the pixblock area. * When enabled, decreases filter rendering speed massively. */ -inline void _check_index(NRPixBlock const * const pb, int const location, int const line) +inline static void _check_index(NRPixBlock const * const pb, int const location, int const line) { if(false) { int max_loc = pb->rs * (pb->area.y1 - pb->area.y0); @@ -88,10 +88,10 @@ inline void _check_index(NRPixBlock const * const pb, int const location, int co } } -void scale_bicubic(NRPixBlock *to, NRPixBlock *from) +static void scale_bicubic_rgba(NRPixBlock *to, NRPixBlock *from) { if (NR_PIXBLOCK_BPP(from) != 4 || NR_PIXBLOCK_BPP(to) != 4) { - g_warning("A non-32-bpp image passed to scale_bicubic: scaling aborted."); + g_warning("A non-32-bpp image passed to scale_bicubic_rgba: scaling aborted."); return; } @@ -192,6 +192,83 @@ void scale_bicubic(NRPixBlock *to, NRPixBlock *from) } } +void scale_bicubic_alpha(NRPixBlock *to, NRPixBlock *from) +{ + if (NR_PIXBLOCK_BPP(from) != 1 || NR_PIXBLOCK_BPP(to) != 1) { + g_warning("A non-8-bpp image passed to scale_bicubic_alpha: scaling aborted."); + return; + } + + // Precalculate sizes of source and destination pixblocks + int from_width = from->area.x1 - from->area.x0; + int from_height = from->area.y1 - from->area.y0; + int to_width = to->area.x1 - to->area.x0; + int to_height = to->area.y1 - to->area.y0; + + // from_step: when advancing one pixel in destination image, + // how much we should advance in source image + double from_stepx = (double)from_width / (double)to_width; + double from_stepy = (double)from_height / (double)to_height; + + // Loop through every pixel of destination image, a line at a time + for (int to_y = 0 ; to_y < to_height ; to_y++) { + double from_y = to_y * from_stepy + from_stepy / 2; + // Pre-calculate beginning of the four horizontal lines, from + // which we should read + int from_line[4]; + for (int i = 0 ; i < 4 ; i++) { + if ((int)floor(from_y) + i - 1 >= 0) { + if ((int)floor(from_y) + i - 1 < from_height) { + from_line[i] = ((int)floor(from_y) + i - 1) * from->rs; + } else { + from_line[i] = (from_height - 1) * from->rs; + } + } else { + from_line[i] = 0; + } + } + // Loop through this horizontal line in destination image + // For every pixel, calculate the color of pixel with + // bicubic interpolation and set the pixel value in destination image + for (int to_x = 0 ; to_x < to_width ; to_x++) { + double from_x = to_x * from_stepx + from_stepx / 2; + int line[4]; + for (int i = 0 ; i < 4 ; i++) { + int k = (int)floor(from_x) + i - 1; + if (k < 0) k = 0; + if (k >= from_width) k = from_width - 1; + _check_index(from, from_line[0] + k, __LINE__); + _check_index(from, from_line[1] + k, __LINE__); + _check_index(from, from_line[2] + k, __LINE__); + _check_index(from, from_line[3] + k, __LINE__); + line[i] = sampley(NR_PIXBLOCK_PX(from)[from_line[0] + k], + NR_PIXBLOCK_PX(from)[from_line[1] + k], + NR_PIXBLOCK_PX(from)[from_line[2] + k], + NR_PIXBLOCK_PX(from)[from_line[3] + k], + from_y); + } + int result; + result = samplex(line[0], line[1], line[2], line[3], + from_x); + + _check_index(to, to_y * to->rs + to_x, __LINE__); + + NR_PIXBLOCK_PX(to)[to_y * to->rs + to_x] = clamp(result); + } + } +} + +void scale_bicubic(NRPixBlock *to, NRPixBlock *from) +{ + if (NR_PIXBLOCK_BPP(to) == 4 && NR_PIXBLOCK_BPP(from) == 4) { + scale_bicubic_rgba(to, from); + } else if (NR_PIXBLOCK_BPP(to) == 1 && NR_PIXBLOCK_BPP(from) == 1) { + scale_bicubic_alpha(to, from); + } else { + g_warning("NR::scale_bicubic: unsupported bitdepths for scaling: to %d, from %d", NR_PIXBLOCK_BPP(to), NR_PIXBLOCK_BPP(from)); + } +} + } /* namespace NR */ /* Local Variables: diff --git a/src/sp-item.cpp b/src/sp-item.cpp index 309c42c90..338a70c4e 100644 --- a/src/sp-item.cpp +++ b/src/sp-item.cpp @@ -623,6 +623,13 @@ sp_item_update(SPObject *object, SPCtx *ctx, guint flags) } } + if (item->display && item->display->arenaitem) { + NRRect item_bbox; + sp_item_invoke_bbox(item, &item_bbox, NR::identity(), TRUE, SPItem::GEOMETRIC_BBOX); + NR::Maybe i_bbox = item_bbox; + nr_arena_item_set_item_bbox(item->display->arenaitem, i_bbox); + } + // Update libavoid with item geometry (for connector routing). item->avoidRef->handleSettingChange(); } @@ -917,6 +924,10 @@ sp_item_invoke_show(SPItem *item, NRArena *arena, unsigned key, unsigned flags) SP_OBJECT(mask)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } NR_ARENA_ITEM_SET_DATA(ai, item); + NRRect item_bbox; + sp_item_invoke_bbox(item, &item_bbox, NR::identity(), TRUE, SPItem::GEOMETRIC_BBOX); + NR::Maybe i_bbox = item_bbox; + nr_arena_item_set_item_bbox(ai, i_bbox); } return ai; -- 2.30.2