/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Pix
*
* Copyright (C) 2011 The Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include "cairo-blur.h"
typedef struct {
GthAsyncTask *task;
gulong total_lines;
gulong processed_lines;
gboolean cancelled;
} ProgressData;
static inline gboolean
progress_data_inc_processed_lines (ProgressData *progress_data)
{
double progress;
if (progress_data->task == NULL)
return TRUE;
gth_async_task_get_data (progress_data->task, NULL, &progress_data->cancelled, NULL);
if (progress_data->cancelled)
return FALSE;
progress = (double) progress_data->processed_lines++ / progress_data->total_lines;
gth_async_task_set_data (progress_data->task, NULL, NULL, &progress);
return TRUE;
}
static gboolean
_cairo_image_surface_gaussian_blur (cairo_surface_t *source,
int radius,
ProgressData *progress_data)
{
/* FIXME: to do */
return FALSE;
}
static gboolean
box_blur (cairo_surface_t *source,
cairo_surface_t *destination,
int radius,
guchar *div_kernel_size,
ProgressData *progress_data)
{
int width, height, src_rowstride, dest_rowstride;
guchar *p_src, *p_dest, *c1, *c2;
int x, y, i, i1, i2, width_minus_1, height_minus_1, radius_plus_1;
int r, g, b, a;
guchar *p_dest_row, *p_dest_col;
width = cairo_image_surface_get_width (source);
height = cairo_image_surface_get_height (source);
radius_plus_1 = radius + 1;
/* horizontal blur */
p_src = _cairo_image_surface_flush_and_get_data (source);
p_dest = _cairo_image_surface_flush_and_get_data (destination);
src_rowstride = cairo_image_surface_get_stride (source);
dest_rowstride = cairo_image_surface_get_stride (destination);
width_minus_1 = width - 1;
for (y = 0; y < height; y++) {
if (! progress_data_inc_processed_lines (progress_data))
return FALSE;
/* calculate the initial sums of the kernel */
r = g = b = a = 0;
for (i = -radius; i <= radius; i++) {
c1 = p_src + (CLAMP (i, 0, width_minus_1) * 4);
r += c1[CAIRO_RED];
g += c1[CAIRO_GREEN];
b += c1[CAIRO_BLUE];
/*if (n_channels == 4)
a += c1[CAIRO_ALPHA];*/
}
p_dest_row = p_dest;
for (x = 0; x < width; x++) {
/* set as the mean of the kernel */
p_dest_row[CAIRO_RED] = div_kernel_size[r];
p_dest_row[CAIRO_GREEN] = div_kernel_size[g];
p_dest_row[CAIRO_BLUE] = div_kernel_size[b];
p_dest_row[CAIRO_ALPHA] = 0xff;
/*if (n_channels == 4)
p_dest_row[CAIRO_ALPHA] = div_kernel_size[a];*/
p_dest_row += 4;
/* the pixel to add to the kernel */
i1 = x + radius_plus_1;
if (i1 > width_minus_1)
i1 = width_minus_1;
c1 = p_src + (i1 * 4);
/* the pixel to remove from the kernel */
i2 = x - radius;
if (i2 < 0)
i2 = 0;
c2 = p_src + (i2 * 4);
/* calculate the new sums of the kernel */
r += c1[CAIRO_RED] - c2[CAIRO_RED];
g += c1[CAIRO_GREEN] - c2[CAIRO_GREEN];
b += c1[CAIRO_BLUE] - c2[CAIRO_BLUE];
/*if (n_channels == 4)
a += c1[CAIRO_ALPHA] - c2[CAIRO_ALPHA];*/
}
p_src += src_rowstride;
p_dest += dest_rowstride;
}
cairo_surface_mark_dirty (destination);
/* vertical blur */
p_src = _cairo_image_surface_flush_and_get_data (destination);
p_dest = _cairo_image_surface_flush_and_get_data (source);
src_rowstride = cairo_image_surface_get_stride (destination);
dest_rowstride = cairo_image_surface_get_stride (source);
height_minus_1 = height - 1;
for (x = 0; x < width; x++) {
if (! progress_data_inc_processed_lines (progress_data))
return FALSE;
/* calculate the initial sums of the kernel */
r = g = b = a = 0;
for (i = -radius; i <= radius; i++) {
c1 = p_src + (CLAMP (i, 0, height_minus_1) * src_rowstride);
r += c1[CAIRO_RED];
g += c1[CAIRO_GREEN];
b += c1[CAIRO_BLUE];
/*if (n_channels == 4)
a += c1[CAIRO_ALPHA];*/
}
p_dest_col = p_dest;
for (y = 0; y < height; y++) {
/* set as the mean of the kernel */
p_dest_col[CAIRO_RED] = div_kernel_size[r];
p_dest_col[CAIRO_GREEN] = div_kernel_size[g];
p_dest_col[CAIRO_BLUE] = div_kernel_size[b];
p_dest_col[CAIRO_ALPHA] = 0xff;
/*if (n_channels == 4)
p_dest_row[CAIRO_ALPHA] = div_kernel_size[a];*/
p_dest_col += dest_rowstride;
/* the pixel to add to the kernel */
i1 = y + radius_plus_1;
if (i1 > height_minus_1)
i1 = height_minus_1;
c1 = p_src + (i1 * src_rowstride);
/* the pixel to remove from the kernel */
i2 = y - radius;
if (i2 < 0)
i2 = 0;
c2 = p_src + (i2 * src_rowstride);
/* calculate the new sums of the kernel */
r += c1[CAIRO_RED] - c2[CAIRO_RED];
g += c1[CAIRO_GREEN] - c2[CAIRO_GREEN];
b += c1[CAIRO_BLUE] - c2[CAIRO_BLUE];
/*if (n_channels == 4)
a += c1[CAIRO_ALPHA] - c2[CAIRO_ALPHA];*/
}
p_src += 4;
p_dest += 4;
}
cairo_surface_mark_dirty (source);
return TRUE;
}
static gboolean
_cairo_image_surface_box_blur (cairo_surface_t *source,
int radius,
int iterations,
ProgressData *progress_data)
{
gint64 kernel_size;
guchar *div_kernel_size;
int i;
cairo_surface_t *tmp;
gboolean completed;
kernel_size = 2 * radius + 1;
/* optimization to avoid divisions: div_kernel_size[x] == x / kernel_size */
div_kernel_size = g_new (guchar, 256 * kernel_size);
for (i = 0; i < 256 * kernel_size; i++)
div_kernel_size[i] = (guchar) (i / kernel_size);
completed = TRUE;
tmp = _cairo_image_surface_create_compatible (source);
if (cairo_surface_status (tmp) != CAIRO_STATUS_SUCCESS) {
cairo_surface_destroy (tmp);
return FALSE;
}
while (completed && (iterations-- > 0)) {
completed = box_blur (source, tmp, radius, div_kernel_size, progress_data);
}
cairo_surface_destroy (tmp);
return completed;
}
static gboolean
_cairo_image_surface_blur_with_progress (cairo_surface_t *source,
int radius,
ProgressData *progress_data)
{
if (radius <= 10)
return _cairo_image_surface_box_blur (source, radius, 3, progress_data);
else
return _cairo_image_surface_gaussian_blur (source, radius, progress_data);
}
gboolean
_cairo_image_surface_blur (cairo_surface_t *source,
int radius,
GthAsyncTask *task)
{
ProgressData progress_data;
progress_data.task = task;
progress_data.total_lines = (cairo_image_surface_get_width (source) * 3) + (cairo_image_surface_get_height (source) * 3);
progress_data.processed_lines = 0;
progress_data.cancelled = FALSE;
return _cairo_image_surface_blur_with_progress (source, radius, &progress_data);
}
gboolean
_cairo_image_surface_sharpen (cairo_surface_t *source,
int radius,
double amount,
guchar threshold,
GthAsyncTask *task)
{
ProgressData progress_data;
cairo_surface_t *blurred;
int width, height;
int source_rowstride, blurred_rowstride;
int x, y;
guchar *p_src, *p_blurred;
guchar *p_src_row, *p_blurred_row;
guchar r1, g1, b1;
guchar r2, g2, b2;
int tmp;
progress_data.task = task;
progress_data.total_lines = (cairo_image_surface_get_width (source) * 3) + (cairo_image_surface_get_height (source) * 3) + cairo_image_surface_get_height (source);
progress_data.processed_lines = 0;
progress_data.cancelled = FALSE;
blurred = _cairo_image_surface_copy (source);
if (cairo_surface_status (blurred) != CAIRO_STATUS_SUCCESS) {
cairo_surface_destroy (blurred);
return FALSE;
}
if (! _cairo_image_surface_blur_with_progress (blurred, radius, &progress_data)) {
cairo_surface_destroy (blurred);
return FALSE;
}
width = cairo_image_surface_get_width (source);
height = cairo_image_surface_get_height (source);
source_rowstride = cairo_image_surface_get_stride (source);
blurred_rowstride = cairo_image_surface_get_stride (blurred);
p_src = _cairo_image_surface_flush_and_get_data (source);
p_blurred = _cairo_image_surface_flush_and_get_data (blurred);
#define ASSIGN_INTERPOLATED_VALUE(x1, x2) \
if (ABS (x1 - x2) >= threshold) { \
tmp = interpolate_value (x1, x2, amount); \
x1 = CLAMP (tmp, 0, 255); \
}
for (y = 0; y < height; y++) {
p_src_row = p_src;
p_blurred_row = p_blurred;
if (! progress_data_inc_processed_lines (&progress_data)) {
cairo_surface_destroy (blurred);
return FALSE;
}
for (x = 0; x < width; x++) {
r1 = p_src_row[CAIRO_RED];
g1 = p_src_row[CAIRO_GREEN];
b1 = p_src_row[CAIRO_BLUE];
r2 = p_blurred_row[CAIRO_RED];
g2 = p_blurred_row[CAIRO_GREEN];
b2 = p_blurred_row[CAIRO_BLUE];
ASSIGN_INTERPOLATED_VALUE (r1, r2)
ASSIGN_INTERPOLATED_VALUE (g1, g2)
ASSIGN_INTERPOLATED_VALUE (b1, b2)
p_src_row[CAIRO_RED] = r1;
p_src_row[CAIRO_GREEN] = g1;
p_src_row[CAIRO_BLUE] = b1;
p_src_row += 4;
p_blurred_row += 4;
}
p_src += source_rowstride;
p_blurred += blurred_rowstride;
}
#undef ASSIGN_INTERPOLATED_VALUE
cairo_surface_mark_dirty (source);
cairo_surface_destroy (blurred);
return TRUE;
}