cairo-templates.h revision ccb10249f38c4a7f561a027c1e4f700ec9ba86bd
2N/A/**
2N/A * @file
2N/A * @brief Cairo software blending templates
2N/A *//*
2N/A * Authors:
2N/A * Krzysztof KosiƄski <tweenk.pl@gmail.com>
2N/A *
2N/A * Copyright (C) 2010 Authors
2N/A * Released under GNU GPL, read the file 'COPYING' for more information
2N/A */
2N/A
2N/A#ifndef SEEN_INKSCAPE_DISPLAY_CAIRO_TEMPLATES_H
2N/A#define SEEN_INKSCAPE_DISPLAY_CAIRO_TEMPLATES_H
2N/A
2N/A#ifdef HAVE_OPENMP
2N/A#include <omp.h>
2N/A#include "preferences.h"
2N/A// single-threaded operation if the number of pixels is below this threshold
2N/A#define OPENMP_THRESHOLD 2048
2N/A#endif
2N/A
2N/A#include <algorithm>
2N/A#include <cairo.h>
2N/A#include <glib.h>
2N/A#include <math.h>
2N/A#include "display/nr-3dutils.h"
2N/A
2N/A/**
2N/A * @brief Blend two surfaces using the supplied functor.
2N/A * This template blends two Cairo image surfaces using a blending functor that takes
2N/A * two 32-bit ARGB pixel values and returns a modified 32-bit pixel value.
2N/A * Differences in input surface formats are handled transparently. In future, this template
2N/A * will also handle software fallback for GL surfaces. */
2N/Atemplate <typename Blend>
2N/Avoid ink_cairo_surface_blend(cairo_surface_t *in1, cairo_surface_t *in2, cairo_surface_t *out, Blend blend)
2N/A{
2N/A cairo_surface_flush(in1);
2N/A cairo_surface_flush(in2);
2N/A
2N/A // ASSUMPTIONS
2N/A // 1. Cairo ARGB32 surface strides are always divisible by 4
2N/A // 2. We can only receive CAIRO_FORMAT_ARGB32 or CAIRO_FORMAT_A8 surfaces
2N/A // 3. Both surfaces are of the same size
2N/A // 4. Output surface is ARGB32 if at least one input is ARGB32
2N/A
2N/A int w = cairo_image_surface_get_width(in2);
2N/A int h = cairo_image_surface_get_height(in2);
2N/A int stride1 = cairo_image_surface_get_stride(in1);
2N/A int stride2 = cairo_image_surface_get_stride(in2);
2N/A int strideout = cairo_image_surface_get_stride(out);
2N/A int bpp1 = cairo_image_surface_get_format(in1) == CAIRO_FORMAT_A8 ? 1 : 4;
2N/A int bpp2 = cairo_image_surface_get_format(in2) == CAIRO_FORMAT_A8 ? 1 : 4;
2N/A int bppout = std::max(bpp1, bpp2);
2N/A
2N/A // Check whether we can loop over pixels without taking stride into account.
2N/A bool fast_path = true;
2N/A fast_path &= (stride1 == w * bpp1);
2N/A fast_path &= (stride2 == w * bpp2);
2N/A fast_path &= (strideout == w * bppout);
2N/A
2N/A int limit = w * h;
2N/A
2N/A guint32 *const in1_data = (guint32*) cairo_image_surface_get_data(in1);
2N/A guint32 *const in2_data = (guint32*) cairo_image_surface_get_data(in2);
2N/A guint32 *const out_data = (guint32*) cairo_image_surface_get_data(out);
2N/A
2N/A // NOTE
2N/A // OpenMP probably doesn't help much here.
2N/A // It would be better to render more than 1 tile at a time.
2N/A #if HAVE_OPENMP
2N/A Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2N/A int num_threads = prefs->getIntLimited("/options/threading/numthreads", omp_get_num_procs(), 1, 256);
2N/A if (limit < OPENMP_THRESHOLD) num_threads = 1; // do not spawn threads for very small surfaces
2N/A #endif
2N/A
2N/A // The number of code paths here is evil.
2N/A if (bpp1 == 4) {
2N/A if (bpp2 == 4) {
2N/A if (fast_path) {
2N/A #if HAVE_OPENMP
2N/A #pragma omp parallel for num_threads(num_threads)
2N/A #endif
2N/A for (int i = 0; i < limit; ++i) {
2N/A *(out_data + i) = blend(*(in1_data + i), *(in2_data + i));
2N/A }
2N/A } else {
2N/A #if HAVE_OPENMP
2N/A #pragma omp parallel for num_threads(num_threads)
2N/A #endif
2N/A for (int i = 0; i < h; ++i) {
2N/A guint32 *in1_p = in1_data + i * stride1/4;
2N/A guint32 *in2_p = in2_data + i * stride2/4;
2N/A guint32 *out_p = out_data + i * strideout/4;
2N/A for (int j = 0; j < w; ++j) {
2N/A *out_p = blend(*in1_p, *in2_p);
2N/A ++in1_p; ++in2_p; ++out_p;
2N/A }
2N/A }
2N/A }
2N/A } else {
2N/A // bpp2 == 1
2N/A #if HAVE_OPENMP
2N/A #pragma omp parallel for num_threads(num_threads)
2N/A #endif
2N/A for (int i = 0; i < h; ++i) {
2N/A guint32 *in1_p = in1_data + i * stride1/4;
2N/A guint8 *in2_p = reinterpret_cast<guint8*>(in2_data) + i * stride2;
2N/A guint32 *out_p = out_data + i * strideout/4;
2N/A for (int j = 0; j < w; ++j) {
2N/A guint32 in2_px = *in2_p;
2N/A in2_px <<= 24;
2N/A *out_p = blend(*in1_p, in2_px);
2N/A ++in1_p; ++in2_p; ++out_p;
2N/A }
2N/A }
2N/A }
2N/A } else {
2N/A if (bpp2 == 4) {
2N/A // bpp1 == 1
2N/A #if HAVE_OPENMP
2N/A #pragma omp parallel for num_threads(num_threads)
2N/A #endif
2N/A for (int i = 0; i < h; ++i) {
2N/A guint8 *in1_p = reinterpret_cast<guint8*>(in1_data) + i * stride1;
2N/A guint32 *in2_p = in2_data + i * stride2/4;
2N/A guint32 *out_p = out_data + i * strideout/4;
2N/A for (int j = 0; j < w; ++j) {
2N/A guint32 in1_px = *in1_p;
2N/A in1_px <<= 24;
2N/A *out_p = blend(in1_px, *in2_p);
2N/A ++in1_p; ++in2_p; ++out_p;
2N/A }
2N/A }
2N/A } else {
2N/A // bpp1 == 1 && bpp2 == 1
2N/A if (fast_path) {
2N/A #if HAVE_OPENMP
2N/A #pragma omp parallel for num_threads(num_threads)
2N/A #endif
2N/A for (int i = 0; i < limit; ++i) {
2N/A guint8 *in1_p = reinterpret_cast<guint8*>(in1_data) + i;
2N/A guint8 *in2_p = reinterpret_cast<guint8*>(in2_data) + i;
2N/A guint8 *out_p = reinterpret_cast<guint8*>(out_data) + i;
2N/A guint32 in1_px = *in1_p; in1_px <<= 24;
2N/A guint32 in2_px = *in2_p; in2_px <<= 24;
2N/A guint32 out_px = blend(in1_px, in2_px);
2N/A *out_p = out_px >> 24;
2N/A }
2N/A } else {
2N/A #if HAVE_OPENMP
2N/A #pragma omp parallel for num_threads(num_threads)
2N/A #endif
2N/A for (int i = 0; i < h; ++i) {
2N/A guint8 *in1_p = reinterpret_cast<guint8*>(in1_data) + i * stride1;
2N/A guint8 *in2_p = reinterpret_cast<guint8*>(in2_data) + i * stride2;
2N/A guint8 *out_p = reinterpret_cast<guint8*>(out_data) + i * strideout;
2N/A for (int j = 0; j < w; ++j) {
2N/A guint32 in1_px = *in1_p; in1_px <<= 24;
2N/A guint32 in2_px = *in2_p; in2_px <<= 24;
2N/A guint32 out_px = blend(in1_px, in2_px);
2N/A *out_p = out_px >> 24;
2N/A ++in1_p; ++in2_p; ++out_p;
2N/A }
2N/A }
2N/A }
2N/A }
2N/A }
2N/A
2N/A cairo_surface_mark_dirty(out);
2N/A}
2N/A
2N/Atemplate <typename Filter>
2N/Avoid ink_cairo_surface_filter(cairo_surface_t *in, cairo_surface_t *out, Filter filter)
2N/A{
2N/A cairo_surface_flush(in);
2N/A
2N/A // ASSUMPTIONS
2N/A // 1. Cairo ARGB32 surface strides are always divisible by 4
2N/A // 2. We can only receive CAIRO_FORMAT_ARGB32 or CAIRO_FORMAT_A8 surfaces
2N/A // 3. Surfaces have the same dimensions
2N/A // 4. Output surface is A8 if input is A8
2N/A
2N/A int w = cairo_image_surface_get_width(in);
2N/A int h = cairo_image_surface_get_height(in);
2N/A int stridein = cairo_image_surface_get_stride(in);
2N/A int strideout = cairo_image_surface_get_stride(out);
2N/A int bppin = cairo_image_surface_get_format(in) == CAIRO_FORMAT_A8 ? 1 : 4;
2N/A int bppout = cairo_image_surface_get_format(out) == CAIRO_FORMAT_A8 ? 1 : 4;
2N/A int limit = w * h;
2N/A
2N/A // Check whether we can loop over pixels without taking stride into account.
2N/A bool fast_path = true;
2N/A fast_path &= (stridein == w * bppin);
2N/A fast_path &= (strideout == w * bppout);
2N/A
2N/A guint32 *const in_data = (guint32*) cairo_image_surface_get_data(in);
2N/A guint32 *const out_data = (guint32*) cairo_image_surface_get_data(out);
2N/A
2N/A #if HAVE_OPENMP
2N/A Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2N/A int num_threads = prefs->getIntLimited("/options/threading/numthreads", omp_get_num_procs(), 1, 256);
2N/A if (limit < OPENMP_THRESHOLD) num_threads = 1; // do not spawn threads for very small surfaces
2N/A #endif
2N/A
2N/A if (bppin == 4) {
2N/A if (bppout == 4) {
2N/A // bppin == 4, bppout == 4
2N/A if (fast_path) {
2N/A #if HAVE_OPENMP
2N/A #pragma omp parallel for num_threads(num_threads)
2N/A #endif
2N/A for (int i = 0; i < limit; ++i) {
2N/A *(out_data + i) = filter(*(in_data + i));
2N/A }
2N/A } else {
2N/A #if HAVE_OPENMP
2N/A #pragma omp parallel for num_threads(num_threads)
2N/A #endif
2N/A for (int i = 0; i < h; ++i) {
2N/A guint32 *in_p = in_data + i * stridein/4;
2N/A guint32 *out_p = out_data + i * strideout/4;
2N/A for (int j = 0; j < w; ++j) {
2N/A *out_p = filter(*in_p);
2N/A ++in_p; ++out_p;
2N/A }
2N/A }
2N/A }
2N/A } else {
2N/A // bppin == 4, bppout == 1
2N/A // we use this path with COLORMATRIX_LUMINANCETOALPHA
2N/A #if HAVE_OPENMP
2N/A #pragma omp parallel for num_threads(num_threads)
2N/A #endif
2N/A for (int i = 0; i < h; ++i) {
2N/A guint32 *in_p = in_data + i * stridein/4;
2N/A guint8 *out_p = reinterpret_cast<guint8*>(out_data) + i * strideout;
2N/A for (int j = 0; j < w; ++j) {
2N/A guint32 out_px = filter(*in_p);
2N/A *out_p = out_px >> 24;
2N/A ++in_p; ++out_p;
2N/A }
2N/A }
2N/A }
2N/A } else {
2N/A // bppin == 1, bppout == 1
2N/A // Note: there is no path for bppin == 1, bppout == 4 because it is useless
2N/A if (fast_path) {
2N/A #if HAVE_OPENMP
2N/A #pragma omp parallel for num_threads(num_threads)
2N/A #endif
2N/A for (int i = 0; i < limit; ++i) {
2N/A guint8 *in_p = reinterpret_cast<guint8*>(in_data) + i;
2N/A guint8 *out_p = reinterpret_cast<guint8*>(out_data) + i;
2N/A guint32 in_px = *in_p; in_px <<= 24;
2N/A guint32 out_px = filter(in_px);
2N/A *out_p = out_px >> 24;
2N/A }
2N/A } else {
2N/A #if HAVE_OPENMP
2N/A #pragma omp parallel for num_threads(num_threads)
2N/A #endif
2N/A for (int i = 0; i < h; ++i) {
2N/A guint8 *in_p = reinterpret_cast<guint8*>(in_data) + i * stridein;
2N/A guint8 *out_p = reinterpret_cast<guint8*>(out_data) + i * strideout;
2N/A for (int j = 0; j < w; ++j) {
2N/A guint32 in_px = *in_p; in_px <<= 24;
2N/A guint32 out_px = filter(in_px);
2N/A *out_p = out_px >> 24;
2N/A ++in_p; ++out_p;
2N/A }
2N/A }
2N/A }
2N/A }
2N/A cairo_surface_mark_dirty(out);
2N/A}
2N/A
2N/A
2N/A/**
2N/A * @brief Synthesize surface pixels based on their position.
2N/A * This template accepts a functor that gets called with the x and y coordinates of the pixels,
2N/A * given as integers.
2N/A * @param out Output surface
2N/A * @param out_area The region of the output surface that should be synthesized
2N/A * @param synth Synthesis functor */
2N/Atemplate <typename Synth>
2N/Avoid ink_cairo_surface_synthesize(cairo_surface_t *out, cairo_rectangle_t const &out_area, Synth synth)
2N/A{
2N/A // ASSUMPTIONS
2N/A // 1. Cairo ARGB32 surface strides are always divisible by 4
2N/A // 2. We can only receive CAIRO_FORMAT_ARGB32 or CAIRO_FORMAT_A8 surfaces
2N/A
2N/A int w = out_area.width;
2N/A int h = out_area.height;
2N/A int strideout = cairo_image_surface_get_stride(out);
2N/A int bppout = cairo_image_surface_get_format(out) == CAIRO_FORMAT_A8 ? 1 : 4;
2N/A // NOTE: fast path is not used, because we would need 2 divisions to get pixel indices
2N/A
2N/A unsigned char *out_data = cairo_image_surface_get_data(out);
2N/A
2N/A #if HAVE_OPENMP
2N/A int limit = w * h;
2N/A Inkscape::Preferences *prefs = Inkscape::Preferences::get();
2N/A int num_threads = prefs->getIntLimited("/options/threading/numthreads", omp_get_num_procs(), 1, 256);
2N/A if (limit < OPENMP_THRESHOLD) num_threads = 1; // do not spawn threads for very small surfaces
2N/A #endif
2N/A
2N/A if (bppout == 4) {
2N/A #if HAVE_OPENMP
2N/A #pragma omp parallel for num_threads(num_threads)
2N/A #endif
2N/A for (int i = out_area.y; i < h; ++i) {
2N/A guint32 *out_p = reinterpret_cast<guint32*>(out_data + i * strideout);
2N/A for (int j = out_area.x; j < w; ++j) {
2N/A *out_p = synth(j, i);
2N/A ++out_p;
2N/A }
2N/A }
2N/A } else {
2N/A // bppout == 1
2N/A #if HAVE_OPENMP
2N/A #pragma omp parallel for num_threads(num_threads)
2N/A #endif
2N/A for (int i = out_area.y; i < h; ++i) {
2N/A guint8 *out_p = out_data + i * strideout;
2N/A for (int j = out_area.x; j < w; ++j) {
2N/A guint32 out_px = synth(j, i);
2N/A *out_p = out_px >> 24;
2N/A ++out_p;
2N/A }
2N/A }
2N/A }
2N/A cairo_surface_mark_dirty(out);
2N/A}
2N/A
2N/Atemplate <typename Synth>
2N/Avoid ink_cairo_surface_synthesize(cairo_surface_t *out, Synth synth)
2N/A{
2N/A int w = cairo_image_surface_get_width(out);
2N/A int h = cairo_image_surface_get_height(out);
2N/A
2N/A cairo_rectangle_t area;
2N/A area.x = 0;
2N/A area.y = 0;
2N/A area.width = w;
2N/A area.height = h;
2N/A
2N/A ink_cairo_surface_synthesize(out, area, synth);
2N/A}
2N/A
2N/Astruct SurfaceSynth {
2N/A SurfaceSynth(cairo_surface_t *surface)
2N/A : _px(cairo_image_surface_get_data(surface))
2N/A , _w(cairo_image_surface_get_width(surface))
2N/A , _h(cairo_image_surface_get_height(surface))
2N/A , _stride(cairo_image_surface_get_stride(surface))
2N/A , _alpha(cairo_surface_get_content(surface) == CAIRO_CONTENT_ALPHA)
2N/A {
2N/A cairo_surface_flush(surface);
2N/A }
2N/A
2N/A guint32 pixelAt(int x, int y) const {
2N/A if (_alpha) {
2N/A unsigned char *px = _px + y*_stride + x;
2N/A return *px << 24;
2N/A } else {
2N/A unsigned char *px = _px + y*_stride + x*4;
2N/A return *reinterpret_cast<guint32*>(px);
2N/A }
2N/A }
2N/A guint32 alphaAt(int x, int y) const {
2N/A if (_alpha) {
2N/A unsigned char *px = _px + y*_stride + x;
2N/A return *px;
2N/A } else {
2N/A unsigned char *px = _px + y*_stride + x*4;
2N/A guint32 p = *reinterpret_cast<guint32*>(px);
2N/A return (p & 0xff000000) >> 24;
2N/A }
2N/A }
2N/A
2N/A // retrieve a pixel value with bilinear interpolation
2N/A guint32 pixelAt(double x, double y) const {
2N/A if (_alpha) {
2N/A return alphaAt(x, y) << 24;
2N/A }
2N/A
2N/A double xf = floor(x), yf = floor(y);
2N/A int xi = xf, yi = yf;
2N/A guint32 xif = round((x - xf) * 255), yif = round((y - yf) * 255);
2N/A guint32 p00, p01, p10, p11;
2N/A
2N/A unsigned char *pxi = _px + yi*_stride + xi*4;
2N/A guint32 *pxu = reinterpret_cast<guint32*>(pxi);
2N/A guint32 *pxl = reinterpret_cast<guint32*>(pxi + _stride);
2N/A p00 = *pxu; p10 = *(pxu + 1);
2N/A p01 = *pxl; p11 = *(pxl + 1);
2N/A
2N/A guint32 comp[4];
2N/A
2N/A for (unsigned i = 0; i < 4; ++i) {
2N/A guint32 shift = i*8;
2N/A guint32 mask = 0xff << shift;
2N/A guint32 c00 = (p00 & mask) >> shift;
2N/A guint32 c10 = (p10 & mask) >> shift;
2N/A guint32 c01 = (p01 & mask) >> shift;
2N/A guint32 c11 = (p11 & mask) >> shift;
2N/A
2N/A guint32 iu = (255-xif) * c00 + xif * c10;
2N/A guint32 il = (255-xif) * c01 + xif * c11;
2N/A comp[i] = (255-yif) * iu + yif * il;
2N/A comp[i] = (comp[i] + (255*255/2)) / (255*255);
2N/A }
2N/A
2N/A guint32 result = comp[0] | (comp[1] << 8) | (comp[2] << 16) | (comp[3] << 24);
2N/A return result;
2N/A }
2N/A
2N/A // retrieve an alpha value with bilinear interpolation
2N/A guint32 alphaAt(double x, double y) const {
2N/A double xf = floor(x), yf = floor(y);
2N/A int xi = xf, yi = yf;
2N/A guint32 xif = round((x - xf) * 255), yif = round((y - yf) * 255);
2N/A guint32 p00, p01, p10, p11;
2N/A if (_alpha) {
2N/A unsigned char *pxu = _px + yi*_stride + xi;
2N/A unsigned char *pxl = pxu + _stride;
2N/A p00 = *pxu; p10 = *(pxu + 1);
2N/A p01 = *pxl; p11 = *(pxl + 1);
2N/A } else {
2N/A unsigned char *pxi = _px + yi*_stride + xi*4;
2N/A guint32 *pxu = reinterpret_cast<guint32*>(pxi);
2N/A guint32 *pxl = reinterpret_cast<guint32*>(pxi + _stride);
2N/A p00 = (*pxu & 0xff000000) >> 24; p10 = (*(pxu + 1) & 0xff000000) >> 24;
2N/A p01 = (*pxl & 0xff000000) >> 24; p11 = (*(pxl + 1) & 0xff000000) >> 24;
2N/A }
2N/A guint32 iu = (255-xif) * p00 + xif * p10;
2N/A guint32 il = (255-xif) * p01 + xif * p11;
2N/A guint32 result = (255-yif) * iu + yif * il;
2N/A result = (result + (255*255/2)) / (255*255);
2N/A return result;
2N/A }
2N/A
2N/A // compute surface normal at given coordinates using 3x3 Sobel gradient filter
2N/A NR::Fvector surfaceNormalAt(int x, int y, double scale) const {
2N/A // Below there are some multiplies by zero. They will be optimized out.
2N/A // Do not remove them, because they improve readability.
2N/A // NOTE: fetching using alphaAt is slightly lazy.
2N/A NR::Fvector normal;
2N/A double fx = -scale/255.0, fy = -scale/255.0;
2N/A normal[Z_3D] = 1.0;
2N/A if (G_UNLIKELY(x == 0)) {
2N/A // leftmost column
2N/A if (G_UNLIKELY(y == 0)) {
2N/A // upper left corner
2N/A fx *= (2.0/3.0);
2N/A fy *= (2.0/3.0);
2N/A double p00 = alphaAt(x,y), p10 = alphaAt(x+1, y),
2N/A p01 = alphaAt(x,y+1), p11 = alphaAt(x+1, y+1);
2N/A normal[X_3D] =
2N/A -2.0 * p00 +2.0 * p10
2N/A -1.0 * p01 +1.0 * p11;
2N/A normal[Y_3D] =
2N/A -2.0 * p00 -1.0 * p10
2N/A +2.0 * p01 +1.0 * p11;
2N/A } else if (G_UNLIKELY(y == (_h - 1))) {
2N/A // lower left corner
2N/A fx *= (2.0/3.0);
2N/A fy *= (2.0/3.0);
2N/A double p00 = alphaAt(x,y-1), p10 = alphaAt(x+1, y-1),
2N/A p01 = alphaAt(x,y ), p11 = alphaAt(x+1, y);
2N/A normal[X_3D] =
2N/A -1.0 * p00 +1.0 * p10
2N/A -2.0 * p01 +2.0 * p11;
2N/A normal[Y_3D] =
2N/A -2.0 * p00 -1.0 * p10
2N/A +2.0 * p01 +1.0 * p11;
2N/A } else {
2N/A // leftmost column
2N/A fx *= (1.0/2.0);
2N/A fy *= (1.0/3.0);
2N/A double p00 = alphaAt(x, y-1), p10 = alphaAt(x+1, y-1),
2N/A p01 = alphaAt(x, y ), p11 = alphaAt(x+1, y ),
2N/A p02 = alphaAt(x, y+1), p12 = alphaAt(x+1, y+1);
2N/A normal[X_3D] =
2N/A -1.0 * p00 +1.0 * p10
2N/A -2.0 * p01 +2.0 * p11
2N/A -1.0 * p02 +1.0 * p12;
2N/A normal[Y_3D] =
2N/A -2.0 * p00 -1.0 * p10
2N/A +0.0 * p01 +0.0 * p11 // this will be optimized out
2N/A +2.0 * p02 +1.0 * p12;
2N/A }
2N/A } else if (G_UNLIKELY(x == (_w - 1))) {
2N/A // rightmost column
2N/A if (G_UNLIKELY(y == 0)) {
2N/A // top right corner
2N/A fx *= (2.0/3.0);
2N/A fy *= (2.0/3.0);
2N/A double p00 = alphaAt(x-1,y), p10 = alphaAt(x, y),
2N/A p01 = alphaAt(x-1,y+1), p11 = alphaAt(x, y+1);
2N/A normal[X_3D] =
2N/A -2.0 * p00 +2.0 * p10
2N/A -1.0 * p01 +1.0 * p11;
2N/A normal[Y_3D] =
2N/A -1.0 * p00 -2.0 * p10
2N/A +1.0 * p01 +2.0 * p11;
2N/A } else if (G_UNLIKELY(y == (_h - 1))) {
2N/A // bottom right corner
2N/A fx *= (2.0/3.0);
2N/A fy *= (2.0/3.0);
2N/A double p00 = alphaAt(x-1,y-1), p10 = alphaAt(x, y-1),
2N/A p01 = alphaAt(x-1,y ), p11 = alphaAt(x, y);
2N/A normal[X_3D] =
2N/A -1.0 * p00 +1.0 * p10
2N/A -2.0 * p01 +2.0 * p11;
2N/A normal[Y_3D] =
2N/A -1.0 * p00 -2.0 * p10
2N/A +1.0 * p01 +2.0 * p11;
2N/A } else {
2N/A // rightmost column
2N/A fx *= (1.0/2.0);
2N/A fy *= (1.0/3.0);
2N/A double p00 = alphaAt(x-1, y-1), p10 = alphaAt(x, y-1),
2N/A p01 = alphaAt(x-1, y ), p11 = alphaAt(x, y ),
2N/A p02 = alphaAt(x-1, y+1), p12 = alphaAt(x, y+1);
2N/A normal[X_3D] =
2N/A -1.0 * p00 +1.0 * p10
2N/A -2.0 * p01 +2.0 * p11
2N/A -1.0 * p02 +1.0 * p12;
2N/A normal[Y_3D] =
2N/A -1.0 * p00 -2.0 * p10
2N/A +0.0 * p01 +0.0 * p11
2N/A +1.0 * p02 +2.0 * p12;
2N/A }
2N/A } else {
2N/A // interior
2N/A if (G_UNLIKELY(y == 0)) {
2N/A // top row
2N/A fx *= (1.0/3.0);
2N/A fy *= (1.0/2.0);
2N/A double p00 = alphaAt(x-1, y ), p10 = alphaAt(x, y ), p20 = alphaAt(x+1, y ),
2N/A p01 = alphaAt(x-1, y+1), p11 = alphaAt(x, y+1), p21 = alphaAt(x+1, y+1);
2N/A normal[X_3D] =
2N/A -2.0 * p00 +0.0 * p10 +2.0 * p20
2N/A -1.0 * p01 +0.0 * p11 +1.0 * p21;
2N/A normal[Y_3D] =
2N/A -1.0 * p00 -2.0 * p10 -1.0 * p20
2N/A +1.0 * p01 +2.0 * p11 +1.0 * p21;
2N/A } else if (G_UNLIKELY(y == (_h - 1))) {
2N/A // bottom row
2N/A fx *= (1.0/3.0);
2N/A fy *= (1.0/2.0);
2N/A double p00 = alphaAt(x-1, y-1), p10 = alphaAt(x, y-1), p20 = alphaAt(x+1, y-1),
2N/A p01 = alphaAt(x-1, y ), p11 = alphaAt(x, y ), p21 = alphaAt(x+1, y );
2N/A normal[X_3D] =
2N/A -1.0 * p00 +0.0 * p10 +1.0 * p20
2N/A -2.0 * p01 +0.0 * p11 +2.0 * p21;
2N/A normal[Y_3D] =
2N/A -1.0 * p00 -2.0 * p10 -1.0 * p20
2N/A +1.0 * p01 +2.0 * p11 +1.0 * p21;
2N/A } else {
2N/A // interior pixels
2N/A // note: p11 is actually unused, so we don't fetch its value
2N/A fx *= (1.0/4.0);
2N/A fy *= (1.0/4.0);
2N/A double p00 = alphaAt(x-1, y-1), p10 = alphaAt(x, y-1), p20 = alphaAt(x+1, y-1),
2N/A p01 = alphaAt(x-1, y ), p11 = 0.0, p21 = alphaAt(x+1, y ),
2N/A p02 = alphaAt(x-1, y+1), p12 = alphaAt(x, y+1), p22 = alphaAt(x+1, y+1);
2N/A normal[X_3D] =
2N/A -1.0 * p00 +0.0 * p10 +1.0 * p20
2N/A -2.0 * p01 +0.0 * p11 +2.0 * p21
2N/A -1.0 * p02 +0.0 * p12 +1.0 * p22;
2N/A normal[Y_3D] =
2N/A -1.0 * p00 -2.0 * p10 -1.0 * p20
2N/A +0.0 * p01 +0.0 * p11 +0.0 * p21
2N/A +1.0 * p02 +2.0 * p12 +1.0 * p22;
2N/A }
2N/A }
2N/A normal[X_3D] *= fx;
2N/A normal[Y_3D] *= fy;
2N/A NR::normalize_vector(normal);
2N/A return normal;
2N/A }
2N/A
2N/A unsigned char *_px;
2N/A int _w, _h, _stride;
2N/A bool _alpha;
2N/A};
2N/A
2N/A/*
2N/A// simple pixel accessor for image surface that handles different edge wrapping modes
2N/Aclass PixelAccessor {
2N/Apublic:
typedef PixelAccessor self;
enum EdgeMode {
EDGE_PAD,
EDGE_WRAP,
EDGE_ZERO
};
PixelAccessor(cairo_surface_t *s, EdgeMode e)
: _surface(s)
, _px(cairo_image_surface_get_data(s))
, _x(0), _y(0)
, _w(cairo_image_surface_get_width(s))
, _h(cairo_image_surface_get_height(s))
, _stride(cairo_image_surface_get_stride(s))
, _edge_mode(e)
, _alpha(cairo_image_surface_get_format(s) == CAIRO_FORMAT_A8)
{}
guint32 pixelAt(int x, int y) {
// This is a lot of ifs for a single pixel access. However, branch prediction
// should help us a lot, as the result of ifs is always the same for a single image.
int real_x = x, real_y = y;
switch (_edge_mode) {
case EDGE_PAD:
real_x = CLAMP(x, 0, _w-1);
real_y = CLAMP(y, 0, _h-1);
break;
case EDGE_WRAP:
real_x %= _w;
real_y %= _h;
break;
case EDGE_ZERO:
default:
if (x < 0 || x >= _w || y < 0 || y >= _h)
return 0;
break;
}
if (_alpha) {
return *(_px + real_y*_stride + real_x) << 24;
} else {
guint32 *px = reinterpret_cast<guint32*>(_px +real_y*_stride + real_x*4);
return *px;
}
}
private:
cairo_surface_t *_surface;
guint8 *_px;
int _x, _y, _w, _h, _stride;
EdgeMode _edge_mode;
bool _alpha;
};*/
// Some helpers for pixel manipulation
G_GNUC_CONST inline gint32
pxclamp(gint32 v, gint32 low, gint32 high) {
// NOTE: it is possible to write a "branchless" clamping operation.
// However, it will be slower than this function, because the code below
// is compiled to conditional moves.
if (v < low) return low;
if (v > high) return high;
return v;
}
#define EXTRACT_ARGB32(px,a,r,g,b) \
guint32 a, r, g, b; \
a = (px & 0xff000000) >> 24; \
r = (px & 0x00ff0000) >> 16; \
g = (px & 0x0000ff00) >> 8; \
b = (px & 0x000000ff);
#define ASSEMBLE_ARGB32(px,a,r,g,b) \
guint32 px = (a << 24) | (r << 16) | (g << 8) | b;
#endif
/*
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 :