833 lines
25 KiB
C
833 lines
25 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* 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 3 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
|
#include <gegl.h>
|
|
|
|
#include "libpikamath/pikamath.h"
|
|
|
|
#include "paint-types.h"
|
|
|
|
#include "operations/layer-modes/pika-layer-modes.h"
|
|
|
|
#include "gegl/pika-gegl-utils.h"
|
|
|
|
#include "core/pika-palettes.h"
|
|
#include "core/pikadrawable.h"
|
|
#include "core/pikaimage.h"
|
|
#include "core/pikaimage-undo.h"
|
|
#include "core/pikapickable.h"
|
|
#include "core/pikasymmetry.h"
|
|
#include "core/pikatempbuf.h"
|
|
|
|
#include "pikainkoptions.h"
|
|
#include "pikaink.h"
|
|
#include "pikaink-blob.h"
|
|
#include "pikainkundo.h"
|
|
|
|
#include "pika-intl.h"
|
|
|
|
|
|
#define SUBSAMPLE 8
|
|
|
|
|
|
/* local function prototypes */
|
|
|
|
static void pika_ink_finalize (GObject *object);
|
|
|
|
static void pika_ink_paint (PikaPaintCore *paint_core,
|
|
GList *drawables,
|
|
PikaPaintOptions *paint_options,
|
|
PikaSymmetry *sym,
|
|
PikaPaintState paint_state,
|
|
guint32 time);
|
|
static GeglBuffer * pika_ink_get_paint_buffer (PikaPaintCore *paint_core,
|
|
PikaDrawable *drawable,
|
|
PikaPaintOptions *paint_options,
|
|
PikaLayerMode paint_mode,
|
|
const PikaCoords *coords,
|
|
gint *paint_buffer_x,
|
|
gint *paint_buffer_y,
|
|
gint *paint_width,
|
|
gint *paint_height);
|
|
static PikaUndo * pika_ink_push_undo (PikaPaintCore *core,
|
|
PikaImage *image,
|
|
const gchar *undo_desc);
|
|
|
|
static void pika_ink_motion (PikaPaintCore *paint_core,
|
|
PikaDrawable *drawable,
|
|
PikaPaintOptions *paint_options,
|
|
PikaSymmetry *sym,
|
|
guint32 time);
|
|
|
|
static PikaBlob * ink_pen_ellipse (PikaInkOptions *options,
|
|
gdouble x_center,
|
|
gdouble y_center,
|
|
gdouble pressure,
|
|
gdouble xtilt,
|
|
gdouble ytilt,
|
|
gdouble velocity,
|
|
const PikaMatrix3 *transform);
|
|
|
|
static void render_blob (GeglBuffer *buffer,
|
|
GeglRectangle *rect,
|
|
PikaBlob *blob);
|
|
|
|
|
|
G_DEFINE_TYPE (PikaInk, pika_ink, PIKA_TYPE_PAINT_CORE)
|
|
|
|
#define parent_class pika_ink_parent_class
|
|
|
|
|
|
void
|
|
pika_ink_register (Pika *pika,
|
|
PikaPaintRegisterCallback callback)
|
|
{
|
|
(* callback) (pika,
|
|
PIKA_TYPE_INK,
|
|
PIKA_TYPE_INK_OPTIONS,
|
|
"pika-ink",
|
|
_("Ink"),
|
|
"pika-tool-ink");
|
|
}
|
|
|
|
static void
|
|
pika_ink_class_init (PikaInkClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
PikaPaintCoreClass *paint_core_class = PIKA_PAINT_CORE_CLASS (klass);
|
|
|
|
object_class->finalize = pika_ink_finalize;
|
|
|
|
paint_core_class->paint = pika_ink_paint;
|
|
paint_core_class->get_paint_buffer = pika_ink_get_paint_buffer;
|
|
paint_core_class->push_undo = pika_ink_push_undo;
|
|
}
|
|
|
|
static void
|
|
pika_ink_init (PikaInk *ink)
|
|
{
|
|
}
|
|
|
|
static void
|
|
pika_ink_finalize (GObject *object)
|
|
{
|
|
PikaInk *ink = PIKA_INK (object);
|
|
|
|
if (ink->start_blobs)
|
|
{
|
|
g_list_free_full (ink->start_blobs, g_free);
|
|
ink->start_blobs = NULL;
|
|
}
|
|
|
|
if (ink->last_blobs)
|
|
{
|
|
g_list_free_full (ink->last_blobs, g_free);
|
|
ink->last_blobs = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
pika_ink_paint (PikaPaintCore *paint_core,
|
|
GList *drawables,
|
|
PikaPaintOptions *paint_options,
|
|
PikaSymmetry *sym,
|
|
PikaPaintState paint_state,
|
|
guint32 time)
|
|
{
|
|
PikaInk *ink = PIKA_INK (paint_core);
|
|
PikaCoords *cur_coords;
|
|
PikaCoords last_coords;
|
|
|
|
g_return_if_fail (g_list_length (drawables) == 1);
|
|
|
|
pika_paint_core_get_last_coords (paint_core, &last_coords);
|
|
cur_coords = pika_symmetry_get_origin (sym);
|
|
|
|
switch (paint_state)
|
|
{
|
|
case PIKA_PAINT_STATE_INIT:
|
|
{
|
|
PikaContext *context = PIKA_CONTEXT (paint_options);
|
|
PikaRGB foreground;
|
|
|
|
pika_symmetry_set_stateful (sym, TRUE);
|
|
pika_context_get_foreground (context, &foreground);
|
|
pika_palettes_add_color_history (context->pika,
|
|
&foreground);
|
|
|
|
if (cur_coords->x == last_coords.x &&
|
|
cur_coords->y == last_coords.y)
|
|
{
|
|
if (ink->start_blobs)
|
|
{
|
|
g_list_free_full (ink->start_blobs, g_free);
|
|
ink->start_blobs = NULL;
|
|
}
|
|
|
|
if (ink->last_blobs)
|
|
{
|
|
g_list_free_full (ink->last_blobs, g_free);
|
|
ink->last_blobs = NULL;
|
|
}
|
|
}
|
|
else if (ink->last_blobs)
|
|
{
|
|
PikaBlob *last_blob;
|
|
GList *iter;
|
|
gint i;
|
|
|
|
if (ink->start_blobs)
|
|
{
|
|
g_list_free_full (ink->start_blobs, g_free);
|
|
ink->start_blobs = NULL;
|
|
}
|
|
|
|
/* save the start blobs of each stroke for undo otherwise */
|
|
for (iter = ink->last_blobs, i = 0; iter; iter = g_list_next (iter), i++)
|
|
{
|
|
last_blob = g_list_nth_data (ink->last_blobs, i);
|
|
|
|
ink->start_blobs = g_list_prepend (ink->start_blobs,
|
|
pika_blob_duplicate (last_blob));
|
|
}
|
|
ink->start_blobs = g_list_reverse (ink->start_blobs);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PIKA_PAINT_STATE_MOTION:
|
|
for (GList *iter = drawables; iter; iter = iter->next)
|
|
pika_ink_motion (paint_core, iter->data, paint_options, sym, time);
|
|
break;
|
|
|
|
case PIKA_PAINT_STATE_FINISH:
|
|
pika_symmetry_set_stateful (sym, FALSE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static GeglBuffer *
|
|
pika_ink_get_paint_buffer (PikaPaintCore *paint_core,
|
|
PikaDrawable *drawable,
|
|
PikaPaintOptions *paint_options,
|
|
PikaLayerMode paint_mode,
|
|
const PikaCoords *coords,
|
|
gint *paint_buffer_x,
|
|
gint *paint_buffer_y,
|
|
gint *paint_width,
|
|
gint *paint_height)
|
|
{
|
|
PikaInk *ink = PIKA_INK (paint_core);
|
|
gint x, y;
|
|
gint width, height;
|
|
gint dwidth, dheight;
|
|
gint x1, y1, x2, y2;
|
|
gint offset_change_x, offset_change_y;
|
|
PikaCoords new_coords;
|
|
GList *iter;
|
|
|
|
pika_blob_bounds (ink->cur_blob, &x, &y, &width, &height);
|
|
|
|
x1 = x / SUBSAMPLE - 1;
|
|
y1 = y / SUBSAMPLE - 1;
|
|
x2 = (x + width) / SUBSAMPLE + 2;
|
|
y2 = (y + height) / SUBSAMPLE + 2;
|
|
|
|
pika_paint_core_expand_drawable (paint_core, drawable, paint_options,
|
|
x1, x2, y1, y2,
|
|
&offset_change_x, &offset_change_y);
|
|
|
|
dwidth = pika_item_get_width (PIKA_ITEM (drawable));
|
|
dheight = pika_item_get_height (PIKA_ITEM (drawable));
|
|
|
|
if (offset_change_x || offset_change_y)
|
|
{
|
|
x += SUBSAMPLE * offset_change_x;
|
|
y += SUBSAMPLE * offset_change_y;
|
|
|
|
new_coords = *coords;
|
|
new_coords.x = coords->x + offset_change_x;
|
|
new_coords.y = coords->y + offset_change_y;
|
|
pika_symmetry_set_origin (paint_core->sym, drawable, &new_coords);
|
|
|
|
for (iter = ink->blobs_to_render; iter; iter = g_list_next (iter))
|
|
pika_blob_move (iter->data,
|
|
SUBSAMPLE * offset_change_x,
|
|
SUBSAMPLE * offset_change_y);
|
|
for (iter = ink->last_blobs; iter; iter = g_list_next (iter))
|
|
pika_blob_move (iter->data,
|
|
SUBSAMPLE * offset_change_x,
|
|
SUBSAMPLE * offset_change_y);
|
|
}
|
|
|
|
x1 = CLAMP (x / SUBSAMPLE - 1, 0, dwidth);
|
|
y1 = CLAMP (y / SUBSAMPLE - 1, 0, dheight);
|
|
x2 = CLAMP ((x + width) / SUBSAMPLE + 2, 0, dwidth);
|
|
y2 = CLAMP ((y + height) / SUBSAMPLE + 2, 0, dheight);
|
|
|
|
if (paint_width)
|
|
*paint_width = width / SUBSAMPLE + 3;
|
|
if (paint_height)
|
|
*paint_height = height / SUBSAMPLE + 3;
|
|
|
|
/* configure the canvas buffer */
|
|
if ((x2 - x1) && (y2 - y1))
|
|
{
|
|
PikaTempBuf *temp_buf;
|
|
const Babl *format;
|
|
PikaLayerCompositeMode composite_mode;
|
|
|
|
composite_mode = pika_layer_mode_get_paint_composite_mode (paint_mode);
|
|
|
|
format = pika_layer_mode_get_format (paint_mode,
|
|
PIKA_LAYER_COLOR_SPACE_AUTO,
|
|
PIKA_LAYER_COLOR_SPACE_AUTO,
|
|
composite_mode,
|
|
pika_drawable_get_format (drawable));
|
|
|
|
temp_buf = pika_temp_buf_new ((x2 - x1), (y2 - y1),
|
|
format);
|
|
|
|
*paint_buffer_x = x1;
|
|
*paint_buffer_y = y1;
|
|
|
|
if (paint_core->paint_buffer)
|
|
g_object_unref (paint_core->paint_buffer);
|
|
|
|
paint_core->paint_buffer = pika_temp_buf_create_buffer (temp_buf);
|
|
|
|
pika_temp_buf_unref (temp_buf);
|
|
|
|
return paint_core->paint_buffer;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static PikaUndo *
|
|
pika_ink_push_undo (PikaPaintCore *core,
|
|
PikaImage *image,
|
|
const gchar *undo_desc)
|
|
{
|
|
return pika_image_undo_push (image, PIKA_TYPE_INK_UNDO,
|
|
PIKA_UNDO_INK, undo_desc,
|
|
0,
|
|
"paint-core", core,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
pika_ink_motion (PikaPaintCore *paint_core,
|
|
PikaDrawable *drawable,
|
|
PikaPaintOptions *paint_options,
|
|
PikaSymmetry *sym,
|
|
guint32 time)
|
|
{
|
|
PikaInk *ink = PIKA_INK (paint_core);
|
|
PikaInkOptions *options = PIKA_INK_OPTIONS (paint_options);
|
|
PikaContext *context = PIKA_CONTEXT (paint_options);
|
|
GList *blobs_to_render = NULL;
|
|
GeglBuffer *paint_buffer;
|
|
gint paint_buffer_x;
|
|
gint paint_buffer_y;
|
|
PikaLayerMode paint_mode;
|
|
PikaRGB foreground;
|
|
GeglColor *color;
|
|
PikaBlob *last_blob;
|
|
PikaCoords coords;
|
|
gint off_x, off_y;
|
|
gint n_strokes;
|
|
gint i;
|
|
|
|
pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y);
|
|
coords = *(pika_symmetry_get_origin (sym));
|
|
coords.x -= off_x;
|
|
coords.y -= off_y;
|
|
pika_symmetry_set_origin (sym, drawable, &coords);
|
|
paint_core->sym = sym;
|
|
|
|
n_strokes = pika_symmetry_get_size (sym);
|
|
|
|
if (ink->last_blobs &&
|
|
g_list_length (ink->last_blobs) != n_strokes)
|
|
{
|
|
g_list_free_full (ink->last_blobs, g_free);
|
|
ink->last_blobs = NULL;
|
|
}
|
|
|
|
if (! ink->last_blobs)
|
|
{
|
|
if (ink->start_blobs)
|
|
{
|
|
g_list_free_full (ink->start_blobs, g_free);
|
|
ink->start_blobs = NULL;
|
|
}
|
|
|
|
for (i = 0; i < n_strokes; i++)
|
|
{
|
|
PikaMatrix3 transform;
|
|
|
|
coords = *(pika_symmetry_get_coords (sym, i));
|
|
|
|
pika_symmetry_get_matrix (sym, i, &transform);
|
|
|
|
last_blob = ink_pen_ellipse (options,
|
|
coords.x,
|
|
coords.y,
|
|
coords.pressure,
|
|
coords.xtilt,
|
|
coords.ytilt,
|
|
100,
|
|
&transform);
|
|
|
|
ink->last_blobs = g_list_prepend (ink->last_blobs,
|
|
last_blob);
|
|
ink->start_blobs = g_list_prepend (ink->start_blobs,
|
|
pika_blob_duplicate (last_blob));
|
|
blobs_to_render = g_list_prepend (blobs_to_render,
|
|
pika_blob_duplicate (last_blob));
|
|
}
|
|
ink->start_blobs = g_list_reverse (ink->start_blobs);
|
|
ink->last_blobs = g_list_reverse (ink->last_blobs);
|
|
blobs_to_render = g_list_reverse (blobs_to_render);
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < n_strokes; i++)
|
|
{
|
|
PikaBlob *blob;
|
|
PikaBlob *blob_union = NULL;
|
|
PikaMatrix3 transform;
|
|
|
|
coords = *(pika_symmetry_get_coords (sym, i));
|
|
|
|
pika_symmetry_get_matrix (sym, i, &transform);
|
|
|
|
blob = ink_pen_ellipse (options,
|
|
coords.x,
|
|
coords.y,
|
|
coords.pressure,
|
|
coords.xtilt,
|
|
coords.ytilt,
|
|
coords.velocity * 100,
|
|
&transform);
|
|
|
|
last_blob = g_list_nth_data (ink->last_blobs, i);
|
|
blob_union = pika_blob_convex_union (last_blob, blob);
|
|
|
|
g_free (last_blob);
|
|
g_list_nth (ink->last_blobs, i)->data = blob;
|
|
|
|
blobs_to_render = g_list_prepend (blobs_to_render, blob_union);
|
|
}
|
|
blobs_to_render = g_list_reverse (blobs_to_render);
|
|
}
|
|
|
|
paint_mode = pika_context_get_paint_mode (context);
|
|
|
|
pika_context_get_foreground (context, &foreground);
|
|
pika_pickable_srgb_to_image_color (PIKA_PICKABLE (drawable),
|
|
&foreground, &foreground);
|
|
color = pika_gegl_color_new (&foreground, pika_drawable_get_space (drawable));
|
|
ink->blobs_to_render = blobs_to_render;
|
|
|
|
for (i = 0; i < n_strokes; i++)
|
|
{
|
|
PikaBlob *blob_to_render = g_list_nth_data (blobs_to_render, i);
|
|
|
|
coords = *(pika_symmetry_get_coords (sym, i));
|
|
|
|
ink->cur_blob = blob_to_render;
|
|
paint_buffer = pika_paint_core_get_paint_buffer (paint_core, drawable,
|
|
paint_options,
|
|
paint_mode,
|
|
&coords,
|
|
&paint_buffer_x,
|
|
&paint_buffer_y,
|
|
NULL, NULL);
|
|
ink->cur_blob = NULL;
|
|
|
|
if (! paint_buffer)
|
|
continue;
|
|
|
|
gegl_buffer_set_color (paint_buffer, NULL, color);
|
|
|
|
/* draw the blob directly to the canvas_buffer */
|
|
render_blob (paint_core->canvas_buffer,
|
|
GEGL_RECTANGLE (paint_core->paint_buffer_x,
|
|
paint_core->paint_buffer_y,
|
|
gegl_buffer_get_width (paint_core->paint_buffer),
|
|
gegl_buffer_get_height (paint_core->paint_buffer)),
|
|
blob_to_render);
|
|
|
|
/* draw the paint_area using the just rendered canvas_buffer as mask */
|
|
pika_paint_core_paste (paint_core,
|
|
NULL,
|
|
paint_core->paint_buffer_x,
|
|
paint_core->paint_buffer_y,
|
|
drawable,
|
|
PIKA_OPACITY_OPAQUE,
|
|
pika_context_get_opacity (context),
|
|
paint_mode,
|
|
PIKA_PAINT_CONSTANT);
|
|
|
|
}
|
|
|
|
g_object_unref (color);
|
|
|
|
g_list_free_full (blobs_to_render, g_free);
|
|
}
|
|
|
|
static PikaBlob *
|
|
ink_pen_ellipse (PikaInkOptions *options,
|
|
gdouble x_center,
|
|
gdouble y_center,
|
|
gdouble pressure,
|
|
gdouble xtilt,
|
|
gdouble ytilt,
|
|
gdouble velocity,
|
|
const PikaMatrix3 *transform)
|
|
{
|
|
PikaBlobFunc blob_function;
|
|
gdouble size;
|
|
gdouble tsin, tcos;
|
|
gdouble aspect, radmin;
|
|
gdouble x,y;
|
|
gdouble tscale;
|
|
gdouble tscale_c;
|
|
gdouble tscale_s;
|
|
|
|
/* Adjust the size depending on pressure. */
|
|
|
|
size = options->size * (1.0 + options->size_sensitivity *
|
|
(2.0 * pressure - 1.0));
|
|
|
|
/* Adjust the size further depending on pointer velocity and
|
|
* velocity-sensitivity. These 'magic constants' are 'feels
|
|
* natural' tigert-approved. --ADM
|
|
*/
|
|
|
|
if (velocity < 3.0)
|
|
velocity = 3.0;
|
|
|
|
#ifdef VERBOSE
|
|
g_printerr ("%g (%g) -> ", size, velocity);
|
|
#endif
|
|
|
|
size = (options->vel_sensitivity *
|
|
((4.5 * size) / (1.0 + options->vel_sensitivity * (2.0 * velocity)))
|
|
+ (1.0 - options->vel_sensitivity) * size);
|
|
|
|
#ifdef VERBOSE
|
|
g_printerr ("%g\n", (gfloat) size);
|
|
#endif
|
|
|
|
/* Clamp resulting size to sane limits */
|
|
|
|
if (size > options->size * (1.0 + options->size_sensitivity))
|
|
size = options->size * (1.0 + options->size_sensitivity);
|
|
|
|
if (size * SUBSAMPLE < 1.0)
|
|
size = 1.0 / SUBSAMPLE;
|
|
|
|
/* Add brush angle/aspect to tilt vectorially */
|
|
|
|
/* I'm not happy with the way the brush widget info is combined with
|
|
* tilt info from the brush. My personal feeling is that
|
|
* representing both as affine transforms would make the most
|
|
* sense. -RLL
|
|
*/
|
|
|
|
tscale = options->tilt_sensitivity * 10.0;
|
|
tscale_c = tscale * cos (pika_deg_to_rad (options->tilt_angle));
|
|
tscale_s = tscale * sin (pika_deg_to_rad (options->tilt_angle));
|
|
|
|
x = (options->blob_aspect * cos (options->blob_angle) +
|
|
xtilt * tscale_c - ytilt * tscale_s);
|
|
y = (options->blob_aspect * sin (options->blob_angle) +
|
|
ytilt * tscale_c + xtilt * tscale_s);
|
|
|
|
#ifdef VERBOSE
|
|
g_printerr ("angle %g aspect %g; %g %g; %g %g\n",
|
|
options->blob_angle, options->blob_aspect,
|
|
tscale_c, tscale_s, x, y);
|
|
#endif
|
|
|
|
aspect = sqrt (SQR (x) + SQR (y));
|
|
|
|
if (aspect != 0)
|
|
{
|
|
tcos = x / aspect;
|
|
tsin = y / aspect;
|
|
}
|
|
else
|
|
{
|
|
tcos = cos (options->blob_angle);
|
|
tsin = sin (options->blob_angle);
|
|
}
|
|
|
|
pika_matrix3_transform_point (transform,
|
|
tcos, tsin,
|
|
&tcos, &tsin);
|
|
|
|
aspect = CLAMP (aspect, 1.0, 10.0);
|
|
|
|
radmin = MAX (1.0, SUBSAMPLE * size / aspect);
|
|
|
|
switch (options->blob_type)
|
|
{
|
|
case PIKA_INK_BLOB_TYPE_CIRCLE:
|
|
blob_function = pika_blob_ellipse;
|
|
break;
|
|
|
|
case PIKA_INK_BLOB_TYPE_SQUARE:
|
|
blob_function = pika_blob_square;
|
|
break;
|
|
|
|
case PIKA_INK_BLOB_TYPE_DIAMOND:
|
|
blob_function = pika_blob_diamond;
|
|
break;
|
|
|
|
default:
|
|
g_return_val_if_reached (NULL);
|
|
break;
|
|
}
|
|
|
|
return (* blob_function) (x_center * SUBSAMPLE,
|
|
y_center * SUBSAMPLE,
|
|
radmin * aspect * tcos,
|
|
radmin * aspect * tsin,
|
|
-radmin * tsin,
|
|
radmin * tcos);
|
|
}
|
|
|
|
|
|
/*********************************/
|
|
/* Rendering functions */
|
|
/*********************************/
|
|
|
|
/* Some of this stuff should probably be combined with the
|
|
* code it was copied from in paint_core.c; but I wanted
|
|
* to learn this stuff, so I've kept it simple.
|
|
*
|
|
* The following only supports CONSTANT mode. Incremental
|
|
* would, I think, interact strangely with the way we
|
|
* do things. But it wouldn't be hard to implement at all.
|
|
*/
|
|
|
|
enum
|
|
{
|
|
ROW_START,
|
|
ROW_STOP
|
|
};
|
|
|
|
/* The insertion sort here, for SUBSAMPLE = 8, tends to beat out
|
|
* qsort() by 4x with CFLAGS=-O2, 2x with CFLAGS=-g
|
|
*/
|
|
static void
|
|
insert_sort (gint *data,
|
|
gint n)
|
|
{
|
|
gint i, j, k;
|
|
|
|
for (i = 2; i < 2 * n; i += 2)
|
|
{
|
|
gint tmp1 = data[i];
|
|
gint tmp2 = data[i + 1];
|
|
|
|
j = 0;
|
|
|
|
while (data[j] < tmp1)
|
|
j += 2;
|
|
|
|
for (k = i; k > j; k -= 2)
|
|
{
|
|
data[k] = data[k - 2];
|
|
data[k + 1] = data[k - 1];
|
|
}
|
|
|
|
data[j] = tmp1;
|
|
data[j + 1] = tmp2;
|
|
}
|
|
}
|
|
|
|
static void
|
|
fill_run (gfloat *dest,
|
|
gfloat alpha,
|
|
gint w)
|
|
{
|
|
if (alpha == 1.0)
|
|
{
|
|
while (w--)
|
|
{
|
|
*dest = 1.0;
|
|
dest++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (w--)
|
|
{
|
|
*dest = MAX (*dest, alpha);
|
|
dest++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
render_blob_line (PikaBlob *blob,
|
|
gfloat *dest,
|
|
gint x,
|
|
gint y,
|
|
gint width)
|
|
{
|
|
gint buf[4 * SUBSAMPLE];
|
|
gint *data = buf;
|
|
gint n = 0;
|
|
gint i, j;
|
|
gint current = 0; /* number of filled rows at this point
|
|
* in the scan line
|
|
*/
|
|
gint last_x;
|
|
|
|
/* Sort start and ends for all lines */
|
|
|
|
j = y * SUBSAMPLE - blob->y;
|
|
for (i = 0; i < SUBSAMPLE; i++)
|
|
{
|
|
if (j >= blob->height)
|
|
break;
|
|
|
|
if ((j > 0) && (blob->data[j].left <= blob->data[j].right))
|
|
{
|
|
data[2 * n] = blob->data[j].left;
|
|
data[2 * n + 1] = ROW_START;
|
|
data[2 * SUBSAMPLE + 2 * n] = blob->data[j].right;
|
|
data[2 * SUBSAMPLE + 2 * n + 1] = ROW_STOP;
|
|
n++;
|
|
}
|
|
j++;
|
|
}
|
|
|
|
/* If we have less than SUBSAMPLE rows, compress */
|
|
if (n < SUBSAMPLE)
|
|
{
|
|
for (i = 0; i < 2 * n; i++)
|
|
data[2 * n + i] = data[2 * SUBSAMPLE + i];
|
|
}
|
|
|
|
/* Now count start and end separately */
|
|
n *= 2;
|
|
|
|
insert_sort (data, n);
|
|
|
|
/* Discard portions outside of tile */
|
|
|
|
while ((n > 0) && (data[0] < SUBSAMPLE*x))
|
|
{
|
|
if (data[1] == ROW_START)
|
|
current++;
|
|
else
|
|
current--;
|
|
data += 2;
|
|
n--;
|
|
}
|
|
|
|
while ((n > 0) && (data[2*(n-1)] >= SUBSAMPLE*(x+width)))
|
|
n--;
|
|
|
|
/* Render the row */
|
|
|
|
last_x = 0;
|
|
for (i = 0; i < n;)
|
|
{
|
|
gint cur_x = data[2 * i] / SUBSAMPLE - x;
|
|
gint pixel;
|
|
|
|
/* Fill in portion leading up to this pixel */
|
|
if (current && cur_x != last_x)
|
|
fill_run (dest + last_x, (gfloat) current / SUBSAMPLE, cur_x - last_x);
|
|
|
|
/* Compute the value for this pixel */
|
|
pixel = current * SUBSAMPLE;
|
|
|
|
while (i<n)
|
|
{
|
|
gint tmp_x = data[2 * i] / SUBSAMPLE;
|
|
|
|
if (tmp_x - x != cur_x)
|
|
break;
|
|
|
|
if (data[2 * i + 1] == ROW_START)
|
|
{
|
|
current++;
|
|
pixel += ((tmp_x + 1) * SUBSAMPLE) - data[2 * i];
|
|
}
|
|
else
|
|
{
|
|
current--;
|
|
pixel -= ((tmp_x + 1) * SUBSAMPLE) - data[2 * i];
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
dest[cur_x] = MAX (dest[cur_x], (gfloat) pixel / (SUBSAMPLE * SUBSAMPLE));
|
|
|
|
last_x = cur_x + 1;
|
|
}
|
|
|
|
if (current != 0)
|
|
fill_run (dest + last_x, (gfloat) current / SUBSAMPLE, width - last_x);
|
|
}
|
|
|
|
static void
|
|
render_blob (GeglBuffer *buffer,
|
|
GeglRectangle *rect,
|
|
PikaBlob *blob)
|
|
{
|
|
GeglBufferIterator *iter;
|
|
GeglRectangle *roi;
|
|
|
|
iter = gegl_buffer_iterator_new (buffer, rect, 0, babl_format ("Y float"),
|
|
GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1);
|
|
roi = &iter->items[0].roi;
|
|
|
|
while (gegl_buffer_iterator_next (iter))
|
|
{
|
|
gfloat *d = iter->items[0].data;
|
|
gint h = roi->height;
|
|
gint y;
|
|
|
|
for (y = 0; y < h; y++, d += roi->width * 1)
|
|
{
|
|
render_blob_line (blob, d, roi->x, roi->y + y, roi->width);
|
|
}
|
|
}
|
|
}
|