[gegl] long-shadow: add new operation to workshop
- From: Ell <ell src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gegl] long-shadow: add new operation to workshop
- Date: Mon, 30 Jul 2018 16:08:18 +0000 (UTC)
commit ef6a3e948eedcee271b7fa5a6aa7d77654bddd99
Author: Ell <ell_se yahoo com>
Date: Mon Jul 30 11:51:54 2018 -0400
long-shadow: add new operation to workshop
Add a gegl:long-shadow operation, which (surprise surprise) creates
a long-shadow effect. The operation supports three shadow styles:
finite, infinite, and fading. The shadow's color, angle, length
(for finite shadows), and midpoint (for fading shadows) are
controllable.
Additionally, the output composition is also controllable, and can
be one of:
- Shadow plus image: Both the shadow and the input image are
included in the output.
- Shadow only: Only the shadow is included in the output.
- Shadow minus image: Only the parts of the shadow that aren't
obscured by the input image are included in the input. In
GIMP, such output can be combined with the original layer using
"Merge" layer mode, to achieve the same result as "Shadow plus
image", using separate layers for the image and the shadow.
The operation preserves partial-transparency, and produces anti-
aliased results.
operations/workshop/Makefile.am | 1 +
operations/workshop/long-shadow.c | 1106 +++++++++++++++++++++++++++++++++++++
po/POTFILES.in | 1 +
3 files changed, 1108 insertions(+)
---
diff --git a/operations/workshop/Makefile.am b/operations/workshop/Makefile.am
index e01d2cc91..8e69cdab3 100644
--- a/operations/workshop/Makefile.am
+++ b/operations/workshop/Makefile.am
@@ -23,6 +23,7 @@ op_LTLIBRARIES = \
inpaint.la \
integral-image.la \
linear-sinusoid.la \
+ long-shadow.la \
rawbayer-load.la \
segment-kmeans.la \
selective-hue-saturation.la
diff --git a/operations/workshop/long-shadow.c b/operations/workshop/long-shadow.c
new file mode 100644
index 000000000..0ac702072
--- /dev/null
+++ b/operations/workshop/long-shadow.c
@@ -0,0 +1,1106 @@
+/* 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) 2018 Ell
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#ifdef GEGL_PROPERTIES
+
+enum_start (gegl_long_shadow_style)
+ enum_value (GEGL_LONG_SHADOW_STYLE_FINITE, "finite", N_("Finite"))
+ enum_value (GEGL_LONG_SHADOW_STYLE_INFINITE, "infinite", N_("Infinite"))
+ enum_value (GEGL_LONG_SHADOW_STYLE_FADING, "fading", N_("Fading"))
+enum_end (GeglLongShadowStyle)
+
+enum_start (gegl_long_shadow_composition)
+ enum_value (GEGL_LONG_SHADOW_COMPOSITION_SHADOW_PLUS_IMAGE, "shadow-plus-image", N_("Shadow plus image"))
+ enum_value (GEGL_LONG_SHADOW_COMPOSITION_SHADOW_ONLY, "shadow-only", N_("Shadow only"))
+ enum_value (GEGL_LONG_SHADOW_COMPOSITION_SHADOW_MINUS_IMAGE, "shadow-minus-image", N_("Shadow minus
image"))
+enum_end (GeglLongShadowComposition)
+
+property_enum (style, _("Style"),
+ GeglLongShadowStyle, gegl_long_shadow_style,
+ GEGL_LONG_SHADOW_STYLE_FINITE)
+ description (_("Shadow style"))
+
+property_double (angle, _("Angle"), 45.0)
+ description (_("Shadow angle"))
+ value_range (-180.0, 180.0)
+ ui_meta ("unit", "degree")
+ ui_meta ("direction", "cw")
+
+property_double (length, _("Length"), 100.0)
+ description (_("Shadow length"))
+ value_range (0.0, G_MAXDOUBLE)
+ ui_range (0.0, 1000.0)
+ ui_meta ("visible", "style {finite}")
+
+property_double (midpoint, _("Midpoint"), 100.0)
+ description (_("Shadow fade midpoint"))
+ value_range (0.0, G_MAXDOUBLE)
+ ui_range (0.0, 1000.0)
+ ui_meta ("visible", "style {fading}")
+
+property_color (color, _("Color"), "black")
+ description (_("Shadow color"))
+ ui_meta ("role", "color-primary")
+
+property_enum (composition, _("Composition"),
+ GeglLongShadowComposition, gegl_long_shadow_composition,
+ GEGL_LONG_SHADOW_COMPOSITION_SHADOW_PLUS_IMAGE)
+ description (_("Output composition"))
+
+#else
+
+#define GEGL_OP_FILTER
+#define GEGL_OP_NAME long_shadow
+#define GEGL_OP_C_SOURCE long-shadow.c
+
+#include "gegl-op.h"
+#include <math.h>
+
+#define SCREEN_RESOLUTION 16
+#define EPSILON 1e-6
+
+#define SWAP(type, x, y) \
+ G_STMT_START \
+ { \
+ type t = (x); \
+ (x) = (y); \
+ (y) = t; \
+ } \
+ G_STMT_END
+
+typedef struct
+{
+ gfloat value;
+ gint fy;
+} Shadow;
+
+typedef struct ShadowItem
+{
+ Shadow shadow;
+
+ struct ShadowItem *next;
+ struct ShadowItem *prev;
+} ShadowItem;
+
+typedef struct
+{
+ Shadow shadow;
+ ShadowItem *queue;
+} Pixel;
+
+typedef struct
+{
+ GeglProperties options;
+
+ /* image -> filter coordinate transformation */
+ gboolean flip_horizontally;
+ gboolean flip_vertically;
+ gboolean flip_diagonally;
+
+ /* in filter coordinates */
+ gdouble tan_angle;
+
+ gdouble sample_offset_x;
+ gdouble sample_offset_y;
+
+ gint shadow_height;
+
+ gfloat fade_rate;
+
+ GeglRectangle input_bounds;
+ GeglRectangle roi;
+ GeglRectangle area;
+
+ /* in screen coordinates */
+ gint u0, u1;
+
+ gpointer screen;
+ gint pixel_size;
+ gint n_active_pixels;
+ gint prev_n_active_pixels;
+
+ gdouble filter_pixel_shadow_width;
+
+ GeglBuffer *input;
+ GeglBuffer *output;
+
+ const Babl *format;
+
+ gfloat *input_row;
+ gfloat *output_row;
+
+ gfloat *input_row0;
+ gfloat *output_row0;
+ gint row_step;
+
+ GeglSampler *sampler;
+ GeglSamplerGetFun sampler_get_fun;
+
+ gfloat color[4];
+
+ gint level;
+ gdouble scale;
+} Context;
+
+static void
+init_options (Context *ctx,
+ const GeglProperties *options,
+ gint level)
+{
+ ctx->options = *options;
+
+ ctx->level = level;
+ ctx->scale = 1.0 / (1 << level);
+
+ ctx->options.length *= ctx->scale;
+ ctx->options.midpoint *= ctx->scale;
+}
+
+static void
+init_geometry (Context *ctx)
+{
+ ctx->flip_horizontally = FALSE;
+ ctx->flip_vertically = FALSE;
+ ctx->flip_diagonally = FALSE;
+
+ /* set up the image -> filter coordinate transformation, such that the
+ * shadow's angle is always inside the [0 deg., 45 deg.] range, relative to
+ * the positive (filter-space) y-axis, counter-clockwise.
+ */
+
+ ctx->options.angle = 90.0 - ctx->options.angle;
+ if (ctx->options.angle > 180.0)
+ ctx->options.angle -= 360.0;
+
+ if (ctx->options.angle < 0.0)
+ {
+ ctx->options.angle = -ctx->options.angle;
+ ctx->flip_horizontally = TRUE;
+ }
+
+ if (ctx->options.angle > 90.0)
+ {
+ ctx->options.angle = 180.0 - ctx->options.angle;
+ ctx->flip_vertically = TRUE;
+ }
+
+ if (ctx->options.angle > 45.0)
+ {
+ ctx->options.angle = 90.0 - ctx->options.angle;
+ ctx->flip_diagonally = TRUE;
+
+ SWAP (gboolean, ctx->flip_horizontally, ctx->flip_vertically);
+ }
+
+ ctx->options.angle *= G_PI / 180.0;
+
+ ctx->tan_angle = tan (ctx->options.angle);
+
+ ctx->sample_offset_x = -sin (ctx->options.angle) * ctx->options.length;
+ ctx->sample_offset_y = -cos (ctx->options.angle) * ctx->options.length;
+
+ ctx->shadow_height = floor (-ctx->sample_offset_y);
+
+ if (ctx->options.midpoint > EPSILON)
+ {
+ ctx->fade_rate = pow (0.5, 1.0 / (cos (ctx->options.angle) *
+ ctx->options.midpoint));
+ }
+ else
+ {
+ ctx->fade_rate = 0.0f;
+ }
+}
+
+static inline void
+transform_coords_to_filter (Context *ctx,
+ gdouble ix,
+ gdouble iy,
+ gdouble *fx,
+ gdouble *fy)
+{
+ if (ctx->flip_diagonally)
+ SWAP (gdouble, ix, iy);
+
+ if (ctx->flip_horizontally)
+ ix = -ix;
+
+ if (ctx->flip_vertically)
+ iy = -iy;
+
+ *fx = ix;
+ *fy = iy;
+}
+
+static inline void
+transform_coords_to_image (Context *ctx,
+ gdouble fx,
+ gdouble fy,
+ gdouble *ix,
+ gdouble *iy)
+{
+ if (ctx->flip_vertically)
+ fy = -fy;
+
+ if (ctx->flip_horizontally)
+ fx = -fx;
+
+ if (ctx->flip_diagonally)
+ SWAP (gdouble, fx, fy);
+
+ *ix = fx;
+ *iy = fy;
+}
+
+static inline void
+transform_rect_to_filter (Context *ctx,
+ const GeglRectangle *irect,
+ GeglRectangle *frect)
+{
+ *frect = *irect;
+
+ if (ctx->flip_diagonally)
+ {
+ SWAP (gint, frect->x, frect->y);
+ SWAP (gint, frect->width, frect->height);
+ }
+
+ if (ctx->flip_horizontally)
+ frect->x = -frect->x - frect->width;
+
+ if (ctx->flip_vertically)
+ frect->y = -frect->y - frect->height;
+}
+
+static inline void
+transform_rect_to_image (Context *ctx,
+ const GeglRectangle *frect,
+ GeglRectangle *irect)
+{
+ *irect = *frect;
+
+ if (ctx->flip_vertically)
+ irect->y = -irect->y - irect->height;
+
+ if (ctx->flip_horizontally)
+ irect->x = -irect->x - irect->width;
+
+ if (ctx->flip_diagonally)
+ {
+ SWAP (gint, irect->x, irect->y);
+ SWAP (gint, irect->width, irect->height);
+ }
+}
+
+static inline gdouble
+project_to_screen (Context *ctx,
+ gdouble fx,
+ gdouble fy)
+{
+ return SCREEN_RESOLUTION * (fx - ctx->tan_angle * fy);
+}
+
+static inline gdouble
+project_to_filter (Context *ctx,
+ gdouble u,
+ gdouble fy)
+{
+ return u / SCREEN_RESOLUTION + ctx->tan_angle * fy;
+}
+
+static inline void
+get_affected_screen_range (Context *ctx,
+ gint fx0,
+ gint fx1,
+ gint fy,
+ gint *u0,
+ gint *u1)
+{
+ if (u0) *u0 = floor (project_to_screen (ctx, fx0, fy + 0.5) + 0.5);
+ if (u1) *u1 = floor (project_to_screen (ctx, fx1, fy - 0.5) + 0.5);
+}
+
+static inline void
+get_affected_filter_range (Context *ctx,
+ gint u0,
+ gint u1,
+ gint fy,
+ gint *fx0,
+ gint *fx1)
+{
+ if (fx0) *fx0 = floor (project_to_filter (ctx, u0, fy));
+ if (fx1) *fx1 = ceil (project_to_filter (ctx, u1, fy));
+}
+
+static inline void
+get_affecting_screen_range (Context *ctx,
+ gint fx0,
+ gint fx1,
+ gint fy,
+ gint *u0,
+ gint *u1)
+{
+ if (u0) *u0 = floor (project_to_screen (ctx, fx0, fy));
+ if (u1) *u1 = ceil (project_to_screen (ctx, fx1, fy));
+}
+
+static inline void
+get_affecting_filter_range (Context *ctx,
+ gint u0,
+ gint u1,
+ gint fy,
+ gint *fx0,
+ gint *fx1)
+{
+ if (fx0) *fx0 = floor (project_to_filter (ctx, u0 + 0.5, fy - 0.5));
+ if (fx1) *fx1 = ceil (project_to_filter (ctx, u1 - 0.5, fy + 0.5));
+}
+
+static void
+init_area (Context *ctx,
+ GeglOperation *operation,
+ const GeglRectangle *roi)
+{
+ GeglRectangle *input_bounds;
+
+ input_bounds = gegl_operation_source_get_bounding_box (operation, "input");
+
+ if (input_bounds)
+ transform_rect_to_filter (ctx, input_bounds, &ctx->input_bounds);
+ else
+ ctx->input_bounds = *GEGL_RECTANGLE (0, 0, 0, 0);
+
+ transform_rect_to_filter (ctx, roi, &ctx->roi);
+
+ get_affecting_screen_range (ctx,
+ ctx->roi.x, 0,
+ ctx->roi.y + ctx->roi.height - 1,
+ &ctx->u0, NULL);
+ get_affecting_screen_range (ctx,
+ 0, ctx->roi.x + ctx->roi.width,
+ ctx->roi.y,
+ NULL, &ctx->u1);
+
+ ctx->area = ctx->roi;
+
+ if (ctx->options.style == GEGL_LONG_SHADOW_STYLE_FINITE)
+ {
+ gint u0;
+
+ ctx->area.y -= ctx->shadow_height;
+
+ get_affecting_screen_range (ctx,
+ ctx->roi.x, 0,
+ ctx->roi.y,
+ &u0, NULL);
+ get_affecting_filter_range (ctx,
+ u0, 0,
+ ctx->area.y,
+ &ctx->area.x, NULL);
+
+ ctx->area.x--;
+
+ ctx->area.x = MAX (ctx->area.x, ctx->input_bounds.x);
+ ctx->area.y = MAX (ctx->area.y, ctx->input_bounds.y);
+
+ ctx->area.width += ctx->roi.x - ctx->area.x;
+ ctx->area.height += ctx->roi.y - ctx->area.y;
+ }
+}
+
+static void
+init_screen (Context *ctx)
+{
+ if (ctx->options.style == GEGL_LONG_SHADOW_STYLE_FINITE)
+ ctx->pixel_size = sizeof (Pixel);
+ else
+ ctx->pixel_size = sizeof (gfloat);
+
+ ctx->screen = g_malloc0 (ctx->pixel_size * (ctx->u1 - ctx->u0));
+ ctx->screen = (guchar *) ctx->screen - ctx->pixel_size * ctx->u0;
+
+ ctx->n_active_pixels = 0;
+ ctx->prev_n_active_pixels = 1;
+
+ ctx->filter_pixel_shadow_width = SCREEN_RESOLUTION * (1.0 + ctx->tan_angle);
+}
+
+static void
+cleanup_screen (Context *ctx)
+{
+ if (ctx->options.style == GEGL_LONG_SHADOW_STYLE_FINITE)
+ {
+ Pixel *p = ctx->screen;
+ gint u;
+
+ for (u = ctx->u0; u < ctx->u1; u++)
+ {
+ ShadowItem *item = p[u].queue;
+
+ while (item)
+ {
+ ShadowItem *next = item->next;
+
+ g_slice_free (ShadowItem, item);
+
+ item = next;
+ }
+ }
+ }
+
+ ctx->screen = (guchar *) ctx->screen + ctx->pixel_size * ctx->u0;
+
+ g_free (ctx->screen);
+}
+
+static inline void
+shift_pixel (Context *ctx,
+ Pixel *pixel)
+{
+ ShadowItem *item = pixel->queue;
+
+ if (! item)
+ {
+ pixel->shadow.value = 0.0f;
+
+ ctx->n_active_pixels--;
+
+ return;
+ }
+
+ pixel->shadow = item->shadow;
+ pixel->queue = item->next;
+ if (pixel->queue)
+ pixel->queue->prev = item->prev;
+
+ g_slice_free (ShadowItem, item);
+}
+
+static void
+trim_shadow (Context *ctx,
+ gint fy)
+{
+ ctx->prev_n_active_pixels = ctx->n_active_pixels;
+
+ if (! ctx->n_active_pixels)
+ return;
+
+ switch (ctx->options.style)
+ {
+ case GEGL_LONG_SHADOW_STYLE_FINITE:
+ {
+ Pixel *p = ctx->screen;
+ gint u;
+
+ fy -= ctx->shadow_height;
+
+ for (u = ctx->u0; u < ctx->u1; u++)
+ {
+ Pixel *pixel = &p[u];
+
+ while (pixel->shadow.value && pixel->shadow.fy < fy)
+ shift_pixel (ctx, pixel);
+ }
+ }
+ break;
+
+ case GEGL_LONG_SHADOW_STYLE_FADING:
+ {
+ gfloat *p = ctx->screen;
+ gboolean active_pixels = FALSE;
+ gint u;
+
+ for (u = ctx->u0; u < ctx->u1; u++)
+ {
+ p[u] *= ctx->fade_rate;
+
+ if (p[u] < EPSILON)
+ p[u] = 0.0f;
+ else
+ active_pixels = TRUE;
+ }
+
+ ctx->n_active_pixels = active_pixels;
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+add_shadow (Context *ctx,
+ gint u0,
+ gint u1,
+ gint fy,
+ gfloat value)
+{
+ gint u;
+
+ if (! value)
+ return;
+
+ u0 = MAX (u0, ctx->u0);
+ u1 = MIN (u1, ctx->u1);
+
+ if (ctx->options.style == GEGL_LONG_SHADOW_STYLE_FINITE)
+ {
+ Pixel *p = ctx->screen;
+
+ for (u = u0; u < u1; u++)
+ {
+ Pixel *pixel = &p[u];
+
+ if (value >= pixel->shadow.value)
+ {
+ pixel->shadow.value = value;
+ pixel->shadow.fy = fy;
+
+ ctx->n_active_pixels += (pixel->shadow.value != 0.0f);
+ }
+ else if (! pixel->queue)
+ {
+ pixel->queue = g_slice_new (ShadowItem);
+
+ pixel->queue->shadow.value = value;
+ pixel->queue->shadow.fy = fy;
+ pixel->queue->next = NULL;
+ pixel->queue->prev = pixel->queue;
+ }
+ else
+ {
+ ShadowItem *item = pixel->queue->prev;
+ ShadowItem *last_item = item;
+ ShadowItem *new_item = NULL;
+
+ do
+ {
+ if (value < item->shadow.value)
+ break;
+
+ g_slice_free (ShadowItem, new_item);
+ new_item = item;
+
+ item = item->prev;
+ }
+ while (item != last_item);
+
+ if (! new_item)
+ {
+ new_item = g_slice_new (ShadowItem);
+
+ new_item->prev = last_item;
+ last_item->next = new_item;
+ }
+
+ new_item->shadow.value = value;
+ new_item->shadow.fy = fy;
+ new_item->next = NULL;
+
+ pixel->queue->prev = new_item;
+ }
+ }
+ }
+ else
+ {
+ gfloat *p = ctx->screen;
+
+ for (u = u0; u < u1; u++)
+ p[u] = MAX (p[u], value);
+
+ ctx->n_active_pixels = 1;
+ }
+}
+
+static inline void
+add_shadow_at (Context *ctx,
+ gdouble u0,
+ gint fy,
+ gfloat value)
+{
+ add_shadow (ctx,
+ floor (u0 + 0.5),
+ floor (u0 + ctx->filter_pixel_shadow_width + 0.5),
+ fy,
+ value);
+}
+
+static inline gfloat
+get_pixel_shadow (Context *ctx,
+ gconstpointer pixel,
+ gint fy)
+{
+ if (ctx->options.style == GEGL_LONG_SHADOW_STYLE_FINITE)
+ {
+ const Pixel *p = pixel;
+
+ return p->shadow.value;
+ }
+ else
+ {
+ const gfloat *p = pixel;
+
+ return *p;
+ }
+}
+
+static void
+init_buffers (Context *ctx,
+ GeglBuffer *input,
+ GeglBuffer *output)
+{
+ ctx->input = input;
+ ctx->output = output;
+
+ ctx->format = gegl_buffer_get_format (output);
+
+ gegl_color_get_pixel (ctx->options.color, ctx->format, ctx->color);
+
+ ctx->input_row = g_new (gfloat, 4 * ctx->area.width);
+ ctx->output_row = ctx->input_row;
+
+ if (ctx->options.composition !=
+ GEGL_LONG_SHADOW_COMPOSITION_SHADOW_PLUS_IMAGE)
+ {
+ ctx->output_row = g_new (gfloat, 4 * ctx->roi.width);
+
+ gegl_memset_pattern (ctx->output_row,
+ ctx->color, 4 * sizeof (float),
+ ctx->roi.width);
+ }
+
+ if (! ctx->flip_horizontally)
+ {
+ if (ctx->output_row == ctx->input_row)
+ ctx->output_row += 4 * (ctx->roi.x - ctx->area.x);
+
+ ctx->input_row0 = ctx->input_row;
+ ctx->output_row0 = ctx->output_row;
+
+ ctx->row_step = +4;
+ }
+ else
+ {
+ ctx->input_row0 = ctx->input_row + 4 * (ctx->area.width - 1);
+ ctx->output_row0 = ctx->output_row + 4 * (ctx->area.width - 1);
+
+ ctx->row_step = -4;
+
+ if (ctx->output_row == ctx->input_row)
+ ctx->output_row0 -= 4 * (ctx->roi.x - ctx->area.x);
+ }
+
+ if (ctx->options.style == GEGL_LONG_SHADOW_STYLE_FINITE)
+ {
+ ctx->sampler = gegl_buffer_sampler_new_at_level (input, ctx->format,
+ GEGL_SAMPLER_LINEAR,
+ ctx->level);
+
+ ctx->sampler_get_fun = gegl_sampler_get_fun (ctx->sampler);
+ }
+ else
+ {
+ ctx->sampler = NULL;
+ }
+}
+
+static void
+cleanup_buffers (Context *ctx)
+{
+ g_clear_object (&ctx->sampler);
+
+ if (ctx->options.composition !=
+ GEGL_LONG_SHADOW_COMPOSITION_SHADOW_PLUS_IMAGE)
+ {
+ g_free (ctx->output_row);
+ }
+
+ g_free (ctx->input_row);
+}
+
+static void
+get_row (Context *ctx,
+ gint fy)
+{
+ GeglRectangle row = {ctx->area.x, fy, ctx->area.width, 1};
+
+ transform_rect_to_image (ctx, &row, &row);
+
+ gegl_buffer_get (ctx->input,
+ &row, ctx->scale, ctx->format, ctx->input_row,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+}
+
+static void
+set_row (Context *ctx,
+ gint fy)
+{
+ GeglRectangle row = {ctx->roi.x, fy, ctx->roi.width, 1};
+
+ transform_rect_to_image (ctx, &row, &row);
+
+ gegl_buffer_set (ctx->output,
+ &row, ctx->level, ctx->format, ctx->output_row,
+ GEGL_AUTO_ROWSTRIDE);
+}
+
+static gfloat
+get_shadow (Context *ctx,
+ gdouble u0,
+ gdouble u1,
+ gint fx,
+ gint fy)
+{
+ gfloat result = 0.0f;
+
+ if (ctx->n_active_pixels)
+ {
+ gdouble u0i, u0f;
+ gdouble u1i, u1f;
+ gint a, b;
+ const guchar *pixel;
+ gint u;
+
+ u0 = MAX (u0, ctx->u0);
+ u1 = MIN (u1, ctx->u1);
+
+ u0i = ceil (u0);
+ u0f = u0i - u0;
+
+ u1i = floor (u1);
+ u1f = u1 - u1i;
+
+ a = u0i;
+ b = u1i;
+
+ pixel = ctx->screen;
+ pixel += (a - 1) * ctx->pixel_size;
+
+ if (u0f)
+ result += get_pixel_shadow (ctx, pixel, fy) * u0f;
+ pixel += ctx->pixel_size;
+
+ for (u = a; u < b; u++)
+ {
+ result += get_pixel_shadow (ctx, pixel, fy);
+ pixel += ctx->pixel_size;
+ }
+
+ if (u1f)
+ result += get_pixel_shadow (ctx, pixel, fy) * u1f;
+
+ result /= SCREEN_RESOLUTION;
+ }
+
+ if (ctx->sampler && (ctx->n_active_pixels || ctx->prev_n_active_pixels))
+ {
+ gdouble ix, iy;
+ gfloat sample[4];
+
+ transform_coords_to_image (ctx,
+ fx + 0.5 + ctx->sample_offset_x,
+ fy + 0.5 + ctx->sample_offset_y,
+ &ix, &iy);
+
+ ctx->sampler_get_fun (ctx->sampler,
+ ix, iy, NULL, sample, GEGL_ABYSS_NONE);
+
+ result = MAX (result, sample[3]);
+ }
+
+ return result;
+}
+
+static void
+set_output_pixel (Context *ctx,
+ const gfloat *input_pixel,
+ gfloat *output_pixel,
+ gfloat shadow_value)
+{
+ shadow_value = MAX (shadow_value, input_pixel[3]);
+
+ switch (ctx->options.composition)
+ {
+ case GEGL_LONG_SHADOW_COMPOSITION_SHADOW_PLUS_IMAGE:
+ shadow_value = (shadow_value - input_pixel[3]) * ctx->color[3];
+
+ if (shadow_value > 0.0f)
+ {
+ gfloat alpha = input_pixel[3] + shadow_value;
+ gfloat alpha_inv = 1.0f / alpha;
+ gint i;
+
+ for (i = 0; i < 3; i++)
+ {
+ output_pixel[i] = (input_pixel[3] * input_pixel[i] +
+ shadow_value * ctx->color[i]) * alpha_inv;
+ }
+ output_pixel[3] = alpha;
+ }
+ break;
+
+ case GEGL_LONG_SHADOW_COMPOSITION_SHADOW_ONLY:
+ output_pixel[3] = shadow_value * ctx->color[3];
+ break;
+
+ case GEGL_LONG_SHADOW_COMPOSITION_SHADOW_MINUS_IMAGE:
+ output_pixel[3] = MAX (shadow_value - input_pixel[3], 0.0f) *
+ ctx->color[3];
+ break;
+ }
+}
+
+static GeglRectangle
+get_required_for_output (GeglOperation *operation,
+ const gchar *input_pad,
+ const GeglRectangle *roi)
+{
+ GeglProperties *o = GEGL_PROPERTIES (operation);
+ GeglRectangle result = {};
+
+ if (o->style == GEGL_LONG_SHADOW_STYLE_FINITE)
+ {
+ Context ctx;
+ gdouble sample_x;
+ gdouble sample_y;
+
+ init_options (&ctx, o, 0);
+ init_geometry (&ctx);
+ init_area (&ctx, operation, roi);
+
+ sample_x = ctx.roi.x + ctx.sample_offset_x;
+ sample_y = ctx.roi.y + ctx.sample_offset_y;
+
+ result.x = floor (sample_x);
+ result.y = floor (sample_y);
+ result.width = roi->width + (result.x < sample_x);
+ result.height = roi->height + (result.y < sample_y);
+
+ gegl_rectangle_bounding_box (&result, &result, &ctx.area);
+
+ gegl_rectangle_intersect (&result, &result, &ctx.input_bounds);
+
+ transform_rect_to_image (&ctx, &result, &result);
+ }
+ else
+ {
+ GeglRectangle *in_rect =
+ gegl_operation_source_get_bounding_box (operation, "input");
+
+ if (in_rect)
+ result = *in_rect;
+ }
+
+ return result;
+}
+
+static GeglRectangle
+get_invalidated_by_change (GeglOperation *operation,
+ const gchar *input_pad,
+ const GeglRectangle *roi)
+{
+ GeglProperties *o = GEGL_PROPERTIES (operation);
+ GeglRectangle result = {};
+
+ if (o->style == GEGL_LONG_SHADOW_STYLE_FINITE)
+ {
+ Context ctx;
+ gint u1;
+ gint fx1;
+
+ init_options (&ctx, o, 0);
+ init_geometry (&ctx);
+
+ transform_rect_to_filter (&ctx, roi, &result);
+
+ get_affected_screen_range (&ctx,
+ 0, result.x + result.width,
+ result.y,
+ NULL, &u1);
+ get_affected_filter_range (&ctx,
+ 0, u1,
+ result.y + ctx.shadow_height,
+ NULL, &fx1);
+
+ fx1++;
+
+ result.width += ceil (-ctx.sample_offset_x);
+ result.height += ceil (-ctx.sample_offset_y);
+
+ result.width = MAX (result.width, fx1 - result.x);
+
+ transform_rect_to_image (&ctx, &result, &result);
+ }
+ else
+ {
+ GeglRectangle *in_rect =
+ gegl_operation_source_get_bounding_box (operation, "input");
+
+ if (in_rect)
+ result = *in_rect;
+ }
+
+ return result;
+}
+
+static GeglRectangle
+get_bounding_box (GeglOperation *operation)
+{
+ GeglRectangle result = {};
+ GeglRectangle *in_rect = gegl_operation_source_get_bounding_box (operation,
+ "input");
+
+ if (in_rect)
+ {
+ GeglProperties *o = GEGL_PROPERTIES (operation);
+
+ if (o->style == GEGL_LONG_SHADOW_STYLE_FINITE)
+ result = get_invalidated_by_change (operation, "input", in_rect);
+ else
+ result = *in_rect;
+ }
+
+ return result;
+}
+
+static GeglRectangle
+get_cached_region (GeglOperation *operation,
+ const GeglRectangle *roi)
+{
+ GeglProperties *o = GEGL_PROPERTIES (operation);
+
+ if (o->style == GEGL_LONG_SHADOW_STYLE_FINITE)
+ return *roi;
+ else
+ return get_bounding_box (operation);
+}
+
+static gboolean
+process (GeglOperation *operation,
+ GeglBuffer *input,
+ GeglBuffer *output,
+ const GeglRectangle *roi,
+ gint level)
+{
+ Context ctx;
+ gint fx, fy;
+
+ init_options (&ctx, GEGL_PROPERTIES (operation), level);
+ init_geometry (&ctx);
+ init_area (&ctx, operation, roi);
+ init_screen (&ctx);
+ init_buffers (&ctx, input, output);
+
+ for (fy = ctx.area.y; fy < ctx.area.y + ctx.area.height; fy++)
+ {
+ const gfloat *input_pixel;
+ gfloat *output_pixel;
+ gdouble u0;
+ gdouble shadow_offset;
+ gint fx0, fx1;
+
+ get_row (&ctx, fy);
+
+ if (fy > ctx.roi.y)
+ trim_shadow (&ctx, fy);
+
+ get_affecting_filter_range (&ctx,
+ ctx.u0, ctx.u1,
+ fy,
+ &fx0, &fx1);
+
+ fx0 = MAX (fx0, ctx.area.x);
+ fx1 = MIN (fx1, ctx.area.x + ctx.area.width);
+
+ u0 = project_to_screen (&ctx, fx0, fy);
+
+ shadow_offset = project_to_screen (&ctx, fx0, fy + 0.5) - u0;
+
+ input_pixel = ctx.input_row0 + (fx0 - ctx.area.x) * ctx.row_step;
+ output_pixel = ctx.output_row0;
+
+ for (fx = fx0; fx < fx1; fx++)
+ {
+ add_shadow_at (&ctx, u0 + shadow_offset, fy, input_pixel[3]);
+
+ if (fy >= ctx.roi.y && fx >= ctx.roi.x)
+ {
+ gfloat shadow_value;
+
+ shadow_value = get_shadow (&ctx,
+ u0, u0 + SCREEN_RESOLUTION,
+ fx, fy);
+
+ set_output_pixel (&ctx,
+ input_pixel, output_pixel,
+ shadow_value);
+
+ output_pixel += ctx.row_step;
+ }
+
+ u0 += SCREEN_RESOLUTION;
+
+ input_pixel += ctx.row_step;
+ }
+
+ set_row (&ctx, fy);
+ }
+
+ cleanup_buffers (&ctx);
+ cleanup_screen (&ctx);
+
+ return TRUE;
+}
+
+static void
+gegl_op_class_init (GeglOpClass *klass)
+{
+ GeglOperationClass *operation_class;
+ GeglOperationFilterClass *filter_class;
+
+ operation_class = GEGL_OPERATION_CLASS (klass);
+ filter_class = GEGL_OPERATION_FILTER_CLASS (klass);
+
+ operation_class->get_required_for_output = get_required_for_output;
+ operation_class->get_invalidated_by_change = get_invalidated_by_change;
+ operation_class->get_bounding_box = get_bounding_box;
+ operation_class->get_cached_region = get_cached_region;
+
+ /* FIXME: we want 'threaded == TRUE, want_in_place == FALSE' for finite
+ * shadows, and 'threaded == FALSE, want_in_place == TRUE' for infinite and
+ * fading shadows. right now, since there's no way to control these
+ * attributes dynamically, we settle for the lowest common denominator.
+ */
+ operation_class->threaded = FALSE;
+ operation_class->want_in_place = FALSE;
+
+ filter_class->process = process;
+
+ gegl_operation_class_set_keys (operation_class,
+ "name", "gegl:long-shadow",
+ "title", _("Long Shadow"),
+ "categories", "light",
+ "description", _("Creates a long-shadow effect"),
+ NULL);
+}
+
+#endif
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 812b43597..ee26d4bfe 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -264,6 +264,7 @@ operations/workshop/gradient-map.c
operations/workshop/hstack.c
operations/workshop/integral-image.c
operations/workshop/linear-sinusoid.c
+operations/workshop/long-shadow.c
operations/workshop/rawbayer-load.c
operations/workshop/segment-kmeans.c
operations/workshop/selective-hue-saturation.c
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]