/* 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 .
*
* Copyright 2011 Robert Sasu
*
* Based on "Cubism" GIMP plugin
* Copyright (C) 1996 Spencer Kimball, Tracy Scott
* You can contact the original GIMP authors at gimp@xcf.berkeley.edu
* Speedups by Elliot Lee
*/
#include "config.h"
#include
#ifdef GEGL_CHANT_PROPERTIES
gegl_chant_double (tile_size, _("Tile size"), 0.0, 256.0, 10.0,
_("Tile size"))
gegl_chant_double (tile_saturation, _("Tile saturation"), 0.0, 10.0, 2.5,
_("Tile saturation"))
gegl_chant_int (seed, _("Seed"), 0 , G_MAXINT, 1,
_("Random seed"))
# else
#define GEGL_CHANT_TYPE_AREA_FILTER
#define GEGL_CHANT_C_FILE "cubism.c"
#include "gegl-chant.h"
#include
#include
#define SCALE_WIDTH 125
#define BLACK 0
#define BG 1
#define SUPERSAMPLE 4
#define MAX_POINTS 4
#define RANDOMNESS 5
typedef struct
{
gint x,y;
} Vector2;
typedef struct
{
gint npts;
Vector2 pts[MAX_POINTS];
} Polygon;
static void prepare (GeglOperation *operation)
{
GeglChantO *o = GEGL_CHANT_PROPERTIES (operation);
GeglOperationAreaFilter *op_area = GEGL_OPERATION_AREA_FILTER (operation);
gint tmp;
/*
* Calculate the needed extension for the ROI
* MAX (o->tile_size +
* g_rand_double_range (gr, 0, o->tile_size / 4.0) -
* o->tile_size / 8.0) * o->tile_saturation)
*/
tmp = ceil ((9 * o->tile_size / 8.0) * o->tile_saturation);
op_area->left = op_area->right = op_area->top = op_area->bottom = tmp;
gegl_operation_set_format (operation, "input", babl_format ("RGBA float"));
gegl_operation_set_format (operation, "output", babl_format ("RGBA float"));
}
static inline gdouble
calc_alpha_blend (gdouble *vec,
gdouble one_over_dist,
gdouble x,
gdouble y)
{
gdouble r;
if (! one_over_dist)
return 1.0;
r = (vec[0] * x + vec[1] * y) * one_over_dist;
return CLAMP (r, 0.2, 1.0);
}
static void
convert_segment (gint x1,
gint y1,
gint x2,
gint y2,
gint offset,
gint *min,
gint *max)
{
gint ydiff, y, tmp;
gdouble xinc, xstart;
if (y1 > y2)
{
tmp = y2; y2 = y1; y1 = tmp;
tmp = x2; x2 = x1; x1 = tmp;
}
ydiff = (y2 - y1);
if (ydiff)
{
xinc = (gdouble) (x2 - x1) / (gdouble) ydiff;
xstart = x1 + 0.5 * xinc;
for (y = y1 ; y < y2; y++)
{
if (xstart < min[y - offset])
min[y-offset] = xstart;
if (xstart > max[y - offset])
max[y-offset] = xstart;
xstart += xinc;
}
}
}
static void
randomize_indices (gint count,
gint *indices,
GRand *gr)
{
gint i;
gint index1, index2;
gint tmp;
for (i = 0; i < count * RANDOMNESS; i++)
{
index1 = g_rand_int_range (gr, 0, count);
index2 = g_rand_int_range (gr, 0, count);
tmp = indices[index1];
indices[index1] = indices[index2];
indices[index2] = tmp;
}
}
static void
polygon_add_point (Polygon *poly,
gdouble x,
gdouble y)
{
if (poly->npts < MAX_POINTS)
{
poly->pts[poly->npts].x = x;
poly->pts[poly->npts].y = y;
poly->npts++;
}
else
g_print ("Unable to add additional point.\n");
}
static void
polygon_rotate (Polygon *poly,
gdouble theta)
{
gint i;
gdouble ct, st;
gdouble ox, oy;
ct = cos (theta);
st = sin (theta);
for (i = 0; i < poly->npts; i++)
{
ox = poly->pts[i].x;
oy = poly->pts[i].y;
poly->pts[i].x = ct * ox - st * oy;
poly->pts[i].y = st * ox + ct * oy;
}
}
static gint
polygon_extents (Polygon *poly,
gdouble *x1,
gdouble *y1,
gdouble *x2,
gdouble *y2)
{
gint i;
if (!poly->npts)
return 0;
*x1 = *x2 = poly->pts[0].x;
*y1 = *y2 = poly->pts[0].y;
for (i = 1; i < poly->npts; i++)
{
if (poly->pts[i].x < *x1)
*x1 = poly->pts[i].x;
if (poly->pts[i].x > *x2)
*x2 = poly->pts[i].x;
if (poly->pts[i].y < *y1)
*y1 = poly->pts[i].y;
if (poly->pts[i].y > *y2)
*y2 = poly->pts[i].y;
}
return 1;
}
static void
polygon_translate (Polygon *poly,
gdouble tx,
gdouble ty)
{
gint i;
for (i = 0; i < poly->npts; i++)
{
poly->pts[i].x += tx;
poly->pts[i].y += ty;
}
}
static void
polygon_reset (Polygon *poly)
{
poly->npts = 0;
}
static void
fill_poly_color (Polygon *poly,
const GeglRectangle *extended,
const GeglRectangle *boundary,
gfloat *dst_buf,
gfloat *color)
{
gdouble dmin_x = 0.0;
gdouble dmin_y = 0.0;
gdouble dmax_x = 0.0;
gdouble dmax_y = 0.0;
gint xs, ys;
gint xe, ye;
gint min_x, min_y;
gint max_x, max_y;
gint size_x, size_y;
gint *max_scanlines, *max_scanlines_iter;
gint *min_scanlines, *min_scanlines_iter;
gdouble val;
gfloat alpha;
gfloat buf[4];
gint i, j, x, y;
gdouble sx, sy;
gdouble ex, ey;
gdouble xx, yy;
gdouble vec[2];
gdouble dist, one_over_dist;
gint x1, y1, x2, y2;
gint *vals, *vals_iter, *vals_end;
gint b;
sx = poly->pts[0].x;
sy = poly->pts[0].y;
ex = poly->pts[1].x;
ey = poly->pts[1].y;
dist = sqrt (pow((ex - sx),2) + pow((ey - sy),2));
if (dist > 0.0)
{
one_over_dist = 1.0 / dist;
vec[0] = (ex - sx) * one_over_dist;
vec[1] = (ey - sy) * one_over_dist;
}
else
{
one_over_dist = 0.0;
vec[0] = 0.0;
vec[1] = 0.0;
}
x1 = boundary->x;
y1 = boundary->y;
x2 = boundary->x + boundary->width;
y2 = boundary->y + boundary->height;
polygon_extents (poly, &dmin_x, &dmin_y, &dmax_x, &dmax_y);
min_x = (gint) dmin_x;
min_y = (gint) dmin_y;
max_x = (gint) dmax_x;
max_y = (gint) dmax_y;
size_y = (max_y - min_y) * SUPERSAMPLE;
size_x = (max_x - min_x) * SUPERSAMPLE;
min_scanlines = min_scanlines_iter = g_new0 (gint, size_y);
max_scanlines = max_scanlines_iter = g_new0 (gint, size_y);
for (i = 0; i < size_y; i++)
{
min_scanlines[i] = max_x * SUPERSAMPLE;
max_scanlines[i] = min_x * SUPERSAMPLE;
}
if (poly->npts)
{
Vector2 *curptr;
gint poly_npts = poly->npts;
xs = (gint) (poly->pts[poly_npts-1].x);
ys = (gint) (poly->pts[poly_npts-1].y);
xe = (gint) poly->pts[0].x;
ye = (gint) poly->pts[0].y;
xs *= SUPERSAMPLE;
ys *= SUPERSAMPLE;
xe *= SUPERSAMPLE;
ye *= SUPERSAMPLE;
convert_segment (xs, ys, xe, ye, min_y * SUPERSAMPLE,
min_scanlines, max_scanlines);
for (i = 1, curptr = &poly->pts[0]; i < poly_npts; i++)
{
xs = (gint) curptr->x;
ys = (gint) curptr->y;
curptr++;
xe = (gint) curptr->x;
ye = (gint) curptr->y;
xs *= SUPERSAMPLE;
ys *= SUPERSAMPLE;
xe *= SUPERSAMPLE;
ye *= SUPERSAMPLE;
convert_segment (xs, ys, xe, ye, min_y * SUPERSAMPLE,
min_scanlines, max_scanlines);
}
}
vals = g_new0 (gint, size_x);
for (i = 0; i < size_y; i++, min_scanlines_iter++, max_scanlines_iter++)
{
if (! (i % SUPERSAMPLE))
{
memset (vals, 0, sizeof (gint) * size_x);
}
yy = (gdouble)i / (gdouble)SUPERSAMPLE + min_y;
for (j = *min_scanlines_iter; j < *max_scanlines_iter; j++)
{
x = j - min_x * SUPERSAMPLE;
vals[x] += 1;
}
if (! ((i + 1) % SUPERSAMPLE))
{
y = (i / SUPERSAMPLE) + min_y;
if (y >= y1 && y < y2)
{
for (j = 0; j < size_x; j += SUPERSAMPLE)
{
x = (j / SUPERSAMPLE) + min_x;
if (x >= x1 && x < x2)
{
for (val = 0, vals_iter = &vals[j],
vals_end = &vals_iter[SUPERSAMPLE];
vals_iter < vals_end;
vals_iter++)
val += *vals_iter;
val /= pow((SUPERSAMPLE),2);
if (val > 0)
{
xx = (gdouble) j / (gdouble) SUPERSAMPLE + min_x;
alpha = (gfloat) (val * calc_alpha_blend (vec,
one_over_dist,
xx - sx,
yy - sy));
for (b = 0; b < 4; b++)
buf[b] = dst_buf[( (y-extended->y) * extended->width
+ (x-extended->x)) * 4 + b];
for (b = 0; b < 4; b++)
buf[b] = (color[b] * alpha) + (buf[b] * (1 - alpha));
for (b = 0; b < 4; b++)
dst_buf[((y-extended->y) * extended->width +
(x - extended->x)) * 4 + b] = buf[b];
}
}
}
}
}
}
g_free (vals);
g_free (min_scanlines);
g_free (max_scanlines);
}
static GeglRectangle
get_effective_area (GeglOperation *operation)
{
GeglRectangle result = {0,0,0,0};
GeglRectangle *in_rect = gegl_operation_source_get_bounding_box (operation, "input");
gegl_rectangle_copy(&result, in_rect);
return result;
}
static gboolean
process (GeglOperation *operation,
GeglBuffer *input,
GeglBuffer *output,
const GeglRectangle *result,
gint level)
{
GeglChantO *o = GEGL_CHANT_PROPERTIES (operation);
GeglOperationAreaFilter *op_area = GEGL_OPERATION_AREA_FILTER (operation);
GeglRectangle boundary = get_effective_area (operation);
GeglRectangle extended;
const Babl *format = babl_format ("RGBA float");
GRand *gr = g_rand_new_with_seed (o->seed);
gfloat color[4];
gint cols, rows, num_tiles, count;
gint *random_indices;
gfloat *dst_buf;
Polygon poly;
gint i;
extended.x = CLAMP (result->x - op_area->left, boundary.x, boundary.x +
boundary.width);
extended.width = CLAMP (result->width + op_area->left + op_area->right, 0,
boundary.width);
extended.y = CLAMP (result->y - op_area->top, boundary.y, boundary.y +
boundary.width);
extended.height = CLAMP (result->height + op_area->top + op_area->bottom, 0,
boundary.height);
dst_buf = g_new0 (gfloat, extended.width * extended.height * 4);
cols = (result->width + o->tile_size - 1) / o->tile_size;
rows = (result->height + o->tile_size - 1) / o->tile_size;
num_tiles = (rows + 1) * (cols + 1);
random_indices = g_new0 (gint, num_tiles);
for (i = 0; i < num_tiles; i++)
random_indices[i] = i;
randomize_indices (num_tiles, random_indices, gr);
for (count = 0; count < num_tiles; count++)
{
gint i, j, ix, iy;
gdouble x, y, width, height, theta;
i = random_indices[count] / (cols + 1);
j = random_indices[count] % (cols + 1);
x = j * o->tile_size + (o->tile_size / 4.0)
- g_rand_double_range (gr, 0, (o->tile_size /2.0)) + result->x;
y = i * o->tile_size + (o->tile_size / 4.0)
- g_rand_double_range (gr, 0, (o->tile_size /2.0)) + result->y;
width = (o->tile_size +
g_rand_double_range (gr, -o->tile_size / 8.0, o->tile_size / 8.0))
* o->tile_saturation;
height = (o->tile_size +
g_rand_double_range (gr, -o->tile_size / 8.0, o->tile_size / 8.0))
* o->tile_saturation;
theta = g_rand_double_range (gr, 0, 2 * G_PI);
polygon_reset (&poly);
polygon_add_point (&poly, -width / 2.0, -height / 2.0);
polygon_add_point (&poly, width / 2.0, -height / 2.0);
polygon_add_point (&poly, width / 2.0, height / 2.0);
polygon_add_point (&poly, -width / 2.0, height / 2.0);
polygon_rotate (&poly, theta);
polygon_translate (&poly, x, y);
ix = CLAMP (x, boundary.x, boundary.x + boundary.width - 1);
iy = CLAMP (y, boundary.y, boundary.y + boundary.height - 1);
gegl_buffer_sample (input, ix, iy, NULL, color, format,
GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
fill_poly_color (&poly, &extended, &boundary, dst_buf, color);
}
gegl_buffer_set (output, &extended, 0, format, dst_buf, GEGL_AUTO_ROWSTRIDE);
g_free (dst_buf);
g_free (random_indices);
g_free (gr);
return TRUE;
}
static GeglRectangle
get_bounding_box (GeglOperation *operation)
{
GeglRectangle result = {0,0,0,0};
GeglRectangle *in_rect = gegl_operation_source_get_bounding_box (operation, "input");
if (!in_rect){
return result;
}
gegl_rectangle_copy(&result, in_rect);
#ifdef TRACE
g_warning ("< get_bounding_box result = %dx%d+%d+%d", result.width, result.height, result.x, result.y);
#endif
return result;
}
/* Compute the input rectangle required to compute the specified region of interest (roi).
*/
static GeglRectangle
get_required_for_output (GeglOperation *operation,
const gchar *input_pad,
const GeglRectangle *roi)
{
GeglRectangle result = get_effective_area (operation);
#ifdef TRACE
g_warning ("> get_required_for_output src=%dx%d+%d+%d", result.width, result.height, result.x, result.y);
if (roi)
g_warning (" ROI == %dx%d+%d+%d", roi->width, roi->height, roi->x, roi->y);
#endif
return result;
}
static GeglRectangle
get_cached_region (GeglOperation *operation,
const GeglRectangle *roi)
{
return *gegl_operation_source_get_bounding_box (operation, "input");
}
static void
gegl_chant_class_init (GeglChantClass *klass)
{
GeglOperationClass *operation_class;
GeglOperationFilterClass *filter_class;
operation_class = GEGL_OPERATION_CLASS (klass);
filter_class = GEGL_OPERATION_FILTER_CLASS (klass);
filter_class->process = process;
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_cached_region = get_cached_region;
gegl_operation_class_set_keys (operation_class,
"categories", "artistic",
"name", "gegl:cubism",
"description", _("A filter that somehow resembles a cubist painting style"),
NULL);
}
#endif