diff --git a/Cargo.lock b/Cargo.lock index 4f9054f8301ad86c2e9691019d1d1e8658a1377f..5fe2039f3b3b2d8f2cac868cc2ad3ede7f4c7350 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -178,6 +178,14 @@ name = "matches" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "matrixmultiply" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rawpointer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "memchr" version = "2.0.1" @@ -186,6 +194,33 @@ dependencies = [ "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "num" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-iter" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num-traits" version = "0.2.5" @@ -316,6 +351,11 @@ dependencies = [ "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rawpointer" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "regex" version = "1.0.1" @@ -357,6 +397,16 @@ dependencies = [ "phf 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", "phf_codegen 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rulinalg 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rulinalg" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matrixmultiply 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -459,7 +509,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739" "checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1" "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" +"checksum matrixmultiply 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cac1a66eab356036af85ea093101a14223dc6e3f4c02a59b7d572e5b93270bf7" "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" +"checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" +"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" +"checksum num-iter 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "af3fdbbc3291a5464dc57b03860ec37ca6bf915ed6ee385e7c6c052c422b2124" "checksum num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "630de1ef5cc79d0cdd78b7e33b81f083cbfe90de0f4b2b2f07f905867c70e9fe" "checksum pango 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "45374801e224373c3c0393cd48073c81093494c8735721e81d1dbaa4096b2767" "checksum pango-sys 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94039b3921a4af4058a3e4335e5d15099101f298a92f5afc40bab3a3027594a1" @@ -474,8 +528,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum procedural-masquerade 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9a1574a51c3fd37b26d2c0032b649d08a7d51d4cca9c41bbc5bf7118fa4509d0" "checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8" "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" +"checksum rawpointer 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ebac11a9d2e11f2af219b8b8d833b76b1ea0e054aa0e8d8e9e4cbde353bdf019" "checksum regex 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13c93d55961981ba9226a213b385216f83ab43bd6ac53ab16b2eeb47e337cf4e" "checksum regex-syntax 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05b06a75f5217880fc5e905952a42750bf44787e56a6c6d6852ed0992f5e1d54" +"checksum rulinalg 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "04ada202c9685e1d72a7420c578e92b358dbf807d3dfabb676a3dab9cc3bb12f" "checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537" "checksum smallvec 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "312a7df010092e73d6bbaf141957e868d4f30efd2bfd9bb1028ad91abec58514" "checksum syn 0.13.11 (registry+https://github.com/rust-lang/crates.io-index)" = "14f9bf6292f3a61d2c716723fdb789a41bbe104168e6f496dc6497e531ea1b9b" diff --git a/Makefile.am b/Makefile.am index 30b2f14243af0926d6369386c3d3755ed13c4bd4..f505de5a2269497d248059cf3623e5cd1d8f2d02 100644 --- a/Makefile.am +++ b/Makefile.am @@ -22,8 +22,6 @@ BUILT_SOURCES += $(enum_sources) librsvg_@RSVG_API_MAJOR_VERSION@_la_SOURCES = \ librsvg/filters/common.c \ librsvg/filters/common.h \ - librsvg/filters/color_matrix.c \ - librsvg/filters/convolve_matrix.c \ librsvg/filters/diffuse_lighting.c \ librsvg/filters/displacement_map.c \ librsvg/filters/erode.c \ @@ -79,8 +77,10 @@ RUST_SRC = \ rsvg_internals/src/drawing_ctx.rs \ rsvg_internals/src/filters/bounds.rs \ rsvg_internals/src/filters/blend.rs \ + rsvg_internals/src/filters/color_matrix.rs \ rsvg_internals/src/filters/component_transfer.rs \ rsvg_internals/src/filters/composite.rs \ + rsvg_internals/src/filters/convolve_matrix.rs \ rsvg_internals/src/filters/context.rs \ rsvg_internals/src/filters/error.rs \ rsvg_internals/src/filters/ffi.rs \ diff --git a/librsvg/filters/color_matrix.c b/librsvg/filters/color_matrix.c deleted file mode 100644 index 51b28ed5e992cf3b1af7179f83662f5dc30b688e..0000000000000000000000000000000000000000 --- a/librsvg/filters/color_matrix.c +++ /dev/null @@ -1,300 +0,0 @@ -/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* vim: set sw=4 sts=4 expandtab: */ -/* - rsvg-filter.c: Provides filters - - Copyright (C) 2004 Caleb Moore - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this program; if not, write to the - Free Software Foundation, Inc., 59 Temple Place - Suite 330, - Boston, MA 02111-1307, USA. - - Author: Caleb Moore -*/ - -#include "config.h" - -#include "../rsvg-private.h" -#include "../rsvg-styles.h" -#include "../rsvg-css.h" -#include "common.h" - -typedef struct _RsvgFilterPrimitiveColorMatrix RsvgFilterPrimitiveColorMatrix; - -struct _RsvgFilterPrimitiveColorMatrix { - RsvgFilterPrimitive super; - gint *KernelMatrix; -}; - -static void -rsvg_filter_primitive_color_matrix_render (RsvgNode *node, RsvgComputedValues *values, - RsvgFilterPrimitive *primitive, - RsvgFilterContext *ctx, - RsvgDrawingCtx *draw_ctx) -{ - RsvgFilterPrimitiveColorMatrix *color_matrix = (RsvgFilterPrimitiveColorMatrix *) primitive; - - guchar ch; - gint x, y; - gint i; - gint rowstride, height, width; - RsvgIRect boundarys; - - guchar *in_pixels; - guchar *output_pixels; - - cairo_surface_t *output, *in; - - int sum; - - boundarys = rsvg_filter_primitive_get_bounds (primitive, ctx, draw_ctx); - - in = rsvg_filter_get_in (primitive->in, ctx, draw_ctx); - if (in == NULL) - return; - - cairo_surface_flush (in); - - in_pixels = cairo_image_surface_get_data (in); - - height = cairo_image_surface_get_height (in); - width = cairo_image_surface_get_width (in); - - rowstride = cairo_image_surface_get_stride (in); - - output = _rsvg_image_surface_new (width, height); - if (output == NULL) { - cairo_surface_destroy (in); - return; - } - - output_pixels = cairo_image_surface_get_data (output); - - const int *ctx_channelmap = rsvg_filter_context_get_channelmap(ctx); - - for (y = boundarys.y0; y < boundarys.y1; y++) - for (x = boundarys.x0; x < boundarys.x1; x++) { - int umch; - int alpha = in_pixels[4 * x + y * rowstride + ctx_channelmap[3]]; - if (!alpha) - for (umch = 0; umch < 4; umch++) { - sum = color_matrix->KernelMatrix[umch * 5 + 4]; - if (sum > 255) - sum = 255; - if (sum < 0) - sum = 0; - output_pixels[4 * x + y * rowstride + ctx_channelmap[umch]] = sum; - } else - for (umch = 0; umch < 4; umch++) { - int umi; - ch = ctx_channelmap[umch]; - sum = 0; - for (umi = 0; umi < 4; umi++) { - i = ctx_channelmap[umi]; - if (umi != 3) - sum += color_matrix->KernelMatrix[umch * 5 + umi] * - in_pixels[4 * x + y * rowstride + i] / alpha; - else - sum += color_matrix->KernelMatrix[umch * 5 + umi] * - in_pixels[4 * x + y * rowstride + i] / 255; - } - sum += color_matrix->KernelMatrix[umch * 5 + 4]; - - - - if (sum > 255) - sum = 255; - if (sum < 0) - sum = 0; - - output_pixels[4 * x + y * rowstride + ch] = sum; - } - for (umch = 0; umch < 3; umch++) { - ch = ctx_channelmap[umch]; - output_pixels[4 * x + y * rowstride + ch] = - output_pixels[4 * x + y * rowstride + ch] * - output_pixels[4 * x + y * rowstride + ctx_channelmap[3]] / 255; - } - } - - cairo_surface_mark_dirty (output); - - RsvgFilterPrimitiveOutput op; - op.surface = output; - op.bounds = boundarys; - rsvg_filter_store_output(primitive->result, op, ctx); - /* rsvg_filter_store_result (primitive->result, output, ctx); */ - - cairo_surface_destroy (in); -} - -static void -rsvg_filter_primitive_color_matrix_free (gpointer impl) -{ - RsvgFilterPrimitiveColorMatrix *matrix = impl; - - g_free (matrix->KernelMatrix); - - rsvg_filter_primitive_free (impl); -} - -static void -rsvg_filter_primitive_color_matrix_set_atts (RsvgNode *node, gpointer impl, RsvgHandle *handle, RsvgPropertyBag atts) -{ - RsvgFilterPrimitiveColorMatrix *filter = impl; - gint type; - gsize listlen = 0; - RsvgPropertyBagIter *iter; - const char *key; - RsvgAttribute attr; - const char *value; - - type = 0; - - filter_primitive_set_x_y_width_height_atts ((RsvgFilterPrimitive *) filter, atts); - - iter = rsvg_property_bag_iter_begin (atts); - - while (rsvg_property_bag_iter_next (iter, &key, &attr, &value)) { - switch (attr) { - case RSVG_ATTRIBUTE_IN: - g_string_assign (filter->super.in, value); - break; - - case RSVG_ATTRIBUTE_RESULT: - g_string_assign (filter->super.result, value); - break; - - case RSVG_ATTRIBUTE_VALUES: { - unsigned int i; - double *temp; - if (!rsvg_css_parse_number_list (value, - NUMBER_LIST_LENGTH_MAXIMUM, - 20, - &temp, - &listlen)) { - rsvg_node_set_attribute_parse_error (node, "values", "invalid number list"); - goto out; - } - - filter->KernelMatrix = g_new0 (int, listlen); - for (i = 0; i < listlen; i++) - filter->KernelMatrix[i] = temp[i] * 255.; - g_free (temp); - break; - } - - case RSVG_ATTRIBUTE_TYPE: - if (!strcmp (value, "matrix")) - type = 0; - else if (!strcmp (value, "saturate")) - type = 1; - else if (!strcmp (value, "hueRotate")) - type = 2; - else if (!strcmp (value, "luminanceToAlpha")) - type = 3; - else - type = 0; - break; - - default: - break; - } - } - - if (type == 0) { - if (listlen != 20) { - if (filter->KernelMatrix != NULL) - g_free (filter->KernelMatrix); - filter->KernelMatrix = g_new0 (int, 20); - } - } else if (type == 1) { - float s; - if (listlen != 0) { - s = filter->KernelMatrix[0]; - g_free (filter->KernelMatrix); - } else - s = 255; - filter->KernelMatrix = g_new0 (int, 20); - - filter->KernelMatrix[0] = 0.213 * 255. + 0.787 * s; - filter->KernelMatrix[1] = 0.715 * 255. - 0.715 * s; - filter->KernelMatrix[2] = 0.072 * 255. - 0.072 * s; - filter->KernelMatrix[5] = 0.213 * 255. - 0.213 * s; - filter->KernelMatrix[6] = 0.715 * 255. + 0.285 * s; - filter->KernelMatrix[7] = 0.072 * 255. - 0.072 * s; - filter->KernelMatrix[10] = 0.213 * 255. - 0.213 * s; - filter->KernelMatrix[11] = 0.715 * 255. - 0.715 * s; - filter->KernelMatrix[12] = 0.072 * 255. + 0.928 * s; - filter->KernelMatrix[18] = 255; - } else if (type == 2) { - double cosval, sinval, arg; - - if (listlen != 0) { - arg = (double) filter->KernelMatrix[0] / 255.; - g_free (filter->KernelMatrix); - } else - arg = 0; - - cosval = cos (arg); - sinval = sin (arg); - - filter->KernelMatrix = g_new0 (int, 20); - - filter->KernelMatrix[0] = (0.213 + cosval * 0.787 + sinval * -0.213) * 255.; - filter->KernelMatrix[1] = (0.715 + cosval * -0.715 + sinval * -0.715) * 255.; - filter->KernelMatrix[2] = (0.072 + cosval * -0.072 + sinval * 0.928) * 255.; - filter->KernelMatrix[5] = (0.213 + cosval * -0.213 + sinval * 0.143) * 255.; - filter->KernelMatrix[6] = (0.715 + cosval * 0.285 + sinval * 0.140) * 255.; - filter->KernelMatrix[7] = (0.072 + cosval * -0.072 + sinval * -0.283) * 255.; - filter->KernelMatrix[10] = (0.213 + cosval * -0.213 + sinval * -0.787) * 255.; - filter->KernelMatrix[11] = (0.715 + cosval * -0.715 + sinval * 0.715) * 255.; - filter->KernelMatrix[12] = (0.072 + cosval * 0.928 + sinval * 0.072) * 255.; - filter->KernelMatrix[18] = 255; - } else if (type == 3) { - if (filter->KernelMatrix != NULL) - g_free (filter->KernelMatrix); - - filter->KernelMatrix = g_new0 (int, 20); - - filter->KernelMatrix[15] = 0.2125 * 255.; - filter->KernelMatrix[16] = 0.7154 * 255.; - filter->KernelMatrix[17] = 0.0721 * 255.; - } else { - g_assert_not_reached (); - } - -out: - rsvg_property_bag_iter_end (iter); -} - -RsvgNode * -rsvg_new_filter_primitive_color_matrix (const char *element_name, RsvgNode *parent, const char *id, const char *klass) -{ - RsvgFilterPrimitiveColorMatrix *filter; - - filter = g_new0 (RsvgFilterPrimitiveColorMatrix, 1); - filter->super.in = g_string_new ("none"); - filter->super.result = g_string_new ("none"); - filter->KernelMatrix = NULL; - filter->super.render = rsvg_filter_primitive_color_matrix_render; - - return rsvg_rust_cnode_new (RSVG_NODE_TYPE_FILTER_PRIMITIVE_COLOR_MATRIX, - parent, - id, - klass, - filter, - rsvg_filter_primitive_color_matrix_set_atts, - rsvg_filter_primitive_color_matrix_free); -} diff --git a/librsvg/filters/convolve_matrix.c b/librsvg/filters/convolve_matrix.c deleted file mode 100644 index 7e9e4ea3e2299a8dba00972a74626dc64eb74f7f..0000000000000000000000000000000000000000 --- a/librsvg/filters/convolve_matrix.c +++ /dev/null @@ -1,372 +0,0 @@ -/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* vim: set sw=4 sts=4 expandtab: */ -/* - rsvg-filter.c: Provides filters - - Copyright (C) 2004 Caleb Moore - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this program; if not, write to the - Free Software Foundation, Inc., 59 Temple Place - Suite 330, - Boston, MA 02111-1307, USA. - - Author: Caleb Moore -*/ - -#include "config.h" - -#include "../rsvg-private.h" -#include "../rsvg-styles.h" -#include "../rsvg-css.h" -#include "common.h" - -typedef struct _RsvgFilterPrimitiveConvolveMatrix RsvgFilterPrimitiveConvolveMatrix; - -typedef enum { - EDGE_MODE_DUPLICATE, - EDGE_MODE_WRAP, - EDGE_MODE_NONE -} EdgeMode; - -struct _RsvgFilterPrimitiveConvolveMatrix { - RsvgFilterPrimitive super; - double *KernelMatrix; - double divisor; - gint orderx, ordery; - double dx, dy; - double bias; - gint targetx, targety; - gboolean preservealpha; - EdgeMode edgemode; -}; - -static void -rsvg_filter_primitive_convolve_matrix_render (RsvgNode *node, RsvgComputedValues *values, - RsvgFilterPrimitive *primitive, - RsvgFilterContext *ctx, - RsvgDrawingCtx *draw_ctx) -{ - RsvgFilterPrimitiveConvolveMatrix *convolve = (RsvgFilterPrimitiveConvolveMatrix *) primitive; - - guchar ch; - gint x, y; - gint i, j; - gint rowstride, height, width; - RsvgIRect boundarys; - - guchar *in_pixels; - guchar *output_pixels; - - cairo_surface_t *output, *in; - - gint sx, sy, kx, ky; - guchar sval; - double kval, sum, dx, dy, targetx, targety; - int umch; - - gint tempresult; - - boundarys = rsvg_filter_primitive_get_bounds (primitive, ctx, draw_ctx); - - in = rsvg_filter_get_in (primitive->in, ctx, draw_ctx); - if (in == NULL) - return; - - cairo_surface_flush (in); - - in_pixels = cairo_image_surface_get_data (in); - - height = cairo_image_surface_get_height (in); - width = cairo_image_surface_get_width (in); - - cairo_matrix_t ctx_paffine = rsvg_filter_context_get_paffine(ctx); - targetx = convolve->targetx * ctx_paffine.xx; - targety = convolve->targety * ctx_paffine.yy; - - if (convolve->dx != 0 || convolve->dy != 0) { - dx = convolve->dx * ctx_paffine.xx; - dy = convolve->dy * ctx_paffine.yy; - } else - dx = dy = 1; - - rowstride = cairo_image_surface_get_stride (in); - - output = _rsvg_image_surface_new (width, height); - if (output == NULL) { - cairo_surface_destroy (in); - return; - } - - output_pixels = cairo_image_surface_get_data (output); - - const int *ctx_channelmap = rsvg_filter_context_get_channelmap(ctx); - - for (y = boundarys.y0; y < boundarys.y1; y++) { - for (x = boundarys.x0; x < boundarys.x1; x++) { - for (umch = 0; umch < 3 + !convolve->preservealpha; umch++) { - ch = ctx_channelmap[umch]; - sum = 0; - for (i = 0; i < convolve->ordery; i++) { - for (j = 0; j < convolve->orderx; j++) { - int alpha; - sx = x - targetx + j * dx; - sy = y - targety + i * dy; - if (convolve->edgemode == EDGE_MODE_DUPLICATE) { - if (sx < boundarys.x0) - sx = boundarys.x0; - if (sx >= boundarys.x1) - sx = boundarys.x1 - 1; - if (sy < boundarys.y0) - sy = boundarys.y0; - if (sy >= boundarys.y1) - sy = boundarys.y1 - 1; - } else if (convolve->edgemode == EDGE_MODE_WRAP) { - if (sx < boundarys.x0 || (sx >= boundarys.x1)) - sx = boundarys.x0 + (sx - boundarys.x0) % - (boundarys.x1 - boundarys.x0); - if (sy < boundarys.y0 || (sy >= boundarys.y1)) - sy = boundarys.y0 + (sy - boundarys.y0) % - (boundarys.y1 - boundarys.y0); - } else if (convolve->edgemode == EDGE_MODE_NONE) { - if (sx < boundarys.x0 || (sx >= boundarys.x1) || - sy < boundarys.y0 || (sy >= boundarys.y1)) - continue; - } else { - g_assert_not_reached (); - } - - kx = convolve->orderx - j - 1; - ky = convolve->ordery - i - 1; - alpha = in_pixels[4 * sx + sy * rowstride + 3]; - if (ch == 3) - sval = alpha; - else if (alpha) - sval = in_pixels[4 * sx + sy * rowstride + ch] * 255 / alpha; - else - sval = 0; - kval = convolve->KernelMatrix[kx + ky * convolve->orderx]; - sum += (double) sval *kval; - } - } - - tempresult = sum / convolve->divisor + convolve->bias; - - if (tempresult > 255) - tempresult = 255; - if (tempresult < 0) - tempresult = 0; - - output_pixels[4 * x + y * rowstride + ch] = tempresult; - } - if (convolve->preservealpha) - output_pixels[4 * x + y * rowstride + ctx_channelmap[3]] = - in_pixels[4 * x + y * rowstride + ctx_channelmap[3]]; - for (umch = 0; umch < 3; umch++) { - ch = ctx_channelmap[umch]; - output_pixels[4 * x + y * rowstride + ch] = - output_pixels[4 * x + y * rowstride + ch] * - output_pixels[4 * x + y * rowstride + ctx_channelmap[3]] / 255; - } - } - } - - cairo_surface_mark_dirty (output); - - RsvgFilterPrimitiveOutput op; - op.surface = output; - op.bounds = boundarys; - rsvg_filter_store_output(primitive->result, op, ctx); - /* rsvg_filter_store_result (primitive->result, output, ctx); */ - - cairo_surface_destroy (in); -} - -static void -rsvg_filter_primitive_convolve_matrix_free (gpointer impl) -{ - RsvgFilterPrimitiveConvolveMatrix *convolve = impl; - - g_free (convolve->KernelMatrix); - - rsvg_filter_primitive_free (impl); -} - -static void -rsvg_filter_primitive_convolve_matrix_set_atts (RsvgNode *node, gpointer impl, RsvgHandle *handle, RsvgPropertyBag atts) -{ - RsvgFilterPrimitiveConvolveMatrix *filter = impl; - gint i, j; - gboolean has_target_x, has_target_y; - - RsvgPropertyBagIter *iter; - const char *key; - RsvgAttribute attr; - const char *value; - - has_target_x = 0; - has_target_y = 0; - - filter_primitive_set_x_y_width_height_atts ((RsvgFilterPrimitive *) filter, atts); - - iter = rsvg_property_bag_iter_begin (atts); - - while (rsvg_property_bag_iter_next (iter, &key, &attr, &value)) { - switch (attr) { - case RSVG_ATTRIBUTE_IN: - g_string_assign (filter->super.in, value); - break; - - case RSVG_ATTRIBUTE_RESULT: - g_string_assign (filter->super.result, value); - break; - - case RSVG_ATTRIBUTE_TARGET_X: - has_target_x = 1; - filter->targetx = atoi (value); - break; - - case RSVG_ATTRIBUTE_TARGET_Y: - has_target_y = 1; - filter->targety = atoi (value); - break; - - case RSVG_ATTRIBUTE_BIAS: - filter->bias = atof (value); - break; - - case RSVG_ATTRIBUTE_PRESERVE_ALPHA: - if (!strcmp (value, "true")) - filter->preservealpha = TRUE; - else - filter->preservealpha = FALSE; - break; - - case RSVG_ATTRIBUTE_DIVISOR: - filter->divisor = atof (value); - break; - - case RSVG_ATTRIBUTE_ORDER: { - double tempx, tempy; - if (rsvg_css_parse_number_optional_number (value, &tempx, &tempy) - && tempx >= 1.0 && tempy <= 100.0 - && tempy >= 1.0 && tempy <= 100.0) { - filter->orderx = (int) tempx; - filter->ordery = (int) tempy; - g_assert (filter->orderx >= 1); - g_assert (filter->ordery >= 1); - -#define SIZE_OVERFLOWS(a,b) (G_UNLIKELY ((b) > 0 && (a) > G_MAXSIZE / (b))) - - if (SIZE_OVERFLOWS (filter->orderx, filter->ordery)) { - rsvg_node_set_attribute_parse_error (node, "order", "number of kernelMatrix elements would be too big"); - goto out; - } - } else { - rsvg_node_set_attribute_parse_error (node, "order", "invalid size for convolve matrix"); - goto out; - } - break; - } - - case RSVG_ATTRIBUTE_KERNEL_UNIT_LENGTH: - if (!rsvg_css_parse_number_optional_number (value, &filter->dx, &filter->dy)) { - rsvg_node_set_attribute_parse_error (node, "kernelUnitLength", "expected number-optional-number"); - goto out; - } - break; - - case RSVG_ATTRIBUTE_KERNEL_MATRIX: { - gsize num_elems; - gsize got_num_elems; - - num_elems = filter->orderx * filter->ordery; - - if (!rsvg_css_parse_number_list (value, - NUMBER_LIST_LENGTH_EXACT, - num_elems, - &filter->KernelMatrix, - &got_num_elems)) { - rsvg_node_set_attribute_parse_error (node, "kernelMatrix", "expected a matrix of numbers"); - goto out; - } - - g_assert (num_elems == got_num_elems); - break; - } - - case RSVG_ATTRIBUTE_EDGE_MODE: - if (!strcmp (value, "duplicate")) { - filter->edgemode = EDGE_MODE_DUPLICATE; - } else if (!strcmp (value, "wrap")) { - filter->edgemode = EDGE_MODE_WRAP; - } else if (!strcmp (value, "none")) { - filter->edgemode = EDGE_MODE_NONE; - } else { - rsvg_node_set_attribute_parse_error (node, "edgeMode", "expected 'duplicate' | 'wrap' | 'none'"); - goto out; - } - break; - - default: - break; - } - } - - if (filter->divisor == 0) { - for (j = 0; j < filter->orderx; j++) - for (i = 0; i < filter->ordery; i++) - filter->divisor += filter->KernelMatrix[j + i * filter->orderx]; - } - - if (filter->divisor == 0) - filter->divisor = 1; - - if (!has_target_x) { - filter->targetx = floor (filter->orderx / 2); - } - if (!has_target_y) { - filter->targety = floor (filter->ordery / 2); - } - -out: - - rsvg_property_bag_iter_end (iter); -} - -RsvgNode * -rsvg_new_filter_primitive_convolve_matrix (const char *element_name, RsvgNode *parent, const char *id, const char *klass) -{ - RsvgFilterPrimitiveConvolveMatrix *filter; - - filter = g_new0 (RsvgFilterPrimitiveConvolveMatrix, 1); - filter->super.in = g_string_new ("none"); - filter->super.result = g_string_new ("none"); - filter->KernelMatrix = NULL; - filter->divisor = 0; - filter->orderx = 3; /* https://www.w3.org/TR/SVG/filters.html#feConvolveMatrixElementOrderAttribute */ - filter->ordery = 3; - filter->bias = 0; - filter->dx = 0; - filter->dy = 0; - filter->preservealpha = FALSE; - filter->edgemode = EDGE_MODE_DUPLICATE; - filter->super.render = rsvg_filter_primitive_convolve_matrix_render; - - return rsvg_rust_cnode_new (RSVG_NODE_TYPE_FILTER_PRIMITIVE_CONVOLVE_MATRIX, - parent, - id, - klass, - filter, - rsvg_filter_primitive_convolve_matrix_set_atts, - rsvg_filter_primitive_convolve_matrix_free); -} diff --git a/rsvg_internals/Cargo.toml b/rsvg_internals/Cargo.toml index 76c2540224341f15f7a73d5120d47a6c41f768a2..0fc53ac92e08ef730870abf2bdd9f5650f3e415d 100644 --- a/rsvg_internals/Cargo.toml +++ b/rsvg_internals/Cargo.toml @@ -32,6 +32,7 @@ cssparser = "0.23" lazy_static = "1.0.0" phf = "0.7.21" float-cmp = "0.4.0" +rulinalg = "0.4" [dependencies.cairo-sys-rs] version = "0.6" diff --git a/rsvg_internals/src/filters/blend.rs b/rsvg_internals/src/filters/blend.rs index 2ceec4a9360b118da2973354c9931985a66d23bc..f63d18e3866bcf1581103d45a33f01d53dd4e2ce 100644 --- a/rsvg_internals/src/filters/blend.rs +++ b/rsvg_internals/src/filters/blend.rs @@ -13,7 +13,7 @@ use surface_utils::shared_surface::SharedImageSurface; use super::context::{FilterContext, FilterOutput, FilterResult}; use super::input::Input; -use super::{make_result, Filter, FilterError, PrimitiveWithInput}; +use super::{Filter, FilterError, PrimitiveWithInput}; /// Enumeration of the possible blending modes. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] @@ -79,8 +79,8 @@ impl Filter for Blend { ctx: &FilterContext, draw_ctx: &mut DrawingCtx, ) -> Result { - let input = make_result(self.base.get_input(ctx, draw_ctx))?; - let input_2 = make_result(ctx.get_input(draw_ctx, self.in2.borrow().as_ref()))?; + let input = self.base.get_input(ctx, draw_ctx)?; + let input_2 = ctx.get_input(draw_ctx, self.in2.borrow().as_ref())?; let bounds = self .base .get_bounds(ctx) @@ -91,7 +91,7 @@ impl Filter for Blend { let output_surface = input_2 .surface() .copy_surface(bounds) - .map_err(FilterError::OutputSurfaceCreation)?; + .map_err(FilterError::IntermediateSurfaceCreation)?; { let cr = cairo::Context::new(&output_surface); cr.rectangle( @@ -110,7 +110,8 @@ impl Filter for Blend { Ok(FilterResult { name: self.base.result.borrow().clone(), output: FilterOutput { - surface: SharedImageSurface::new(output_surface).unwrap(), + surface: SharedImageSurface::new(output_surface) + .map_err(FilterError::BadIntermediateSurfaceStatus)?, bounds, }, }) diff --git a/rsvg_internals/src/filters/color_matrix.rs b/rsvg_internals/src/filters/color_matrix.rs new file mode 100644 index 0000000000000000000000000000000000000000..d8555c371a0461ddb4d05adc4a668211a06f63d7 --- /dev/null +++ b/rsvg_internals/src/filters/color_matrix.rs @@ -0,0 +1,268 @@ +use std::cell::RefCell; + +use cairo::{self, ImageSurface}; +use rulinalg::{ + self, + matrix::{BaseMatrix, BaseMatrixMut, Matrix}, +}; + +use attributes::Attribute; +use drawing_ctx::DrawingCtx; +use error::NodeError; +use handle::RsvgHandle; +use node::{NodeResult, NodeTrait, RsvgCNodeImpl, RsvgNode}; +use parsers::{self, ListLength, NumberListError, ParseError}; +use property_bag::PropertyBag; +use surface_utils::{ + iterators::Pixels, + shared_surface::SharedImageSurface, + ImageSurfaceDataExt, + Pixel, +}; +use util::clamp; + +use super::context::{FilterContext, FilterOutput, FilterResult}; +use super::{Filter, FilterError, PrimitiveWithInput}; + +/// Color matrix operation types. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +enum OperationType { + Matrix, + Saturate, + HueRotate, + LuminanceToAlpha, +} + +/// The `feColorMatrix` filter primitive. +pub struct ColorMatrix { + base: PrimitiveWithInput, + matrix: RefCell>, +} + +impl ColorMatrix { + /// Constructs a new `ColorMatrix` with empty properties. + #[inline] + pub fn new() -> ColorMatrix { + ColorMatrix { + base: PrimitiveWithInput::new::(), + matrix: RefCell::new(Matrix::identity(5)), + } + } +} + +impl NodeTrait for ColorMatrix { + fn set_atts( + &self, + node: &RsvgNode, + handle: *const RsvgHandle, + pbag: &PropertyBag, + ) -> NodeResult { + self.base.set_atts(node, handle, pbag)?; + + // First, determine the operation type. + let mut operation_type = OperationType::Matrix; + for (_, attr, value) in pbag.iter().filter(|(_, attr, _)| *attr == Attribute::Type) { + operation_type = OperationType::parse(attr, value)?; + } + + // Now read the matrix correspondingly. + // LuminanceToAlpha doesn't accept any matrix. + if operation_type == OperationType::LuminanceToAlpha { + self.matrix.replace(matrix![ + 0.0, 0.0, 0.0, 0.0, 0.0; + 0.0, 0.0, 0.0, 0.0, 0.0; + 0.0, 0.0, 0.0, 0.0, 0.0; + 0.2125, 0.7154, 0.0721, 0.0, 0.0; + 0.0, 0.0, 0.0, 0.0, 1.0 + ]); + } else { + for (_, attr, value) in pbag + .iter() + .filter(|(_, attr, _)| *attr == Attribute::Values) + { + let new_matrix = match operation_type { + OperationType::LuminanceToAlpha => unreachable!(), + OperationType::Matrix => { + let top = Matrix::new( + 4, + 5, + parsers::number_list_from_str(value, ListLength::Exact(20)).map_err( + |err| { + NodeError::parse_error( + attr, + match err { + NumberListError::IncorrectNumberOfElements => { + ParseError::new( + "incorrect number of elements: expected 20", + ) + } + NumberListError::Parse(err) => err, + }, + ) + }, + )?, + ); + + let mut matrix = Matrix::identity(5); + matrix.sub_slice_mut([0, 0], 4, 5).set_to(top); + matrix + } + OperationType::Saturate => { + let s = parsers::number(value) + .map_err(|err| NodeError::parse_error(attr, err))?; + if s < 0.0 || s > 1.0 { + return Err(NodeError::value_error(attr, "expected value from 0 to 1")); + } + + matrix![ + 0.213 + 0.787 * s, 0.715 - 0.715 * s, 0.072 - 0.072 * s, 0.0, 0.0; + 0.213 - 0.213 * s, 0.715 + 0.285 * s, 0.072 - 0.072 * s, 0.0, 0.0; + 0.213 - 0.213 * s, 0.715 - 0.715 * s, 0.072 + 0.928 * s, 0.0, 0.0; + 0.0, 0.0, 0.0, 1.0, 0.0; + 0.0, 0.0, 0.0, 0.0, 1.0 + ] + } + OperationType::HueRotate => { + let degrees = parsers::number(value) + .map_err(|err| NodeError::parse_error(attr, err))?; + + let (sin, cos) = degrees.to_radians().sin_cos(); + + let a = matrix![ + 0.213, 0.715, 0.072; + 0.213, 0.715, 0.072; + 0.213, 0.715, 0.072 + ]; + + let b = matrix![ + 0.787, -0.715, -0.072; + -0.213, 0.285, -0.072; + -0.213, -0.715, 0.928 + ]; + + let c = matrix![ + -0.213, -0.715, 0.928; + 0.143, 0.140, -0.283; + -0.787, 0.715, 0.072 + ]; + + let top_left = a + b * cos + c * sin; + + let mut matrix = Matrix::identity(5); + matrix.sub_slice_mut([0, 0], 3, 3).set_to(top_left); + matrix + } + }; + + self.matrix.replace(new_matrix); + } + } + + Ok(()) + } + + #[inline] + fn get_c_impl(&self) -> *const RsvgCNodeImpl { + self.base.get_c_impl() + } +} + +impl Filter for ColorMatrix { + fn render( + &self, + _node: &RsvgNode, + ctx: &FilterContext, + draw_ctx: &mut DrawingCtx, + ) -> Result { + let input = self.base.get_input(ctx, draw_ctx)?; + let bounds = self + .base + .get_bounds(ctx) + .add_input(&input) + .into_irect(draw_ctx); + + let matrix = &*self.matrix.borrow(); + + /// Multiplies a matrix by a vector and puts the result into a slice. + #[inline] + fn mul_into(out: &mut [f64], m: &Matrix, v: &[f64]) { + assert_eq!(v.len(), m.cols()); + assert_eq!(v.len(), out.len()); + + for (i, row) in m.row_iter().enumerate() { + out[i] = rulinalg::utils::dot(row.raw_slice(), v); + } + } + + let mut output_surface = ImageSurface::create( + cairo::Format::ARgb32, + ctx.source_graphic().width(), + ctx.source_graphic().height(), + ).map_err(FilterError::IntermediateSurfaceCreation)?; + + let output_stride = output_surface.get_stride() as usize; + { + let mut output_data = output_surface.get_data().unwrap(); + + for (x, y, pixel) in Pixels::new(input.surface(), bounds) { + let alpha = f64::from(pixel.a) / 255f64; + + let pixel_vec = if alpha == 0.0 { + [0.0, 0.0, 0.0, 0.0, 1.0] + } else { + [ + f64::from(pixel.r) / 255f64 / alpha, + f64::from(pixel.g) / 255f64 / alpha, + f64::from(pixel.b) / 255f64 / alpha, + alpha, + 1.0, + ] + }; + let mut new_pixel_vec = [0.0; 5]; + mul_into(&mut new_pixel_vec, &matrix, &pixel_vec); + + let new_alpha = clamp(new_pixel_vec[3], 0.0, 1.0); + + let premultiply = |x: f64| (clamp(x, 0.0, 1.0) * new_alpha * 255f64).round() as u8; + + let output_pixel = Pixel { + r: premultiply(new_pixel_vec[0]), + g: premultiply(new_pixel_vec[1]), + b: premultiply(new_pixel_vec[2]), + a: (new_alpha * 255f64).round() as u8, + }; + + output_data.set_pixel(output_stride, output_pixel, x, y); + } + } + + Ok(FilterResult { + name: self.base.result.borrow().clone(), + output: FilterOutput { + surface: SharedImageSurface::new(output_surface) + .map_err(FilterError::BadIntermediateSurfaceStatus)?, + bounds, + }, + }) + } + + #[inline] + fn is_affected_by_color_interpolation_filters() -> bool { + true + } +} + +impl OperationType { + fn parse(attr: Attribute, s: &str) -> Result { + match s { + "matrix" => Ok(OperationType::Matrix), + "saturate" => Ok(OperationType::Saturate), + "hueRotate" => Ok(OperationType::HueRotate), + "luminanceToAlpha" => Ok(OperationType::LuminanceToAlpha), + _ => Err(NodeError::parse_error( + attr, + ParseError::new("invalid value"), + )), + } + } +} diff --git a/rsvg_internals/src/filters/component_transfer.rs b/rsvg_internals/src/filters/component_transfer.rs index b17c92098595cbc2437f31db2d38956e5bf200bc..5523a607fb764c04d6287d7161f91def3c9e9027 100644 --- a/rsvg_internals/src/filters/component_transfer.rs +++ b/rsvg_internals/src/filters/component_transfer.rs @@ -19,7 +19,7 @@ use surface_utils::{ use util::clamp; use super::context::{FilterContext, FilterOutput, FilterResult}; -use super::{make_result, Filter, FilterError, PrimitiveWithInput}; +use super::{Filter, FilterError, PrimitiveWithInput}; /// The `feComponentTransfer` filter primitive. pub struct ComponentTransfer { @@ -283,7 +283,7 @@ impl Filter for ComponentTransfer { ctx: &FilterContext, draw_ctx: &mut DrawingCtx, ) -> Result { - let input = make_result(self.base.get_input(ctx, draw_ctx))?; + let input = self.base.get_input(ctx, draw_ctx)?; let bounds = self .base .get_bounds(ctx) @@ -295,7 +295,7 @@ impl Filter for ComponentTransfer { cairo::Format::ARgb32, ctx.source_graphic().width(), ctx.source_graphic().height(), - ).map_err(FilterError::OutputSurfaceCreation)?; + ).map_err(FilterError::IntermediateSurfaceCreation)?; // Enumerate all child nodes. let functions = node @@ -377,7 +377,8 @@ impl Filter for ComponentTransfer { Ok(FilterResult { name: self.base.result.borrow().clone(), output: FilterOutput { - surface: SharedImageSurface::new(output_surface).unwrap(), + surface: SharedImageSurface::new(output_surface) + .map_err(FilterError::BadIntermediateSurfaceStatus)?, bounds, }, }) diff --git a/rsvg_internals/src/filters/composite.rs b/rsvg_internals/src/filters/composite.rs index d39d0fcb9eb8f84f2c0d46dc0398db843287f4de..4507c184254a491f7e779fe7b4b3aef3590650fd 100644 --- a/rsvg_internals/src/filters/composite.rs +++ b/rsvg_internals/src/filters/composite.rs @@ -20,7 +20,7 @@ use util::clamp; use super::context::{FilterContext, FilterOutput, FilterResult}; use super::input::Input; -use super::{make_result, Filter, FilterError, PrimitiveWithInput}; +use super::{Filter, FilterError, PrimitiveWithInput}; /// Enumeration of the possible compositing operations. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] @@ -107,8 +107,8 @@ impl Filter for Composite { ctx: &FilterContext, draw_ctx: &mut DrawingCtx, ) -> Result { - let input = make_result(self.base.get_input(ctx, draw_ctx))?; - let input_2 = make_result(ctx.get_input(draw_ctx, self.in2.borrow().as_ref()))?; + let input = self.base.get_input(ctx, draw_ctx)?; + let input_2 = ctx.get_input(draw_ctx, self.in2.borrow().as_ref())?; let bounds = self .base .get_bounds(ctx) @@ -121,7 +121,7 @@ impl Filter for Composite { cairo::Format::ARgb32, input.surface().width(), input.surface().height(), - ).map_err(FilterError::OutputSurfaceCreation)?; + ).map_err(FilterError::IntermediateSurfaceCreation)?; let output_stride = output_surface.get_stride() as usize; { @@ -170,7 +170,7 @@ impl Filter for Composite { let output_surface = input_2 .surface() .copy_surface(bounds) - .map_err(FilterError::OutputSurfaceCreation)?; + .map_err(FilterError::IntermediateSurfaceCreation)?; let cr = cairo::Context::new(&output_surface); cr.rectangle( @@ -191,7 +191,8 @@ impl Filter for Composite { Ok(FilterResult { name: self.base.result.borrow().clone(), output: FilterOutput { - surface: SharedImageSurface::new(output_surface).unwrap(), + surface: SharedImageSurface::new(output_surface) + .map_err(FilterError::BadIntermediateSurfaceStatus)?, bounds, }, }) diff --git a/rsvg_internals/src/filters/context.rs b/rsvg_internals/src/filters/context.rs index bf6bb98be91c88a8cd71ca543c845de68d669e2e..3642bd640807842c78a149af737dcd151c66a660 100644 --- a/rsvg_internals/src/filters/context.rs +++ b/rsvg_internals/src/filters/context.rs @@ -15,10 +15,11 @@ use length::RsvgLength; use node::RsvgNode; use paint_server::{self, PaintServer}; use srgb::{linearize_surface, unlinearize_surface}; -use surface_utils::{iterators::Pixels, shared_surface::SharedImageSurface, Pixel}; +use surface_utils::shared_surface::SharedImageSurface; use unitinterval::UnitInterval; use super::bounds::BoundsBuilder; +use super::error::FilterError; use super::input::Input; use super::node::NodeFilter; use super::RsvgFilterPrimitive; @@ -88,7 +89,7 @@ pub struct FilterContext { /// Surfaces of the previous filter primitives by name. previous_results: HashMap, /// The background surface. Computed lazily. - background_surface: UnsafeCell>>, + background_surface: UnsafeCell>>, /// The filter effects region. effects_region: BoundingBox, /// Whether the currently rendered filter primitive uses linear RGB for color operations. @@ -112,37 +113,17 @@ pub struct FilterContext { /// This is to be used in conjunction with setting the viewbox size to account for the scaling. /// For `filterUnits == userSpaceOnUse`, the viewbox will have the actual resolution size, and /// for `filterUnits == objectBoundingBox`, the viewbox will have the size of 1, 1. - affine: cairo::Matrix, + _affine: cairo::Matrix, /// The filter primitive affine matrix. /// - /// See the comments for `affine`, they largely apply here. + /// See the comments for `_affine`, they largely apply here. paffine: cairo::Matrix, /// Obsolete; remove when all filters are ported to Rust. channelmap: [i32; 4], } -/// Returns a surface with black background and alpha channel matching the input surface. -fn extract_alpha( - surface: &SharedImageSurface, - bounds: IRect, -) -> Result { - let mut output_surface = - cairo::ImageSurface::create(cairo::Format::ARgb32, surface.width(), surface.height())?; - - let output_stride = output_surface.get_stride() as usize; - { - let mut output_data = output_surface.get_data().unwrap(); - - for (x, y, Pixel { a, .. }) in Pixels::new(surface, bounds) { - output_data[y as usize * output_stride + x as usize * 4 + 3] = a; - } - } - - Ok(output_surface) -} - /// Computes and returns the filter effects region. fn compute_effects_region( filter_node: &RsvgNode, @@ -220,6 +201,19 @@ impl IRect { pub fn contains(self, x: i32, y: i32) -> bool { x >= self.x0 && x < self.x1 && y >= self.y0 && y < self.y1 } + + /// Returns an `IRect` scaled by the given amounts. + /// + /// The returned `IRect` encompasses all, even partially covered, pixels after the scaling. + #[inline] + pub fn scale(self, x: f64, y: f64) -> IRect { + IRect { + x0: (f64::from(self.x0) * x).floor() as i32, + y0: (f64::from(self.y0) * y).floor() as i32, + x1: (f64::from(self.x1) * x).ceil() as i32, + y1: (f64::from(self.y1) * y).ceil() as i32, + } + } } impl FilterContext { @@ -286,7 +280,7 @@ impl FilterContext { f64::from(height), ), processing_linear_rgb: false, - affine, + _affine: affine, paffine, channelmap, }; @@ -326,8 +320,10 @@ impl FilterContext { /// Returns the surface containing the source graphic alpha. #[inline] - pub fn source_alpha(&self, bounds: IRect) -> Result { - extract_alpha(self.source_graphic(), bounds) + pub fn source_alpha(&self, bounds: IRect) -> Result { + self.source_surface + .extract_alpha(bounds) + .map_err(FilterError::IntermediateSurfaceCreation) } /// Computes and returns the background image snapshot. @@ -362,7 +358,7 @@ impl FilterContext { pub fn background_image( &self, draw_ctx: &DrawingCtx, - ) -> Result<&SharedImageSurface, cairo::Status> { + ) -> Result<&SharedImageSurface, FilterError> { { // At this point either no, or only immutable references to background_surface exist, so // it's ok to make an immutable reference. @@ -381,7 +377,11 @@ impl FilterContext { *bg = Some( self.compute_background_image(draw_ctx) - .and_then(SharedImageSurface::new), + .map_err(FilterError::IntermediateSurfaceCreation) + .and_then(|surface| { + SharedImageSurface::new(surface) + .map_err(FilterError::BadIntermediateSurfaceStatus) + }), ); // Return the only existing reference as immutable. @@ -394,9 +394,10 @@ impl FilterContext { &self, draw_ctx: &DrawingCtx, bounds: IRect, - ) -> Result { - self.background_image(draw_ctx) - .and_then(|surface| extract_alpha(surface, bounds)) + ) -> Result { + self.background_image(draw_ctx)? + .extract_alpha(bounds) + .map_err(FilterError::IntermediateSurfaceCreation) } /// Returns the output of the filter primitive by its result name. @@ -416,13 +417,16 @@ impl FilterContext { /// Stores a filter primitive result into the context. #[inline] - pub fn store_result(&mut self, mut result: FilterResult) { + pub fn store_result(&mut self, mut result: FilterResult) -> Result<(), FilterError> { // Unlinearize the surface if needed. if self.processing_linear_rgb { - // TODO: unwrap() on unlinearize_surface() can panic if we run out of memory for Cairo. - result.output.surface = SharedImageSurface::new( - unlinearize_surface(&result.output.surface, result.output.bounds).unwrap(), - ).unwrap(); + result.output.surface = + unlinearize_surface(&result.output.surface, result.output.bounds) + .map_err(FilterError::IntermediateSurfaceCreation) + .and_then(|surface| { + SharedImageSurface::new(surface) + .map_err(FilterError::BadIntermediateSurfaceStatus) + })?; } if let Some(name) = result.name { @@ -430,6 +434,7 @@ impl FilterContext { } self.last_result = Some(result.output); + Ok(()) } /// Returns the paffine matrix. @@ -509,16 +514,19 @@ impl FilterContext { /// Retrieves the filter input surface according to the SVG rules. /// /// Does not take `processing_linear_rgb` into account. - // TODO: pass the errors through. - fn get_input_raw(&self, draw_ctx: &mut DrawingCtx, in_: Option<&Input>) -> Option { + fn get_input_raw( + &self, + draw_ctx: &mut DrawingCtx, + in_: Option<&Input>, + ) -> Result { if in_.is_none() { // No value => use the last result. // As per the SVG spec, if the filter primitive is the first in the chain, return the // source graphic. if let Some(output) = self.last_result().cloned() { - return Some(FilterInput::PrimitiveOutput(output)); + return Ok(FilterInput::PrimitiveOutput(output)); } else { - return Some(FilterInput::StandardInput(self.source_graphic().clone())); + return Ok(FilterInput::StandardInput(self.source_graphic().clone())); } } @@ -526,48 +534,62 @@ impl FilterContext { let values = cascaded.get(); match *in_.unwrap() { - Input::SourceGraphic => Some(FilterInput::StandardInput(self.source_graphic().clone())), + Input::SourceGraphic => Ok(FilterInput::StandardInput(self.source_graphic().clone())), Input::SourceAlpha => self .source_alpha(self.effects_region().rect.unwrap().into()) - .ok() - .map(|surface| SharedImageSurface::new(surface).unwrap()) + .and_then(|surface| { + SharedImageSurface::new(surface) + .map_err(FilterError::BadIntermediateSurfaceStatus) + }) .map(FilterInput::StandardInput), Input::BackgroundImage => self .background_image(draw_ctx) - .ok() - .cloned() + .map(Clone::clone) .map(FilterInput::StandardInput), Input::BackgroundAlpha => self .background_alpha(draw_ctx, self.effects_region().rect.unwrap().into()) - .ok() - .map(|surface| SharedImageSurface::new(surface).unwrap()) + .and_then(|surface| { + SharedImageSurface::new(surface) + .map_err(FilterError::BadIntermediateSurfaceStatus) + }) .map(FilterInput::StandardInput), Input::FillPaint => self .get_paint_server_surface(draw_ctx, &values.fill.0, values.fill_opacity.0) - .ok() - .map(|surface| SharedImageSurface::new(surface).unwrap()) + .map_err(FilterError::IntermediateSurfaceCreation) + .and_then(|surface| { + SharedImageSurface::new(surface) + .map_err(FilterError::BadIntermediateSurfaceStatus) + }) .map(FilterInput::StandardInput), Input::StrokePaint => self .get_paint_server_surface(draw_ctx, &values.stroke.0, values.stroke_opacity.0) - .ok() - .map(|surface| SharedImageSurface::new(surface).unwrap()) + .map_err(FilterError::IntermediateSurfaceCreation) + .and_then(|surface| { + SharedImageSurface::new(surface) + .map_err(FilterError::BadIntermediateSurfaceStatus) + }) .map(FilterInput::StandardInput), Input::FilterOutput(ref name) => self .filter_output(name) .cloned() - .map(FilterInput::PrimitiveOutput), + .map(FilterInput::PrimitiveOutput) + .ok_or(FilterError::InvalidInput), } } /// Retrieves the filter input surface according to the SVG rules. - pub fn get_input(&self, draw_ctx: &mut DrawingCtx, in_: Option<&Input>) -> Option { - let raw = self.get_input_raw(draw_ctx, in_); + pub fn get_input( + &self, + draw_ctx: &mut DrawingCtx, + in_: Option<&Input>, + ) -> Result { + let raw = self.get_input_raw(draw_ctx, in_)?; // Linearize the returned surface if needed. - if raw.is_some() && self.processing_linear_rgb { - let (surface, bounds) = match raw.as_ref().unwrap() { + if self.processing_linear_rgb { + let (surface, bounds) = match raw { FilterInput::StandardInput(ref surface) => { (surface, self.effects_region().rect.unwrap().into()) } @@ -578,16 +600,19 @@ impl FilterContext { }; linearize_surface(surface, bounds) - .ok() - .map(|surface| SharedImageSurface::new(surface).unwrap()) - .map(|surface| match raw.as_ref().unwrap() { + .map_err(FilterError::IntermediateSurfaceCreation) + .and_then(|surface| { + SharedImageSurface::new(surface) + .map_err(FilterError::BadIntermediateSurfaceStatus) + }) + .map(|surface| match raw { FilterInput::StandardInput(_) => FilterInput::StandardInput(surface), - FilterInput::PrimitiveOutput(output) => { + FilterInput::PrimitiveOutput(ref output) => { FilterInput::PrimitiveOutput(FilterOutput { surface, ..*output }) } }) } else { - raw + Ok(raw) } } @@ -697,7 +722,7 @@ pub unsafe extern "C" fn rsvg_filter_store_output( }, }; - (*ctx).store_result(result); + (*ctx).store_result(result).unwrap(); } #[no_mangle] @@ -766,7 +791,7 @@ pub unsafe extern "C" fn rsvg_filter_get_result( let ctx = &*ctx; match ctx.get_input(draw_ctx, input.as_ref()) { - None => RsvgFilterPrimitiveOutput { + Err(_) => RsvgFilterPrimitiveOutput { surface: ptr::null_mut(), bounds: IRect { x0: 0, @@ -775,7 +800,7 @@ pub unsafe extern "C" fn rsvg_filter_get_result( y1: 0, }, }, - Some(input) => { + Ok(input) => { // HACK because to_glib_full() is unimplemented!() on ImageSurface. let ptr = input.surface().to_glib_none().0; @@ -806,6 +831,7 @@ pub unsafe extern "C" fn rsvg_filter_get_in( #[cfg(test)] mod tests { use super::*; + use surface_utils::iterators::Pixels; #[test] fn test_extract_alpha() { @@ -839,7 +865,7 @@ mod tests { } let surface = SharedImageSurface::new(surface).unwrap(); - let alpha = SharedImageSurface::new(extract_alpha(&surface, BOUNDS).unwrap()).unwrap(); + let alpha = SharedImageSurface::new(surface.extract_alpha(BOUNDS).unwrap()).unwrap(); for (x, y, p, pa) in Pixels::new(&surface, FULL_BOUNDS).map(|(x, y, p)| (x, y, p, alpha.get_pixel(x, y))) diff --git a/rsvg_internals/src/filters/convolve_matrix.rs b/rsvg_internals/src/filters/convolve_matrix.rs new file mode 100644 index 0000000000000000000000000000000000000000..70a46fed8efa28094f62586d49e0702ecec4c1fb --- /dev/null +++ b/rsvg_internals/src/filters/convolve_matrix.rs @@ -0,0 +1,410 @@ +use std::cell::{Cell, RefCell}; + +use cairo::{self, ImageSurface}; +use rulinalg::matrix::{BaseMatrix, Matrix}; + +use attributes::Attribute; +use drawing_ctx::DrawingCtx; +use error::NodeError; +use handle::RsvgHandle; +use node::{NodeResult, NodeTrait, RsvgCNodeImpl, RsvgNode}; +use parsers::{self, ListLength, NumberListError, ParseError}; +use property_bag::PropertyBag; +use surface_utils::{ + iterators::{PixelRectangle, Pixels}, + shared_surface::SharedImageSurface, + EdgeMode, + ImageSurfaceDataExt, + Pixel, +}; +use util::clamp; + +use super::context::{FilterContext, FilterOutput, FilterResult, IRect}; +use super::{Filter, FilterError, PrimitiveWithInput}; + +/// The `feConvolveMatrix` filter primitive. +pub struct ConvolveMatrix { + base: PrimitiveWithInput, + order: Cell<(u32, u32)>, + kernel_matrix: RefCell>>, + divisor: Cell>, + bias: Cell, + target_x: Cell>, + target_y: Cell>, + edge_mode: Cell, + kernel_unit_length: Cell>, + preserve_alpha: Cell, +} + +impl ConvolveMatrix { + /// Constructs a new `ConvolveMatrix` with empty properties. + #[inline] + pub fn new() -> ConvolveMatrix { + ConvolveMatrix { + base: PrimitiveWithInput::new::(), + order: Cell::new((3, 3)), + kernel_matrix: RefCell::new(None), + divisor: Cell::new(None), + bias: Cell::new(0.0), + target_x: Cell::new(None), + target_y: Cell::new(None), + edge_mode: Cell::new(EdgeMode::Duplicate), + kernel_unit_length: Cell::new(None), + preserve_alpha: Cell::new(false), + } + } +} + +impl NodeTrait for ConvolveMatrix { + fn set_atts( + &self, + node: &RsvgNode, + handle: *const RsvgHandle, + pbag: &PropertyBag, + ) -> NodeResult { + self.base.set_atts(node, handle, pbag)?; + + for (_key, attr, value) in pbag.iter() { + match attr { + Attribute::Order => self.order.set( + parsers::integer_optional_integer(value) + .map_err(|err| NodeError::parse_error(attr, err)) + .and_then(|(x, y)| { + if x > 0 && y > 0 { + Ok((x as u32, y as u32)) + } else { + Err(NodeError::value_error( + attr, + "values must be greater than 0", + )) + } + })?, + ), + Attribute::Divisor => self.divisor.set(Some( + parsers::number(value) + .map_err(|err| NodeError::parse_error(attr, err)) + .and_then(|x| { + if x != 0.0 { + Ok(x) + } else { + Err(NodeError::value_error(attr, "divisor cannot be equal to 0")) + } + })?, + )), + Attribute::Bias => self + .bias + .set(parsers::number(value).map_err(|err| NodeError::parse_error(attr, err))?), + Attribute::EdgeMode => self.edge_mode.set(EdgeMode::parse(attr, value)?), + Attribute::KernelUnitLength => self.kernel_unit_length.set(Some( + parsers::number_optional_number(value) + .map_err(|err| NodeError::parse_error(attr, err)) + .and_then(|(x, y)| { + if x > 0.0 && y > 0.0 { + Ok((x, y)) + } else { + Err(NodeError::value_error( + attr, + "kernelUnitLength can't be less or equal to zero", + )) + } + })?, + )), + Attribute::PreserveAlpha => self.preserve_alpha.set(match value { + "false" => false, + "true" => true, + _ => { + return Err(NodeError::parse_error( + attr, + ParseError::new("expected false or true"), + )) + } + }), + _ => (), + } + } + + // target_x and target_y depend on order. + for (_key, attr, value) in pbag.iter() { + match attr { + Attribute::TargetX => self.target_x.set(Some( + parsers::integer(value) + .map_err(|err| NodeError::parse_error(attr, err)) + .and_then(|x| { + if x >= 0 && x < self.order.get().0 as i32 { + Ok(x as u32) + } else { + Err(NodeError::value_error( + attr, + "targetX must be greater or equal to zero and less than orderX", + )) + } + })?, + )), + Attribute::TargetY => self.target_y.set(Some( + parsers::integer(value) + .map_err(|err| NodeError::parse_error(attr, err)) + .and_then(|x| { + if x >= 0 && x < self.order.get().1 as i32 { + Ok(x as u32) + } else { + Err(NodeError::value_error( + attr, + "targetY must be greater or equal to zero and less than orderY", + )) + } + })?, + )), + _ => (), + } + } + + // Default values for target_x and target_y. + if self.target_x.get().is_none() { + self.target_x.set(Some(self.order.get().0 / 2)); + } + if self.target_y.get().is_none() { + self.target_y.set(Some(self.order.get().1 / 2)); + } + + // Finally, parse the kernel matrix. + for (_, attr, value) in pbag + .iter() + .filter(|(_, attr, _)| *attr == Attribute::KernelMatrix) + { + self.kernel_matrix.replace(Some({ + let number_of_elements = self.order.get().0 as usize * self.order.get().1 as usize; + + Matrix::new( + self.order.get().1 as usize, + self.order.get().0 as usize, + parsers::number_list_from_str(value, ListLength::Exact(number_of_elements)) + .map_err(|err| { + NodeError::parse_error( + attr, + match err { + NumberListError::IncorrectNumberOfElements => { + ParseError::new(format!( + "incorrect number of elements: expected {}", + number_of_elements + )) + } + NumberListError::Parse(err) => err, + }, + ) + })?, + ) + })); + } + + // Default value for the divisor. + if self.divisor.get().is_none() { + self.divisor + .set(Some(self.kernel_matrix.borrow().as_ref().unwrap().sum())); + + if self.divisor.get().unwrap() == 0.0 { + self.divisor.set(Some(1.0)); + } + } + + Ok(()) + } + + #[inline] + fn get_c_impl(&self) -> *const RsvgCNodeImpl { + self.base.get_c_impl() + } +} + +impl Filter for ConvolveMatrix { + fn render( + &self, + _node: &RsvgNode, + ctx: &FilterContext, + draw_ctx: &mut DrawingCtx, + ) -> Result { + let input = self.base.get_input(ctx, draw_ctx)?; + let mut bounds = self + .base + .get_bounds(ctx) + .add_input(&input) + .into_irect(draw_ctx); + let original_bounds = bounds; + + let mut input_surface = if self.preserve_alpha.get() { + // preserve_alpha means we need to premultiply and unpremultiply the values. + // + // HACK: this is storing unpremultiplied pixels in an ARGB32 image surface (which is + // supposed to be premultiplied pixels). + let mut unpremultiplied_surface = ImageSurface::create( + cairo::Format::ARgb32, + input.surface().width(), + input.surface().height(), + ).map_err(FilterError::IntermediateSurfaceCreation)?; + + let stride = unpremultiplied_surface.get_stride() as usize; + { + let mut data = unpremultiplied_surface.get_data().unwrap(); + + for (x, y, pixel) in Pixels::new(input.surface(), bounds) { + let new_pixel = if pixel.a == 0 { + pixel + } else { + let alpha = f64::from(pixel.a) / 255.0; + let unpremultiply = |x| (f64::from(x) / alpha).round() as u8; + + Pixel { + r: unpremultiply(pixel.r), + g: unpremultiply(pixel.g), + b: unpremultiply(pixel.b), + a: pixel.a, + } + }; + + data.set_pixel(stride, new_pixel, x, y); + } + } + + SharedImageSurface::new(unpremultiplied_surface) + .map_err(FilterError::BadIntermediateSurfaceStatus)? + } else { + input.surface().clone() + }; + + let scale = self.kernel_unit_length.get().map(|(dx, dy)| { + let paffine = ctx.paffine(); + let ox = paffine.xx * dx + paffine.xy * dy; + let oy = paffine.yx * dx + paffine.yy * dy; + + (ox, oy) + }); + + if let Some((ox, oy)) = scale { + // Scale the input surface to match kernel_unit_length. + let (new_surface, new_bounds) = input_surface + .scale(bounds, 1.0 / ox, 1.0 / oy) + .map_err(FilterError::IntermediateSurfaceCreation)?; + + input_surface = new_surface; + bounds = new_bounds; + } + + let matrix = self.kernel_matrix.borrow(); + let matrix = matrix.as_ref().unwrap(); + + let mut output_surface = ImageSurface::create( + cairo::Format::ARgb32, + input_surface.width(), + input_surface.height(), + ).map_err(FilterError::IntermediateSurfaceCreation)?; + + let output_stride = output_surface.get_stride() as usize; + { + let mut output_data = output_surface.get_data().unwrap(); + + for (x, y, pixel) in Pixels::new(&input_surface, bounds) { + // Compute the convolution rectangle bounds. + let kernel_bounds = IRect { + x0: x as i32 - self.target_x.get().unwrap() as i32, + y0: y as i32 - self.target_y.get().unwrap() as i32, + x1: x as i32 - self.target_x.get().unwrap() as i32 + self.order.get().0 as i32, + y1: y as i32 - self.target_y.get().unwrap() as i32 + self.order.get().1 as i32, + }; + + // Do the convolution. + let mut r = 0.0; + let mut g = 0.0; + let mut b = 0.0; + let mut a = 0.0; + + for (x, y, pixel) in + PixelRectangle::new(&input_surface, bounds, kernel_bounds, self.edge_mode.get()) + { + let kernel_x = (kernel_bounds.x1 - x - 1) as usize; + let kernel_y = (kernel_bounds.y1 - y - 1) as usize; + + r += f64::from(pixel.r) / 255.0 * matrix[[kernel_y, kernel_x]]; + g += f64::from(pixel.g) / 255.0 * matrix[[kernel_y, kernel_x]]; + b += f64::from(pixel.b) / 255.0 * matrix[[kernel_y, kernel_x]]; + + if !self.preserve_alpha.get() { + a += f64::from(pixel.a) / 255.0 * matrix[[kernel_y, kernel_x]]; + } + } + + // If preserve_alpha is true, set a to the source alpha value. + if self.preserve_alpha.get() { + a = f64::from(pixel.a) / 255.0; + } else { + a = a / self.divisor.get().unwrap() + self.bias.get(); + } + + let clamped_a = clamp(a, 0.0, 1.0); + + let compute = |x| { + let x = x / self.divisor.get().unwrap() + self.bias.get() * a; + + let x = if self.preserve_alpha.get() { + // Premultiply the output value. + clamp(x, 0.0, 1.0) * clamped_a + } else { + clamp(x, 0.0, clamped_a) + }; + + (x * 255.0).round() as u8 + }; + + let output_pixel = Pixel { + r: compute(r), + g: compute(g), + b: compute(b), + a: (clamped_a * 255.0).round() as u8, + }; + + output_data.set_pixel(output_stride, output_pixel, x, y); + } + } + + let mut output_surface = SharedImageSurface::new(output_surface) + .map_err(FilterError::BadIntermediateSurfaceStatus)?; + + if let Some((ox, oy)) = scale { + // Scale the output surface back. + output_surface = output_surface + .scale_to( + ctx.source_graphic().width(), + ctx.source_graphic().height(), + original_bounds, + ox, + oy, + ) + .map_err(FilterError::IntermediateSurfaceCreation)?; + } + + Ok(FilterResult { + name: self.base.result.borrow().clone(), + output: FilterOutput { + surface: output_surface, + bounds, + }, + }) + } + + #[inline] + fn is_affected_by_color_interpolation_filters() -> bool { + true + } +} + +impl EdgeMode { + fn parse(attr: Attribute, s: &str) -> Result { + match s { + "duplicate" => Ok(EdgeMode::Duplicate), + "wrap" => Ok(EdgeMode::Wrap), + "none" => Ok(EdgeMode::None), + _ => Err(NodeError::parse_error( + attr, + ParseError::new("invalid value"), + )), + } + } +} diff --git a/rsvg_internals/src/filters/error.rs b/rsvg_internals/src/filters/error.rs index 831bce0e24ffd6ef2106da49781ba4d78b98439b..3522a5317240b8635aa17a53795ef4b3269e3a46 100644 --- a/rsvg_internals/src/filters/error.rs +++ b/rsvg_internals/src/filters/error.rs @@ -4,14 +4,16 @@ use std::fmt; use cairo; /// An enumeration of errors that can occur during filter primitive rendering. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum FilterError { /// The filter was passed invalid input (the `in` attribute). InvalidInput, /// The filter input surface has an unsuccessful status. BadInputSurfaceStatus(cairo::Status), - /// Couldn't create the output surface. - OutputSurfaceCreation(cairo::Status), + /// Couldn't create an intermediate surface. + IntermediateSurfaceCreation(cairo::Status), + /// An intermediate surface has an unsuccessful status. + BadIntermediateSurfaceStatus(cairo::Status), } impl Error for FilterError { @@ -20,7 +22,12 @@ impl Error for FilterError { match *self { FilterError::InvalidInput => "invalid value of the `in` attribute", FilterError::BadInputSurfaceStatus(_) => "invalid status of the input surface", - FilterError::OutputSurfaceCreation(_) => "couldn't create the output surface", + FilterError::IntermediateSurfaceCreation(_) => { + "couldn't create an intermediate surface" + } + FilterError::BadIntermediateSurfaceStatus(_) => { + "invalid status of an intermediate surface" + } } } @@ -33,10 +40,9 @@ impl Error for FilterError { impl fmt::Display for FilterError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - FilterError::BadInputSurfaceStatus(ref status) => { - write!(f, "{}: {:?}", self.description(), status) - } - FilterError::OutputSurfaceCreation(ref status) => { + FilterError::BadInputSurfaceStatus(ref status) + | FilterError::IntermediateSurfaceCreation(ref status) + | FilterError::BadIntermediateSurfaceStatus(ref status) => { write!(f, "{}: {:?}", self.description(), status) } _ => write!(f, "{}", self.description()), diff --git a/rsvg_internals/src/filters/ffi.rs b/rsvg_internals/src/filters/ffi.rs index 3a333cbd0e3824b6a369782ca2db90cf51f9ea78..b6450b66332e95a14614df25ed2a13359134f683 100644 --- a/rsvg_internals/src/filters/ffi.rs +++ b/rsvg_internals/src/filters/ffi.rs @@ -136,8 +136,10 @@ pub fn filter_render( }) .for_each(|(mut c, linear_rgb)| match c.get_type() { NodeType::FilterPrimitiveBlend + | NodeType::FilterPrimitiveColorMatrix | NodeType::FilterPrimitiveComponentTransfer | NodeType::FilterPrimitiveComposite + | NodeType::FilterPrimitiveConvolveMatrix | NodeType::FilterPrimitiveFlood | NodeType::FilterPrimitiveImage | NodeType::FilterPrimitiveMerge @@ -145,9 +147,10 @@ pub fn filter_render( let pointers = unsafe { *(c.get_c_impl() as *const FilterFunctionPointers) }; let mut render = |filter_ctx: &mut FilterContext| { - match (pointers.render)(&c, filter_ctx, draw_ctx) { - Ok(result) => filter_ctx.store_result(result), - Err(_) => { /* Do nothing for now */ } + if let Err(_) = (pointers.render)(&c, filter_ctx, draw_ctx) + .and_then(|result| filter_ctx.store_result(result)) + { + // Do nothing for now. } }; diff --git a/rsvg_internals/src/filters/flood.rs b/rsvg_internals/src/filters/flood.rs index 0012a8cac464fa5f14f6825648551c71d4c83156..646184393e99fb61290f42dac186dc8c13706bdb 100644 --- a/rsvg_internals/src/filters/flood.rs +++ b/rsvg_internals/src/filters/flood.rs @@ -55,7 +55,7 @@ impl Filter for Flood { cairo::Format::ARgb32, ctx.source_graphic().width(), ctx.source_graphic().height(), - ).map_err(FilterError::OutputSurfaceCreation)?; + ).map_err(FilterError::IntermediateSurfaceCreation)?; let cascaded = node.get_cascaded_values(); let values = cascaded.get(); @@ -88,7 +88,8 @@ impl Filter for Flood { Ok(FilterResult { name: self.base.result.borrow().clone(), output: FilterOutput { - surface: SharedImageSurface::new(output_surface).unwrap(), + surface: SharedImageSurface::new(output_surface) + .map_err(FilterError::BadIntermediateSurfaceStatus)?, bounds, }, }) diff --git a/rsvg_internals/src/filters/image.rs b/rsvg_internals/src/filters/image.rs index 24db030fd760e8ce76459c30158c2acb9202d0f3..730bea386712759902bcb8f6410b24c22c7d1606 100644 --- a/rsvg_internals/src/filters/image.rs +++ b/rsvg_internals/src/filters/image.rs @@ -64,7 +64,7 @@ impl Image { cairo::Format::ARgb32, ctx.source_graphic().width(), ctx.source_graphic().height(), - ).map_err(FilterError::OutputSurfaceCreation)?; + ).map_err(FilterError::IntermediateSurfaceCreation)?; draw_ctx.get_cairo_context().set_matrix(ctx.paffine()); @@ -87,7 +87,7 @@ impl Image { cairo::Format::ARgb32, ctx.source_graphic().width(), ctx.source_graphic().height(), - ).map_err(FilterError::OutputSurfaceCreation)?; + ).map_err(FilterError::IntermediateSurfaceCreation)?; let cr = cairo::Context::new(&output_surface); cr.rectangle( @@ -142,7 +142,7 @@ impl Image { cairo::Format::ARgb32, ctx.source_graphic().width(), ctx.source_graphic().height(), - ).map_err(FilterError::OutputSurfaceCreation)?; + ).map_err(FilterError::IntermediateSurfaceCreation)?; // TODO: this goes through a f64->i32->f64 conversion. let render_bounds = bounds_builder.into_irect_without_clipping(draw_ctx); @@ -244,7 +244,8 @@ impl Filter for Image { Ok(FilterResult { name: self.base.result.borrow().clone(), output: FilterOutput { - surface: SharedImageSurface::new(output_surface).unwrap(), + surface: SharedImageSurface::new(output_surface) + .map_err(FilterError::BadIntermediateSurfaceStatus)?, bounds, }, }) diff --git a/rsvg_internals/src/filters/merge.rs b/rsvg_internals/src/filters/merge.rs index 5365ade87095a300f5c4a8624ad7015c3fff9b86..a41024986a8bd9d42436fbff50ca35d3bb658a04 100644 --- a/rsvg_internals/src/filters/merge.rs +++ b/rsvg_internals/src/filters/merge.rs @@ -11,7 +11,7 @@ use surface_utils::shared_surface::SharedImageSurface; use super::context::{FilterContext, FilterOutput, FilterResult, IRect}; use super::input::Input; -use super::{make_result, Filter, FilterError, Primitive}; +use super::{Filter, FilterError, Primitive}; /// The `feMerge` filter primitive. pub struct Merge { @@ -89,13 +89,13 @@ impl MergeNode { bounds: IRect, output_surface: Option, ) -> Result { - let input = make_result(ctx.get_input(draw_ctx, self.in_.borrow().as_ref()))?; + let input = ctx.get_input(draw_ctx, self.in_.borrow().as_ref())?; if output_surface.is_none() { return input .surface() .copy_surface(bounds) - .map_err(FilterError::OutputSurfaceCreation); + .map_err(FilterError::IntermediateSurfaceCreation); } let output_surface = output_surface.unwrap(); @@ -129,9 +129,9 @@ impl Filter for Merge { .children() .filter(|c| c.get_type() == NodeType::FilterPrimitiveMergeNode) { - bounds = bounds.add_input(&child.with_impl(|c: &MergeNode| { - make_result(ctx.get_input(draw_ctx, c.in_.borrow().as_ref())) - })?); + bounds = bounds.add_input( + &child.with_impl(|c: &MergeNode| ctx.get_input(draw_ctx, c.in_.borrow().as_ref()))?, + ); } let bounds = bounds.into_irect(draw_ctx); @@ -152,13 +152,14 @@ impl Filter for Merge { cairo::Format::ARgb32, ctx.source_graphic().width(), ctx.source_graphic().height(), - ).map_err(FilterError::OutputSurfaceCreation)?, + ).map_err(FilterError::IntermediateSurfaceCreation)?, }; Ok(FilterResult { name: self.base.result.borrow().clone(), output: FilterOutput { - surface: SharedImageSurface::new(output_surface).unwrap(), + surface: SharedImageSurface::new(output_surface) + .map_err(FilterError::BadIntermediateSurfaceStatus)?, bounds, }, }) diff --git a/rsvg_internals/src/filters/mod.rs b/rsvg_internals/src/filters/mod.rs index 9891786183d7470bd7684c0fc12b43a393d4b6c6..9c2c3993e7fdd5b1037fc4b1405a8213e8e01fd0 100644 --- a/rsvg_internals/src/filters/mod.rs +++ b/rsvg_internals/src/filters/mod.rs @@ -31,8 +31,10 @@ pub mod node; use self::node::NodeFilter; pub mod blend; +pub mod color_matrix; pub mod component_transfer; pub mod composite; +pub mod convolve_matrix; pub mod flood; pub mod image; pub mod merge; @@ -77,14 +79,6 @@ struct PrimitiveWithInput { in_: RefCell>, } -/// Calls `ok_or()` on `get_input()` output. -/// -/// A small convenience function for filter implementations. -#[inline] -fn make_result(x: Option) -> Result { - x.ok_or(FilterError::InvalidInput) -} - impl Primitive { /// Constructs a new `Primitive` with empty properties. #[inline] @@ -196,7 +190,11 @@ impl PrimitiveWithInput { /// Returns the input Cairo surface for this filter primitive. #[inline] - fn get_input(&self, ctx: &FilterContext, draw_ctx: &mut DrawingCtx) -> Option { + fn get_input( + &self, + ctx: &FilterContext, + draw_ctx: &mut DrawingCtx, + ) -> Result { ctx.get_input(draw_ctx, self.in_.borrow().as_ref()) } } diff --git a/rsvg_internals/src/filters/offset.rs b/rsvg_internals/src/filters/offset.rs index d55f8052c265bb84773b27facac65d9075a81b7d..0c3af4b70d0bbca6239db01946bef036a08ccc1c 100644 --- a/rsvg_internals/src/filters/offset.rs +++ b/rsvg_internals/src/filters/offset.rs @@ -13,7 +13,7 @@ use surface_utils::shared_surface::SharedImageSurface; use util::clamp; use super::context::{FilterContext, FilterOutput, FilterResult, IRect}; -use super::{make_result, Filter, FilterError, PrimitiveWithInput}; +use super::{Filter, FilterError, PrimitiveWithInput}; /// The `feOffset` filter primitive. pub struct Offset { @@ -71,7 +71,7 @@ impl Filter for Offset { ctx: &FilterContext, draw_ctx: &mut DrawingCtx, ) -> Result { - let input = make_result(self.base.get_input(ctx, draw_ctx))?; + let input = self.base.get_input(ctx, draw_ctx)?; let bounds = self .base .get_bounds(ctx) @@ -97,7 +97,7 @@ impl Filter for Offset { cairo::Format::ARgb32, ctx.source_graphic().width(), ctx.source_graphic().height(), - ).map_err(FilterError::OutputSurfaceCreation)?; + ).map_err(FilterError::IntermediateSurfaceCreation)?; { let cr = cairo::Context::new(&output_surface); @@ -116,7 +116,8 @@ impl Filter for Offset { Ok(FilterResult { name: self.base.result.borrow().clone(), output: FilterOutput { - surface: SharedImageSurface::new(output_surface).unwrap(), + surface: SharedImageSurface::new(output_surface) + .map_err(FilterError::BadIntermediateSurfaceStatus)?, bounds, }, }) diff --git a/rsvg_internals/src/image.rs b/rsvg_internals/src/image.rs index bb02cdf3322c113eb412beeffd21127ea0ba63d9..5a3459771d97eabc71d9c94fb6a6b1f563e1dd49 100644 --- a/rsvg_internals/src/image.rs +++ b/rsvg_internals/src/image.rs @@ -126,6 +126,17 @@ impl NodeTrait for NodeImage { dc.clip(x, y, w, h); } + // The bounding box for is decided by the values of x, y, w, h and not by + // the final computed image bounds. + let bbox = BoundingBox::new(&dc.get_cairo_context().get_matrix()).with_rect(Some( + cairo::Rectangle { + x, + y, + width: w, + height: h, + }, + )); + let width = surface.get_width(); let height = surface.get_height(); if clipping || width == 0 || height == 0 { @@ -138,15 +149,6 @@ impl NodeTrait for NodeImage { let (x, y, w, h) = aspect.compute(width, height, x, y, w, h); let cr = dc.get_cairo_context(); - let affine = cr.get_matrix(); - - // This is the target bbox after drawing. - let bbox = BoundingBox::new(&affine).with_rect(Some(cairo::Rectangle { - x, - y, - width: w, - height: h, - })); cr.save(); diff --git a/rsvg_internals/src/lib.rs b/rsvg_internals/src/lib.rs index 3c023f393c5868b5c9af4154e5f5fa3128d35f16..ba1f9cf209e39eaa3ad1b06591677a3d9989cb46 100644 --- a/rsvg_internals/src/lib.rs +++ b/rsvg_internals/src/lib.rs @@ -22,6 +22,9 @@ extern crate lazy_static; #[macro_use] extern crate downcast_rs; +#[macro_use] +extern crate rulinalg; + pub use attributes::rsvg_attribute_from_name; pub use cnode::{rsvg_rust_cnode_get_impl, rsvg_rust_cnode_new}; diff --git a/rsvg_internals/src/load.rs b/rsvg_internals/src/load.rs index d5e999980bf00a06ed8f3a223dd7669bcc90bee5..89adae53238e615ad50b7792596d887184105875 100644 --- a/rsvg_internals/src/load.rs +++ b/rsvg_internals/src/load.rs @@ -6,8 +6,10 @@ use std::ptr; use attributes::Attribute; use clip_path::NodeClipPath; use filters::blend::Blend; +use filters::color_matrix::ColorMatrix; use filters::component_transfer::{ComponentTransfer, FuncX}; use filters::composite::Composite; +use filters::convolve_matrix::ConvolveMatrix; use filters::flood::Flood; use filters::image::Image; use filters::merge::{Merge, MergeNode}; @@ -29,18 +31,6 @@ use util::utf8_cstr; #[allow(improper_ctypes)] extern "C" { - fn rsvg_new_filter_primitive_color_matrix( - _: *const libc::c_char, - _: *const RsvgNode, - _: *const libc::c_char, - _: *const libc::c_char, - ) -> *const RsvgNode; - fn rsvg_new_filter_primitive_convolve_matrix( - _: *const libc::c_char, - _: *const RsvgNode, - _: *const libc::c_char, - _: *const libc::c_char, - ) -> *const RsvgNode; fn rsvg_new_filter_primitive_diffuse_lighting( _: *const libc::c_char, _: *const RsvgNode, @@ -103,8 +93,6 @@ lazy_static! { #[cfg_attr(rustfmt, rustfmt_skip)] static ref NODE_CREATORS_C: HashMap<&'static str, (bool, NodeCreateCFn)> = { let mut h = HashMap::new(); - h.insert("feColorMatrix", (true, rsvg_new_filter_primitive_color_matrix as NodeCreateCFn)); - h.insert("feConvolveMatrix", (true, rsvg_new_filter_primitive_convolve_matrix as NodeCreateCFn)); h.insert("feDiffuseLighting", (true, rsvg_new_filter_primitive_diffuse_lighting as NodeCreateCFn)); h.insert("feDisplacementMap", (true, rsvg_new_filter_primitive_displacement_map as NodeCreateCFn)); h.insert("feDistantLight", (false, rsvg_new_node_light_source as NodeCreateCFn)); @@ -134,6 +122,11 @@ macro_rules! node_create_fn { node_create_fn!(create_circle, Circle, NodeCircle::new); node_create_fn!(create_clip_path, ClipPath, NodeClipPath::new); node_create_fn!(create_blend, FilterPrimitiveBlend, Blend::new); +node_create_fn!( + create_color_matrix, + FilterPrimitiveColorMatrix, + ColorMatrix::new +); node_create_fn!( create_component_transfer, FilterPrimitiveComponentTransfer, @@ -160,6 +153,11 @@ node_create_fn!( FuncX::new_a ); node_create_fn!(create_composite, FilterPrimitiveComposite, Composite::new); +node_create_fn!( + create_convolve_matrix, + FilterPrimitiveConvolveMatrix, + ConvolveMatrix::new +); node_create_fn!(create_defs, Defs, NodeDefs::new); node_create_fn!(create_ellipse, Ellipse, NodeEllipse::new); node_create_fn!(create_filter, Filter, NodeFilter::new); @@ -222,8 +220,10 @@ lazy_static! { /* h.insert("desc", (true, as NodeCreateFn)); */ h.insert("ellipse", (true, create_ellipse as NodeCreateFn)); h.insert("feBlend", (true, create_blend as NodeCreateFn)); + h.insert("feColorMatrix", (true, create_color_matrix as NodeCreateFn)); h.insert("feComponentTransfer", (true, create_component_transfer as NodeCreateFn)); h.insert("feComposite", (true, create_composite as NodeCreateFn)); + h.insert("feConvolveMatrix", (true, create_convolve_matrix as NodeCreateFn)); h.insert("feFuncR", (false, create_component_transfer_func_r as NodeCreateFn)); h.insert("feFuncG", (false, create_component_transfer_func_g as NodeCreateFn)); h.insert("feFuncB", (false, create_component_transfer_func_b as NodeCreateFn)); diff --git a/rsvg_internals/src/parsers.rs b/rsvg_internals/src/parsers.rs index fd9da9e5004a99399342f6e819fbc321bc149f94..fe0a234a3b7866e907740fe992c06e0a3a98eef2 100644 --- a/rsvg_internals/src/parsers.rs +++ b/rsvg_internals/src/parsers.rs @@ -199,6 +199,43 @@ pub fn number_optional_number(s: &str) -> Result<(f64, f64), ParseError> { } } +// integer +// +// https://www.w3.org/TR/SVG11/types.html#DataTypeInteger +pub fn integer(s: &str) -> Result { + let mut input = ParserInput::new(s); + let mut parser = Parser::new(&mut input); + + Ok(parser.expect_integer()?) +} + +// integer-optional-integer +// +// Like number-optional-number but with integers. +pub fn integer_optional_integer(s: &str) -> Result<(i32, i32), ParseError> { + let mut input = ParserInput::new(s); + let mut parser = Parser::new(&mut input); + + let x = parser.expect_integer()?; + + if !parser.is_exhausted() { + let state = parser.state(); + + match *parser.next()? { + Token::Comma => {} + _ => parser.reset(&state), + }; + + let y = parser.expect_integer()?; + + parser.expect_exhausted()?; + + Ok((x, y)) + } else { + Ok((x, x)) + } +} + #[no_mangle] pub extern "C" fn rsvg_css_parse_number_optional_number( s: *const libc::c_char, @@ -528,4 +565,42 @@ mod tests { assert!(number_list_from_str("1", ListLength::Exact(2)).is_err()); assert!(number_list_from_str("1 2", ListLength::Exact(3)).is_err()); } + + #[test] + fn parses_integer() { + assert_eq!(integer("1"), Ok(1)); + assert_eq!(integer("-1"), Ok(-1)); + } + + #[test] + fn invalid_integer() { + assert!(integer("").is_err()); + assert!(integer("1x").is_err()); + assert!(integer("1.5").is_err()); + } + + #[test] + fn parses_integer_optional_integer() { + assert_eq!(integer_optional_integer("1, 2"), Ok((1, 2))); + assert_eq!(integer_optional_integer("1 2"), Ok((1, 2))); + assert_eq!(integer_optional_integer("1"), Ok((1, 1))); + + assert_eq!(integer_optional_integer("-1, -2"), Ok((-1, -2))); + assert_eq!(integer_optional_integer("-1 -2"), Ok((-1, -2))); + assert_eq!(integer_optional_integer("-1"), Ok((-1, -1))); + } + + #[test] + fn invalid_integer_optional_integer() { + assert!(integer_optional_integer("").is_err()); + assert!(integer_optional_integer("1x").is_err()); + assert!(integer_optional_integer("x1").is_err()); + assert!(integer_optional_integer("1 x").is_err()); + assert!(integer_optional_integer("1 , x").is_err()); + assert!(integer_optional_integer("1 , 2x").is_err()); + assert!(integer_optional_integer("1 2 x").is_err()); + assert!(integer_optional_integer("1.5").is_err()); + assert!(integer_optional_integer("1 2.5").is_err()); + assert!(integer_optional_integer("1, 2.5").is_err()); + } } diff --git a/rsvg_internals/src/surface_utils/iterators.rs b/rsvg_internals/src/surface_utils/iterators.rs index 77cbf189743ba1029fa8b9e47cb148659d4724ff..2f94b3178c63e91c4828663a1f81f8e26b711abc 100644 --- a/rsvg_internals/src/surface_utils/iterators.rs +++ b/rsvg_internals/src/surface_utils/iterators.rs @@ -1,8 +1,9 @@ //! Pixel iterators for `SharedImageSurface`. use filters::context::IRect; +use util::clamp; use super::shared_surface::SharedImageSurface; -use super::Pixel; +use super::{EdgeMode, Pixel}; /// Iterator over pixels of a `SharedImageSurface`. #[derive(Debug, Clone, Copy)] @@ -13,6 +14,17 @@ pub struct Pixels<'a> { y: u32, } +/// Iterator over a (potentially out of bounds) rectangle of pixels of a `SharedImageSurface`. +#[derive(Debug, Clone, Copy)] +pub struct PixelRectangle<'a> { + surface: &'a SharedImageSurface, + bounds: IRect, + rectangle: IRect, + edge_mode: EdgeMode, + x: i32, + y: i32, +} + impl<'a> Pixels<'a> { /// Creates an iterator over the image surface pixels, constrained within the given bounds. #[inline] @@ -36,6 +48,39 @@ impl<'a> Pixels<'a> { } } +impl<'a> PixelRectangle<'a> { + /// Creates an iterator over the image surface pixels, constrained within the given bounds. + #[inline] + pub fn new( + surface: &'a SharedImageSurface, + bounds: IRect, + rectangle: IRect, + edge_mode: EdgeMode, + ) -> Self { + // Sanity checks. + assert!(bounds.x0 >= 0); + assert!(bounds.x0 <= surface.width()); + assert!(bounds.x1 >= bounds.x0); + assert!(bounds.x1 <= surface.width()); + assert!(bounds.y0 >= 0); + assert!(bounds.y0 <= surface.height()); + assert!(bounds.y1 >= bounds.y0); + assert!(bounds.y1 <= surface.height()); + + assert!(rectangle.x1 >= rectangle.x0); + assert!(rectangle.y1 >= rectangle.y0); + + Self { + surface, + bounds, + rectangle, + edge_mode, + x: rectangle.x0, + y: rectangle.y0, + } + } +} + impl<'a> Iterator for Pixels<'a> { type Item = (u32, u32, Pixel); @@ -59,6 +104,69 @@ impl<'a> Iterator for Pixels<'a> { } } +impl<'a> Iterator for PixelRectangle<'a> { + type Item = (i32, i32, Pixel); + + #[inline] + fn next(&mut self) -> Option { + // This means we hit the end on the last iteration. + if self.x == self.rectangle.x1 || self.y == self.rectangle.y1 { + return None; + } + + let rv = { + let get_pixel = |x, y| { + if x < self.bounds.x0 + || y < self.bounds.y0 + || x >= self.bounds.x1 + || y >= self.bounds.y1 + { + match self.edge_mode { + EdgeMode::None => Pixel { + r: 0, + g: 0, + b: 0, + a: 0, + }, + EdgeMode::Duplicate => { + let x = clamp(x, self.bounds.x0, self.bounds.x1); + let y = clamp(y, self.bounds.y0, self.bounds.y1); + self.surface.get_pixel(x as u32, y as u32) + } + EdgeMode::Wrap => { + let wrap = |mut x, v| { + while x < 0 { + x += v; + } + x % v + }; + + let x = self.bounds.x0 + + wrap(x - self.bounds.x0, self.bounds.x1 - self.bounds.x0); + let y = self.bounds.y0 + + wrap(y - self.bounds.y0, self.bounds.y1 - self.bounds.y0); + self.surface.get_pixel(x as u32, y as u32) + } + } + } else { + self.surface.get_pixel(x as u32, y as u32) + } + }; + + Some((self.x, self.y, get_pixel(self.x, self.y))) + }; + + if self.x + 1 == self.rectangle.x1 { + self.x = self.rectangle.x0; + self.y += 1; + } else { + self.x += 1; + } + + rv + } +} + #[cfg(test)] mod tests { use super::*; @@ -139,4 +247,32 @@ mod tests { }; assert_eq!(Pixels::new(&surface, bounds).count(), 0); } + + #[test] + fn pixel_rectangle() { + const WIDTH: i32 = 32; + const HEIGHT: i32 = 64; + + let surface = SharedImageSurface::new( + ImageSurface::create(cairo::Format::ARgb32, WIDTH, HEIGHT).unwrap(), + ).unwrap(); + + let bounds = IRect { + x0: 0, + y0: 0, + x1: WIDTH, + y1: HEIGHT, + }; + + let rect_bounds = IRect { + x0: -8, + y0: -8, + x1: 8, + y1: 8, + }; + assert_eq!( + PixelRectangle::new(&surface, bounds, rect_bounds, EdgeMode::None).count(), + (16 * 16) as usize + ); + } } diff --git a/rsvg_internals/src/surface_utils/mod.rs b/rsvg_internals/src/surface_utils/mod.rs index b07c26aafbeac9c653f55834bc8bd4ef30d8b487..d319ae915c20ec88a525c1eb278e363f8c0f37f1 100644 --- a/rsvg_internals/src/surface_utils/mod.rs +++ b/rsvg_internals/src/surface_utils/mod.rs @@ -15,6 +15,19 @@ pub struct Pixel { pub a: u8, } +/// Modes which specify how the values of out of bounds pixels are computed. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum EdgeMode { + /// The nearest inbounds pixel value is returned. + Duplicate, + /// The image is extended by taking the color values from the opposite of the image. + /// + /// Imagine the image being tiled infinitely, with the original image at the origin. + Wrap, + /// Zero RGBA values are returned. + None, +} + /// Extension methods for `cairo::ImageSurfaceData`. pub trait ImageSurfaceDataExt: DerefMut { /// Sets the pixel at the given coordinates. diff --git a/rsvg_internals/src/surface_utils/shared_surface.rs b/rsvg_internals/src/surface_utils/shared_surface.rs index b87375982520b0fb395668baa12caa89df599a65..fd8a7af0543ccb18bfb76a516941a5dc9cb281dc 100644 --- a/rsvg_internals/src/surface_utils/shared_surface.rs +++ b/rsvg_internals/src/surface_utils/shared_surface.rs @@ -8,7 +8,7 @@ use glib::translate::{Stash, ToGlibPtr}; use filters::context::IRect; -use super::Pixel; +use super::{iterators::Pixels, ImageSurfaceDataExt, Pixel}; /// Wrapper for a Cairo image surface that allows shared access. /// @@ -147,6 +147,77 @@ impl SharedImageSurface { Ok(output_surface) } + /// Scales the given surface by `x` and `y` into a surface `width`×`height` in size, clipped by + /// `bounds`. + pub fn scale_to( + &self, + width: i32, + height: i32, + bounds: IRect, + x: f64, + y: f64, + ) -> Result { + let output_surface = ImageSurface::create(cairo::Format::ARgb32, width, height)?; + + { + let cr = cairo::Context::new(&output_surface); + cr.rectangle( + bounds.x0 as f64, + bounds.y0 as f64, + (bounds.x1 - bounds.x0) as f64, + (bounds.y1 - bounds.y0) as f64, + ); + cr.clip(); + + cr.scale(x, y); + self.set_as_source_surface(&cr, 0.0, 0.0); + cr.paint(); + } + + Ok(SharedImageSurface::new(output_surface)?) + } + + /// Returns a scaled version of a surface and bounds. + #[inline] + pub fn scale( + &self, + bounds: IRect, + x: f64, + y: f64, + ) -> Result<(SharedImageSurface, IRect), cairo::Status> { + let new_width = (f64::from(self.width) * x).ceil() as i32; + let new_height = (f64::from(self.height) * x).ceil() as i32; + let new_bounds = bounds.scale(x, y); + + Ok(( + self.scale_to(new_width, new_height, new_bounds, x, y)?, + new_bounds, + )) + } + + /// Returns a surface with black background and alpha channel matching this surface. + pub fn extract_alpha(&self, bounds: IRect) -> Result { + let mut output_surface = + ImageSurface::create(cairo::Format::ARgb32, self.width, self.height)?; + + let output_stride = output_surface.get_stride() as usize; + { + let mut output_data = output_surface.get_data().unwrap(); + + for (x, y, Pixel { a, .. }) in Pixels::new(self, bounds) { + let output_pixel = Pixel { + r: 0, + g: 0, + b: 0, + a, + }; + output_data.set_pixel(output_stride, output_pixel, x, y); + } + } + + Ok(output_surface) + } + /// Returns a raw pointer to the underlying surface. /// /// # Safety diff --git a/tests/fixtures/reftests/filter-conv-bounds-ref.png b/tests/fixtures/reftests/filter-conv-bounds-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..604bdfebef9b9f7a680dcc358307b9d5eb45d093 Binary files /dev/null and b/tests/fixtures/reftests/filter-conv-bounds-ref.png differ diff --git a/tests/fixtures/reftests/filter-conv-bounds.svg b/tests/fixtures/reftests/filter-conv-bounds.svg new file mode 100644 index 0000000000000000000000000000000000000000..7d6eb44db73a947306a0d7b31776d54b0d24d4c6 --- /dev/null +++ b/tests/fixtures/reftests/filter-conv-bounds.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/fixtures/reftests/svg1.1/filters-color-01-b-ref.png b/tests/fixtures/reftests/svg1.1/filters-color-01-b-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..d6714c56daee55a5a1a9e88e4617d379a5b76198 Binary files /dev/null and b/tests/fixtures/reftests/svg1.1/filters-color-01-b-ref.png differ diff --git a/tests/fixtures/reftests/svg1.1/filters-color-01-b.svg b/tests/fixtures/reftests/svg1.1/filters-color-01-b.svg new file mode 100644 index 0000000000000000000000000000000000000000..53b993a0e42dc9ec15ba76d0e4d2604ed576f5ff --- /dev/null +++ b/tests/fixtures/reftests/svg1.1/filters-color-01-b.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + +

+ Test which verifies the basic facilities of + feColorMatrix. +

+

+ This test uses the following elements : a nested + <svg> with a viewBox attribute, <linearGradient>, + <filter>, <feColorMatrix>, <feComposite>. +

+

+ The test case shows five rectangles filled with a + gradient showing the effects of feColorMatrix: an + unfiltered rectangle acting as a reference, use of the + feColorMatrix matrix option to convert to grayscale, + use of the feColorMatrix saturate option, use of the + feColorMatrix hueRotate option, and use of the + feColorMatrix luminanceToAlpha option. +

+

+ The test is somewhat self-explanatory as the strings + document the type of feColorMatrix operation that is + being used. +

+ + +

+ Run the test. No interaction required. +

+
+ +

+ The rendered picture should match the reference image + exactly, except for possible variations in the + labelling text (per CSS2 rules). +

+
+ + $RCSfile: filters-color-01-b.svg,v $ + + + + + + + + + + Example feColorMatrix - Examples of feColorMatrix operations + Five rectangles filled with a gradient showing the effects of feColorMatrix: an unfiltered rectangle acting as a reference, use of the feColorMatrix matrix option to convert to grayscale, use of the feColorMatrix saturate option, use of the feColorMatrix hueRotate option, and use of the feColorMatrix luminanceToAlpha option. + + + + + + + + + + + + + + + + + + + + + + + + + Unfiltered + + type="matrix" (grayscale matrix) + + type="saturate" values=".4" + + type="hueRotate" values="90" + + type="luminanceToAlpha" + + + + + $Revision: 1.6 $ + + + + + diff --git a/tests/fixtures/reftests/svg1.1/filters-conv-01-f-ref.png b/tests/fixtures/reftests/svg1.1/filters-conv-01-f-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..4100575cb85060c6c72b06512dc029ea6656c117 Binary files /dev/null and b/tests/fixtures/reftests/svg1.1/filters-conv-01-f-ref.png differ diff --git a/tests/fixtures/reftests/svg1.1/filters-conv-01-f.svg b/tests/fixtures/reftests/svg1.1/filters-conv-01-f.svg new file mode 100644 index 0000000000000000000000000000000000000000..0ff4c9f39616d9d7996e0576fa3589eff7b05ad0 --- /dev/null +++ b/tests/fixtures/reftests/svg1.1/filters-conv-01-f.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + +

+ Test which verifies the basic facilities of + feConvolveMatrix. +

+

+ This test defines six filters that exercise traditional + convolutions: uniform blur, vertical and horizontal + blurs, edge detection, embossing and sharpening. Note + that the edge detection filter produces a fully + transparent image because the alpha channel is convolved + and produces 0 values. +

+ + +

+ Run the test. No interaction required. +

+
+ +

+ The rendered picture should match the reference image + exactly, except for possible variations in the + labelling text (per CSS2 rules). +

+
+ + $RCSfile: filters-conv-01-f.svg,v $ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Blur (3x3) + + + + Edge Detection (3x3) + + + + Sharpening (3x3) + + + + Embossing (3x3) + + + + Horizontal blur (3x1) + + + + Vertical blur (1x3) + + + + + + + + $Revision: 1.6 $ + + + + + diff --git a/tests/fixtures/reftests/svg1.1/filters-conv-02-f-ref.png b/tests/fixtures/reftests/svg1.1/filters-conv-02-f-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..6120ce8e28080b9b020e9027c252f98aefe81d3c Binary files /dev/null and b/tests/fixtures/reftests/svg1.1/filters-conv-02-f-ref.png differ diff --git a/tests/fixtures/reftests/svg1.1/filters-conv-02-f.svg b/tests/fixtures/reftests/svg1.1/filters-conv-02-f.svg new file mode 100644 index 0000000000000000000000000000000000000000..8f16e18acddab029b48e4b2eac2359e77f346b7a --- /dev/null +++ b/tests/fixtures/reftests/svg1.1/filters-conv-02-f.svg @@ -0,0 +1,77 @@ + + + + + + + + + + +

+ Tests feConvolveMatrix with different values for the 'order' attribute. +

+ + +

Run the test. No interaction required. + +

+
+ +

You should see three filtered images. Each image is the same + and has the same filter applied to it. + The test has passed if all the three filtered images look the same, and the filtered result shows bright white edges on a dark background. + The rendered picture should match the reference image. +

+
+ + $RCSfile: filters-conv-02-f.svg,v $ + + + + + + + + + + + + + + + + + + + + + feConvolveMatrix 'order' attribute + + + without order + + + order="3" + + + order="3 3" + + + $Revision: 1.9 $ + + + + diff --git a/tests/fixtures/reftests/svg1.1/filters-conv-03-f-ref.png b/tests/fixtures/reftests/svg1.1/filters-conv-03-f-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..215208e573762eaeb3f5bc0477836e0ce82b4e8b Binary files /dev/null and b/tests/fixtures/reftests/svg1.1/filters-conv-03-f-ref.png differ diff --git a/tests/fixtures/reftests/svg1.1/filters-conv-03-f.svg b/tests/fixtures/reftests/svg1.1/filters-conv-03-f.svg new file mode 100644 index 0000000000000000000000000000000000000000..2e5460b361f23a6ca684f6b36da7d83b9001238d --- /dev/null +++ b/tests/fixtures/reftests/svg1.1/filters-conv-03-f.svg @@ -0,0 +1,104 @@ + + + + + + + + + + +

+ Tests the 'in1' DOM attribute on 'feConvolveMatrix'. +

+

+ Load the testcase, you should see three nearly identical images that say "FAIL". + After 3 seconds all three images should be replaced by the same image of a bird. + The two images to the right have filters applied, while the one on the left is always unfiltered. +

+ + +

+ Run the test. No interaction required. +

+
+ +

+ The test has passed if: +

+
    +
  • the two images inside the blue rects look exactly the same
  • +
  • the same base image is used in all three rects
  • +
  • the purple image that says "FAIL" is replaced after 3 seconds by an image of a bird
  • +
+
+ + $RCSfile: filters-conv-03-f.svg,v $ + + + + + + + + + + + + + + + + + + + + + + PASS + + + + + feConvolveMatrix 'in1' DOM + + + + + + + Original image + Animated filter + Scripted filter + + + $Revision: 1.8 $ + + + + diff --git a/tests/fixtures/reftests/svg1.1/filters-conv-04-f-ref.png b/tests/fixtures/reftests/svg1.1/filters-conv-04-f-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..b9017e5a2112f2ac318b5eb0e44ff22fb14561e8 Binary files /dev/null and b/tests/fixtures/reftests/svg1.1/filters-conv-04-f-ref.png differ diff --git a/tests/fixtures/reftests/svg1.1/filters-conv-04-f.svg b/tests/fixtures/reftests/svg1.1/filters-conv-04-f.svg new file mode 100644 index 0000000000000000000000000000000000000000..a66cf323795c7e9c1f627d0afa8ecd9d07d29d70 --- /dev/null +++ b/tests/fixtures/reftests/svg1.1/filters-conv-04-f.svg @@ -0,0 +1,163 @@ + + + + + + + + + + +

+ Tests the 'bias' attribute on 'feConvolveMatrix'. +

+

+ The test uses a raster image and a vector graphic to test the effect + that the 'bias' attribute on 'feConvolveMatrix' has. +

+

+ The first row of images in the test are four identical raster images. + The first image is the original unfiltered image. The second has the + filter kernel applied with no bias value specified. The third and fourth + images both have a bias value specified for the filter. +

+

+ The second row of images in the test are four rectangle objects with a + gradient fill. The gradient fill transitions from opaque green to + transparent green. The first image is the original unfiltered graphic. The + second graphic has a filter kernel applied with no bias value specified. + The third and forth images both have a bias value specified for the + filter. +

+

+ Behind each filter result there's a checkerboard pattern placed, to help + verify that there's transparency in the lower row, but not in the upper. +

+ + +

+ Run the test. No interaction required. +

+
+ +

+ The test has passed if: +

+
    +
  • + The raster images in the top row appears more faded and coarse for each instance + further to the right. +
      +
    • The first image (left most) must be smooth and clear
    • +
    • + The second image (second from the left) must contain the same colours as + the first image but have course outlining around the objects. +
    • +
    • + The third image (second from the right) must contain colour that is very faded + but have course outlining around the faded objects. +
    • +
    • + The last image (right most) must be completed faded such that all the colour + in the first image appears to have gone to white. Some course outlining should + appear in the image. +
    • +
    +
  • +
  • + The top row has no checkerboard pattern visible where the filtered results are. +
  • +
  • + The rectangle with a green gradient going from left to right appears + more faded for each instance further to the right. +
      +
    • + The first image (left most) must be a rect filled with a linear gradient that + transitions from solid green to transparent green. +
    • +
    • The second image (second from the left) must be identical to the first image.
    • +
    • + The third image (second from the right) must contain a linear gradient that transitions + from a solid faded green to transparent faded green. +
    • +
    • + The last image (right most) must contain a linear gradient that transitions from + solid white to transparent white. +
    • +
    +
  • +
  • + The bottom row must show 95% of the checkerboard pattern where the filtered results are + since the gradients are transparent. +
  • +
+
+ + $RCSfile: filters-conv-04-f.svg,v $ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $Revision: 1.4 $ + + + + + + DRAFT + + diff --git a/tests/fixtures/reftests/svg1.1/filters-conv-05-f-ref.png b/tests/fixtures/reftests/svg1.1/filters-conv-05-f-ref.png new file mode 100644 index 0000000000000000000000000000000000000000..999c72f5bcb767139cedb36fabc6e17210d91c82 Binary files /dev/null and b/tests/fixtures/reftests/svg1.1/filters-conv-05-f-ref.png differ diff --git a/tests/fixtures/reftests/svg1.1/filters-conv-05-f.svg b/tests/fixtures/reftests/svg1.1/filters-conv-05-f.svg new file mode 100644 index 0000000000000000000000000000000000000000..b60f774ee5efbaa3ba890c59619805890241a102 --- /dev/null +++ b/tests/fixtures/reftests/svg1.1/filters-conv-05-f.svg @@ -0,0 +1,83 @@ + + + + + + + + + + +

+ Tests feConvolveMatrix and the 'edgeMode' attribute. +

+ + +

+ Run the test. No interaction required. +

+
+ +

+ You should see three filtered images, each result should be slightly different, if they all look the same the test has failed. + The rendered picture should match the reference image. +

+
+ + $RCSfile: filters-conv-05-f.svg,v $ + + + + + + + + + + + + + + + + + + + + + + + + + + + + feConvolveMatrix 'edgeMode' + + + none + + + wrap + + + duplicate + + + $Revision: 1.2 $ + + + + + + DRAFT + + diff --git a/tests/fixtures/reftests/svg1.1/images/DisplaceChecker.png b/tests/fixtures/reftests/svg1.1/images/DisplaceChecker.png new file mode 100644 index 0000000000000000000000000000000000000000..25c77d0a9d4f9b6cf50da1e8977319aa2b181f4c Binary files /dev/null and b/tests/fixtures/reftests/svg1.1/images/DisplaceChecker.png differ diff --git a/tests/fixtures/reftests/svg1.1/images/filters-conv-01-f.includeimage.png b/tests/fixtures/reftests/svg1.1/images/filters-conv-01-f.includeimage.png new file mode 100644 index 0000000000000000000000000000000000000000..f8fb85230a34c5a48fc30a3cf8264ab23925d0fb Binary files /dev/null and b/tests/fixtures/reftests/svg1.1/images/filters-conv-01-f.includeimage.png differ diff --git a/tests/fixtures/reftests/svg1.1/images/purplesquidj.png b/tests/fixtures/reftests/svg1.1/images/purplesquidj.png new file mode 100644 index 0000000000000000000000000000000000000000..90000a8bc254ea3dfec0bdc31351d62e861ad577 Binary files /dev/null and b/tests/fixtures/reftests/svg1.1/images/purplesquidj.png differ diff --git a/tests/fixtures/reftests/svg1.1/images/stefan_252_tRNS_opti.png b/tests/fixtures/reftests/svg1.1/images/stefan_252_tRNS_opti.png new file mode 100644 index 0000000000000000000000000000000000000000..1937a31541b078ba30d51c953cb3636147b0c1fa Binary files /dev/null and b/tests/fixtures/reftests/svg1.1/images/stefan_252_tRNS_opti.png differ diff --git a/tests/fixtures/reftests/svg1.1/images/townsville.jpg b/tests/fixtures/reftests/svg1.1/images/townsville.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3a4a19944d719e2d54218d4ecb505a0b9e07b06b Binary files /dev/null and b/tests/fixtures/reftests/svg1.1/images/townsville.jpg differ