// SPDX-License-Identifier: GPL-2.0-or-later /* * feConvolveMatrix filter primitive renderer * * Authors: * Felipe CorrĂȘa da Silva Sanches * Jasper van de Gronde * * Copyright (C) 2007,2009 authors * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include #include "display/cairo-templates.h" #include "display/cairo-utils.h" #include "display/nr-filter-convolve-matrix.h" #include "display/nr-filter-slot.h" #include "display/nr-filter-units.h" #include "display/nr-filter-utils.h" namespace Inkscape { namespace Filters { FilterConvolveMatrix::FilterConvolveMatrix() = default; FilterPrimitive * FilterConvolveMatrix::create() { return new FilterConvolveMatrix(); } FilterConvolveMatrix::~FilterConvolveMatrix() = default; enum PreserveAlphaMode { PRESERVE_ALPHA, NO_PRESERVE_ALPHA }; template struct ConvolveMatrix : public SurfaceSynth { ConvolveMatrix(cairo_surface_t *s, int targetX, int targetY, int orderX, int orderY, double divisor, double bias, std::vector const &kernel) : SurfaceSynth(s) , _kernel(kernel.size()) , _targetX(targetX) , _targetY(targetY) , _orderX(orderX) , _orderY(orderY) , _bias(bias) { for (unsigned i = 0; i < _kernel.size(); ++i) { _kernel[i] = kernel[i] / divisor; } // the matrix is given rotated 180 degrees // which corresponds to reverse element order std::reverse(_kernel.begin(), _kernel.end()); } guint32 operator()(int x, int y) const { int startx = std::max(0, x - _targetX); int starty = std::max(0, y - _targetY); int endx = std::min(_w, startx + _orderX); int endy = std::min(_h, starty + _orderY); int limitx = endx - startx; int limity = endy - starty; double suma = 0.0, sumr = 0.0, sumg = 0.0, sumb = 0.0; for (int i = 0; i < limity; ++i) { for (int j = 0; j < limitx; ++j) { guint32 px = pixelAt(startx + j, starty + i); double coeff = _kernel[i * _orderX + j]; EXTRACT_ARGB32(px, a,r,g,b) sumr += r * coeff; sumg += g * coeff; sumb += b * coeff; if (preserve_alpha == NO_PRESERVE_ALPHA) { suma += a * coeff; } } } if (preserve_alpha == PRESERVE_ALPHA) { suma = alphaAt(x, y); } else { suma += _bias * 255; } guint32 ao = pxclamp(round(suma), 0, 255); guint32 ro = pxclamp(round(sumr + ao * _bias), 0, ao); guint32 go = pxclamp(round(sumg + ao * _bias), 0, ao); guint32 bo = pxclamp(round(sumb + ao * _bias), 0, ao); ASSEMBLE_ARGB32(pxout, ao,ro,go,bo); return pxout; } private: std::vector _kernel; int _targetX, _targetY, _orderX, _orderY; double _bias; }; void FilterConvolveMatrix::render_cairo(FilterSlot &slot) { static bool bias_warning = false; static bool edge_warning = false; if (orderX<=0 || orderY<=0) { g_warning("Empty kernel!"); return; } if (targetX<0 || targetX>=orderX || targetY<0 || targetY>=orderY) { g_warning("Invalid target!"); return; } if (kernelMatrix.size()!=(unsigned int)(orderX*orderY)) { //g_warning("kernelMatrix does not have orderX*orderY elements!"); return; } cairo_surface_t *input = slot.getcairo(_input); cairo_surface_t *out = ink_cairo_surface_create_identical(input); // We may need to transform input surface to correct color interpolation space. The input surface // might be used as input to another primitive but it is likely that all the primitives in a given // filter use the same color interpolation space so we don't copy the input before converting. SPColorInterpolation ci_fp = SP_CSS_COLOR_INTERPOLATION_AUTO; if( _style ) { ci_fp = (SPColorInterpolation)_style->color_interpolation_filters.computed; set_cairo_surface_ci(out, ci_fp); } set_cairo_surface_ci( input, ci_fp ); if (bias!=0 && !bias_warning) { g_warning("It is unknown whether Inkscape's implementation of bias in feConvolveMatrix " "is correct!"); bias_warning = true; // The SVG specification implies that feConvolveMatrix is defined for premultiplied // colors (which makes sense). It also says that bias should simply be added to the result // for each color (without taking the alpha into account). However, it also says that one // purpose of bias is "to have .5 gray value be the zero response of the filter". // It seems sensible to indeed support the latter behaviour instead of the former, // but this does appear to go against the standard. // Note that Batik simply does not support bias!=0 } if (edgeMode!=CONVOLVEMATRIX_EDGEMODE_NONE && !edge_warning) { g_warning("Inkscape only supports edgeMode=\"none\" (and a filter uses a different one)!"); edge_warning = true; } //guint32 *in_data = reinterpret_cast(cairo_image_surface_get_data(input)); //guint32 *out_data = reinterpret_cast(cairo_image_surface_get_data(out)); //int width = cairo_image_surface_get_width(input); //int height = cairo_image_surface_get_height(input); // Set up predivided kernel matrix /*std::vector kernel(kernelMatrix); for(size_t i=0; i(out_data, in_data, width, height, &kernel.front(), orderX, orderY, // targetX, targetY, bias); ink_cairo_surface_synthesize(out, ConvolveMatrix(input, targetX, targetY, orderX, orderY, divisor, bias, kernelMatrix)); } else { //convolve2D(out_data, in_data, width, height, &kernel.front(), orderX, orderY, // targetX, targetY, bias); ink_cairo_surface_synthesize(out, ConvolveMatrix(input, targetX, targetY, orderX, orderY, divisor, bias, kernelMatrix)); } slot.set(_output, out); cairo_surface_destroy(out); } void FilterConvolveMatrix::set_targetX(int coord) { targetX = coord; } void FilterConvolveMatrix::set_targetY(int coord) { targetY = coord; } void FilterConvolveMatrix::set_orderX(int coord) { orderX = coord; } void FilterConvolveMatrix::set_orderY(int coord) { orderY = coord; } void FilterConvolveMatrix::set_divisor(double d) { divisor = d; } void FilterConvolveMatrix::set_bias(double b) { bias = b; } void FilterConvolveMatrix::set_kernelMatrix(std::vector &km) { kernelMatrix = km; } void FilterConvolveMatrix::set_edgeMode(FilterConvolveMatrixEdgeMode mode){ edgeMode = mode; } void FilterConvolveMatrix::set_preserveAlpha(bool pa){ preserveAlpha = pa; } void FilterConvolveMatrix::area_enlarge(Geom::IntRect &area, Geom::Affine const &/*trans*/) { //Seems to me that since this filter's operation is resolution dependent, // some spurious pixels may still appear at the borders when low zooming or rotating. Needs a better fix. area.setMin(area.min() - Geom::IntPoint(targetX, targetY)); // This makes sure the last row/column in the original image corresponds // to the last row/column in the new image that can be convolved without // adjusting the boundary conditions). area.setMax(area.max() + Geom::IntPoint(orderX - targetX - 1, orderY - targetY -1)); } double FilterConvolveMatrix::complexity(Geom::Affine const &) { return kernelMatrix.size(); } } /* namespace Filters */ } /* namespace Inkscape */ /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :