gtk2/gtk/gtkcairoblur.c
Jasper St. Pierre 45bdec84f5 gtkcairoblur: Blur a CAIRO_A8 surface instead of a full CAIRO_ARGB32
This is considerably faster to draw and paint.
2014-07-29 10:49:39 +02:00

231 lines
5.8 KiB
C

/*
* Copyright (C) 2012 Canonical Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* Authored by Andrea Cimitan <andrea.cimitan@canonical.com>
* Original code from Mirco Mueller <mirco.mueller@canonical.com>
*
*/
#include "gtkcairoblurprivate.h"
#include <math.h>
/*
* Notes:
* based on exponential-blur algorithm by Jani Huhtanen
*/
static inline void
_blurinner (guchar* pixel,
gint *zA,
gint alpha,
gint aprec,
gint zprec)
{
guchar A;
A = *pixel;
*zA += (alpha * ((A << zprec) - *zA)) >> aprec;
*pixel = *zA >> zprec;
}
static inline void
_blurrow (guchar* pixels,
gint width,
gint height,
gint rowstride,
gint line,
gint alpha,
gint aprec,
gint zprec)
{
gint zA;
gint index;
guchar* scanline;
scanline = &pixels[line * rowstride];
zA = *scanline << zprec;
for (index = 0; index < width; index ++)
_blurinner (&scanline[index],
&zA,
alpha,
aprec,
zprec);
for (index = width - 2; index >= 0; index--)
_blurinner (&scanline[index],
&zA,
alpha,
aprec,
zprec);
}
static inline void
_blurcol (guchar* pixels,
gint width,
gint height,
gint rowstride,
gint x,
gint alpha,
gint aprec,
gint zprec)
{
gint zA;
gint index;
guchar* ptr;
ptr = pixels;
ptr += x;
zA = *ptr << zprec;
for (index = 0; index < height; index++)
_blurinner (&ptr[index * rowstride],
&zA,
alpha,
aprec,
zprec);
for (index = height - 2; index >= 0; index--)
_blurinner (&ptr[index * rowstride],
&zA,
alpha,
aprec,
zprec);
}
/*
* _expblur:
* @pixels: image data
* @width: image width
* @height: image height
* @rowstride: image rowstride
* @radius: kernel radius
* @aprec: precision of alpha parameter in fixed-point format 0.aprec
* @zprec: precision of state parameters zR,zG,zB and zA in fp format 8.zprec
*
* Performs an in-place blur of image data “pixels”
* with kernel of approximate radius “radius”.
*
* Blurs with two sided exponential impulse response.
*
*/
static void
_expblur (guchar* pixels,
gint width,
gint height,
gint rowstride,
double radius,
gint aprec,
gint zprec)
{
gint alpha;
int row, col;
/* Calculate the alpha such that 90% of
* the kernel is within the radius.
* (Kernel extends to infinity) */
alpha = (gint) ((1 << aprec) * (1.0f - expf (-2.3f / (radius + 1.f))));
for (row = 0; row < height; row++)
_blurrow (pixels,
width,
height,
rowstride,
row,
alpha,
aprec,
zprec);
for(col = 0; col < width; col++)
_blurcol (pixels,
width,
height,
rowstride,
col,
alpha,
aprec,
zprec);
}
/*
* _gtk_cairo_blur_surface:
* @surface: a cairo image surface.
* @radius: the blur radius.
*
* Blurs the cairo image surface at the given radius.
*/
void
_gtk_cairo_blur_surface (cairo_surface_t* surface,
double radius)
{
cairo_format_t format;
g_return_if_fail (surface != NULL);
g_return_if_fail (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE);
format = cairo_image_surface_get_format (surface);
g_return_if_fail (format == CAIRO_FORMAT_A8);
if (radius == 0)
return;
/* Before we mess with the surface execute any pending drawing. */
cairo_surface_flush (surface);
_expblur (cairo_image_surface_get_data (surface),
cairo_image_surface_get_width (surface),
cairo_image_surface_get_height (surface),
cairo_image_surface_get_stride (surface),
radius,
16,
7);
/* Inform cairo we altered the surfaces contents. */
cairo_surface_mark_dirty (surface);
}
/**
* _gtk_cairo_blur_compute_pixels:
* @radius: the radius to compute the pixels for
*
* Computes the number of pixels necessary to extend an image in one
* direction to hold the image with shadow.
*
* This is just the number of pixels added by the blur radius, shadow
* offset and spread are not included.
*
* Much of this, the 3 * sqrt(2 * pi) / 4, is the known value for
* approximating a Gaussian using box blurs. This yields quite a good
* approximation for a Gaussian. Then we multiply this by 1.5 since our
* code wants the radius of the entire triple-box-blur kernel instead of
* the diameter of an individual box blur. For more details, see:
* http://www.w3.org/TR/SVG11/filters.html#feGaussianBlurElement
* https://bugzilla.mozilla.org/show_bug.cgi?id=590039#c19
*/
#define GAUSSIAN_SCALE_FACTOR ((3.0 * sqrt(2 * G_PI) / 4) * 1.5)
int
_gtk_cairo_blur_compute_pixels (double radius)
{
return floor (radius * GAUSSIAN_SCALE_FACTOR + 0.5);
}