Commit 59fd2d12 authored by Ell's avatar Ell

lens-blur: new operation in common-cxx

gegl:lens-blur simulates an out-of-focus lens blur, currently
limited to a round aperture.  The op allows increasing the
relative highlight strength, creating a bokeh effect.

The op can produce either a fixed-radius blur, or a varying-radius
blur based on a mask given through an aux input.
parent 9cc79768
/* This file is an image processing operation for GEGL
*
* GEGL 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 3 of the License, or (at your option) any later version.
*
* GEGL 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 GEGL; if not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2017 Ell
*/
#include "config.h"
#include <glib/gi18n-lib.h>
#ifdef GEGL_PROPERTIES
property_double (radius, _("Radius"), 10.0)
description (_("Blur radius"))
value_range (0.0, G_MAXDOUBLE)
ui_range (0.0, 100.0)
ui_gamma (2.0)
ui_meta ("unit", "pixel-distance")
property_double (highlight_factor, _("Highlight factor"), 0.0)
description (_("Relative highlight strength"))
value_range (0.0, 1.0)
property_double (highlight_threshold_low, _("Highlight threshold (low)"), 0.0)
ui_range (0.0, 1.0)
ui_meta ("role", "range-start")
ui_meta ("unit", "luminance")
ui_meta ("label", "range-label")
ui_meta ("range-label", _("Highlight threshold"))
property_double (highlight_threshold_high, _("Highlight threshold (high)"), 1.0)
ui_range (0.0, 1.0)
ui_meta ("role", "range-end")
ui_meta ("unit", "luminance")
ui_meta ("label", "$highlight-threshold-low.label")
property_boolean (clip, _("Clip to input extents"), TRUE)
description (_("Clip output to the input extents"))
property_boolean (linear_mask, _("Linear mask"), FALSE)
description (_("Use linear mask values"))
#else
#define GEGL_OP_COMPOSER
#define GEGL_OP_NAME lens_blur
#define GEGL_OP_C_SOURCE lens-blur.cc
#include "gegl-op.h"
static void
prepare (GeglOperation *operation)
{
GeglProperties *o = GEGL_PROPERTIES (operation);
const Babl *space;
const Babl *format;
space = gegl_operation_get_source_space (operation, "input");
format = babl_format_with_space ("RGBA float", space);
gegl_operation_set_format (operation, "input", format);
gegl_operation_set_format (operation, "output", format);
gegl_operation_set_format (operation, "aux",
babl_format_with_space (
o->linear_mask ? "Y float" : "Y' float",
gegl_operation_get_source_space (operation,
"aux")));
o->user_data = (gpointer) babl_fish (format,
babl_format_with_space ("Y float",
space));
}
static GeglRectangle
get_bounding_box (GeglOperation *operation)
{
GeglProperties *o = GEGL_PROPERTIES (operation);
const GeglRectangle *in_rect;
GeglRectangle result = {};
in_rect = gegl_operation_source_get_bounding_box (operation, "input");
if (in_rect)
{
result = *in_rect;
if (! o->clip)
{
gint iradius = floor (o->radius + 0.5);
result.x -= iradius;
result.y -= iradius;
result.width += 2 * iradius;
result.height += 2 * iradius;
}
}
return result;
}
static GeglRectangle
get_required_for_output (GeglOperation *operation,
const gchar *input_pad,
const GeglRectangle *roi)
{
GeglProperties *o = GEGL_PROPERTIES (operation);
GeglRectangle result = *roi;
gint iradius = floor (o->radius + 0.5);
result.x -= iradius;
result.y -= iradius;
result.width += 2 * iradius;
result.height += 2 * iradius;
return result;
}
static gboolean
operation_process (GeglOperation *operation,
GeglOperationContext *context,
const gchar *output_prop,
const GeglRectangle *roi,
gint level)
{
GeglProperties *o = GEGL_PROPERTIES (operation);
if (o->radius < 0.5)
{
gegl_operation_context_set_object (
context, "output",
gegl_operation_context_get_object (context, "input"));
return TRUE;
}
return GEGL_OPERATION_CLASS (gegl_op_parent_class)->process (
operation, context, output_prop, roi, level);
}
static gboolean
process (GeglOperation *operation,
GeglBuffer *input,
GeglBuffer *aux,
GeglBuffer *output,
const GeglRectangle *roi,
gint level)
{
GeglProperties *o = GEGL_PROPERTIES (operation);
const Babl *format = gegl_operation_get_format (operation, "input");
const Babl *aux_format = gegl_operation_get_format (operation, "aux");
const Babl *weight_fish = (const Babl *) o->user_data;
GeglRectangle rect;
gfloat *in;
gfloat *in_w;
gfloat *out;
gfloat *out_w;
gfloat *mask = NULL;
gfloat highlight_threshold_low;
gfloat highlight_threshold_high;
gfloat highlight_factor;
gfloat highlight_max;
gfloat radius = o->radius;
gint iradius = floorf (radius + 0.5f);
gint size = 2 * iradius + 1;
gint y;
highlight_threshold_low = o->highlight_threshold_low;
highlight_threshold_high = o->highlight_threshold_high;
highlight_factor = 10.0 * o->highlight_factor * G_LN2;
highlight_max = expf (highlight_factor);
rect.x = roi->x - iradius;
rect.y = roi->y - iradius;
rect.width = roi->width + 2 * iradius;
rect.height = roi->height + 2 * iradius;
if (o->clip)
{
gegl_rectangle_intersect (
&rect,
&rect,
gegl_operation_source_get_bounding_box (operation, "input"));
}
size = MIN (size, rect.height);
in = (gfloat *) gegl_malloc (4 * sizeof (gfloat) * rect.width * size);
in_w = (gfloat *) gegl_malloc ( sizeof (gfloat) * rect.width * size);
out = (gfloat *) gegl_malloc (4 * sizeof (gfloat) * roi->width);
out_w = (gfloat *) gegl_malloc ( sizeof (gfloat) * roi->width);
if (aux)
mask = (gfloat *) gegl_malloc (sizeof (gfloat) * rect.width * size);
auto row_index = [&] (gint y)
{
return (y - rect.y) % size;
};
auto weight = [&] (gfloat v)
{
v = (v - highlight_threshold_low) /
(highlight_threshold_high - highlight_threshold_low);
if (v <= 0.0f)
return 1.0f;
else if (v >= 1.0f)
return highlight_max;
return expf (v * highlight_factor);
};
auto read = [&] (gint y,
gint height)
{
gfloat *row;
gfloat *row_w;
gfloat *row_m = NULL;
gint n = rect.width * height;
gint r_i;
gint i;
r_i = row_index (y);
row = in + 4 * rect.width * r_i;
row_w = in_w + rect.width * r_i;
gegl_buffer_get (input,
GEGL_RECTANGLE (rect.x, y, rect.width, height),
1.0,
format,
row,
GEGL_AUTO_ROWSTRIDE,
GEGL_ABYSS_NONE);
if (mask)
{
row_m = mask + rect.width * r_i;
gegl_buffer_get (aux,
GEGL_RECTANGLE (rect.x, y, rect.width, height),
1.0,
aux_format,
row_m,
GEGL_AUTO_ROWSTRIDE,
GEGL_ABYSS_NONE);
}
if (highlight_factor)
{
babl_process (weight_fish, row, row_w, n);
}
else
{
gfloat w = 1.0f;
gegl_memset_pattern (row_w, &w, sizeof (gfloat), n);
}
for (i = 0; i < n; i++)
{
gint c;
if (highlight_factor)
row_w[i] = weight (row_w[i]);
if (mask)
{
row_m[i] = row_m[i] * radius + 0.5f;
row_m[i] *= row_m[i];
row_w[i] /= G_PI * row_m[i];
}
row[4 * i + 3] *= row_w[i];
for (c = 0; c < 3; c++)
row[4 * i + c] *= row[4 * i + 3];
}
};
read (rect.y, MIN (roi->y + iradius + 1 - rect.y, rect.height));
for (y = roi->y; y < roi->y + roi->height; y++)
{
gint r1, r2;
gint x;
memset (out, 0, 4 * sizeof (gfloat) * roi->width);
memset (out_w, 0, sizeof (gfloat) * roi->width);
r1 = MAX (-iradius, - (y - rect.y));
r2 = MIN (+iradius, (rect.height - 1) - (y - rect.y));
if (! mask)
{
gint r;
for (r = r1; r <= r2; r++)
{
gfloat *o = out;
gfloat *o_w = out_w;
const gfloat *row;
const gfloat *row_w;
gfloat accum[4] = {};
gfloat accum_w = 0.0f;
gint r_i;
gint s;
gint x1, x2;
r_i = row_index (y + r);
row = in + 4 * (rect.width * r_i + (roi->x - rect.x));
row_w = in_w + (rect.width * r_i + (roi->x - rect.x));
s = sqrtf ((radius + 0.5f) * (radius + 0.5f) - r * r);
x1 = MAX (-s, - (roi->x - rect.x));
x2 = MIN (+s, (rect.width - 1) - (roi->x - rect.x));
for (x = x1; x <= x2; x++)
{
gint c;
for (c = 0; c < 4; c++)
accum[c] += row[4 * x + c];
accum_w += row_w[x];
}
x1 = - (roi->x - rect.x) + s;
x2 = (rect.width - 1) - (roi->x - rect.x) - s;
for (x = 0; x < roi->width; x++)
{
gint c;
for (c = 0; c < 4; c++)
o[c] += accum[c];
*o_w += accum_w;
o += 4;
o_w++;
if (x >= x1)
{
for (c = 0; c < 4; c++)
accum[c] -= row[4 * (-s) + c];
accum_w -= row_w[-s];
}
row += 4;
row_w++;
if (x <= x2)
{
for (c = 0; c < 4; c++)
accum[c] += row[4 * (+s) + c];
accum_w += row_w[+s];
}
}
}
}
else
{
gint r;
for (r = r1; r <= r2; r++)
{
const gfloat *row;
const gfloat *row_w;
const gfloat *row_m;
gfloat r2 = r * r;
gint r_i;
gint s;
gint x1, x2;
r_i = row_index (y + r);
row = in + 4 * (rect.width * r_i + (roi->x - rect.x));
row_w = in_w + (rect.width * r_i + (roi->x - rect.x));
row_m = mask + (rect.width * r_i + (roi->x - rect.x));
s = sqrtf ((radius + 0.5f) * (radius + 0.5f) - r * r);
x1 = MAX ( - s,
- (roi->x - rect.x));
x2 = MIN ((roi->width - 1) + s,
(rect.width - 1) - (roi->x - rect.x));
row += 4 * x1;
row_w += x1;
row_m += x1;
for (x = x1; x <= x2; x++, row += 4, row_w++, row_m++)
{
gint xl, xr;
gint c;
if (*row_m < r2)
continue;
s = sqrtf (*row_m - r2);
xl = x - s;
xr = x + s + 1;
xl = MAX (xl, 0);
if (xl >= xr || xl >= roi->width)
continue;
for (c = 0; c < 4; c++)
out[4 * xl + c] += row[c];
out_w[xl] += *row_w;
if (xr < roi->width)
{
for (c = 0; c < 4; c++)
out[4 * xr + c] -= row[c];
out_w[xr] -= *row_w;
}
}
}
for (x = 1; x < roi->width; x++)
{
gint c;
for (c = 0; c < 4; c++)
out[4 * x + c] += out[4 * (x - 1) + c];
out_w[x] += out_w[x - 1];
}
}
for (x = 0; x < roi->width; x++)
{
gint c;
for (c = 0; c < 3; c++)
out[4 * x + c] /= out[4 * x + 3];
out[4 * x + 3] /= out_w[x];
}
gegl_buffer_set (output,
GEGL_RECTANGLE (roi->x,
y,
roi->width,
1),
0,
format,
out,
GEGL_AUTO_ROWSTRIDE);
if ((y + 1) + iradius < rect.y + rect.height)
read ((y + 1) + iradius, 1);
}
gegl_free (mask);
gegl_free (out_w);
gegl_free (out);
gegl_free (in_w);
gegl_free (in);
return TRUE;
}
static void
gegl_op_class_init (GeglOpClass *klass)
{
GeglOperationClass *operation_class;
GeglOperationComposerClass *composer_class;
operation_class = GEGL_OPERATION_CLASS (klass);
composer_class = GEGL_OPERATION_COMPOSER_CLASS (klass);
operation_class->prepare = prepare;
operation_class->get_bounding_box = get_bounding_box;
operation_class->get_required_for_output = get_required_for_output;
operation_class->get_invalidated_by_change = get_required_for_output;
operation_class->process = operation_process;
composer_class->process = process;
gegl_operation_class_set_keys (operation_class,
"name", "gegl:lens-blur",
"title", _("Lens Blur"),
"categories", "blur",
"description", _("Simulate out-of-focus lens blur"),
NULL);
}
#endif
......@@ -2,6 +2,7 @@
gegl_common_cxx_sources = files(
'distance-transform.cc',
'focus-blur.c',
'lens-blur.cc',
'piecewise-blend.cc',
'variable-blur.c',
'warp.cc',
......
......@@ -128,6 +128,7 @@ operations/common/weighted-blend.c
operations/common/write-buffer.c
operations/common-cxx/distance-transform.cc
operations/common-cxx/focus-blur.c
operations/common-cxx/lens-blur.cc
operations/common-cxx/piecewise-blend.cc
operations/common-cxx/variable-blur.c
operations/common-cxx/warp.cc
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment