nr-filter-composite.cpp revision c73973b946e8f567076c2a67b4e54f3d72b16ce5
/*
* feComposite filter effect renderer
*
* Authors:
* Niko Kiirala <niko@kiirala.com>
*
* Copyright (C) 2007 authors
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
#include <cmath>
#include "2geom/isnan.h"
#include "display/cairo-templates.h"
#include "display/cairo-utils.h"
#include "display/nr-filter-composite.h"
#include "display/nr-filter-pixops.h"
#include "display/nr-filter-slot.h"
#include "display/nr-filter-units.h"
#include "display/nr-filter-utils.h"
#include "libnr/nr-blit.h"
#include "libnr/nr-pixblock.h"
#include "libnr/nr-pixops.h"
inline void
composite_over(unsigned char *r, unsigned char const *a, unsigned char const *b)
{
r[0] = NR_COMPOSEPPP_1111(a[0],a[3],b[0]);
r[1] = NR_COMPOSEPPP_1111(a[1],a[3],b[1]);
r[2] = NR_COMPOSEPPP_1111(a[2],a[3],b[2]);
r[3] = NR_COMPOSEPPP_1111(a[3],a[3],b[3]);
}
inline void
composite_in(unsigned char *r, unsigned char const *a, unsigned char const *b)
{
r[0] = NR_NORMALIZE_21(a[0] * b[3]);
r[1] = NR_NORMALIZE_21(a[1] * b[3]);
r[2] = NR_NORMALIZE_21(a[2] * b[3]);
r[3] = NR_NORMALIZE_21(a[3] * b[3]);
}
inline void
composite_out(unsigned char *r, unsigned char const *a, unsigned char const *b)
{
r[0] = NR_NORMALIZE_21(a[0] * (255 - b[3]));
r[1] = NR_NORMALIZE_21(a[1] * (255 - b[3]));
r[2] = NR_NORMALIZE_21(a[2] * (255 - b[3]));
r[3] = NR_NORMALIZE_21(a[3] * (255 - b[3]));
}
inline void
composite_atop(unsigned char *r, unsigned char const *a, unsigned char const *b)
{
r[0] = NR_NORMALIZE_21(a[0] * b[3] + b[0] * (255 - a[3]));
r[1] = NR_NORMALIZE_21(a[1] * b[3] + b[1] * (255 - a[3]));
r[2] = NR_NORMALIZE_21(a[2] * b[3] + b[2] * (255 - a[3]));
r[3] = b[3];
}
inline void
composite_xor(unsigned char *r, unsigned char const *a, unsigned char const *b)
{
r[0] = NR_NORMALIZE_21(a[0] * (255 - b[3]) + b[0] * (255 - a[3]));
r[1] = NR_NORMALIZE_21(a[1] * (255 - b[3]) + b[1] * (255 - a[3]));
r[2] = NR_NORMALIZE_21(a[2] * (255 - b[3]) + b[2] * (255 - a[3]));
r[3] = NR_NORMALIZE_21(a[3] * (255 - b[3]) + b[3] * (255 - a[3]));
}
// BUGBUG / TODO
// This makes arithmetic compositing non re-entrant and non thread safe.
static int arith_k1, arith_k2, arith_k3, arith_k4;
inline void
composite_arithmetic(unsigned char *r, unsigned char const *a, unsigned char const *b)
{
using Inkscape::Filters::clamp3;
r[0] = NR_NORMALIZE_31(clamp3(arith_k1 * a[0] * b[0]
+ arith_k2 * a[0] + arith_k3 * b[0] + arith_k4));
r[1] = NR_NORMALIZE_31(clamp3(arith_k1 * a[1] * b[1]
+ arith_k2 * a[1] + arith_k3 * b[1] + arith_k4));
r[2] = NR_NORMALIZE_31(clamp3(arith_k1 * a[2] * b[2]
+ arith_k2 * a[2] + arith_k3 * b[2] + arith_k4));
r[3] = NR_NORMALIZE_31(clamp3(arith_k1 * a[3] * b[3]
+ arith_k2 * a[3] + arith_k3 * b[3] + arith_k4));
}
namespace Inkscape {
namespace Filters {
FilterComposite::FilterComposite() :
op(COMPOSITE_DEFAULT), k1(0), k2(0), k3(0), k4(0),
_input2(Inkscape::Filters::NR_FILTER_SLOT_NOT_SET)
{}
FilterPrimitive * FilterComposite::create() {
return new FilterComposite();
}
FilterComposite::~FilterComposite()
{}
struct BlendArithmetic {
BlendArithmetic(double k1, double k2, double k3, double k4)
: _k1(round(k1 * 255))
, _k2(round(k2 * 255*255))
, _k3(round(k3 * 255*255))
, _k4(round(k4 * 255*255*255))
{}
guint32 operator()(guint32 in1, guint32 in2) {
EXTRACT_ARGB32(in1, aa, ra, ga, ba)
EXTRACT_ARGB32(in2, ab, rb, gb, bb)
guint32 ao = _k1*aa*ab + _k2*aa + _k3*ab + _k4; ao = (ao + 255*255) / (255*255);
guint32 ro = _k1*ra*rb + _k2*ra + _k3*rb + _k4; ro = (ro + 255*255) / (255*255);
guint32 go = _k1*ga*gb + _k2*ga + _k3*gb + _k4; go = (go + 255*255) / (255*255);
guint32 bo = _k1*ba*bb + _k2*ba + _k3*bb + _k4; bo = (bo + 255*255) / (255*255);
ASSEMBLE_ARGB32(pxout, ao, ro, go, bo)
return pxout;
}
private:
guint32 _k1, _k2, _k3, _k4;
};
#if 0
int FilterComposite::render(FilterSlot &slot, FilterUnits const &/*units*/) {
NRPixBlock *in1 = slot.get(_input);
NRPixBlock *in2 = slot.get(_input2);
NRPixBlock *original_in1 = in1;
NRPixBlock *original_in2 = in2;
NRPixBlock *out;
// Bail out if either one of source images is missing
if (!in1 || !in2) {
g_warning("Missing source image for feComposite (in=%d in2=%d)", _input, _input2);
return 1;
}
out = new NRPixBlock;
NRRectL out_area;
nr_rect_l_union(&out_area, &in1->area, &in2->area);
nr_pixblock_setup_fast(out, NR_PIXBLOCK_MODE_R8G8B8A8P,
out_area.x0, out_area.y0, out_area.x1, out_area.y1,
true);
// Blending modes are defined for premultiplied RGBA values,
// thus convert them to that format before blending
if (in1->mode != NR_PIXBLOCK_MODE_R8G8B8A8P) {
in1 = nr_pixblock_new_fast(NR_PIXBLOCK_MODE_R8G8B8A8P,
original_in1->area.x0, original_in1->area.y0,
original_in1->area.x1, original_in1->area.y1,
false);
nr_blit_pixblock_pixblock(in1, original_in1);
}
if (in2->mode != NR_PIXBLOCK_MODE_R8G8B8A8P) {
in2 = nr_pixblock_new_fast(NR_PIXBLOCK_MODE_R8G8B8A8P,
original_in2->area.x0, original_in2->area.y0,
original_in2->area.x1, original_in2->area.y1,
false);
nr_blit_pixblock_pixblock(in2, original_in2);
}
/* pixops_mix is defined in display/nr-filter-pixops.h
* It mixes the two input images with the function given as template
* and places the result in output image.
*/
switch (op) {
case COMPOSITE_IN:
pixops_mix<composite_in>(*out, *in1, *in2);
break;
case COMPOSITE_OUT:
pixops_mix<composite_out>(*out, *in1, *in2);
break;
case COMPOSITE_ATOP:
pixops_mix<composite_atop>(*out, *in1, *in2);
break;
case COMPOSITE_XOR:
pixops_mix<composite_xor>(*out, *in1, *in2);
break;
case COMPOSITE_ARITHMETIC:
arith_k1 = (int)round(k1 * 255);
arith_k2 = (int)round(k2 * 255 * 255);
arith_k3 = (int)round(k3 * 255 * 255);
arith_k4 = (int)round(k4 * 255 * 255 * 255);
pixops_mix<composite_arithmetic>(*out, *in1, *in2);
break;
case COMPOSITE_DEFAULT:
case COMPOSITE_OVER:
default:
pixops_mix<composite_over>(*out, *in1, *in2);
break;
}
if (in1 != original_in1) {
nr_pixblock_free(in1);
}
if (in2 != original_in2) {
nr_pixblock_free(in2);
}
out->empty = FALSE;
slot.set(_output, out);
return 0;
}
#endif
void FilterComposite::render_cairo(FilterSlot &slot)
{
cairo_surface_t *input1 = slot.getcairo(_input);
cairo_surface_t *input2 = slot.getcairo(_input2);
cairo_surface_t *out = ink_cairo_surface_create_output(input1, input2);
if (op == COMPOSITE_ARITHMETIC) {
ink_cairo_surface_blend(input1, input2, out, BlendArithmetic(k1, k2, k3, k4));
} else {
ink_cairo_surface_blit(input2, out);
cairo_t *ct = cairo_create(out);
cairo_set_source_surface(ct, input1, 0, 0);
switch(op) {
case COMPOSITE_IN:
cairo_set_operator(ct, CAIRO_OPERATOR_IN);
break;
case COMPOSITE_OUT:
cairo_set_operator(ct, CAIRO_OPERATOR_OUT);
break;
case COMPOSITE_ATOP:
cairo_set_operator(ct, CAIRO_OPERATOR_ATOP);
break;
case COMPOSITE_XOR:
cairo_set_operator(ct, CAIRO_OPERATOR_XOR);
break;
case COMPOSITE_OVER:
case COMPOSITE_DEFAULT:
default:
// OVER is the default operator
break;
}
cairo_paint(ct);
cairo_destroy(ct);
}
slot.set(_output, out);
cairo_surface_destroy(out);
}
bool FilterComposite::can_handle_affine(Geom::Matrix const &)
{
return true;
}
void FilterComposite::set_input(int input) {
_input = input;
}
void FilterComposite::set_input(int input, int slot) {
if (input == 0) _input = slot;
if (input == 1) _input2 = slot;
}
void FilterComposite::set_operator(FeCompositeOperator op) {
if (op == COMPOSITE_DEFAULT) {
this->op = COMPOSITE_OVER;
} else if (op == COMPOSITE_OVER ||
op == COMPOSITE_IN ||
op == COMPOSITE_OUT ||
op == COMPOSITE_ATOP ||
op == COMPOSITE_XOR ||
op == COMPOSITE_ARITHMETIC)
{
this->op = op;
}
}
void FilterComposite::set_arithmetic(double k1, double k2, double k3, double k4) {
if (!IS_FINITE(k1) || !IS_FINITE(k2) || !IS_FINITE(k3) || !IS_FINITE(k4)) {
g_warning("Non-finite parameter for feComposite arithmetic operator");
return;
}
this->k1 = k1;
this->k2 = k2;
this->k3 = k3;
this->k4 = k4;
}
} /* 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:encoding=utf-8:textwidth=99 :