int FilterConvolveMatrix::render(FilterSlot &slot, FilterUnits const &/*units*/) {
    NRPixBlock *in = slot.get(_input);
    if (!in) {
        g_warning("Missing source image for feConvolveMatrix (in=%d)", _input);
        return 1;
    }
    if (orderX<=0 || orderY<=0) {
        g_warning("Empty kernel!");
        return 1;
    }
    if (targetX<0 || targetX>=orderX || targetY<0 || targetY>=orderY) {
        g_warning("Invalid target!");
        return 1;
    }
    if (kernelMatrix.size()!=(unsigned int)(orderX*orderY)) {
        g_warning("kernelMatrix does not have orderX*orderY elements!");
        return 1;
    }

    if (bias!=0) {
        g_warning("It is unknown whether Inkscape's implementation of bias in feConvolveMatrix is correct!");
        // 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) {
        g_warning("Inkscape only supports edgeMode=\"none\" (and a filter uses a different one)!");
        // Note that to properly support edgeMode the interaction with area_enlarge should be well understood (and probably something needs to change)
        // area_enlarge should NOT let Inkscape enlarge the area beyond the filter area, it should only enlarge the rendered area if a part of the object is rendered to make it overlapping (enough) with adjacent parts.
    }

    NRPixBlock *out = new NRPixBlock;

    nr_pixblock_setup_fast(out, in->mode,
                           in->area.x0, in->area.y0, in->area.x1, in->area.y1,
                           true);

    unsigned char *in_data = NR_PIXBLOCK_PX(in);
    unsigned char *out_data = NR_PIXBLOCK_PX(out);

    unsigned int const width = in->area.x1 - in->area.x0;
    unsigned int const height = in->area.y1 - in->area.y0;

    // Set up predivided kernel matrix
    std::vector<double> kernel(kernelMatrix);
    for(size_t i=0; i<kernel.size(); i++) {
        kernel[i] /= divisor; // The code that creates this object makes sure that divisor != 0
    }

    if (in->mode==NR_PIXBLOCK_MODE_R8G8B8A8P) {
        if (preserveAlpha) {
            convolve2D<true,true>(out_data, in_data, width, height, &kernel.front(), orderX, orderY, targetX, targetY, bias);
        } else {
            convolve2D<true,false>(out_data, in_data, width, height, &kernel.front(), orderX, orderY, targetX, targetY, bias);
        }
    } else {
        if (preserveAlpha) {
            convolve2D<false,true>(out_data, in_data, width, height, &kernel.front(), orderX, orderY, targetX, targetY, bias);
        } else {
            convolve2D<false,false>(out_data, in_data, width, height, &kernel.front(), orderX, orderY, targetX, targetY, bias);
        }
    }

    out->empty = FALSE;
    slot.set(_output, out);
    return 0;
}
int FilterSpecularLighting::render(FilterSlot &slot, FilterUnits const &units) {
    NRPixBlock *in = slot.get(_input);
    if (!in) {
        g_warning("Missing source image for feSpecularLighting (in=%d)", _input);
        return 1;
    }

    NRPixBlock *out = new NRPixBlock;

    //Fvector *L = NULL; //vector to the light

    int w = in->area.x1 - in->area.x0;
    int h = in->area.y1 - in->area.y0;
    int x0 = in->area.x0;
    int y0 = in->area.y0;
    int i, j;
    //As long as FilterRes and kernel unit is not supported we hardcode the
    //default value
    int dx = 1; //TODO setup
    int dy = 1; //TODO setup
    //surface scale
    Geom::Matrix trans = units.get_matrix_primitiveunits2pb();
    gdouble ss = surfaceScale * trans[0];
    gdouble ks = specularConstant; //diffuse lighting constant
    NR::Fvector L, N, LC, H;
    gdouble inter;

    nr_pixblock_setup_fast(out, NR_PIXBLOCK_MODE_R8G8B8A8N,
            in->area.x0, in->area.y0, in->area.x1, in->area.y1,
            true);
    unsigned char *data_i = NR_PIXBLOCK_PX (in);
    unsigned char *data_o = NR_PIXBLOCK_PX (out);
    //No light, nothing to do
    switch (light_type) {
        case DISTANT_LIGHT:
            //the light vector is constant
            {
            DistantLight *dl = new DistantLight(light.distant, lighting_color);
            dl->light_vector(L);
            dl->light_components(LC);
            NR::normalized_sum(H, L, NR::EYE_VECTOR);
            //finish the work
            for (i = 0, j = 0; i < w*h; i++) {
                NR::compute_surface_normal(N, ss, in, i / w, i % w, dx, dy);
                COMPUTE_INTER(inter, N, H, ks, specularExponent);

                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_RED]); // CLAMP includes rounding!
                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_GREEN]);
                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_BLUE]);
                data_o[j] = MAX(MAX(data_o[j-3], data_o[j-2]), data_o[j-1]);
                ++j;
            }
            out->empty = FALSE;
            delete dl;
            }
            break;
        case POINT_LIGHT:
            {
            PointLight *pl = new PointLight(light.point, lighting_color, trans);
            pl->light_components(LC);
        //TODO we need a reference to the filter to determine primitiveUnits
        //if objectBoundingBox is used, use a different matrix for light_vector
        // UPDATE: trans is now correct matrix from primitiveUnits to
        // pixblock coordinates
            //finish the work
            for (i = 0, j = 0; i < w*h; i++) {
                NR::compute_surface_normal(N, ss, in, i / w, i % w, dx, dy);
                pl->light_vector(L,
                        i % w + x0,
                        i / w + y0,
                        ss * (double) data_i[4*i+3]/ 255);
                NR::normalized_sum(H, L, NR::EYE_VECTOR);
                COMPUTE_INTER(inter, N, H, ks, specularExponent);

                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_RED]);
                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_GREEN]);
                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_BLUE]);
                data_o[j] = MAX(MAX(data_o[j-3], data_o[j-2]), data_o[j-1]);
                ++j;
            }
            out->empty = FALSE;
            delete pl;
            }
            break;
        case SPOT_LIGHT:
            {
            SpotLight *sl = new SpotLight(light.spot, lighting_color, trans);
        //TODO we need a reference to the filter to determine primitiveUnits
        //if objectBoundingBox is used, use a different matrix for light_vector
        // UPDATE: trans is now correct matrix from primitiveUnits to
        // pixblock coordinates
            //finish the work
            for (i = 0, j = 0; i < w*h; i++) {
                NR::compute_surface_normal(N, ss, in, i / w, i % w, dx, dy);
                sl->light_vector(L,
                    i % w + x0,
                    i / w + y0,
                    ss * (double) data_i[4*i+3]/ 255);
                sl->light_components(LC, L);
                NR::normalized_sum(H, L, NR::EYE_VECTOR);
                COMPUTE_INTER(inter, N, H, ks, specularExponent);

                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_RED]);
                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_GREEN]);
                data_o[j++] = CLAMP_D_TO_U8(inter * LC[LIGHT_BLUE]);
                data_o[j] = MAX(MAX(data_o[j-3], data_o[j-2]), data_o[j-1]);
                ++j;
            }
            out->empty = FALSE;
            delete sl;
            }
            break;
        //else unknown light source, doing nothing
        case NO_LIGHT:
        default:
            {
            if (light_type != NO_LIGHT)
                g_warning("unknown light source %d", light_type);
            out->empty = false;
            }
    }

    //finishing
    slot.set(_output, out);
    //nr_pixblock_release(in);
    //delete in;
    return 0;
}