Use gimp_symmetry_get_transform() instead of
gimp_symmetry_get_operation() throughout the paint code, where
possible. This allows us to combine the symmetry transform with
the ordinary brush transform, simplifying the code, improving
performance, and avoiding multiple resamplings. This also fixes
the paint-buffer size when using mandala symmetry with non-round
brushes, avoiding artifacts.
(cherry picked from commit b63af476bd)
1345 lines
47 KiB
C
1345 lines
47 KiB
C
/* GIMP - The GNU Image Manipulation Program
|
|
* 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 "libgimpmath/gimpmath.h"
|
|
|
|
#include "paint-types.h"
|
|
|
|
#include "operations/layer-modes/gimp-layer-modes.h"
|
|
|
|
#include "gegl/gimp-babl.h"
|
|
#include "gegl/gimp-gegl-loops.h"
|
|
|
|
#include "core/gimpbrush-header.h"
|
|
#include "core/gimpbrushgenerated.h"
|
|
#include "core/gimpdrawable.h"
|
|
#include "core/gimpdynamics.h"
|
|
#include "core/gimpdynamicsoutput.h"
|
|
#include "core/gimperror.h"
|
|
#include "core/gimpimage.h"
|
|
#include "core/gimpmarshal.h"
|
|
#include "core/gimpsymmetry.h"
|
|
#include "core/gimptempbuf.h"
|
|
|
|
#include "gimpbrushcore.h"
|
|
#include "gimpbrushcore-loops.h"
|
|
#include "gimpbrushcore-kernels.h"
|
|
|
|
#include "gimppaintoptions.h"
|
|
|
|
#include "gimp-intl.h"
|
|
|
|
|
|
#define EPSILON 0.00001
|
|
|
|
enum
|
|
{
|
|
SET_BRUSH,
|
|
SET_DYNAMICS,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
|
|
/* local function prototypes */
|
|
|
|
static void gimp_brush_core_finalize (GObject *object);
|
|
|
|
static gboolean gimp_brush_core_start (GimpPaintCore *core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options,
|
|
const GimpCoords *coords,
|
|
GError **error);
|
|
static gboolean gimp_brush_core_pre_paint (GimpPaintCore *core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options,
|
|
GimpPaintState paint_state,
|
|
guint32 time);
|
|
static void gimp_brush_core_post_paint (GimpPaintCore *core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options,
|
|
GimpPaintState paint_state,
|
|
guint32 time);
|
|
static void gimp_brush_core_interpolate (GimpPaintCore *core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options,
|
|
guint32 time);
|
|
|
|
static GeglBuffer * gimp_brush_core_get_paint_buffer(GimpPaintCore *paint_core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options,
|
|
GimpLayerMode paint_mode,
|
|
const GimpCoords *coords,
|
|
gint *paint_buffer_x,
|
|
gint *paint_buffer_y,
|
|
gint *paint_width,
|
|
gint *paint_height);
|
|
|
|
static void gimp_brush_core_real_set_brush (GimpBrushCore *core,
|
|
GimpBrush *brush);
|
|
static void gimp_brush_core_real_set_dynamics (GimpBrushCore *core,
|
|
GimpDynamics *dynamics);
|
|
|
|
static gdouble gimp_brush_core_get_angle (GimpBrushCore *core);
|
|
static gboolean gimp_brush_core_get_reflect (GimpBrushCore *core);
|
|
|
|
static const GimpTempBuf *
|
|
gimp_brush_core_transform_mask (GimpBrushCore *core,
|
|
GimpBrush *brush);
|
|
|
|
static void gimp_brush_core_invalidate_cache (GimpBrush *brush,
|
|
GimpBrushCore *core);
|
|
|
|
|
|
G_DEFINE_TYPE (GimpBrushCore, gimp_brush_core, GIMP_TYPE_PAINT_CORE)
|
|
|
|
#define parent_class gimp_brush_core_parent_class
|
|
|
|
static guint core_signals[LAST_SIGNAL] = { 0, };
|
|
|
|
|
|
static void
|
|
gimp_brush_core_class_init (GimpBrushCoreClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
|
|
|
|
core_signals[SET_BRUSH] =
|
|
g_signal_new ("set-brush",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GimpBrushCoreClass, set_brush),
|
|
NULL, NULL,
|
|
gimp_marshal_VOID__OBJECT,
|
|
G_TYPE_NONE, 1,
|
|
GIMP_TYPE_BRUSH);
|
|
|
|
core_signals[SET_DYNAMICS] =
|
|
g_signal_new ("set-dynamics",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GimpBrushCoreClass, set_dynamics),
|
|
NULL, NULL,
|
|
gimp_marshal_VOID__OBJECT,
|
|
G_TYPE_NONE, 1,
|
|
GIMP_TYPE_DYNAMICS);
|
|
|
|
object_class->finalize = gimp_brush_core_finalize;
|
|
|
|
paint_core_class->start = gimp_brush_core_start;
|
|
paint_core_class->pre_paint = gimp_brush_core_pre_paint;
|
|
paint_core_class->post_paint = gimp_brush_core_post_paint;
|
|
paint_core_class->interpolate = gimp_brush_core_interpolate;
|
|
paint_core_class->get_paint_buffer = gimp_brush_core_get_paint_buffer;
|
|
|
|
klass->handles_changing_brush = FALSE;
|
|
klass->handles_transforming_brush = TRUE;
|
|
klass->handles_dynamic_transforming_brush = TRUE;
|
|
|
|
klass->set_brush = gimp_brush_core_real_set_brush;
|
|
klass->set_dynamics = gimp_brush_core_real_set_dynamics;
|
|
}
|
|
|
|
static void
|
|
gimp_brush_core_init (GimpBrushCore *core)
|
|
{
|
|
gint i, j;
|
|
|
|
core->main_brush = NULL;
|
|
core->brush = NULL;
|
|
core->dynamics = NULL;
|
|
core->spacing = 1.0;
|
|
core->scale = 1.0;
|
|
core->angle = 0.0;
|
|
core->reflect = FALSE;
|
|
core->hardness = 1.0;
|
|
core->aspect_ratio = 0.0;
|
|
|
|
core->symmetry_angle = 0.0;
|
|
core->symmetry_reflect = FALSE;
|
|
|
|
core->pressure_brush = NULL;
|
|
|
|
core->last_solid_brush_mask = NULL;
|
|
core->solid_cache_invalid = FALSE;
|
|
|
|
core->transform_brush = NULL;
|
|
core->transform_pixmap = NULL;
|
|
|
|
core->last_subsample_brush_mask = NULL;
|
|
core->subsample_cache_invalid = FALSE;
|
|
|
|
core->rand = g_rand_new ();
|
|
|
|
for (i = 0; i < BRUSH_CORE_SOLID_SUBSAMPLE; i++)
|
|
{
|
|
for (j = 0; j < BRUSH_CORE_SOLID_SUBSAMPLE; j++)
|
|
{
|
|
core->solid_brushes[i][j] = NULL;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < BRUSH_CORE_JITTER_LUTSIZE - 1; ++i)
|
|
{
|
|
core->jitter_lut_y[i] = cos (gimp_deg_to_rad (i * 360 /
|
|
BRUSH_CORE_JITTER_LUTSIZE));
|
|
core->jitter_lut_x[i] = sin (gimp_deg_to_rad (i * 360 /
|
|
BRUSH_CORE_JITTER_LUTSIZE));
|
|
}
|
|
|
|
gimp_assert (BRUSH_CORE_SUBSAMPLE == KERNEL_SUBSAMPLE);
|
|
|
|
for (i = 0; i < KERNEL_SUBSAMPLE + 1; i++)
|
|
{
|
|
for (j = 0; j < KERNEL_SUBSAMPLE + 1; j++)
|
|
{
|
|
core->subsample_brushes[i][j] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_brush_core_finalize (GObject *object)
|
|
{
|
|
GimpBrushCore *core = GIMP_BRUSH_CORE (object);
|
|
gint i, j;
|
|
|
|
g_clear_pointer (&core->pressure_brush, gimp_temp_buf_unref);
|
|
|
|
for (i = 0; i < BRUSH_CORE_SOLID_SUBSAMPLE; i++)
|
|
for (j = 0; j < BRUSH_CORE_SOLID_SUBSAMPLE; j++)
|
|
g_clear_pointer (&core->solid_brushes[i][j], gimp_temp_buf_unref);
|
|
|
|
g_clear_pointer (&core->rand, g_rand_free);
|
|
|
|
for (i = 0; i < KERNEL_SUBSAMPLE + 1; i++)
|
|
for (j = 0; j < KERNEL_SUBSAMPLE + 1; j++)
|
|
g_clear_pointer (&core->subsample_brushes[i][j], gimp_temp_buf_unref);
|
|
|
|
if (core->main_brush)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (core->main_brush,
|
|
gimp_brush_core_invalidate_cache,
|
|
core);
|
|
gimp_brush_end_use (core->main_brush);
|
|
g_clear_object (&core->main_brush);
|
|
}
|
|
|
|
g_clear_object (&core->dynamics);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static gboolean
|
|
gimp_brush_core_pre_paint (GimpPaintCore *paint_core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options,
|
|
GimpPaintState paint_state,
|
|
guint32 time)
|
|
{
|
|
GimpBrushCore *core = GIMP_BRUSH_CORE (paint_core);
|
|
|
|
if (paint_state == GIMP_PAINT_STATE_MOTION)
|
|
{
|
|
GimpCoords last_coords;
|
|
GimpCoords current_coords;
|
|
gdouble scale;
|
|
|
|
gimp_paint_core_get_last_coords (paint_core, &last_coords);
|
|
gimp_paint_core_get_current_coords (paint_core, ¤t_coords);
|
|
|
|
/* If we current point == last point, check if the brush
|
|
* wants to be painted in that case. (Direction dependent
|
|
* pixmap brush pipes don't, as they don't know which
|
|
* pixmap to select.)
|
|
*/
|
|
if (last_coords.x == current_coords.x &&
|
|
last_coords.y == current_coords.y &&
|
|
! gimp_brush_want_null_motion (core->main_brush,
|
|
&last_coords,
|
|
¤t_coords))
|
|
{
|
|
return FALSE;
|
|
}
|
|
/*No drawing anything if the scale is too small*/
|
|
if (GIMP_BRUSH_CORE_GET_CLASS (core)->handles_transforming_brush)
|
|
{
|
|
GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
|
|
gdouble fade_point;
|
|
|
|
if (GIMP_BRUSH_CORE_GET_CLASS (core)->handles_dynamic_transforming_brush)
|
|
{
|
|
gdouble width;
|
|
gdouble height;
|
|
|
|
fade_point = gimp_paint_options_get_fade (paint_options, image,
|
|
paint_core->pixel_dist);
|
|
width = gimp_brush_get_width (core->main_brush);
|
|
height = gimp_brush_get_height (core->main_brush);
|
|
|
|
scale = paint_options->brush_size /
|
|
MAX (width, height) *
|
|
gimp_dynamics_get_linear_value (core->dynamics,
|
|
GIMP_DYNAMICS_OUTPUT_SIZE,
|
|
¤t_coords,
|
|
paint_options,
|
|
fade_point);
|
|
|
|
if (paint_options->brush_lock_to_view &&
|
|
MAX (current_coords.xscale, current_coords.yscale) > 0)
|
|
{
|
|
scale /= MAX (current_coords.xscale, current_coords.yscale);
|
|
|
|
/* Cap transform result for brushes or OOM can occur */
|
|
if ((scale * MAX (width, height)) > GIMP_BRUSH_MAX_SIZE)
|
|
{
|
|
scale = GIMP_BRUSH_MAX_SIZE / MAX (width, height);
|
|
}
|
|
}
|
|
|
|
if (scale < 0.0000001)
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
if (GIMP_BRUSH_CORE_GET_CLASS (paint_core)->handles_changing_brush)
|
|
{
|
|
core->brush = gimp_brush_select_brush (core->main_brush,
|
|
&last_coords,
|
|
¤t_coords);
|
|
}
|
|
if ((! GIMP_IS_BRUSH_GENERATED(core->main_brush)) &&
|
|
(paint_options->brush_hardness != gimp_brush_get_blur_hardness(core->main_brush)))
|
|
{
|
|
gimp_brush_flush_blur_caches(core->main_brush);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gimp_brush_core_post_paint (GimpPaintCore *paint_core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options,
|
|
GimpPaintState paint_state,
|
|
guint32 time)
|
|
{
|
|
GimpBrushCore *core = GIMP_BRUSH_CORE (paint_core);
|
|
|
|
if (paint_state == GIMP_PAINT_STATE_MOTION)
|
|
{
|
|
core->brush = core->main_brush;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gimp_brush_core_start (GimpPaintCore *paint_core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options,
|
|
const GimpCoords *coords,
|
|
GError **error)
|
|
{
|
|
GimpBrushCore *core = GIMP_BRUSH_CORE (paint_core);
|
|
GimpContext *context = GIMP_CONTEXT (paint_options);
|
|
|
|
gimp_brush_core_set_brush (core, gimp_context_get_brush (context));
|
|
|
|
gimp_brush_core_set_dynamics (core, gimp_context_get_dynamics (context));
|
|
|
|
if (! core->main_brush)
|
|
{
|
|
g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
|
|
_("No brushes available for use with this tool."));
|
|
return FALSE;
|
|
}
|
|
|
|
if (! core->dynamics)
|
|
{
|
|
g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
|
|
_("No paint dynamics available for use with this tool."));
|
|
return FALSE;
|
|
}
|
|
|
|
if (GIMP_BRUSH_CORE_GET_CLASS (core)->handles_transforming_brush)
|
|
{
|
|
gimp_brush_core_eval_transform_dynamics (core,
|
|
drawable,
|
|
paint_options,
|
|
coords);
|
|
|
|
gimp_brush_core_eval_transform_symmetry (core, NULL, 0);
|
|
}
|
|
|
|
core->spacing = paint_options->brush_spacing;
|
|
|
|
core->brush = core->main_brush;
|
|
|
|
core->jitter =
|
|
gimp_paint_options_get_jitter (paint_options,
|
|
gimp_item_get_image (GIMP_ITEM (drawable)));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gimp_avoid_exact_integer
|
|
* @x: points to a gdouble
|
|
*
|
|
* Adjusts *x such that it is not too close to an integer. This is used
|
|
* for decision algorithms that would be vulnerable to rounding glitches
|
|
* if exact integers were input.
|
|
*
|
|
* Side effects: Changes the value of *x
|
|
**/
|
|
static void
|
|
gimp_avoid_exact_integer (gdouble *x)
|
|
{
|
|
const gdouble integral = floor (*x);
|
|
const gdouble fractional = *x - integral;
|
|
|
|
if (fractional < EPSILON)
|
|
{
|
|
*x = integral + EPSILON;
|
|
}
|
|
else if (fractional > (1 -EPSILON))
|
|
{
|
|
*x = integral + (1 - EPSILON);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_brush_core_interpolate (GimpPaintCore *paint_core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options,
|
|
guint32 time)
|
|
{
|
|
GimpBrushCore *core = GIMP_BRUSH_CORE (paint_core);
|
|
GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
|
|
GimpDynamicsOutput *spacing_output;
|
|
GimpCoords last_coords;
|
|
GimpCoords current_coords;
|
|
GimpVector2 delta_vec;
|
|
gdouble delta_pressure;
|
|
gdouble delta_xtilt, delta_ytilt;
|
|
gdouble delta_wheel;
|
|
gdouble delta_velocity;
|
|
gdouble temp_direction;
|
|
GimpVector2 temp_vec;
|
|
gint n, num_points;
|
|
gdouble t0, dt, tn;
|
|
gdouble st_factor, st_offset;
|
|
gdouble initial;
|
|
gdouble dist;
|
|
gdouble total;
|
|
gdouble pixel_dist;
|
|
gdouble pixel_initial;
|
|
gdouble xd, yd;
|
|
gdouble mag;
|
|
gdouble dyn_spacing = core->spacing;
|
|
gdouble fade_point;
|
|
gboolean use_dyn_spacing;
|
|
|
|
g_return_if_fail (GIMP_IS_BRUSH (core->brush));
|
|
|
|
gimp_paint_core_get_last_coords (paint_core, &last_coords);
|
|
gimp_paint_core_get_current_coords (paint_core, ¤t_coords);
|
|
|
|
gimp_avoid_exact_integer (&last_coords.x);
|
|
gimp_avoid_exact_integer (&last_coords.y);
|
|
gimp_avoid_exact_integer (¤t_coords.x);
|
|
gimp_avoid_exact_integer (¤t_coords.y);
|
|
|
|
delta_vec.x = current_coords.x - last_coords.x;
|
|
delta_vec.y = current_coords.y - last_coords.y;
|
|
delta_pressure = current_coords.pressure - last_coords.pressure;
|
|
delta_xtilt = current_coords.xtilt - last_coords.xtilt;
|
|
delta_ytilt = current_coords.ytilt - last_coords.ytilt;
|
|
delta_wheel = current_coords.wheel - last_coords.wheel;
|
|
delta_velocity = current_coords.velocity - last_coords.velocity;
|
|
temp_direction = current_coords.direction;
|
|
|
|
/* return if there has been no motion */
|
|
if (! delta_vec.x &&
|
|
! delta_vec.y &&
|
|
! delta_pressure &&
|
|
! delta_xtilt &&
|
|
! delta_ytilt &&
|
|
! delta_wheel &&
|
|
! delta_velocity)
|
|
return;
|
|
|
|
pixel_dist = gimp_vector2_length (&delta_vec);
|
|
pixel_initial = paint_core->pixel_dist;
|
|
|
|
/* Zero sized brushes are unfit for interpolate, so we just let
|
|
* paint core fail on its own
|
|
*/
|
|
if (core->scale == 0.0)
|
|
{
|
|
gimp_paint_core_set_last_coords (paint_core, ¤t_coords);
|
|
|
|
gimp_paint_core_paint (paint_core, drawable, paint_options,
|
|
GIMP_PAINT_STATE_MOTION, time);
|
|
|
|
paint_core->pixel_dist = pixel_initial + pixel_dist; /* Don't forget to update pixel distance*/
|
|
|
|
return;
|
|
}
|
|
|
|
/* Handle dynamic spacing */
|
|
spacing_output = gimp_dynamics_get_output (core->dynamics,
|
|
GIMP_DYNAMICS_OUTPUT_SPACING);
|
|
|
|
fade_point = gimp_paint_options_get_fade (paint_options, image,
|
|
paint_core->pixel_dist);
|
|
|
|
use_dyn_spacing = gimp_dynamics_output_is_enabled (spacing_output);
|
|
|
|
if (use_dyn_spacing)
|
|
{
|
|
dyn_spacing = gimp_dynamics_output_get_linear_value (spacing_output,
|
|
¤t_coords,
|
|
paint_options,
|
|
fade_point);
|
|
|
|
/* Dynamic spacing assumes that the value set in core is the min
|
|
* value and the max is full 200% spacing. This approach differs
|
|
* from the usual factor from user input approach because making
|
|
* spacing smaller than the nominal value is unlikely and
|
|
* spacing has a hard defined max.
|
|
*/
|
|
dyn_spacing = (core->spacing +
|
|
((2.0 - core->spacing) * (1.0 - dyn_spacing)));
|
|
|
|
/* Limiting spacing to minimum 1% */
|
|
dyn_spacing = MAX (core->spacing, dyn_spacing);
|
|
}
|
|
|
|
/* calculate the distance traveled in the coordinate space of the brush */
|
|
temp_vec = gimp_brush_get_x_axis (core->brush);
|
|
gimp_vector2_mul (&temp_vec, core->scale);
|
|
gimp_vector2_rotate (&temp_vec, core->angle * G_PI * 2);
|
|
|
|
mag = gimp_vector2_length (&temp_vec);
|
|
xd = gimp_vector2_inner_product (&delta_vec, &temp_vec) / (mag * mag);
|
|
|
|
temp_vec = gimp_brush_get_y_axis (core->brush);
|
|
gimp_vector2_mul (&temp_vec, core->scale);
|
|
gimp_vector2_rotate (&temp_vec, core->angle * G_PI * 2);
|
|
|
|
mag = gimp_vector2_length (&temp_vec);
|
|
yd = gimp_vector2_inner_product (&delta_vec, &temp_vec) / (mag * mag);
|
|
|
|
dist = 0.5 * sqrt (xd * xd + yd * yd);
|
|
total = dist + paint_core->distance;
|
|
initial = paint_core->distance;
|
|
|
|
|
|
if (delta_vec.x * delta_vec.x > delta_vec.y * delta_vec.y)
|
|
{
|
|
st_factor = delta_vec.x;
|
|
st_offset = last_coords.x - 0.5;
|
|
}
|
|
else
|
|
{
|
|
st_factor = delta_vec.y;
|
|
st_offset = last_coords.y - 0.5;
|
|
}
|
|
|
|
if (use_dyn_spacing)
|
|
{
|
|
gint s0;
|
|
|
|
num_points = dist / dyn_spacing;
|
|
|
|
s0 = (gint) floor (st_offset + 0.5);
|
|
t0 = (s0 - st_offset) / st_factor;
|
|
dt = dyn_spacing / dist;
|
|
|
|
if (num_points == 0)
|
|
return;
|
|
}
|
|
else if (fabs (st_factor) > dist / core->spacing)
|
|
{
|
|
/* The stripe principle leads to brush positions that are spaced
|
|
* *closer* than the official brush spacing. Use the official
|
|
* spacing instead. This is the common case when the brush spacing
|
|
* is large.
|
|
* The net effect is then to put a lower bound on the spacing, but
|
|
* one that varies with the slope of the line. This is suppose to
|
|
* make thin lines (say, with a 1x1 brush) prettier while leaving
|
|
* lines with larger brush spacing as they used to look in 1.2.x.
|
|
*/
|
|
|
|
dt = core->spacing / dist;
|
|
n = (gint) (initial / core->spacing + 1.0 + EPSILON);
|
|
t0 = (n * core->spacing - initial) / dist;
|
|
num_points = 1 + (gint) floor ((1 + EPSILON - t0) / dt);
|
|
|
|
/* if we arnt going to paint anything this time and the brush
|
|
* has only moved on one axis return without updating the brush
|
|
* position, distance etc. so that we can more accurately space
|
|
* brush strokes when curves are supplied to us in single pixel
|
|
* chunks.
|
|
*/
|
|
|
|
if (num_points == 0 && (delta_vec.x == 0 || delta_vec.y == 0))
|
|
return;
|
|
}
|
|
else if (fabs (st_factor) < EPSILON)
|
|
{
|
|
/* Hm, we've hardly moved at all. Don't draw anything, but reset the
|
|
* old coordinates and hope we've gone longer the next time...
|
|
*/
|
|
current_coords.x = last_coords.x;
|
|
current_coords.y = last_coords.y;
|
|
|
|
gimp_paint_core_set_current_coords (paint_core, ¤t_coords);
|
|
|
|
/* ... but go along with the current pressure, tilt and wheel */
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
gint direction = st_factor > 0 ? 1 : -1;
|
|
gint x, y;
|
|
gint s0, sn;
|
|
|
|
/* Choose the first and last stripe to paint.
|
|
* FIRST PRIORITY is to avoid gaps painting with a 1x1 aliasing
|
|
* brush when a horizontalish line segment follows a verticalish
|
|
* one or vice versa - no matter what the angle between the two
|
|
* lines is. This will also limit the local thinning that a 1x1
|
|
* subsampled brush may suffer in the same situation.
|
|
* SECOND PRIORITY is to avoid making free-hand drawings
|
|
* unpleasantly fat by plotting redundant points.
|
|
* These are achieved by the following rules, but it is a little
|
|
* tricky to see just why. Do not change this algorithm unless you
|
|
* are sure you know what you're doing!
|
|
*/
|
|
|
|
/* Basic case: round the beginning and ending point to nearest
|
|
* stripe center.
|
|
*/
|
|
s0 = (gint) floor (st_offset + 0.5);
|
|
sn = (gint) floor (st_offset + st_factor + 0.5);
|
|
|
|
t0 = (s0 - st_offset) / st_factor;
|
|
tn = (sn - st_offset) / st_factor;
|
|
|
|
x = (gint) floor (last_coords.x + t0 * delta_vec.x);
|
|
y = (gint) floor (last_coords.y + t0 * delta_vec.y);
|
|
|
|
if (t0 < 0.0 && !( x == (gint) floor (last_coords.x) &&
|
|
y == (gint) floor (last_coords.y) ))
|
|
{
|
|
/* Exception A: If the first stripe's brush position is
|
|
* EXTRApolated into a different pixel square than the
|
|
* ideal starting point, don't plot it.
|
|
*/
|
|
s0 += direction;
|
|
}
|
|
else if (x == (gint) floor (paint_core->last_paint.x) &&
|
|
y == (gint) floor (paint_core->last_paint.y))
|
|
{
|
|
/* Exception B: If first stripe's brush position is within the
|
|
* same pixel square as the last plot of the previous line,
|
|
* don't plot it either.
|
|
*/
|
|
s0 += direction;
|
|
}
|
|
|
|
x = (gint) floor (last_coords.x + tn * delta_vec.x);
|
|
y = (gint) floor (last_coords.y + tn * delta_vec.y);
|
|
|
|
if (tn > 1.0 && !( x == (gint) floor (current_coords.x) &&
|
|
y == (gint) floor (current_coords.y)))
|
|
{
|
|
/* Exception C: If the last stripe's brush position is
|
|
* EXTRApolated into a different pixel square than the
|
|
* ideal ending point, don't plot it.
|
|
*/
|
|
sn -= direction;
|
|
}
|
|
|
|
t0 = (s0 - st_offset) / st_factor;
|
|
tn = (sn - st_offset) / st_factor;
|
|
dt = direction * 1.0 / st_factor;
|
|
num_points = 1 + direction * (sn - s0);
|
|
|
|
if (num_points >= 1)
|
|
{
|
|
/* Hack the reported total distance such that it looks to the
|
|
* next line as if the the last pixel plotted were at an integer
|
|
* multiple of the brush spacing. This helps prevent artifacts
|
|
* for connected lines when the brush spacing is such that some
|
|
* slopes will use the stripe regime and other slopes will use
|
|
* the nominal brush spacing.
|
|
*/
|
|
|
|
if (tn < 1)
|
|
total = initial + tn * dist;
|
|
|
|
total = core->spacing * (gint) (total / core->spacing + 0.5);
|
|
total += (1.0 - tn) * dist;
|
|
}
|
|
}
|
|
|
|
for (n = 0; n < num_points; n++)
|
|
{
|
|
gdouble t = t0 + n * dt;
|
|
gdouble p = (gdouble) n / num_points;
|
|
|
|
current_coords.x = last_coords.x + t * delta_vec.x;
|
|
current_coords.y = last_coords.y + t * delta_vec.y;
|
|
current_coords.pressure = last_coords.pressure + p * delta_pressure;
|
|
current_coords.xtilt = last_coords.xtilt + p * delta_xtilt;
|
|
current_coords.ytilt = last_coords.ytilt + p * delta_ytilt;
|
|
current_coords.wheel = last_coords.wheel + p * delta_wheel;
|
|
current_coords.velocity = last_coords.velocity + p * delta_velocity;
|
|
current_coords.direction = temp_direction;
|
|
current_coords.xscale = last_coords.xscale;
|
|
current_coords.yscale = last_coords.yscale;
|
|
current_coords.angle = last_coords.angle;
|
|
current_coords.reflect = last_coords.reflect;
|
|
|
|
if (core->jitter > 0.0)
|
|
{
|
|
GimpVector2 x_axis;
|
|
GimpVector2 y_axis;
|
|
gdouble dyn_jitter;
|
|
gdouble jitter_dist;
|
|
gint32 jitter_angle;
|
|
|
|
x_axis = gimp_brush_get_x_axis (core->brush);
|
|
y_axis = gimp_brush_get_y_axis (core->brush);
|
|
|
|
dyn_jitter = (core->jitter *
|
|
gimp_dynamics_get_linear_value (core->dynamics,
|
|
GIMP_DYNAMICS_OUTPUT_JITTER,
|
|
¤t_coords,
|
|
paint_options,
|
|
fade_point));
|
|
|
|
jitter_dist = g_rand_double_range (core->rand, 0, dyn_jitter);
|
|
jitter_angle = g_rand_int_range (core->rand,
|
|
0, BRUSH_CORE_JITTER_LUTSIZE);
|
|
|
|
current_coords.x +=
|
|
(x_axis.x + y_axis.x) *
|
|
jitter_dist * core->jitter_lut_x[jitter_angle] * core->scale;
|
|
|
|
current_coords.y +=
|
|
(y_axis.y + x_axis.y) *
|
|
jitter_dist * core->jitter_lut_y[jitter_angle] * core->scale;
|
|
}
|
|
|
|
gimp_paint_core_set_current_coords (paint_core, ¤t_coords);
|
|
|
|
paint_core->distance = initial + t * dist;
|
|
paint_core->pixel_dist = pixel_initial + t * pixel_dist;
|
|
|
|
gimp_paint_core_paint (paint_core, drawable, paint_options,
|
|
GIMP_PAINT_STATE_MOTION, time);
|
|
}
|
|
|
|
current_coords.x = last_coords.x + delta_vec.x;
|
|
current_coords.y = last_coords.y + delta_vec.y;
|
|
current_coords.pressure = last_coords.pressure + delta_pressure;
|
|
current_coords.xtilt = last_coords.xtilt + delta_xtilt;
|
|
current_coords.ytilt = last_coords.ytilt + delta_ytilt;
|
|
current_coords.wheel = last_coords.wheel + delta_wheel;
|
|
current_coords.velocity = last_coords.velocity + delta_velocity;
|
|
current_coords.xscale = last_coords.xscale;
|
|
current_coords.yscale = last_coords.yscale;
|
|
current_coords.angle = last_coords.angle;
|
|
current_coords.reflect = last_coords.reflect;
|
|
|
|
gimp_paint_core_set_current_coords (paint_core, ¤t_coords);
|
|
gimp_paint_core_set_last_coords (paint_core, ¤t_coords);
|
|
|
|
paint_core->distance = total;
|
|
paint_core->pixel_dist = pixel_initial + pixel_dist;
|
|
}
|
|
|
|
static GeglBuffer *
|
|
gimp_brush_core_get_paint_buffer (GimpPaintCore *paint_core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options,
|
|
GimpLayerMode paint_mode,
|
|
const GimpCoords *coords,
|
|
gint *paint_buffer_x,
|
|
gint *paint_buffer_y,
|
|
gint *paint_width,
|
|
gint *paint_height)
|
|
{
|
|
GimpBrushCore *core = GIMP_BRUSH_CORE (paint_core);
|
|
gint x, y;
|
|
gint x1, y1, x2, y2;
|
|
gint drawable_width, drawable_height;
|
|
gint brush_width, brush_height;
|
|
|
|
gimp_brush_transform_size (core->brush,
|
|
core->scale, core->aspect_ratio,
|
|
gimp_brush_core_get_angle (core),
|
|
gimp_brush_core_get_reflect (core),
|
|
&brush_width, &brush_height);
|
|
|
|
if (paint_width)
|
|
*paint_width = brush_width;
|
|
if (paint_height)
|
|
*paint_height = brush_height;
|
|
|
|
/* adjust the x and y coordinates to the upper left corner of the brush */
|
|
x = (gint) floor (coords->x) - (brush_width / 2);
|
|
y = (gint) floor (coords->y) - (brush_height / 2);
|
|
|
|
drawable_width = gimp_item_get_width (GIMP_ITEM (drawable));
|
|
drawable_height = gimp_item_get_height (GIMP_ITEM (drawable));
|
|
|
|
x1 = CLAMP (x - 1, 0, drawable_width);
|
|
y1 = CLAMP (y - 1, 0, drawable_height);
|
|
x2 = CLAMP (x + brush_width + 1, 0, drawable_width);
|
|
y2 = CLAMP (y + brush_height + 1, 0, drawable_height);
|
|
|
|
/* configure the canvas buffer */
|
|
if ((x2 - x1) && (y2 - y1))
|
|
{
|
|
GimpTempBuf *temp_buf;
|
|
const Babl *format;
|
|
GimpLayerCompositeMode composite_mode;
|
|
|
|
composite_mode = gimp_layer_mode_get_paint_composite_mode (paint_mode);
|
|
|
|
format = gimp_layer_mode_get_format (paint_mode,
|
|
GIMP_LAYER_COLOR_SPACE_AUTO,
|
|
GIMP_LAYER_COLOR_SPACE_AUTO,
|
|
composite_mode,
|
|
gimp_drawable_get_format (drawable));
|
|
|
|
if (paint_core->paint_buffer &&
|
|
gegl_buffer_get_width (paint_core->paint_buffer) == (x2 - x1) &&
|
|
gegl_buffer_get_height (paint_core->paint_buffer) == (y2 - y1) &&
|
|
gegl_buffer_get_format (paint_core->paint_buffer) == format)
|
|
{
|
|
*paint_buffer_x = x1;
|
|
*paint_buffer_y = y1;
|
|
|
|
return paint_core->paint_buffer;
|
|
}
|
|
|
|
g_clear_object (&paint_core->paint_buffer);
|
|
|
|
temp_buf = gimp_temp_buf_new ((x2 - x1), (y2 - y1),
|
|
format);
|
|
|
|
*paint_buffer_x = x1;
|
|
*paint_buffer_y = y1;
|
|
|
|
paint_core->paint_buffer = gimp_temp_buf_create_buffer (temp_buf);
|
|
|
|
gimp_temp_buf_unref (temp_buf);
|
|
|
|
return paint_core->paint_buffer;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
gimp_brush_core_real_set_brush (GimpBrushCore *core,
|
|
GimpBrush *brush)
|
|
{
|
|
if (brush == core->main_brush)
|
|
return;
|
|
|
|
if (core->main_brush)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (core->main_brush,
|
|
gimp_brush_core_invalidate_cache,
|
|
core);
|
|
gimp_brush_end_use (core->main_brush);
|
|
}
|
|
|
|
g_set_object (&core->main_brush, brush);
|
|
|
|
if (core->main_brush)
|
|
{
|
|
gimp_brush_begin_use (core->main_brush);
|
|
g_signal_connect (core->main_brush, "invalidate-preview",
|
|
G_CALLBACK (gimp_brush_core_invalidate_cache),
|
|
core);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gimp_brush_core_real_set_dynamics (GimpBrushCore *core,
|
|
GimpDynamics *dynamics)
|
|
{
|
|
g_set_object (&core->dynamics, dynamics);
|
|
}
|
|
|
|
void
|
|
gimp_brush_core_set_brush (GimpBrushCore *core,
|
|
GimpBrush *brush)
|
|
{
|
|
g_return_if_fail (GIMP_IS_BRUSH_CORE (core));
|
|
g_return_if_fail (brush == NULL || GIMP_IS_BRUSH (brush));
|
|
|
|
if (brush != core->main_brush)
|
|
g_signal_emit (core, core_signals[SET_BRUSH], 0, brush);
|
|
}
|
|
|
|
void
|
|
gimp_brush_core_set_dynamics (GimpBrushCore *core,
|
|
GimpDynamics *dynamics)
|
|
{
|
|
g_return_if_fail (GIMP_IS_BRUSH_CORE (core));
|
|
g_return_if_fail (dynamics == NULL || GIMP_IS_DYNAMICS (dynamics));
|
|
|
|
if (dynamics != core->dynamics)
|
|
g_signal_emit (core, core_signals[SET_DYNAMICS], 0, dynamics);
|
|
}
|
|
|
|
void
|
|
gimp_brush_core_paste_canvas (GimpBrushCore *core,
|
|
GimpDrawable *drawable,
|
|
const GimpCoords *coords,
|
|
gdouble brush_opacity,
|
|
gdouble image_opacity,
|
|
GimpLayerMode paint_mode,
|
|
GimpBrushApplicationMode brush_hardness,
|
|
gdouble dynamic_force,
|
|
GimpPaintApplicationMode mode)
|
|
{
|
|
const GimpTempBuf *brush_mask;
|
|
|
|
brush_mask = gimp_brush_core_get_brush_mask (core, coords,
|
|
brush_hardness,
|
|
dynamic_force);
|
|
|
|
if (brush_mask)
|
|
{
|
|
GimpPaintCore *paint_core = GIMP_PAINT_CORE (core);
|
|
gint x;
|
|
gint y;
|
|
gint off_x;
|
|
gint off_y;
|
|
|
|
x = (gint) floor (coords->x) - (gimp_temp_buf_get_width (brush_mask) >> 1);
|
|
y = (gint) floor (coords->y) - (gimp_temp_buf_get_height (brush_mask) >> 1);
|
|
|
|
off_x = (x < 0) ? -x : 0;
|
|
off_y = (y < 0) ? -y : 0;
|
|
|
|
gimp_paint_core_paste (paint_core, brush_mask,
|
|
off_x, off_y,
|
|
drawable,
|
|
brush_opacity,
|
|
image_opacity,
|
|
paint_mode,
|
|
mode);
|
|
}
|
|
}
|
|
|
|
/* Similar to gimp_brush_core_paste_canvas, but replaces the alpha channel
|
|
* rather than using it to composite (i.e. transparent over opaque
|
|
* becomes transparent rather than opauqe.
|
|
*/
|
|
void
|
|
gimp_brush_core_replace_canvas (GimpBrushCore *core,
|
|
GimpDrawable *drawable,
|
|
const GimpCoords *coords,
|
|
gdouble brush_opacity,
|
|
gdouble image_opacity,
|
|
GimpBrushApplicationMode brush_hardness,
|
|
gdouble dynamic_force,
|
|
GimpPaintApplicationMode mode)
|
|
{
|
|
const GimpTempBuf *brush_mask;
|
|
|
|
brush_mask = gimp_brush_core_get_brush_mask (core, coords,
|
|
brush_hardness,
|
|
dynamic_force);
|
|
|
|
if (brush_mask)
|
|
{
|
|
GimpPaintCore *paint_core = GIMP_PAINT_CORE (core);
|
|
gint x;
|
|
gint y;
|
|
gint off_x;
|
|
gint off_y;
|
|
|
|
x = (gint) floor (coords->x) - (gimp_temp_buf_get_width (brush_mask) >> 1);
|
|
y = (gint) floor (coords->y) - (gimp_temp_buf_get_height (brush_mask) >> 1);
|
|
|
|
off_x = (x < 0) ? -x : 0;
|
|
off_y = (y < 0) ? -y : 0;
|
|
|
|
gimp_paint_core_replace (paint_core, brush_mask,
|
|
off_x, off_y,
|
|
drawable,
|
|
brush_opacity,
|
|
image_opacity,
|
|
mode);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
gimp_brush_core_invalidate_cache (GimpBrush *brush,
|
|
GimpBrushCore *core)
|
|
{
|
|
/* Make sure we don't cache data for a brush that has changed */
|
|
|
|
core->subsample_cache_invalid = TRUE;
|
|
core->solid_cache_invalid = TRUE;
|
|
|
|
/* Notify of the brush change */
|
|
|
|
g_signal_emit (core, core_signals[SET_BRUSH], 0, brush);
|
|
}
|
|
|
|
|
|
/************************************************************
|
|
* LOCAL FUNCTION DEFINITIONS *
|
|
************************************************************/
|
|
|
|
static gdouble
|
|
gimp_brush_core_get_angle (GimpBrushCore *core)
|
|
{
|
|
gdouble angle = core->angle;
|
|
|
|
if (core->reflect)
|
|
angle -= core->symmetry_angle;
|
|
else
|
|
angle += core->symmetry_angle;
|
|
|
|
angle = fmod (angle, 1.0);
|
|
|
|
if (angle < 0.0)
|
|
angle += 1.0;
|
|
|
|
return angle;
|
|
}
|
|
|
|
static gboolean
|
|
gimp_brush_core_get_reflect (GimpBrushCore *core)
|
|
{
|
|
return core->reflect ^ core->symmetry_reflect;
|
|
}
|
|
|
|
static const GimpTempBuf *
|
|
gimp_brush_core_transform_mask (GimpBrushCore *core,
|
|
GimpBrush *brush)
|
|
{
|
|
const GimpTempBuf *mask;
|
|
|
|
if (core->scale <= 0.0)
|
|
return NULL;
|
|
|
|
mask = gimp_brush_transform_mask (brush,
|
|
core->scale,
|
|
core->aspect_ratio,
|
|
gimp_brush_core_get_angle (core),
|
|
gimp_brush_core_get_reflect (core),
|
|
core->hardness);
|
|
|
|
if (mask == core->transform_brush)
|
|
return mask;
|
|
|
|
core->transform_brush = mask;
|
|
core->subsample_cache_invalid = TRUE;
|
|
core->solid_cache_invalid = TRUE;
|
|
|
|
return core->transform_brush;
|
|
}
|
|
|
|
const GimpTempBuf *
|
|
gimp_brush_core_get_brush_mask (GimpBrushCore *core,
|
|
const GimpCoords *coords,
|
|
GimpBrushApplicationMode brush_hardness,
|
|
gdouble dynamic_force)
|
|
{
|
|
const GimpTempBuf *mask;
|
|
|
|
if (dynamic_force <= 0.0)
|
|
return NULL;
|
|
|
|
mask = gimp_brush_core_transform_mask (core, core->brush);
|
|
|
|
if (! mask)
|
|
return NULL;
|
|
|
|
switch (brush_hardness)
|
|
{
|
|
case GIMP_BRUSH_SOFT:
|
|
return gimp_brush_core_subsample_mask (core, mask,
|
|
coords->x,
|
|
coords->y);
|
|
break;
|
|
|
|
case GIMP_BRUSH_HARD:
|
|
return gimp_brush_core_solidify_mask (core, mask,
|
|
coords->x,
|
|
coords->y);
|
|
break;
|
|
|
|
case GIMP_BRUSH_PRESSURE:
|
|
return gimp_brush_core_pressurize_mask (core, mask,
|
|
coords->x,
|
|
coords->y,
|
|
dynamic_force);
|
|
break;
|
|
}
|
|
|
|
g_return_val_if_reached (NULL);
|
|
}
|
|
|
|
const GimpTempBuf *
|
|
gimp_brush_core_get_brush_pixmap (GimpBrushCore *core)
|
|
{
|
|
const GimpTempBuf *pixmap;
|
|
|
|
if (core->scale <= 0.0)
|
|
return NULL;
|
|
|
|
pixmap = gimp_brush_transform_pixmap (core->brush,
|
|
core->scale,
|
|
core->aspect_ratio,
|
|
gimp_brush_core_get_angle (core),
|
|
gimp_brush_core_get_reflect (core),
|
|
core->hardness);
|
|
|
|
if (pixmap == core->transform_pixmap)
|
|
return pixmap;
|
|
|
|
core->transform_pixmap = pixmap;
|
|
core->subsample_cache_invalid = TRUE;
|
|
|
|
return core->transform_pixmap;
|
|
}
|
|
|
|
void
|
|
gimp_brush_core_eval_transform_dynamics (GimpBrushCore *core,
|
|
GimpDrawable *drawable,
|
|
GimpPaintOptions *paint_options,
|
|
const GimpCoords *coords)
|
|
{
|
|
if (core->main_brush)
|
|
{
|
|
gdouble max_side;
|
|
|
|
max_side = MAX (gimp_brush_get_width (core->main_brush),
|
|
gimp_brush_get_height (core->main_brush));
|
|
|
|
core->scale = paint_options->brush_size / max_side;
|
|
|
|
if (paint_options->brush_lock_to_view &&
|
|
MAX (coords->xscale, coords->yscale) > 0)
|
|
{
|
|
core->scale /= MAX (coords->xscale, coords->yscale);
|
|
|
|
/* Cap transform result for brushes or OOM can occur */
|
|
if ((core->scale * max_side) > GIMP_BRUSH_MAX_SIZE)
|
|
{
|
|
core->scale = GIMP_BRUSH_MAX_SIZE / max_side;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
core->scale = -1;
|
|
|
|
core->aspect_ratio = paint_options->brush_aspect_ratio;
|
|
core->angle = paint_options->brush_angle;
|
|
core->reflect = FALSE;
|
|
core->hardness = paint_options->brush_hardness;
|
|
|
|
if (paint_options->brush_lock_to_view)
|
|
{
|
|
core->angle += coords->angle;
|
|
core->reflect = coords->reflect;
|
|
}
|
|
|
|
if (! GIMP_IS_DYNAMICS (core->dynamics))
|
|
return;
|
|
|
|
if (GIMP_BRUSH_CORE_GET_CLASS (core)->handles_dynamic_transforming_brush)
|
|
{
|
|
gdouble fade_point = 1.0;
|
|
|
|
if (drawable)
|
|
{
|
|
GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
|
|
GimpPaintCore *paint_core = GIMP_PAINT_CORE (core);
|
|
|
|
fade_point = gimp_paint_options_get_fade (paint_options, image,
|
|
paint_core->pixel_dist);
|
|
}
|
|
|
|
core->scale *= gimp_dynamics_get_linear_value (core->dynamics,
|
|
GIMP_DYNAMICS_OUTPUT_SIZE,
|
|
coords,
|
|
paint_options,
|
|
fade_point);
|
|
|
|
core->angle += gimp_dynamics_get_angular_value (core->dynamics,
|
|
GIMP_DYNAMICS_OUTPUT_ANGLE,
|
|
coords,
|
|
paint_options,
|
|
fade_point);
|
|
|
|
core->hardness *= gimp_dynamics_get_linear_value (core->dynamics,
|
|
GIMP_DYNAMICS_OUTPUT_HARDNESS,
|
|
coords,
|
|
paint_options,
|
|
fade_point);
|
|
|
|
if (gimp_dynamics_is_output_enabled (core->dynamics,
|
|
GIMP_DYNAMICS_OUTPUT_ASPECT_RATIO))
|
|
{
|
|
gdouble dyn_aspect;
|
|
|
|
dyn_aspect = gimp_dynamics_get_aspect_value (core->dynamics,
|
|
GIMP_DYNAMICS_OUTPUT_ASPECT_RATIO,
|
|
coords,
|
|
paint_options,
|
|
fade_point);
|
|
|
|
/* Zero aspect ratio is special cased to half of all ar range,
|
|
* to force dynamics to have any effect. Forcing to full results
|
|
* in disappearing stamp if applied to maximum.
|
|
*/
|
|
if (core->aspect_ratio == 0.0)
|
|
core->aspect_ratio = 10.0 * dyn_aspect;
|
|
else
|
|
core->aspect_ratio *= dyn_aspect;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
gimp_brush_core_eval_transform_symmetry (GimpBrushCore *core,
|
|
GimpSymmetry *symmetry,
|
|
gint stroke)
|
|
{
|
|
g_return_if_fail (GIMP_IS_BRUSH_CORE (core));
|
|
g_return_if_fail (symmetry == NULL || GIMP_IS_SYMMETRY (symmetry));
|
|
|
|
core->symmetry_angle = 0.0;
|
|
core->symmetry_reflect = FALSE;
|
|
|
|
if (symmetry)
|
|
{
|
|
gimp_symmetry_get_transform (symmetry,
|
|
stroke,
|
|
&core->symmetry_angle,
|
|
&core->symmetry_reflect);
|
|
|
|
core->symmetry_angle /= 360.0;
|
|
}
|
|
}
|
|
|
|
void
|
|
gimp_brush_core_color_area_with_pixmap (GimpBrushCore *core,
|
|
GimpDrawable *drawable,
|
|
const GimpCoords *coords,
|
|
GeglBuffer *area,
|
|
gint area_x,
|
|
gint area_y,
|
|
gboolean apply_mask)
|
|
{
|
|
const GimpTempBuf *pixmap;
|
|
GeglBuffer *pixmap_buffer;
|
|
const GimpTempBuf *mask;
|
|
GeglBuffer *mask_buffer;
|
|
gint area_width;
|
|
gint area_height;
|
|
gint ul_x;
|
|
gint ul_y;
|
|
gint offset_x;
|
|
gint offset_y;
|
|
|
|
g_return_if_fail (GIMP_IS_BRUSH (core->brush));
|
|
g_return_if_fail (gimp_brush_get_pixmap (core->brush) != NULL);
|
|
|
|
/* scale the brush */
|
|
pixmap = gimp_brush_core_get_brush_pixmap (core);
|
|
|
|
if (! pixmap)
|
|
return;
|
|
|
|
if (apply_mask)
|
|
mask = gimp_brush_core_transform_mask (core, core->brush);
|
|
else
|
|
mask = NULL;
|
|
|
|
/* Calculate upper left corner of brush as in
|
|
* gimp_paint_core_get_paint_area. Ugly to have to do this here, too.
|
|
*/
|
|
ul_x = (gint) floor (coords->x) - (gimp_temp_buf_get_width (pixmap) >> 1);
|
|
ul_y = (gint) floor (coords->y) - (gimp_temp_buf_get_height (pixmap) >> 1);
|
|
|
|
/* Not sure why this is necessary, but empirically the code does
|
|
* not work without it for even-sided brushes. See bug #166622.
|
|
*/
|
|
if (gimp_temp_buf_get_width (pixmap) % 2 == 0)
|
|
ul_x += ROUND (coords->x) - floor (coords->x);
|
|
if (gimp_temp_buf_get_height (pixmap) % 2 == 0)
|
|
ul_y += ROUND (coords->y) - floor (coords->y);
|
|
|
|
offset_x = area_x - ul_x;
|
|
offset_y = area_y - ul_y;
|
|
|
|
area_width = gegl_buffer_get_width (area);
|
|
area_height = gegl_buffer_get_height (area);
|
|
|
|
pixmap_buffer = gimp_temp_buf_create_buffer (pixmap);
|
|
|
|
gimp_gegl_buffer_copy (pixmap_buffer,
|
|
GEGL_RECTANGLE (offset_x, offset_y,
|
|
area_width, area_height),
|
|
GEGL_ABYSS_NONE,
|
|
area,
|
|
GEGL_RECTANGLE (0, 0,
|
|
area_width, area_height));
|
|
|
|
g_object_unref (pixmap_buffer);
|
|
|
|
if (mask)
|
|
{
|
|
mask_buffer = gimp_temp_buf_create_buffer (mask);
|
|
|
|
gimp_gegl_apply_mask (mask_buffer,
|
|
GEGL_RECTANGLE (offset_x, offset_y,
|
|
area_width, area_height),
|
|
area,
|
|
GEGL_RECTANGLE (0, 0,
|
|
area_width, area_height),
|
|
1.0);
|
|
|
|
g_object_unref (mask_buffer);
|
|
}
|
|
}
|