/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Pix
*
* Copyright (C) 2014 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 "cairo-effects.h"
#include "gth-curve.h"
gboolean
cairo_image_surface_apply_curves (cairo_surface_t *source,
GthCurve **curve,
GthAsyncTask *task)
{
long *value_map[GTH_HISTOGRAM_N_CHANNELS];
int c, v;
int width;
int height;
int source_stride;
unsigned char *p_source_line;
int x, y, temp;
gboolean cancelled = FALSE;
double progress;
unsigned char *p_source;
unsigned char image_red, image_green, image_blue, image_alpha;
for (c = GTH_HISTOGRAM_CHANNEL_VALUE; c <= GTH_HISTOGRAM_CHANNEL_BLUE; c++) {
value_map[c] = g_new (long, 256);
for (v = 0; v <= 255; v++) {
double u = gth_curve_eval (curve[c], v);
if (c > GTH_HISTOGRAM_CHANNEL_VALUE)
u = value_map[GTH_HISTOGRAM_CHANNEL_VALUE][(int)u];
value_map[c][v] = u;
}
}
width = cairo_image_surface_get_width (source);
height = cairo_image_surface_get_height (source);
source_stride = cairo_image_surface_get_stride (source);
p_source_line = _cairo_image_surface_flush_and_get_data (source);
for (y = 0; y < height; y++) {
gth_async_task_get_data (task, NULL, &cancelled, NULL);
if (cancelled)
break;
progress = (double) y / height;
gth_async_task_set_data (task, NULL, NULL, &progress);
p_source = p_source_line;
for (x = 0; x < width; x++) {
CAIRO_GET_RGBA (p_source, image_red, image_green, image_blue, image_alpha);
image_red = value_map[GTH_HISTOGRAM_CHANNEL_RED][image_red];
image_green = value_map[GTH_HISTOGRAM_CHANNEL_GREEN][image_green];
image_blue = value_map[GTH_HISTOGRAM_CHANNEL_BLUE][image_blue];
CAIRO_SET_RGBA (p_source, image_red, image_green, image_blue, image_alpha);
p_source += 4;
}
p_source_line += source_stride;
}
cairo_surface_mark_dirty (source);
for (c = GTH_HISTOGRAM_CHANNEL_VALUE; c <= GTH_HISTOGRAM_CHANNEL_BLUE; c++)
g_free (value_map[c]);
return ! cancelled;
}
gboolean
cairo_image_surface_apply_vignette (cairo_surface_t *source,
GthCurve **curve,
guchar vignette_alpha,
GthAsyncTask *task)
{
gboolean local_curves;
long *value_map[GTH_HISTOGRAM_N_CHANNELS];
int c, v;
int width;
int height;
int source_stride;
unsigned char *p_source_line;
int x, y;
gboolean cancelled = FALSE;
double progress;
unsigned char *p_source;
int image_red, image_green, image_blue, image_alpha;
int red, green, blue, alpha;
double center_x, center_y, d, min_d, max_d;
int temp;
GthPoint f1, f2, p;
gimp_op_init ();
local_curves = (curve == NULL);
if (local_curves) {
curve = g_new (GthCurve *, GTH_HISTOGRAM_N_CHANNELS);
curve[GTH_HISTOGRAM_CHANNEL_VALUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 3, 0,0, 158,95, /*152,103,*/ 255,255);
curve[GTH_HISTOGRAM_CHANNEL_RED] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
curve[GTH_HISTOGRAM_CHANNEL_GREEN] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
curve[GTH_HISTOGRAM_CHANNEL_BLUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
}
for (c = GTH_HISTOGRAM_CHANNEL_VALUE; c <= GTH_HISTOGRAM_CHANNEL_BLUE; c++) {
value_map[c] = g_new (long, 256);
for (v = 0; v <= 255; v++) {
double u = gth_curve_eval (curve[c], v);
if (c > GTH_HISTOGRAM_CHANNEL_VALUE)
u = value_map[GTH_HISTOGRAM_CHANNEL_VALUE][(int)u];
value_map[c][v] = u;
}
}
width = cairo_image_surface_get_width (source);
height = cairo_image_surface_get_height (source);
source_stride = cairo_image_surface_get_stride (source);
center_x = width / 2.0;
center_y = height / 2.0;
{
double a = MAX (width, height) / 2.0;
double b = MIN (height, width) / 2.0;
a = a - (a / 1.5);
b = b - (b / 1.5);
double e = sqrt (1.0 - SQR (b) / SQR (a));
double c = a * e;
min_d = 2 * sqrt (SQR (b) + SQR (c));
if (width > height) {
f1.x = center_x - c;
f1.y = center_y;
f2.x = center_x + c;
f2.y = center_y;
}
else {
f1.x = center_x;
f1.y = center_y - c;
f2.x = center_x;
f2.y = center_y + c;
}
p.x = 0;
p.y = 0;
max_d = gth_point_distance (&p, &f1) + gth_point_distance (&p, &f2);
}
p_source_line = _cairo_image_surface_flush_and_get_data (source);
for (y = 0; y < height; y++) {
gth_async_task_get_data (task, NULL, &cancelled, NULL);
if (cancelled)
break;
progress = (double) y / height;
gth_async_task_set_data (task, NULL, NULL, &progress);
p_source = p_source_line;
for (x = 0; x < width; x++) {
p.x = x;
p.y = y;
d = gth_point_distance (&p, &f1) + gth_point_distance (&p, &f2);
if (d >= min_d) {
CAIRO_GET_RGBA (p_source, image_red, image_green, image_blue, image_alpha);
red = value_map[GTH_HISTOGRAM_CHANNEL_RED][image_red];
green = value_map[GTH_HISTOGRAM_CHANNEL_GREEN][image_green];
blue = value_map[GTH_HISTOGRAM_CHANNEL_BLUE][image_blue];
if (d <= max_d)
alpha = 255 * ((d - min_d) / (max_d - min_d));
else
alpha = 255;
alpha = ADD_ALPHA (alpha, vignette_alpha);
p_source[CAIRO_RED] = GIMP_OP_NORMAL (red, image_red, alpha);
p_source[CAIRO_GREEN] = GIMP_OP_NORMAL (green, image_green, alpha);
p_source[CAIRO_BLUE] = GIMP_OP_NORMAL (blue, image_blue, alpha);
p_source[CAIRO_ALPHA] = GIMP_OP_NORMAL (255, image_alpha, alpha);
}
p_source += 4;
}
p_source_line += source_stride;
}
cairo_surface_mark_dirty (source);
if (local_curves) {
for (c = GTH_HISTOGRAM_CHANNEL_VALUE; c <= GTH_HISTOGRAM_CHANNEL_BLUE; c++) {
g_object_unref (curve[c]);
g_free (value_map[c]);
}
}
return ! cancelled;
}
gboolean
cairo_image_surface_apply_bcs (cairo_surface_t *source,
double brightness,
double contrast,
double saturation,
GthAsyncTask *task)
{
PixbufCache *cache;
int width;
int height;
int source_stride;
unsigned char *p_source_line;
int x, y;
gboolean cancelled = FALSE;
double progress;
unsigned char *p_source;
unsigned char values[4];
int temp, value;
gimp_op_init ();
cache = pixbuf_cache_new ();
if (saturation < 0)
saturation = tan (saturation * G_PI_2);
width = cairo_image_surface_get_width (source);
height = cairo_image_surface_get_height (source);
source_stride = cairo_image_surface_get_stride (source);
p_source_line = _cairo_image_surface_flush_and_get_data (source);
for (y = 0; y < height; y++) {
gth_async_task_get_data (task, NULL, &cancelled, NULL);
if (cancelled)
break;
progress = (double) y / height;
gth_async_task_set_data (task, NULL, NULL, &progress);
p_source = p_source_line;
for (x = 0; x < width; x++) {
int channel;
CAIRO_GET_RGBA (p_source, values[0], values[1], values[2], values[3]);
/* brightness / contrast */
for (channel = 0; channel < 3; channel++) {
value = values[channel];
if (! pixbuf_cache_get (cache, channel + 1, &value)) {
int tmp = value;
if (brightness > 0)
tmp = interpolate_value (value, 0, brightness);
else if (brightness < 0)
tmp = interpolate_value (value, 255, - brightness);
value = CLAMP (tmp, 0, 255);
if (contrast < 0)
tmp = interpolate_value (value, 127, tan (contrast * G_PI_2));
else if (contrast > 0)
tmp = interpolate_value (value, 127, contrast);
value = CLAMP (tmp, 0, 255);
pixbuf_cache_set (cache, channel + 1, values[channel], value);
}
values[channel] = value;
}
/* saturation */
if (saturation != 0.0) {
guchar min, max, lightness;
int tmp;
max = MAX (MAX (values[0], values[1]), values[2]);
min = MIN (MIN (values[0], values[1]), values[2]);
lightness = (max + min) / 2;
tmp = interpolate_value (values[0], lightness, saturation);
values[0] = CLAMP (tmp, 0, 255);
tmp = interpolate_value (values[1], lightness, saturation);
values[1] = CLAMP (tmp, 0, 255);
tmp = interpolate_value (values[2], lightness, saturation);
values[2] = CLAMP (tmp, 0, 255);
}
CAIRO_SET_RGBA (p_source, values[0], values[1], values[2], values[3]);
p_source += 4;
}
p_source_line += source_stride;
}
cairo_surface_mark_dirty (source);
pixbuf_cache_free (cache);
return ! cancelled;
}
gboolean
cairo_image_surface_colorize (cairo_surface_t *source,
guchar color_red,
guchar color_green,
guchar color_blue,
guchar color_alpha,
GthAsyncTask *task)
{
int i;
double midtone_distance[256];
int width;
int height;
int source_stride;
unsigned char *p_source_line;
int x, y;
gboolean cancelled = FALSE;
double progress;
unsigned char *p_source;
int image_red, image_green, image_blue, image_alpha;
int red, green, blue, alpha;
int temp, min, max, lightness;
gimp_op_init ();
for (i = 0; i < 256; i++)
midtone_distance[i] = 0.667 * (1 - SQR (((double) i - 127.0) / 127.0));
width = cairo_image_surface_get_width (source);
height = cairo_image_surface_get_height (source);
source_stride = cairo_image_surface_get_stride (source);
p_source_line = _cairo_image_surface_flush_and_get_data (source);
for (y = 0; y < height; y++) {
gth_async_task_get_data (task, NULL, &cancelled, NULL);
if (cancelled)
break;
progress = (double) y / height;
gth_async_task_set_data (task, NULL, NULL, &progress);
p_source = p_source_line;
for (x = 0; x < width; x++) {
CAIRO_GET_RGBA (p_source, image_red, image_green, image_blue, image_alpha);
/* desaturate */
max = MAX (MAX (image_red, image_green), image_blue);
min = MIN (MIN (image_red, image_green), image_blue);
lightness = (max + min) / 2;
/* colorize */
red = lightness + color_red * midtone_distance[lightness];
green = lightness + color_green * midtone_distance[lightness];
blue = lightness + color_blue * midtone_distance[lightness];
alpha = ADD_ALPHA (image_alpha, color_alpha);
p_source[CAIRO_RED] = GIMP_OP_NORMAL (red, image_red, alpha);
p_source[CAIRO_GREEN] = GIMP_OP_NORMAL (green, image_green, alpha);
p_source[CAIRO_BLUE] = GIMP_OP_NORMAL (blue, image_blue, alpha);
p_source[CAIRO_ALPHA] = GIMP_OP_NORMAL (255, image_alpha, alpha);
p_source += 4;
}
p_source_line += source_stride;
}
cairo_surface_mark_dirty (source);
return ! cancelled;
}
gboolean
cairo_image_surface_add_color (cairo_surface_t *source,
guchar color_red,
guchar color_green,
guchar color_blue,
guchar color_alpha,
GthAsyncTask *task)
{
int width;
int height;
int source_stride;
unsigned char *p_source_line;
int x, y;
gboolean cancelled = FALSE;
double progress;
unsigned char *p_source;
int image_red, image_green, image_blue, image_alpha;
int temp, alpha;
gimp_op_init ();
width = cairo_image_surface_get_width (source);
height = cairo_image_surface_get_height (source);
source_stride = cairo_image_surface_get_stride (source);
p_source_line = _cairo_image_surface_flush_and_get_data (source);
for (y = 0; y < height; y++) {
gth_async_task_get_data (task, NULL, &cancelled, NULL);
if (cancelled)
break;
progress = (double) y / height;
gth_async_task_set_data (task, NULL, NULL, &progress);
p_source = p_source_line;
for (x = 0; x < width; x++) {
CAIRO_GET_RGBA (p_source, image_red, image_green, image_blue, image_alpha);
alpha = ADD_ALPHA (image_alpha, color_alpha);
p_source[CAIRO_RED] = GIMP_OP_NORMAL (color_red, image_red, alpha);
p_source[CAIRO_GREEN] = GIMP_OP_NORMAL (color_green, image_green, alpha);
p_source[CAIRO_BLUE] = GIMP_OP_NORMAL (color_blue, image_blue, alpha);
p_source[CAIRO_ALPHA] = GIMP_OP_NORMAL (255, image_alpha, alpha);
p_source += 4;
}
p_source_line += source_stride;
}
cairo_surface_mark_dirty (source);
return ! cancelled;
}