gtk2/gtk/gtkcairoblur.c

263 lines
6.4 KiB
C
Raw Normal View History

/*
* 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 *zR,
gint *zG,
gint *zB,
gint *zA,
gint alpha,
gint aprec,
gint zprec)
{
gint R;
gint G;
gint B;
guchar A;
R = *pixel;
G = *(pixel + 1);
B = *(pixel + 2);
A = *(pixel + 3);
*zR += (alpha * ((R << zprec) - *zR)) >> aprec;
*zG += (alpha * ((G << zprec) - *zG)) >> aprec;
*zB += (alpha * ((B << zprec) - *zB)) >> aprec;
*zA += (alpha * ((A << zprec) - *zA)) >> aprec;
*pixel = *zR >> zprec;
*(pixel + 1) = *zG >> zprec;
*(pixel + 2) = *zB >> zprec;
*(pixel + 3) = *zA >> zprec;
}
static inline void
_blurrow (guchar* pixels,
gint width,
gint height,
gint channels,
gint line,
gint alpha,
gint aprec,
gint zprec)
{
gint zR;
gint zG;
gint zB;
gint zA;
gint index;
guchar* scanline;
scanline = &(pixels[line * width * channels]);
zR = *scanline << zprec;
zG = *(scanline + 1) << zprec;
zB = *(scanline + 2) << zprec;
zA = *(scanline + 3) << zprec;
for (index = 0; index < width; index ++)
_blurinner (&scanline[index * channels],
&zR,
&zG,
&zB,
&zA,
alpha,
aprec,
zprec);
for (index = width - 2; index >= 0; index--)
_blurinner (&scanline[index * channels],
&zR,
&zG,
&zB,
&zA,
alpha,
aprec,
zprec);
}
static inline void
_blurcol (guchar* pixels,
gint width,
gint height,
gint channels,
gint x,
gint alpha,
gint aprec,
gint zprec)
{
gint zR;
gint zG;
gint zB;
gint zA;
gint index;
guchar* ptr;
ptr = pixels;
ptr += x * channels;
zR = *((guchar*) ptr ) << zprec;
zG = *((guchar*) ptr + 1) << zprec;
zB = *((guchar*) ptr + 2) << zprec;
zA = *((guchar*) ptr + 3) << zprec;
for (index = width; index < (height - 1) * width; index += width)
_blurinner ((guchar*) &ptr[index * channels],
&zR,
&zG,
&zB,
&zA,
alpha,
aprec,
zprec);
for (index = (height - 2) * width; index >= 0; index -= width)
_blurinner ((guchar*) &ptr[index * channels],
&zR,
&zG,
&zB,
&zA,
alpha,
aprec,
zprec);
}
/*
* _expblur:
* @pixels: image data
* @width: image width
* @height: image height
* @channels: image channels
* @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 channels,
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,
channels,
row,
alpha,
aprec,
zprec);
for(col = 0; col < width; col++)
_blurcol (pixels,
width,
height,
channels,
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;
guchar* pixels;
guint width;
guint height;
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 ||
format == CAIRO_FORMAT_RGB24 ||
format == CAIRO_FORMAT_ARGB32);
if (radius == 0)
return;
/* Before we mess with the surface execute any pending drawing. */
cairo_surface_flush (surface);
pixels = cairo_image_surface_get_data (surface);
width = cairo_image_surface_get_width (surface);
height = cairo_image_surface_get_height (surface);
format = cairo_image_surface_get_format (surface);
switch (format)
{
case CAIRO_FORMAT_ARGB32:
_expblur (pixels, width, height, 4, radius, 16, 7);
break;
case CAIRO_FORMAT_RGB24:
_expblur (pixels, width, height, 3, radius, 16, 7);
break;
case CAIRO_FORMAT_A8:
_expblur (pixels, width, height, 1, radius, 16, 7);
break;
default:
break;
}
/* Inform cairo we altered the surfaces contents. */
cairo_surface_mark_dirty (surface);
}