Initial checkin of Pika from heckimp

This commit is contained in:
2023-09-25 15:35:21 -07:00
commit 891e999216
6761 changed files with 5240685 additions and 0 deletions

68
app/paint/meson.build Normal file
View File

@ -0,0 +1,68 @@
stamp_paint_enums = custom_target('stamp-paint-enums.h',
input : [
files(
'paint-enums.h'
),
],
output: [ 'stamp-paint-enums.h', ],
command: [
mkenums_wrap, perl,
meson.project_source_root(), meson.current_source_dir(),
meson.current_build_dir(),
'paint-',
'#include <gio/gio.h>\n' +
'#include "libpikabase/pikabase.h"\n',
'#include "pika-intl.h"'
],
build_by_default: true
)
libapppaint_sources = [
'pika-paint.c',
'pikaairbrush.c',
'pikaairbrushoptions.c',
'pikabrushcore-loops.cc',
'pikabrushcore.c',
'pikaclone.c',
'pikacloneoptions.c',
'pikaconvolve.c',
'pikaconvolveoptions.c',
'pikadodgeburn.c',
'pikadodgeburnoptions.c',
'pikaeraser.c',
'pikaeraseroptions.c',
'pikaheal.c',
'pikaink-blob.c',
'pikaink.c',
'pikainkoptions.c',
'pikainkundo.c',
'pikamybrushcore.c',
'pikamybrushoptions.c',
'pikamybrushsurface.c',
'pikapaintbrush.c',
'pikapaintcore-loops.cc',
'pikapaintcore-stroke.c',
'pikapaintcore.c',
'pikapaintcoreundo.c',
'pikapaintoptions.c',
'pikapencil.c',
'pikapenciloptions.c',
'pikaperspectiveclone.c',
'pikaperspectivecloneoptions.c',
'pikasmudge.c',
'pikasmudgeoptions.c',
'pikasourcecore.c',
'pikasourceoptions.c',
'paint-enums.c',
stamp_paint_enums,
]
libapppaint = static_library('apppaint',
libapppaint_sources,
include_directories: [ rootInclude, rootAppInclude, ],
c_args: '-DG_LOG_DOMAIN="Pika-Paint"',
dependencies: [
cairo, gegl, gdk_pixbuf, libmypaint,
],
)

105
app/paint/paint-enums.c Normal file
View File

@ -0,0 +1,105 @@
/* Generated data (by pika-mkenums) */
#include "stamp-paint-enums.h"
#include "config.h"
#include <gio/gio.h>
#include "libpikabase/pikabase.h"
#include "paint-enums.h"
#include "pika-intl.h"
/* enumerations from "paint-enums.h" */
GType
pika_brush_application_mode_get_type (void)
{
static const GEnumValue values[] =
{
{ PIKA_BRUSH_HARD, "PIKA_BRUSH_HARD", "hard" },
{ PIKA_BRUSH_SOFT, "PIKA_BRUSH_SOFT", "soft" },
{ 0, NULL, NULL }
};
static const PikaEnumDesc descs[] =
{
{ PIKA_BRUSH_HARD, "PIKA_BRUSH_HARD", NULL },
{ PIKA_BRUSH_SOFT, "PIKA_BRUSH_SOFT", NULL },
{ 0, NULL, NULL }
};
static GType type = 0;
if (G_UNLIKELY (! type))
{
type = g_enum_register_static ("PikaBrushApplicationMode", values);
pika_type_set_translation_context (type, "brush-application-mode");
pika_enum_set_value_descriptions (type, descs);
}
return type;
}
GType
pika_perspective_clone_mode_get_type (void)
{
static const GEnumValue values[] =
{
{ PIKA_PERSPECTIVE_CLONE_MODE_ADJUST, "PIKA_PERSPECTIVE_CLONE_MODE_ADJUST", "adjust" },
{ PIKA_PERSPECTIVE_CLONE_MODE_PAINT, "PIKA_PERSPECTIVE_CLONE_MODE_PAINT", "paint" },
{ 0, NULL, NULL }
};
static const PikaEnumDesc descs[] =
{
{ PIKA_PERSPECTIVE_CLONE_MODE_ADJUST, NC_("perspective-clone-mode", "Modify Perspective"), NULL },
{ PIKA_PERSPECTIVE_CLONE_MODE_PAINT, NC_("perspective-clone-mode", "Perspective Clone"), NULL },
{ 0, NULL, NULL }
};
static GType type = 0;
if (G_UNLIKELY (! type))
{
type = g_enum_register_static ("PikaPerspectiveCloneMode", values);
pika_type_set_translation_context (type, "perspective-clone-mode");
pika_enum_set_value_descriptions (type, descs);
}
return type;
}
GType
pika_source_align_mode_get_type (void)
{
static const GEnumValue values[] =
{
{ PIKA_SOURCE_ALIGN_NO, "PIKA_SOURCE_ALIGN_NO", "no" },
{ PIKA_SOURCE_ALIGN_YES, "PIKA_SOURCE_ALIGN_YES", "yes" },
{ PIKA_SOURCE_ALIGN_REGISTERED, "PIKA_SOURCE_ALIGN_REGISTERED", "registered" },
{ PIKA_SOURCE_ALIGN_FIXED, "PIKA_SOURCE_ALIGN_FIXED", "fixed" },
{ 0, NULL, NULL }
};
static const PikaEnumDesc descs[] =
{
{ PIKA_SOURCE_ALIGN_NO, NC_("source-align-mode", "None"), NULL },
{ PIKA_SOURCE_ALIGN_YES, NC_("source-align-mode", "Aligned"), NULL },
{ PIKA_SOURCE_ALIGN_REGISTERED, NC_("source-align-mode", "Registered"), NULL },
{ PIKA_SOURCE_ALIGN_FIXED, NC_("source-align-mode", "Fixed"), NULL },
{ 0, NULL, NULL }
};
static GType type = 0;
if (G_UNLIKELY (! type))
{
type = g_enum_register_static ("PikaSourceAlignMode", values);
pika_type_set_translation_context (type, "source-align-mode");
pika_enum_set_value_descriptions (type, descs);
}
return type;
}
/* Generated data ends here */

89
app/paint/paint-enums.h Normal file
View File

@ -0,0 +1,89 @@
/* 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/>.
*/
#ifndef __PAINT_ENUMS_H__
#define __PAINT_ENUMS_H__
#if 0
This file is parsed by two scripts, enumgen.pl in pdb,
and pika-mkenums. All enums that are not marked with
/*< pdb-skip >*/ are exported to libpika and the PDB. Enums that are
not marked with /*< skip >*/ are registered with the GType system.
If you want the enum to be skipped by both scripts, you have to use
/*< pdb-skip, skip >*/.
The same syntax applies to enum values.
#endif
/*
* enums that are registered with the type system
*/
#define PIKA_TYPE_BRUSH_APPLICATION_MODE (pika_brush_application_mode_get_type ())
GType pika_brush_application_mode_get_type (void) G_GNUC_CONST;
typedef enum
{
PIKA_BRUSH_HARD,
PIKA_BRUSH_SOFT,
PIKA_BRUSH_PRESSURE /*< pdb-skip, skip >*/
} PikaBrushApplicationMode;
#define PIKA_TYPE_PERSPECTIVE_CLONE_MODE (pika_perspective_clone_mode_get_type ())
GType pika_perspective_clone_mode_get_type (void) G_GNUC_CONST;
typedef enum /*< pdb-skip >*/
{
PIKA_PERSPECTIVE_CLONE_MODE_ADJUST, /*< desc="Modify Perspective" >*/
PIKA_PERSPECTIVE_CLONE_MODE_PAINT /*< desc="Perspective Clone" >*/
} PikaPerspectiveCloneMode;
#define PIKA_TYPE_SOURCE_ALIGN_MODE (pika_source_align_mode_get_type ())
GType pika_source_align_mode_get_type (void) G_GNUC_CONST;
typedef enum /*< pdb-skip >*/
{
PIKA_SOURCE_ALIGN_NO, /*< desc="None" >*/
PIKA_SOURCE_ALIGN_YES, /*< desc="Aligned" >*/
PIKA_SOURCE_ALIGN_REGISTERED, /*< desc="Registered" >*/
PIKA_SOURCE_ALIGN_FIXED /*< desc="Fixed" >*/
} PikaSourceAlignMode;
/*
* non-registered enums; register them if needed
*/
typedef enum /*< skip, pdb-skip >*/
{
PIKA_PAINT_STATE_INIT, /* Setup PaintFunc internals */
PIKA_PAINT_STATE_MOTION, /* PaintFunc performs motion-related rendering */
PIKA_PAINT_STATE_FINISH /* Cleanup and/or reset PaintFunc operation */
} PikaPaintState;
#endif /* __PAINT_ENUMS_H__ */

80
app/paint/paint-types.h Normal file
View File

@ -0,0 +1,80 @@
/* 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/>.
*/
#ifndef __PAINT_TYPES_H__
#define __PAINT_TYPES_H__
#include "core/core-types.h"
#include "paint/paint-enums.h"
/* paint cores */
typedef struct _PikaPaintCore PikaPaintCore;
typedef struct _PikaBrushCore PikaBrushCore;
typedef struct _PikaSourceCore PikaSourceCore;
typedef struct _PikaAirbrush PikaAirbrush;
typedef struct _PikaClone PikaClone;
typedef struct _PikaConvolve PikaConvolve;
typedef struct _PikaDodgeBurn PikaDodgeBurn;
typedef struct _PikaEraser PikaEraser;
typedef struct _PikaHeal PikaHeal;
typedef struct _PikaInk PikaInk;
typedef struct _PikaMybrushCore PikaMybrushCore;
typedef struct _PikaPaintbrush PikaPaintbrush;
typedef struct _PikaPencil PikaPencil;
typedef struct _PikaPerspectiveClone PikaPerspectiveClone;
typedef struct _PikaSmudge PikaSmudge;
/* paint options */
typedef struct _PikaPaintOptions PikaPaintOptions;
typedef struct _PikaSourceOptions PikaSourceOptions;
typedef struct _PikaAirbrushOptions PikaAirbrushOptions;
typedef struct _PikaCloneOptions PikaCloneOptions;
typedef struct _PikaConvolveOptions PikaConvolveOptions;
typedef struct _PikaDodgeBurnOptions PikaDodgeBurnOptions;
typedef struct _PikaEraserOptions PikaEraserOptions;
typedef struct _PikaInkOptions PikaInkOptions;
typedef struct _PikaMybrushOptions PikaMybrushOptions;
typedef struct _PikaPencilOptions PikaPencilOptions;
typedef struct _PikaPerspectiveCloneOptions PikaPerspectiveCloneOptions;
typedef struct _PikaSmudgeOptions PikaSmudgeOptions;
/* functions */
typedef void (* PikaPaintRegisterCallback) (Pika *pika,
GType paint_type,
GType paint_options_type,
const gchar *identifier,
const gchar *blurb,
const gchar *icon_name);
typedef void (* PikaPaintRegisterFunc) (Pika *pika,
PikaPaintRegisterCallback callback);
#endif /* __PAINT_TYPES_H__ */

144
app/paint/pika-paint.c Normal file
View File

@ -0,0 +1,144 @@
/* 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-2001 Spencer Kimball, Peter Mattis and others
*
* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "paint-types.h"
#include "core/pika.h"
#include "core/pikalist.h"
#include "core/pikapaintinfo.h"
#include "pika-paint.h"
#include "pikaairbrush.h"
#include "pikaclone.h"
#include "pikaconvolve.h"
#include "pikadodgeburn.h"
#include "pikaeraser.h"
#include "pikaheal.h"
#include "pikaink.h"
#include "pikamybrushcore.h"
#include "pikapaintoptions.h"
#include "pikapaintbrush.h"
#include "pikapencil.h"
#include "pikaperspectiveclone.h"
#include "pikasmudge.h"
/* local function prototypes */
static void pika_paint_register (Pika *pika,
GType paint_type,
GType paint_options_type,
const gchar *identifier,
const gchar *blurb,
const gchar *icon_name);
/* public functions */
void
pika_paint_init (Pika *pika)
{
PikaPaintRegisterFunc register_funcs[] =
{
pika_dodge_burn_register,
pika_smudge_register,
pika_convolve_register,
pika_perspective_clone_register,
pika_heal_register,
pika_clone_register,
pika_mybrush_core_register,
pika_ink_register,
pika_airbrush_register,
pika_eraser_register,
pika_paintbrush_register,
pika_pencil_register
};
gint i;
g_return_if_fail (PIKA_IS_PIKA (pika));
pika->paint_info_list = pika_list_new (PIKA_TYPE_PAINT_INFO, FALSE);
pika_object_set_static_name (PIKA_OBJECT (pika->paint_info_list),
"paint infos");
pika_container_freeze (pika->paint_info_list);
for (i = 0; i < G_N_ELEMENTS (register_funcs); i++)
{
register_funcs[i] (pika, pika_paint_register);
}
pika_container_thaw (pika->paint_info_list);
}
void
pika_paint_exit (Pika *pika)
{
g_return_if_fail (PIKA_IS_PIKA (pika));
pika_paint_info_set_standard (pika, NULL);
if (pika->paint_info_list)
{
pika_container_foreach (pika->paint_info_list,
(GFunc) g_object_run_dispose, NULL);
g_clear_object (&pika->paint_info_list);
}
}
/* private functions */
static void
pika_paint_register (Pika *pika,
GType paint_type,
GType paint_options_type,
const gchar *identifier,
const gchar *blurb,
const gchar *icon_name)
{
PikaPaintInfo *paint_info;
g_return_if_fail (PIKA_IS_PIKA (pika));
g_return_if_fail (g_type_is_a (paint_type, PIKA_TYPE_PAINT_CORE));
g_return_if_fail (g_type_is_a (paint_options_type, PIKA_TYPE_PAINT_OPTIONS));
g_return_if_fail (identifier != NULL);
g_return_if_fail (blurb != NULL);
paint_info = pika_paint_info_new (pika,
paint_type,
paint_options_type,
identifier,
blurb,
icon_name);
pika_container_add (pika->paint_info_list, PIKA_OBJECT (paint_info));
g_object_unref (paint_info);
if (paint_type == PIKA_TYPE_PAINTBRUSH)
pika_paint_info_set_standard (pika, paint_info);
}

30
app/paint/pika-paint.h Normal file
View File

@ -0,0 +1,30 @@
/* 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-2001 Spencer Kimball, Peter Mattis and others
*
* 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/>.
*/
#ifndef __PIKA_PAINT_H__
#define __PIKA_PAINT_H__
void pika_paint_init (Pika *pika);
void pika_paint_exit (Pika *pika);
#endif /* __PIKA_PAINT_H__ */

275
app/paint/pikaairbrush.c Normal file
View File

@ -0,0 +1,275 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "paint-types.h"
#include "core/pika.h"
#include "core/pikabrush.h"
#include "core/pikadrawable.h"
#include "core/pikadynamics.h"
#include "core/pikagradient.h"
#include "core/pikaimage.h"
#include "core/pikasymmetry.h"
#include "pikaairbrush.h"
#include "pikaairbrushoptions.h"
#include "pika-intl.h"
#define STAMP_MAX_FPS 60
enum
{
STAMP,
LAST_SIGNAL
};
static void pika_airbrush_finalize (GObject *object);
static void pika_airbrush_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time);
static void pika_airbrush_motion (PikaPaintCore *paint_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym);
static gboolean pika_airbrush_timeout (gpointer data);
G_DEFINE_TYPE (PikaAirbrush, pika_airbrush, PIKA_TYPE_PAINTBRUSH)
#define parent_class pika_airbrush_parent_class
static guint airbrush_signals[LAST_SIGNAL] = { 0 };
void
pika_airbrush_register (Pika *pika,
PikaPaintRegisterCallback callback)
{
(* callback) (pika,
PIKA_TYPE_AIRBRUSH,
PIKA_TYPE_AIRBRUSH_OPTIONS,
"pika-airbrush",
_("Airbrush"),
"pika-tool-airbrush");
}
static void
pika_airbrush_class_init (PikaAirbrushClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
PikaPaintCoreClass *paint_core_class = PIKA_PAINT_CORE_CLASS (klass);
object_class->finalize = pika_airbrush_finalize;
paint_core_class->paint = pika_airbrush_paint;
airbrush_signals[STAMP] =
g_signal_new ("stamp",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (PikaAirbrushClass, stamp),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
}
static void
pika_airbrush_init (PikaAirbrush *airbrush)
{
}
static void
pika_airbrush_finalize (GObject *object)
{
PikaAirbrush *airbrush = PIKA_AIRBRUSH (object);
if (airbrush->timeout_id)
{
g_source_remove (airbrush->timeout_id);
airbrush->timeout_id = 0;
}
g_clear_object (&airbrush->sym);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
pika_airbrush_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time)
{
PikaAirbrush *airbrush = PIKA_AIRBRUSH (paint_core);
PikaAirbrushOptions *options = PIKA_AIRBRUSH_OPTIONS (paint_options);
PikaDynamics *dynamics = PIKA_BRUSH_CORE (paint_core)->dynamics;
PikaCoords coords;
g_return_if_fail (g_list_length (drawables) == 1);
if (airbrush->timeout_id)
{
g_source_remove (airbrush->timeout_id);
airbrush->timeout_id = 0;
}
switch (paint_state)
{
case PIKA_PAINT_STATE_INIT:
PIKA_PAINT_CORE_CLASS (parent_class)->paint (paint_core, drawables,
paint_options,
sym,
paint_state, time);
break;
case PIKA_PAINT_STATE_MOTION:
coords = *(pika_symmetry_get_origin (sym));
pika_airbrush_motion (paint_core, drawables->data, paint_options, sym);
if ((options->rate != 0.0) && ! options->motion_only)
{
PikaImage *image = pika_item_get_image (PIKA_ITEM (drawables->data));
gdouble fade_point;
gdouble dynamic_rate;
gint timeout;
fade_point = pika_paint_options_get_fade (paint_options, image,
paint_core->pixel_dist);
airbrush->drawable = drawables->data;
airbrush->paint_options = paint_options;
pika_symmetry_set_origin (sym, drawables->data, &coords);
if (airbrush->sym)
g_object_unref (airbrush->sym);
airbrush->sym = g_object_ref (sym);
/* Base our timeout on the original stroke. */
airbrush->coords = coords;
dynamic_rate = pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_RATE,
&coords,
paint_options,
fade_point);
timeout = (1000.0 / STAMP_MAX_FPS) /
((options->rate / 100.0) * dynamic_rate);
airbrush->timeout_id = g_timeout_add_full (G_PRIORITY_HIGH,
timeout,
pika_airbrush_timeout,
airbrush, NULL);
}
break;
case PIKA_PAINT_STATE_FINISH:
PIKA_PAINT_CORE_CLASS (parent_class)->paint (paint_core, drawables,
paint_options,
sym,
paint_state, time);
g_clear_object (&airbrush->sym);
break;
}
}
static void
pika_airbrush_motion (PikaPaintCore *paint_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym)
{
PikaAirbrushOptions *options = PIKA_AIRBRUSH_OPTIONS (paint_options);
PikaDynamics *dynamics = PIKA_BRUSH_CORE (paint_core)->dynamics;
PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable));
gdouble opacity;
gdouble fade_point;
PikaCoords *coords;
fade_point = pika_paint_options_get_fade (paint_options, image,
paint_core->pixel_dist);
coords = pika_symmetry_get_origin (sym);
opacity = (options->flow / 100.0 *
pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_FLOW,
coords,
paint_options,
fade_point));
_pika_paintbrush_motion (paint_core, drawable, paint_options,
sym, opacity);
}
static gboolean
pika_airbrush_timeout (gpointer data)
{
PikaAirbrush *airbrush = PIKA_AIRBRUSH (data);
airbrush->timeout_id = 0;
g_signal_emit (airbrush, airbrush_signals[STAMP], 0);
return G_SOURCE_REMOVE;
}
/* public functions */
void
pika_airbrush_stamp (PikaAirbrush *airbrush)
{
GList *drawables;
g_return_if_fail (PIKA_IS_AIRBRUSH (airbrush));
pika_symmetry_set_origin (airbrush->sym,
airbrush->drawable, &airbrush->coords);
drawables = g_list_prepend (NULL, airbrush->drawable),
pika_airbrush_paint (PIKA_PAINT_CORE (airbrush),
drawables,
airbrush->paint_options,
airbrush->sym,
PIKA_PAINT_STATE_MOTION, 0);
g_list_free (drawables);
pika_symmetry_clear_origin (airbrush->sym);
}

68
app/paint/pikaairbrush.h Normal file
View File

@ -0,0 +1,68 @@
/* 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/>.
*/
#ifndef __PIKA_AIRBRUSH_H__
#define __PIKA_AIRBRUSH_H__
#include "pikapaintbrush.h"
#define PIKA_TYPE_AIRBRUSH (pika_airbrush_get_type ())
#define PIKA_AIRBRUSH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_AIRBRUSH, PikaAirbrush))
#define PIKA_AIRBRUSH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_AIRBRUSH, PikaAirbrushClass))
#define PIKA_IS_AIRBRUSH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_AIRBRUSH))
#define PIKA_IS_AIRBRUSH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_AIRBRUSH))
#define PIKA_AIRBRUSH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_AIRBRUSH, PikaAirbrushClass))
typedef struct _PikaAirbrushClass PikaAirbrushClass;
struct _PikaAirbrush
{
PikaPaintbrush parent_instance;
guint timeout_id;
PikaSymmetry *sym;
PikaDrawable *drawable;
PikaPaintOptions *paint_options;
PikaCoords coords;
};
struct _PikaAirbrushClass
{
PikaPaintbrushClass parent_class;
/* signals */
void (* stamp) (PikaAirbrush *airbrush);
};
void pika_airbrush_register (Pika *pika,
PikaPaintRegisterCallback callback);
GType pika_airbrush_get_type (void) G_GNUC_CONST;
void pika_airbrush_stamp (PikaAirbrush *airbrush);
#endif /* __PIKA_AIRBRUSH_H__ */

View File

@ -0,0 +1,160 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikaconfig/pikaconfig.h"
#include "paint-types.h"
#include "pikaairbrushoptions.h"
#include "pika-intl.h"
#define AIRBRUSH_DEFAULT_RATE 50.0
#define AIRBRUSH_DEFAULT_FLOW 10.0
#define AIRBRUSH_DEFAULT_MOTION_ONLY FALSE
enum
{
PROP_0,
PROP_RATE,
PROP_MOTION_ONLY,
PROP_FLOW,
PROP_PRESSURE /*for backwards copatibility of tool options*/
};
static void pika_airbrush_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_airbrush_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
G_DEFINE_TYPE (PikaAirbrushOptions, pika_airbrush_options,
PIKA_TYPE_PAINT_OPTIONS)
static void
pika_airbrush_options_class_init (PikaAirbrushOptionsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = pika_airbrush_options_set_property;
object_class->get_property = pika_airbrush_options_get_property;
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_RATE,
"rate",
C_("airbrush-tool", "Rate"),
NULL,
0.0, 100.0, AIRBRUSH_DEFAULT_RATE,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_MOTION_ONLY,
"motion-only",
_("Motion only"),
NULL,
AIRBRUSH_DEFAULT_MOTION_ONLY,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_FLOW,
"flow",
_("Flow"),
NULL,
0.0, 100.0, AIRBRUSH_DEFAULT_FLOW,
PIKA_PARAM_STATIC_STRINGS);
/* backwads-compadibility prop for flow fomerly known as pressure */
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_PRESSURE,
"pressure",
NULL, NULL,
0.0, 100.0, AIRBRUSH_DEFAULT_FLOW,
PIKA_CONFIG_PARAM_IGNORE);
}
static void
pika_airbrush_options_init (PikaAirbrushOptions *options)
{
}
static void
pika_airbrush_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaAirbrushOptions *options = PIKA_AIRBRUSH_OPTIONS (object);
switch (property_id)
{
case PROP_RATE:
options->rate = g_value_get_double (value);
break;
case PROP_MOTION_ONLY:
options->motion_only = g_value_get_boolean (value);
break;
case PROP_PRESSURE:
case PROP_FLOW:
options->flow = g_value_get_double (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_airbrush_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaAirbrushOptions *options = PIKA_AIRBRUSH_OPTIONS (object);
switch (property_id)
{
case PROP_RATE:
g_value_set_double (value, options->rate);
break;
case PROP_MOTION_ONLY:
g_value_set_boolean (value, options->motion_only);
break;
case PROP_PRESSURE:
case PROP_FLOW:
g_value_set_double (value, options->flow);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}

View File

@ -0,0 +1,57 @@
/* 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/>.
*/
#ifndef __PIKA_AIRBRUSH_OPTIONS_H__
#define __PIKA_AIRBRUSH_OPTIONS_H__
#include "pikapaintoptions.h"
#define PIKA_TYPE_AIRBRUSH_OPTIONS (pika_airbrush_options_get_type ())
#define PIKA_AIRBRUSH_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_AIRBRUSH_OPTIONS, PikaAirbrushOptions))
#define PIKA_AIRBRUSH_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_AIRBRUSH_OPTIONS, PikaAirbrushOptionsClass))
#define PIKA_IS_AIRBRUSH_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_AIRBRUSH_OPTIONS))
#define PIKA_IS_AIRBRUSH_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_AIRBRUSH_OPTIONS))
#define PIKA_AIRBRUSH_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_AIRBRUSH_OPTIONS, PikaAirbrushOptionsClass))
typedef struct _PikaAirbrushOptionsClass PikaAirbrushOptionsClass;
struct _PikaAirbrushOptions
{
PikaPaintOptions parent_instance;
gdouble rate;
gboolean motion_only;
gdouble flow;
};
struct _PikaAirbrushOptionsClass
{
PikaPaintOptionsClass parent_class;
};
GType pika_airbrush_options_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_AIRBRUSH_OPTIONS_H__ */

View File

@ -0,0 +1,116 @@
/* pikabrushcore-kernels.h
*
* This file was generated using kernelgen as found in the tools dir.
* (threshold = 0.25)
*/
#ifndef __PIKA_BRUSH_CORE_KERNELS_H__
#define __PIKA_BRUSH_CORE_KERNELS_H__
#define KERNEL_WIDTH 3
#define KERNEL_HEIGHT 3
#define KERNEL_SUBSAMPLE 4
#ifdef __cplusplus
template <class T>
struct Kernel;
template <>
struct Kernel<guchar>
{
using value_type = guchar;
using kernel_type = guint;
using accum_type = gulong;
static constexpr kernel_type
coeff (kernel_type x)
{
return x;
}
static constexpr value_type
round (accum_type x)
{
return (x + 128) / 256;
}
};
template <>
struct Kernel<gfloat>
{
using value_type = gfloat;
using kernel_type = gfloat;
using accum_type = gfloat;
static constexpr kernel_type
coeff (kernel_type x)
{
return x / 256.0f;
}
static constexpr value_type
round (accum_type x)
{
return x;
}
};
/* Brush pixel subsampling kernels */
template <class T>
struct Subsample : Kernel<T>
{
#define C(x) (Subsample::coeff (x))
static constexpr typename Subsample::kernel_type kernel[5][5][9] =
{
{
{ C( 64), C( 64), C( 0), C( 64), C( 64), C( 0), C( 0), C( 0), C( 0), },
{ C( 25), C(103), C( 0), C( 25), C(103), C( 0), C( 0), C( 0), C( 0), },
{ C( 0), C(128), C( 0), C( 0), C(128), C( 0), C( 0), C( 0), C( 0), },
{ C( 0), C(103), C( 25), C( 0), C(103), C( 25), C( 0), C( 0), C( 0), },
{ C( 0), C( 64), C( 64), C( 0), C( 64), C( 64), C( 0), C( 0), C( 0), }
},
{
{ C( 25), C( 25), C( 0), C(103), C(103), C( 0), C( 0), C( 0), C( 0), },
{ C( 6), C( 44), C( 0), C( 44), C(162), C( 0), C( 0), C( 0), C( 0), },
{ C( 0), C( 50), C( 0), C( 0), C(206), C( 0), C( 0), C( 0), C( 0), },
{ C( 0), C( 44), C( 6), C( 0), C(162), C( 44), C( 0), C( 0), C( 0), },
{ C( 0), C( 25), C( 25), C( 0), C(103), C(103), C( 0), C( 0), C( 0), }
},
{
{ C( 0), C( 0), C( 0), C(128), C(128), C( 0), C( 0), C( 0), C( 0), },
{ C( 0), C( 0), C( 0), C( 50), C(206), C( 0), C( 0), C( 0), C( 0), },
{ C( 0), C( 0), C( 0), C( 0), C(256), C( 0), C( 0), C( 0), C( 0), },
{ C( 0), C( 0), C( 0), C( 0), C(206), C( 50), C( 0), C( 0), C( 0), },
{ C( 0), C( 0), C( 0), C( 0), C(128), C(128), C( 0), C( 0), C( 0), }
},
{
{ C( 0), C( 0), C( 0), C(103), C(103), C( 0), C( 25), C( 25), C( 0), },
{ C( 0), C( 0), C( 0), C( 44), C(162), C( 0), C( 6), C( 44), C( 0), },
{ C( 0), C( 0), C( 0), C( 0), C(206), C( 0), C( 0), C( 50), C( 0), },
{ C( 0), C( 0), C( 0), C( 0), C(162), C( 44), C( 0), C( 44), C( 6), },
{ C( 0), C( 0), C( 0), C( 0), C(103), C(103), C( 0), C( 25), C( 25), }
},
{
{ C( 0), C( 0), C( 0), C( 64), C( 64), C( 0), C( 64), C( 64), C( 0), },
{ C( 0), C( 0), C( 0), C( 25), C(103), C( 0), C( 25), C(103), C( 0), },
{ C( 0), C( 0), C( 0), C( 0), C(128), C( 0), C( 0), C(128), C( 0), },
{ C( 0), C( 0), C( 0), C( 0), C(103), C( 25), C( 0), C(103), C( 25), },
{ C( 0), C( 0), C( 0), C( 0), C( 64), C( 64), C( 0), C( 64), C( 64), }
}
};
#undef C
};
template <class T>
constexpr typename Subsample<T>::kernel_type Subsample<T>::kernel[5][5][9];
#endif /* __cplusplus */
#endif /* __PIKA_BRUSH_CORE_KERNELS_H__ */

View File

@ -0,0 +1,651 @@
/* 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 <gegl.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "libpikamath/pikamath.h"
extern "C"
{
#include "paint-types.h"
#include "core/pikatempbuf.h"
#include "pikabrushcore.h"
#include "pikabrushcore-loops.h"
} /* extern "C" */
#include "pikabrushcore-kernels.h"
#define PIXELS_PER_THREAD \
(/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)
#define EPSILON 1e-6
static void
clear_edges (PikaTempBuf *buf,
gint top,
gint bottom,
gint left,
gint right)
{
guchar *data;
const Babl *format = pika_temp_buf_get_format (buf);
gint bpp = babl_format_get_bytes_per_pixel (format);
gint width = pika_temp_buf_get_width (buf);
gint height = pika_temp_buf_get_height (buf);
if (top + bottom >= height || left + right >= width)
{
pika_temp_buf_data_clear (buf);
return;
}
data = pika_temp_buf_get_data (buf);
memset (data, 0, (top * width + left) * bpp);
if (left + right)
{
gint stride = width * bpp;
gint size = (left + right) * bpp;
gint y;
data = pika_temp_buf_get_data (buf) +
((top + 1) * width - right) * bpp;
for (y = top; y < height - bottom - 1; y++)
{
memset (data, 0, size);
data += stride;
}
}
data = pika_temp_buf_get_data (buf) +
((height - bottom) * width - right) * bpp;
memset (data, 0, (bottom * width + right) * bpp);
}
template <class T>
static inline void
rotate_pointers (T **p,
gint n)
{
T *tmp;
gint i;
tmp = p[0];
for (i = 0; i < n - 1; i++)
p[i] = p[i + 1];
p[i] = tmp;
}
template <class T>
static void
pika_brush_core_subsample_mask_impl (const PikaTempBuf *mask,
PikaTempBuf *dest,
gint dest_offset_x,
gint dest_offset_y,
gint index1,
gint index2)
{
using value_type = typename Subsample<T>::value_type;
using kernel_type = typename Subsample<T>::kernel_type;
using accum_type = typename Subsample<T>::accum_type;
Subsample<T> subsample;
const kernel_type *kernel = subsample.kernel[index2][index1];
gint mask_width = pika_temp_buf_get_width (mask);
gint mask_height = pika_temp_buf_get_height (mask);
gint dest_width = pika_temp_buf_get_width (dest);
gint dest_height = pika_temp_buf_get_height (dest);
gegl_parallel_distribute_range (
mask_height, PIXELS_PER_THREAD / mask_width,
[=] (gint y, gint height)
{
const value_type *m;
value_type *d;
const kernel_type *k;
gint y0;
gint i, j;
gint r, s;
gint offs;
accum_type *accum[KERNEL_HEIGHT];
/* Allocate and initialize the accum buffer */
for (i = 0; i < KERNEL_HEIGHT ; i++)
accum[i] = gegl_scratch_new0 (accum_type, dest_width + 1);
y0 = MAX (y - (KERNEL_HEIGHT - 1), 0);
m = (const value_type *) pika_temp_buf_get_data (mask) +
y0 * mask_width;
for (i = y0; i < y; i++)
{
for (j = 0; j < mask_width; j++)
{
k = kernel + KERNEL_WIDTH * (y - i);
for (r = y - i; r < KERNEL_HEIGHT; r++)
{
offs = j + dest_offset_x;
s = KERNEL_WIDTH;
while (s--)
accum[r][offs++] += *m * *k++;
}
m++;
}
rotate_pointers (accum, KERNEL_HEIGHT);
}
for (i = y; i < y + height; i++)
{
for (j = 0; j < mask_width; j++)
{
k = kernel;
for (r = 0; r < KERNEL_HEIGHT; r++)
{
offs = j + dest_offset_x;
s = KERNEL_WIDTH;
while (s--)
accum[r][offs++] += *m * *k++;
}
m++;
}
/* store the accum buffer into the destination mask */
d = (value_type *) pika_temp_buf_get_data (dest) +
(i + dest_offset_y) * dest_width;
for (j = 0; j < dest_width; j++)
*d++ = subsample.round (accum[0][j]);
rotate_pointers (accum, KERNEL_HEIGHT);
memset (accum[KERNEL_HEIGHT - 1], 0,
sizeof (accum_type) * dest_width);
}
if (y + height == mask_height)
{
/* store the rest of the accum buffer into the dest mask */
while (i + dest_offset_y < dest_height)
{
d = (value_type *) pika_temp_buf_get_data (dest) +
(i + dest_offset_y) * dest_width;
for (j = 0; j < dest_width; j++)
*d++ = subsample.round (accum[0][j]);
rotate_pointers (accum, KERNEL_HEIGHT);
i++;
}
}
for (i = KERNEL_HEIGHT - 1; i >= 0; i--)
gegl_scratch_free (accum[i]);
});
}
const PikaTempBuf *
pika_brush_core_subsample_mask (PikaBrushCore *core,
const PikaTempBuf *mask,
gdouble x,
gdouble y)
{
PikaTempBuf *dest;
const Babl *mask_format;
gdouble left;
gint index1;
gint index2;
gint dest_offset_x = 0;
gint dest_offset_y = 0;
gint mask_width = pika_temp_buf_get_width (mask);
gint mask_height = pika_temp_buf_get_height (mask);
left = x - floor (x);
index1 = (gint) (left * (gdouble) (KERNEL_SUBSAMPLE + 1));
left = y - floor (y);
index2 = (gint) (left * (gdouble) (KERNEL_SUBSAMPLE + 1));
if ((mask_width % 2) == 0)
{
index1 += KERNEL_SUBSAMPLE >> 1;
if (index1 > KERNEL_SUBSAMPLE)
{
index1 -= KERNEL_SUBSAMPLE + 1;
dest_offset_x = 1;
}
}
if ((mask_height % 2) == 0)
{
index2 += KERNEL_SUBSAMPLE >> 1;
if (index2 > KERNEL_SUBSAMPLE)
{
index2 -= KERNEL_SUBSAMPLE + 1;
dest_offset_y = 1;
}
}
if (mask == core->last_subsample_brush_mask &&
! core->subsample_cache_invalid)
{
if (core->subsample_brushes[index2][index1])
return core->subsample_brushes[index2][index1];
}
else
{
gint i, j;
for (i = 0; i < KERNEL_SUBSAMPLE + 1; i++)
for (j = 0; j < KERNEL_SUBSAMPLE + 1; j++)
g_clear_pointer (&core->subsample_brushes[i][j], pika_temp_buf_unref);
core->last_subsample_brush_mask = mask;
core->subsample_cache_invalid = FALSE;
}
mask_format = pika_temp_buf_get_format (mask);
dest = pika_temp_buf_new (mask_width + 2,
mask_height + 2,
mask_format);
clear_edges (dest, dest_offset_y, 0, 0, 0);
core->subsample_brushes[index2][index1] = dest;
if (mask_format == babl_format ("Y u8"))
{
pika_brush_core_subsample_mask_impl<guchar> (mask, dest,
dest_offset_x, dest_offset_y,
index1, index2);
}
else if (mask_format == babl_format ("Y float"))
{
pika_brush_core_subsample_mask_impl<gfloat> (mask, dest,
dest_offset_x, dest_offset_y,
index1, index2);
}
else
{
g_warn_if_reached ();
}
return dest;
}
/* The simple pressure profile
*
* It is: I'(I) = MIN (2 * pressure * I, 1)
*/
class SimplePressure
{
gfloat scale;
public:
SimplePressure (gdouble pressure)
{
scale = 2.0 * pressure;
}
guchar
operator () (guchar x) const
{
gint v = RINT (scale * x);
return MIN (v, 255);
}
gfloat
operator () (gfloat x) const
{
gfloat v = scale * x;
return MIN (v, 1.0f);
}
template <class T>
T
operator () (T x) const = delete;
};
/* The fancy pressure profile
*
* It is: I'(I) = tanh (20 * (pressure - 0.5) * I) : pressure > 0.5
* I'(I) = 1 - tanh (20 * (0.5 - pressure) * (1 - I)) : pressure < 0.5
*
* It looks like:
*
* low pressure medium pressure high pressure
*
* | / --
* | / /
* / / |
* -- / |
*/
class FancyPressure
{
gfloat map[257];
public:
FancyPressure (gdouble pressure)
{
gdouble ds, s, c;
gint i;
ds = (pressure - 0.5) * (20.0 / 256.0);
s = 0;
c = 1.0;
if (ds > 0)
{
for (i = 0; i < 256; i++)
{
map[i] = s / c;
s += c * ds;
c += s * ds;
}
for (i = 0; i < 256; i++)
map[i] = map[i] / map[255];
}
else
{
ds = -ds;
for (i = 255; i >= 0; i--)
{
map[i] = s / c;
s += c * ds;
c += s * ds;
}
for (i = 255; i >= 0; i--)
map[i] = 1.0f - map[i] / map[0];
}
map[256] = map[255];
}
guchar
operator () (guchar x) const
{
return RINT (255.0f * map[x]);
}
gfloat
operator () (gfloat x) const
{
gint i;
gfloat f;
x *= 255.0f;
i = floorf (x);
f = x - i;
return map[i] + (map[i + 1] - map[i]) * f;
}
template <class T>
T
operator () (T x) const = delete;
};
template <class T>
class CachedPressure
{
T map[T (~0) + 1];
public:
template <class Pressure>
CachedPressure (Pressure pressure)
{
gint i;
for (i = 0; i < (gint) G_N_ELEMENTS (map); i++)
map[i] = pressure (T (i));
}
T
operator () (T x) const
{
return map[x];
}
template <class U>
U
operator () (U x) const = delete;
};
template <class T,
class Pressure>
void
pika_brush_core_pressurize_mask_impl (const PikaTempBuf *mask,
PikaTempBuf *dest,
Pressure pressure)
{
gegl_parallel_distribute_range (
pika_temp_buf_get_width (mask) * pika_temp_buf_get_height (mask),
PIXELS_PER_THREAD,
[=] (gint offset, gint size)
{
const T *m;
T *d;
gint i;
m = (const T *) pika_temp_buf_get_data (mask) + offset;
d = ( T *) pika_temp_buf_get_data (dest) + offset;
for (i = 0; i < size; i++)
*d++ = pressure (*m++);
});
}
/* #define FANCY_PRESSURE */
const PikaTempBuf *
pika_brush_core_pressurize_mask (PikaBrushCore *core,
const PikaTempBuf *brush_mask,
gdouble x,
gdouble y,
gdouble pressure)
{
const PikaTempBuf *subsample_mask;
const Babl *subsample_mask_format;
/* Get the raw subsampled mask */
subsample_mask = pika_brush_core_subsample_mask (core,
brush_mask,
x, y);
/* Special case pressure = 0.5 */
if (fabs (pressure - 0.5) <= EPSILON)
return subsample_mask;
g_clear_pointer (&core->pressure_brush, pika_temp_buf_unref);
subsample_mask_format = pika_temp_buf_get_format (subsample_mask);
core->pressure_brush =
pika_temp_buf_new (pika_temp_buf_get_width (brush_mask) + 2,
pika_temp_buf_get_height (brush_mask) + 2,
subsample_mask_format);
#ifdef FANCY_PRESSURE
using Pressure = FancyPressure;
#else
using Pressure = SimplePressure;
#endif
if (subsample_mask_format == babl_format ("Y u8"))
{
pika_brush_core_pressurize_mask_impl<guchar> (subsample_mask,
core->pressure_brush,
CachedPressure<guchar> (
Pressure (pressure)));
}
else if (subsample_mask_format == babl_format ("Y float"))
{
pika_brush_core_pressurize_mask_impl<gfloat> (subsample_mask,
core->pressure_brush,
Pressure (pressure));
}
else
{
g_warn_if_reached ();
}
return core->pressure_brush;
}
template <class T>
static void
pika_brush_core_solidify_mask_impl (const PikaTempBuf *mask,
PikaTempBuf *dest,
gint dest_offset_x,
gint dest_offset_y)
{
gint mask_width = pika_temp_buf_get_width (mask);
gint mask_height = pika_temp_buf_get_height (mask);
gint dest_width = pika_temp_buf_get_width (dest);
gegl_parallel_distribute_area (
GEGL_RECTANGLE (0, 0, mask_width, mask_height),
PIXELS_PER_THREAD,
[=] (const GeglRectangle *area)
{
const T *m;
gfloat *d;
gint i, j;
m = (const T *) pika_temp_buf_get_data (mask) +
area->y * mask_width + area->x;
d = ((gfloat *) pika_temp_buf_get_data (dest) +
((dest_offset_y + 1 + area->y) * dest_width +
(dest_offset_x + 1 + area->x)));
for (i = 0; i < area->height; i++)
{
for (j = 0; j < area->width; j++)
*d++ = (*m++) ? 1.0 : 0.0;
m += mask_width - area->width;
d += dest_width - area->width;
}
});
}
const PikaTempBuf *
pika_brush_core_solidify_mask (PikaBrushCore *core,
const PikaTempBuf *brush_mask,
gdouble x,
gdouble y)
{
PikaTempBuf *dest;
const Babl *brush_mask_format;
gint dest_offset_x = 0;
gint dest_offset_y = 0;
gint brush_mask_width = pika_temp_buf_get_width (brush_mask);
gint brush_mask_height = pika_temp_buf_get_height (brush_mask);
if ((brush_mask_width % 2) == 0)
{
if (x < 0.0)
x = fmod (x, brush_mask_width) + brush_mask_width;
if ((x - floor (x)) >= 0.5)
dest_offset_x++;
}
if ((brush_mask_height % 2) == 0)
{
if (y < 0.0)
y = fmod (y, brush_mask_height) + brush_mask_height;
if ((y - floor (y)) >= 0.5)
dest_offset_y++;
}
if (! core->solid_cache_invalid &&
brush_mask == core->last_solid_brush_mask)
{
if (core->solid_brushes[dest_offset_y][dest_offset_x])
return core->solid_brushes[dest_offset_y][dest_offset_x];
}
else
{
gint i, j;
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], pika_temp_buf_unref);
core->last_solid_brush_mask = brush_mask;
core->solid_cache_invalid = FALSE;
}
brush_mask_format = pika_temp_buf_get_format (brush_mask);
dest = pika_temp_buf_new (brush_mask_width + 2,
brush_mask_height + 2,
babl_format ("Y float"));
clear_edges (dest,
1 + dest_offset_y, 1 - dest_offset_y,
1 + dest_offset_x, 1 - dest_offset_x);
core->solid_brushes[dest_offset_y][dest_offset_x] = dest;
if (brush_mask_format == babl_format ("Y u8"))
{
pika_brush_core_solidify_mask_impl<guchar> (brush_mask, dest,
dest_offset_x, dest_offset_y);
}
else if (brush_mask_format == babl_format ("Y float"))
{
pika_brush_core_solidify_mask_impl<gfloat> (brush_mask, dest,
dest_offset_x, dest_offset_y);
}
else
{
g_warn_if_reached ();
}
return dest;
}

View File

@ -0,0 +1,41 @@
/* 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/>.
*/
#ifndef __PIKA_BRUSH_CORE_LOOPS_H__
#define __PIKA_BRUSH_CORE_LOOPS_H__
const PikaTempBuf * pika_brush_core_subsample_mask (PikaBrushCore *core,
const PikaTempBuf *mask,
gdouble x,
gdouble y);
const PikaTempBuf * pika_brush_core_pressurize_mask (PikaBrushCore *core,
const PikaTempBuf *brush_mask,
gdouble x,
gdouble y,
gdouble pressure);
const PikaTempBuf * pika_brush_core_solidify_mask (PikaBrushCore *core,
const PikaTempBuf *brush_mask,
gdouble x,
gdouble y);
#endif /* __PIKA_BRUSH_CORE_LOOPS_H__ */

1364
app/paint/pikabrushcore.c Normal file

File diff suppressed because it is too large Load Diff

156
app/paint/pikabrushcore.h Normal file
View File

@ -0,0 +1,156 @@
/* 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/>.
*/
#ifndef __PIKA_BRUSH_CORE_H__
#define __PIKA_BRUSH_CORE_H__
#include "pikapaintcore.h"
#define BRUSH_CORE_SUBSAMPLE 4
#define BRUSH_CORE_SOLID_SUBSAMPLE 2
#define BRUSH_CORE_JITTER_LUTSIZE 360
#define PIKA_TYPE_BRUSH_CORE (pika_brush_core_get_type ())
#define PIKA_BRUSH_CORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_BRUSH_CORE, PikaBrushCore))
#define PIKA_BRUSH_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_BRUSH_CORE, PikaBrushCoreClass))
#define PIKA_IS_BRUSH_CORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_BRUSH_CORE))
#define PIKA_IS_BRUSH_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_BRUSH_CORE))
#define PIKA_BRUSH_CORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_BRUSH_CORE, PikaBrushCoreClass))
typedef struct _PikaBrushCoreClass PikaBrushCoreClass;
struct _PikaBrushCore
{
PikaPaintCore parent_instance;
PikaBrush *main_brush;
PikaBrush *brush;
PikaDynamics *dynamics;
gdouble spacing;
gdouble scale;
gdouble aspect_ratio;
gdouble angle;
gboolean reflect;
gdouble hardness;
gdouble symmetry_angle;
gboolean symmetry_reflect;
/* brush buffers */
PikaTempBuf *pressure_brush;
PikaTempBuf *solid_brushes[BRUSH_CORE_SOLID_SUBSAMPLE][BRUSH_CORE_SOLID_SUBSAMPLE];
const PikaTempBuf *last_solid_brush_mask;
gboolean solid_cache_invalid;
const PikaTempBuf *transform_brush;
const PikaTempBuf *transform_pixmap;
PikaTempBuf *subsample_brushes[BRUSH_CORE_SUBSAMPLE + 1][BRUSH_CORE_SUBSAMPLE + 1];
const PikaTempBuf *last_subsample_brush_mask;
gboolean subsample_cache_invalid;
gdouble jitter;
gdouble jitter_lut_x[BRUSH_CORE_JITTER_LUTSIZE];
gdouble jitter_lut_y[BRUSH_CORE_JITTER_LUTSIZE];
GRand *rand;
};
struct _PikaBrushCoreClass
{
PikaPaintCoreClass parent_class;
/* Set for tools that don't mind if the brush changes while painting */
gboolean handles_changing_brush;
/* Set for tools that don't mind if the brush scales while painting */
gboolean handles_transforming_brush;
/* Set for tools that don't mind if the brush scales mid stroke */
gboolean handles_dynamic_transforming_brush;
void (* set_brush) (PikaBrushCore *core,
PikaBrush *brush);
void (* set_dynamics) (PikaBrushCore *core,
PikaDynamics *brush);
};
GType pika_brush_core_get_type (void) G_GNUC_CONST;
void pika_brush_core_set_brush (PikaBrushCore *core,
PikaBrush *brush);
void pika_brush_core_set_dynamics (PikaBrushCore *core,
PikaDynamics *dynamics);
void pika_brush_core_paste_canvas (PikaBrushCore *core,
PikaDrawable *drawable,
const PikaCoords *coords,
gdouble brush_opacity,
gdouble image_opacity,
PikaLayerMode paint_mode,
PikaBrushApplicationMode brush_hardness,
gdouble dynamic_hardness,
PikaPaintApplicationMode mode);
void pika_brush_core_replace_canvas (PikaBrushCore *core,
PikaDrawable *drawable,
const PikaCoords *coords,
gdouble brush_opacity,
gdouble image_opacity,
PikaBrushApplicationMode brush_hardness,
gdouble dynamic_hardness,
PikaPaintApplicationMode mode);
void pika_brush_core_color_area_with_pixmap
(PikaBrushCore *core,
PikaDrawable *drawable,
const PikaCoords *coords,
GeglBuffer *area,
gint area_x,
gint area_y,
gboolean apply_mask);
const PikaTempBuf * pika_brush_core_get_brush_mask
(PikaBrushCore *core,
const PikaCoords *coords,
PikaBrushApplicationMode brush_hardness,
gdouble dynamic_hardness);
const PikaTempBuf * pika_brush_core_get_brush_pixmap
(PikaBrushCore *core);
void pika_brush_core_eval_transform_dynamics
(PikaBrushCore *core,
PikaImage *image,
PikaPaintOptions *paint_options,
const PikaCoords *coords);
void pika_brush_core_eval_transform_symmetry
(PikaBrushCore *core,
PikaSymmetry *symmetry,
gint stroke);
#endif /* __PIKA_BRUSH_CORE_H__ */

260
app/paint/pikaclone.c Normal file
View File

@ -0,0 +1,260 @@
/* 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 <stdlib.h>
#include <string.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "paint-types.h"
#include "gegl/pika-gegl-apply-operation.h"
#include "gegl/pika-gegl-loops.h"
#include "core/pika.h"
#include "core/pikadrawable.h"
#include "core/pikadynamics.h"
#include "core/pikaerror.h"
#include "core/pikaimage.h"
#include "core/pikapattern.h"
#include "core/pikapickable.h"
#include "core/pikasymmetry.h"
#include "pikaclone.h"
#include "pikacloneoptions.h"
#include "pika-intl.h"
static gboolean pika_clone_start (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
GError **error);
static void pika_clone_motion (PikaSourceCore *source_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
GeglNode *op,
gdouble opacity,
PikaPickable *src_pickable,
GeglBuffer *src_buffer,
GeglRectangle *src_rect,
gint src_offset_x,
gint src_offset_y,
GeglBuffer *paint_buffer,
gint paint_buffer_x,
gint paint_buffer_y,
gint paint_area_offset_x,
gint paint_area_offset_y,
gint paint_area_width,
gint paint_area_height);
static gboolean pika_clone_use_source (PikaSourceCore *source_core,
PikaSourceOptions *options);
G_DEFINE_TYPE (PikaClone, pika_clone, PIKA_TYPE_SOURCE_CORE)
#define parent_class pika_clone_parent_class
void
pika_clone_register (Pika *pika,
PikaPaintRegisterCallback callback)
{
(* callback) (pika,
PIKA_TYPE_CLONE,
PIKA_TYPE_CLONE_OPTIONS,
"pika-clone",
_("Clone"),
"pika-tool-clone");
}
static void
pika_clone_class_init (PikaCloneClass *klass)
{
PikaPaintCoreClass *paint_core_class = PIKA_PAINT_CORE_CLASS (klass);
PikaSourceCoreClass *source_core_class = PIKA_SOURCE_CORE_CLASS (klass);
paint_core_class->start = pika_clone_start;
source_core_class->use_source = pika_clone_use_source;
source_core_class->motion = pika_clone_motion;
}
static void
pika_clone_init (PikaClone *clone)
{
}
static gboolean
pika_clone_start (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
GError **error)
{
PikaCloneOptions *options = PIKA_CLONE_OPTIONS (paint_options);
if (! PIKA_PAINT_CORE_CLASS (parent_class)->start (paint_core, drawables,
paint_options, coords,
error))
{
return FALSE;
}
if (options->clone_type == PIKA_CLONE_PATTERN)
{
if (! pika_context_get_pattern (PIKA_CONTEXT (options)))
{
g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
_("No patterns available for use with this tool."));
return FALSE;
}
}
return TRUE;
}
static void
pika_clone_motion (PikaSourceCore *source_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
GeglNode *op,
gdouble opacity,
PikaPickable *src_pickable,
GeglBuffer *src_buffer,
GeglRectangle *src_rect,
gint src_offset_x,
gint src_offset_y,
GeglBuffer *paint_buffer,
gint paint_buffer_x,
gint paint_buffer_y,
gint paint_area_offset_x,
gint paint_area_offset_y,
gint paint_area_width,
gint paint_area_height)
{
PikaPaintCore *paint_core = PIKA_PAINT_CORE (source_core);
PikaBrushCore *brush_core = PIKA_BRUSH_CORE (source_core);
PikaCloneOptions *options = PIKA_CLONE_OPTIONS (paint_options);
PikaSourceOptions *source_options = PIKA_SOURCE_OPTIONS (paint_options);
PikaContext *context = PIKA_CONTEXT (paint_options);
PikaDynamics *dynamics = brush_core->dynamics;
PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable));
gdouble fade_point;
gdouble force;
if (pika_source_core_use_source (source_core, source_options))
{
if (! op)
{
pika_gegl_buffer_copy (src_buffer,
GEGL_RECTANGLE (src_rect->x,
src_rect->y,
paint_area_width,
paint_area_height),
GEGL_ABYSS_NONE,
paint_buffer,
GEGL_RECTANGLE (paint_area_offset_x,
paint_area_offset_y,
0, 0));
}
else
{
pika_gegl_apply_operation (src_buffer, NULL, NULL, op,
paint_buffer,
GEGL_RECTANGLE (paint_area_offset_x,
paint_area_offset_y,
paint_area_width,
paint_area_height),
FALSE);
}
}
else if (options->clone_type == PIKA_CLONE_PATTERN)
{
PikaPattern *pattern = pika_context_get_pattern (context);
GeglBuffer *src_buffer = pika_pattern_create_buffer (pattern);
src_offset_x += gegl_buffer_get_width (src_buffer) / 2;
src_offset_y += gegl_buffer_get_height (src_buffer) / 2;
gegl_buffer_set_pattern (paint_buffer,
GEGL_RECTANGLE (paint_area_offset_x,
paint_area_offset_y,
paint_area_width,
paint_area_height),
src_buffer,
- paint_buffer_x - src_offset_x,
- paint_buffer_y - src_offset_y);
g_object_unref (src_buffer);
}
else
{
g_return_if_reached ();
}
fade_point = pika_paint_options_get_fade (paint_options, image,
paint_core->pixel_dist);
if (pika_dynamics_is_output_enabled (dynamics, PIKA_DYNAMICS_OUTPUT_FORCE))
force = pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_FORCE,
coords,
paint_options,
fade_point);
else
force = paint_options->brush_force;
pika_brush_core_paste_canvas (PIKA_BRUSH_CORE (paint_core), drawable,
coords,
MIN (opacity, PIKA_OPACITY_OPAQUE),
pika_context_get_opacity (context),
pika_context_get_paint_mode (context),
pika_paint_options_get_brush_mode (paint_options),
force,
/* In fixed mode, paint incremental so the
* individual brushes are properly applied
* on top of each other.
* Otherwise the stuff we paint is seamless
* and we don't need intermediate masking.
*/
source_options->align_mode ==
PIKA_SOURCE_ALIGN_FIXED ?
PIKA_PAINT_INCREMENTAL : PIKA_PAINT_CONSTANT);
}
static gboolean
pika_clone_use_source (PikaSourceCore *source_core,
PikaSourceOptions *options)
{
return PIKA_CLONE_OPTIONS (options)->clone_type == PIKA_CLONE_IMAGE;
}

56
app/paint/pikaclone.h Normal file
View File

@ -0,0 +1,56 @@
/* 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/>.
*/
#ifndef __PIKA_CLONE_H__
#define __PIKA_CLONE_H__
#include "pikasourcecore.h"
#define PIKA_TYPE_CLONE (pika_clone_get_type ())
#define PIKA_CLONE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_CLONE, PikaClone))
#define PIKA_CLONE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_CLONE, PikaCloneClass))
#define PIKA_IS_CLONE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_CLONE))
#define PIKA_IS_CLONE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_CLONE))
#define PIKA_CLONE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_CLONE, PikaCloneClass))
typedef struct _PikaCloneClass PikaCloneClass;
struct _PikaClone
{
PikaSourceCore parent_instance;
};
struct _PikaCloneClass
{
PikaSourceCoreClass parent_class;
};
void pika_clone_register (Pika *pika,
PikaPaintRegisterCallback callback);
GType pika_clone_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_CLONE_H__ */

View File

@ -0,0 +1,119 @@
/* 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 "libpikabase/pikabase.h"
#include "libpikaconfig/pikaconfig.h"
#include "paint-types.h"
#include "pikacloneoptions.h"
#include "pika-intl.h"
enum
{
PROP_0,
PROP_CLONE_TYPE
};
static void pika_clone_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_clone_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
G_DEFINE_TYPE (PikaCloneOptions, pika_clone_options, PIKA_TYPE_SOURCE_OPTIONS)
#define parent_class pika_clone_options_parent_class
static void
pika_clone_options_class_init (PikaCloneOptionsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = pika_clone_options_set_property;
object_class->get_property = pika_clone_options_get_property;
PIKA_CONFIG_PROP_ENUM (object_class, PROP_CLONE_TYPE,
"clone-type",
_("Source"),
NULL,
PIKA_TYPE_CLONE_TYPE,
PIKA_CLONE_IMAGE,
PIKA_PARAM_STATIC_STRINGS);
}
static void
pika_clone_options_init (PikaCloneOptions *options)
{
}
static void
pika_clone_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaCloneOptions *options = PIKA_CLONE_OPTIONS (object);
switch (property_id)
{
case PROP_CLONE_TYPE:
options->clone_type = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_clone_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaCloneOptions *options = PIKA_CLONE_OPTIONS (object);
switch (property_id)
{
case PROP_CLONE_TYPE:
g_value_set_enum (value, options->clone_type);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}

View File

@ -0,0 +1,55 @@
/* 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/>.
*/
#ifndef __PIKA_CLONE_OPTIONS_H__
#define __PIKA_CLONE_OPTIONS_H__
#include "pikasourceoptions.h"
#define PIKA_TYPE_CLONE_OPTIONS (pika_clone_options_get_type ())
#define PIKA_CLONE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_CLONE_OPTIONS, PikaCloneOptions))
#define PIKA_CLONE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_CLONE_OPTIONS, PikaCloneOptionsClass))
#define PIKA_IS_CLONE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_CLONE_OPTIONS))
#define PIKA_IS_CLONE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_CLONE_OPTIONS))
#define PIKA_CLONE_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_CLONE_OPTIONS, PikaCloneOptionsClass))
typedef struct _PikaCloneOptionsClass PikaCloneOptionsClass;
struct _PikaCloneOptions
{
PikaSourceOptions parent_instance;
PikaCloneType clone_type;
};
struct _PikaCloneOptionsClass
{
PikaSourceOptionsClass parent_class;
};
GType pika_clone_options_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_CLONE_OPTIONS_H__ */

290
app/paint/pikaconvolve.c Normal file
View File

@ -0,0 +1,290 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "paint-types.h"
#include "gegl/pika-gegl-loops.h"
#include "core/pika.h"
#include "core/pikabrush.h"
#include "core/pikadrawable.h"
#include "core/pikadynamics.h"
#include "core/pikaimage.h"
#include "core/pikapickable.h"
#include "core/pikasymmetry.h"
#include "core/pikatempbuf.h"
#include "pikaconvolve.h"
#include "pikaconvolveoptions.h"
#include "pika-intl.h"
#define FIELD_COLS 4
#define MIN_BLUR 64 /* (8/9 original pixel) */
#define MAX_BLUR 0.25 /* (1/33 original pixel) */
#define MIN_SHARPEN -512
#define MAX_SHARPEN -64
static void pika_convolve_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time);
static void pika_convolve_motion (PikaPaintCore *paint_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym);
static void pika_convolve_calculate_matrix (PikaConvolve *convolve,
PikaConvolveType type,
gint radius_x,
gint radius_y,
gdouble rate);
static gdouble pika_convolve_sum_matrix (const gfloat *matrix);
G_DEFINE_TYPE (PikaConvolve, pika_convolve, PIKA_TYPE_BRUSH_CORE)
void
pika_convolve_register (Pika *pika,
PikaPaintRegisterCallback callback)
{
(* callback) (pika,
PIKA_TYPE_CONVOLVE,
PIKA_TYPE_CONVOLVE_OPTIONS,
"pika-convolve",
_("Convolve"),
"pika-tool-blur");
}
static void
pika_convolve_class_init (PikaConvolveClass *klass)
{
PikaPaintCoreClass *paint_core_class = PIKA_PAINT_CORE_CLASS (klass);
paint_core_class->paint = pika_convolve_paint;
}
static void
pika_convolve_init (PikaConvolve *convolve)
{
gint i;
for (i = 0; i < 9; i++)
convolve->matrix[i] = 1.0;
convolve->matrix_divisor = 9.0;
}
static void
pika_convolve_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time)
{
g_return_if_fail (g_list_length (drawables) == 1);
switch (paint_state)
{
case PIKA_PAINT_STATE_MOTION:
for (GList *iter = drawables; iter; iter = iter->next)
pika_convolve_motion (paint_core, iter->data, paint_options, sym);
break;
default:
break;
}
}
static void
pika_convolve_motion (PikaPaintCore *paint_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym)
{
PikaConvolve *convolve = PIKA_CONVOLVE (paint_core);
PikaBrushCore *brush_core = PIKA_BRUSH_CORE (paint_core);
PikaConvolveOptions *options = PIKA_CONVOLVE_OPTIONS (paint_options);
PikaContext *context = PIKA_CONTEXT (paint_options);
PikaDynamics *dynamics = PIKA_BRUSH_CORE (paint_core)->dynamics;
PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable));
GeglBuffer *paint_buffer;
gint paint_buffer_x;
gint paint_buffer_y;
PikaTempBuf *temp_buf;
GeglBuffer *convolve_buffer;
gdouble fade_point;
gdouble opacity;
gdouble rate;
PikaCoords coords;
gint off_x, off_y;
gint paint_width, paint_height;
gint n_strokes;
gint i;
fade_point = pika_paint_options_get_fade (paint_options, image,
paint_core->pixel_dist);
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);
opacity = pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_OPACITY,
&coords,
paint_options,
fade_point);
if (opacity == 0.0)
return;
pika_brush_core_eval_transform_dynamics (PIKA_BRUSH_CORE (paint_core),
image,
paint_options,
&coords);
n_strokes = pika_symmetry_get_size (sym);
for (i = 0; i < n_strokes; i++)
{
coords = *(pika_symmetry_get_coords (sym, i));
pika_brush_core_eval_transform_symmetry (brush_core, sym, i);
paint_buffer = pika_paint_core_get_paint_buffer (paint_core, drawable,
paint_options,
PIKA_LAYER_MODE_NORMAL,
&coords,
&paint_buffer_x,
&paint_buffer_y,
&paint_width,
&paint_height);
if (! paint_buffer)
continue;
rate = (options->rate *
pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_RATE,
&coords,
paint_options,
fade_point));
pika_convolve_calculate_matrix (convolve, options->type,
pika_brush_get_width (brush_core->brush) / 2,
pika_brush_get_height (brush_core->brush) / 2,
rate);
/* need a linear buffer for pika_gegl_convolve() */
temp_buf = pika_temp_buf_new (gegl_buffer_get_width (paint_buffer),
gegl_buffer_get_height (paint_buffer),
gegl_buffer_get_format (paint_buffer));
convolve_buffer = pika_temp_buf_create_buffer (temp_buf);
pika_temp_buf_unref (temp_buf);
pika_gegl_buffer_copy (
pika_drawable_get_buffer (drawable),
GEGL_RECTANGLE (paint_buffer_x,
paint_buffer_y,
gegl_buffer_get_width (paint_buffer),
gegl_buffer_get_height (paint_buffer)),
GEGL_ABYSS_NONE,
convolve_buffer,
GEGL_RECTANGLE (0, 0, 0, 0));
pika_gegl_convolve (convolve_buffer,
GEGL_RECTANGLE (0, 0,
gegl_buffer_get_width (convolve_buffer),
gegl_buffer_get_height (convolve_buffer)),
paint_buffer,
GEGL_RECTANGLE (0, 0,
gegl_buffer_get_width (paint_buffer),
gegl_buffer_get_height (paint_buffer)),
convolve->matrix, 3, convolve->matrix_divisor,
PIKA_NORMAL_CONVOL, TRUE);
g_object_unref (convolve_buffer);
pika_brush_core_replace_canvas (brush_core, drawable,
&coords,
MIN (opacity, PIKA_OPACITY_OPAQUE),
pika_context_get_opacity (context),
pika_paint_options_get_brush_mode (paint_options),
1.0,
PIKA_PAINT_INCREMENTAL);
}
}
static void
pika_convolve_calculate_matrix (PikaConvolve *convolve,
PikaConvolveType type,
gint radius_x,
gint radius_y,
gdouble rate)
{
/* find percent of tool pressure */
const gdouble percent = MIN (rate / 100.0, 1.0);
convolve->matrix[0] = (radius_x && radius_y) ? 1.0 : 0.0;
convolve->matrix[1] = (radius_y) ? 1.0 : 0.0;
convolve->matrix[2] = (radius_x && radius_y) ? 1.0 : 0.0;
convolve->matrix[3] = (radius_x) ? 1.0 : 0.0;
/* get the appropriate convolution matrix and size and divisor */
switch (type)
{
case PIKA_CONVOLVE_BLUR:
convolve->matrix[4] = MIN_BLUR + percent * (MAX_BLUR - MIN_BLUR);
break;
case PIKA_CONVOLVE_SHARPEN:
convolve->matrix[4] = MIN_SHARPEN + percent * (MAX_SHARPEN - MIN_SHARPEN);
break;
}
convolve->matrix[5] = (radius_x) ? 1.0 : 0.0;
convolve->matrix[6] = (radius_x && radius_y) ? 1.0 : 0.0;
convolve->matrix[7] = (radius_y) ? 1.0 : 0.0;
convolve->matrix[8] = (radius_x && radius_y) ? 1.0 : 0.0;
convolve->matrix_divisor = pika_convolve_sum_matrix (convolve->matrix);
}
static gdouble
pika_convolve_sum_matrix (const gfloat *matrix)
{
gdouble sum = 0.0;
gint i;
for (i = 0; i < 9; i++)
sum += matrix[i];
return sum;
}

58
app/paint/pikaconvolve.h Normal file
View File

@ -0,0 +1,58 @@
/* 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/>.
*/
#ifndef __PIKA_CONVOLVE_H__
#define __PIKA_CONVOLVE_H__
#include "pikabrushcore.h"
#define PIKA_TYPE_CONVOLVE (pika_convolve_get_type ())
#define PIKA_CONVOLVE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_CONVOLVE, PikaConvolve))
#define PIKA_CONVOLVE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_CONVOLVE, PikaConvolveClass))
#define PIKA_IS_CONVOLVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_CONVOLVE))
#define PIKA_IS_CONVOLVE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_CONVOLVE))
#define PIKA_CONVOLVE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_CONVOLVE, PikaConvolveClass))
typedef struct _PikaConvolveClass PikaConvolveClass;
struct _PikaConvolve
{
PikaBrushCore parent_instance;
gfloat matrix[9];
gfloat matrix_divisor;
};
struct _PikaConvolveClass
{
PikaBrushCoreClass parent_class;
};
void pika_convolve_register (Pika *pika,
PikaPaintRegisterCallback callback);
GType pika_convolve_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_CONVOLVE_H__ */

View File

@ -0,0 +1,134 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikaconfig/pikaconfig.h"
#include "paint-types.h"
#include "pikaconvolveoptions.h"
#include "pika-intl.h"
#define DEFAULT_CONVOLVE_TYPE PIKA_CONVOLVE_BLUR
#define DEFAULT_CONVOLVE_RATE 50.0
enum
{
PROP_0,
PROP_TYPE,
PROP_RATE
};
static void pika_convolve_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_convolve_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
G_DEFINE_TYPE (PikaConvolveOptions, pika_convolve_options,
PIKA_TYPE_PAINT_OPTIONS)
static void
pika_convolve_options_class_init (PikaConvolveOptionsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = pika_convolve_options_set_property;
object_class->get_property = pika_convolve_options_get_property;
PIKA_CONFIG_PROP_ENUM (object_class, PROP_TYPE,
"type",
_("Convolve Type"),
NULL,
PIKA_TYPE_CONVOLVE_TYPE,
DEFAULT_CONVOLVE_TYPE,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_RATE,
"rate",
C_("convolve-tool", "Rate"),
NULL,
0.0, 100.0, DEFAULT_CONVOLVE_RATE,
PIKA_PARAM_STATIC_STRINGS);
}
static void
pika_convolve_options_init (PikaConvolveOptions *options)
{
}
static void
pika_convolve_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaConvolveOptions *options = PIKA_CONVOLVE_OPTIONS (object);
switch (property_id)
{
case PROP_TYPE:
options->type = g_value_get_enum (value);
break;
case PROP_RATE:
options->rate = g_value_get_double (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_convolve_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaConvolveOptions *options = PIKA_CONVOLVE_OPTIONS (object);
switch (property_id)
{
case PROP_TYPE:
g_value_set_enum (value, options->type);
break;
case PROP_RATE:
g_value_set_double (value, options->rate);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}

View File

@ -0,0 +1,56 @@
/* 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/>.
*/
#ifndef __PIKA_CONVOLVE_OPTIONS_H__
#define __PIKA_CONVOLVE_OPTIONS_H__
#include "pikapaintoptions.h"
#define PIKA_TYPE_CONVOLVE_OPTIONS (pika_convolve_options_get_type ())
#define PIKA_CONVOLVE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_CONVOLVE_OPTIONS, PikaConvolveOptions))
#define PIKA_CONVOLVE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_CONVOLVE_OPTIONS, PikaConvolveOptionsClass))
#define PIKA_IS_CONVOLVE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_CONVOLVE_OPTIONS))
#define PIKA_IS_CONVOLVE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_CONVOLVE_OPTIONS))
#define PIKA_CONVOLVE_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_CONVOLVE_OPTIONS, PikaConvolveOptionsClass))
typedef struct _PikaConvolveOptionsClass PikaConvolveOptionsClass;
struct _PikaConvolveOptions
{
PikaPaintOptions parent_instance;
PikaConvolveType type;
gdouble rate;
};
struct _PikaConvolveOptionsClass
{
PikaPaintOptionsClass parent_class;
};
GType pika_convolve_options_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_CONVOLVE_OPTIONS_H__ */

214
app/paint/pikadodgeburn.c Normal file
View File

@ -0,0 +1,214 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikamath/pikamath.h"
#include "paint-types.h"
#include "gegl/pika-gegl-loops.h"
#include "core/pika.h"
#include "core/pikadrawable.h"
#include "core/pikadynamics.h"
#include "core/pikaimage.h"
#include "core/pikasymmetry.h"
#include "pikadodgeburn.h"
#include "pikadodgeburnoptions.h"
#include "pika-intl.h"
static void pika_dodge_burn_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time);
static void pika_dodge_burn_motion (PikaPaintCore *paint_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym);
G_DEFINE_TYPE (PikaDodgeBurn, pika_dodge_burn, PIKA_TYPE_BRUSH_CORE)
#define parent_class pika_dodge_burn_parent_class
void
pika_dodge_burn_register (Pika *pika,
PikaPaintRegisterCallback callback)
{
(* callback) (pika,
PIKA_TYPE_DODGE_BURN,
PIKA_TYPE_DODGE_BURN_OPTIONS,
"pika-dodge-burn",
_("Dodge/Burn"),
"pika-tool-dodge");
}
static void
pika_dodge_burn_class_init (PikaDodgeBurnClass *klass)
{
PikaPaintCoreClass *paint_core_class = PIKA_PAINT_CORE_CLASS (klass);
PikaBrushCoreClass *brush_core_class = PIKA_BRUSH_CORE_CLASS (klass);
paint_core_class->paint = pika_dodge_burn_paint;
brush_core_class->handles_changing_brush = TRUE;
}
static void
pika_dodge_burn_init (PikaDodgeBurn *dodgeburn)
{
}
static void
pika_dodge_burn_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time)
{
g_return_if_fail (g_list_length (drawables) == 1);
switch (paint_state)
{
case PIKA_PAINT_STATE_INIT:
break;
case PIKA_PAINT_STATE_MOTION:
for (GList *iter = drawables; iter; iter = iter->next)
pika_dodge_burn_motion (paint_core, iter->data, paint_options, sym);
break;
case PIKA_PAINT_STATE_FINISH:
break;
}
}
static void
pika_dodge_burn_motion (PikaPaintCore *paint_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym)
{
PikaBrushCore *brush_core = PIKA_BRUSH_CORE (paint_core);
PikaDodgeBurnOptions *options = PIKA_DODGE_BURN_OPTIONS (paint_options);
PikaContext *context = PIKA_CONTEXT (paint_options);
PikaDynamics *dynamics = PIKA_BRUSH_CORE (paint_core)->dynamics;
PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable));
GeglBuffer *src_buffer;
GeglBuffer *paint_buffer;
gint paint_buffer_x;
gint paint_buffer_y;
gdouble fade_point;
gdouble opacity;
gdouble force;
PikaCoords coords;
gint off_x, off_y;
gint paint_width, paint_height;
gint n_strokes;
gint i;
fade_point = pika_paint_options_get_fade (paint_options, image,
paint_core->pixel_dist);
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);
opacity = pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_OPACITY,
&coords,
paint_options,
fade_point);
if (opacity == 0.0)
return;
if (paint_options->application_mode == PIKA_PAINT_CONSTANT)
src_buffer = pika_paint_core_get_orig_image (paint_core, drawable);
else
src_buffer = pika_drawable_get_buffer (drawable);
pika_brush_core_eval_transform_dynamics (brush_core,
image,
paint_options,
&coords);
n_strokes = pika_symmetry_get_size (sym);
for (i = 0; i < n_strokes; i++)
{
coords = *(pika_symmetry_get_coords (sym, i));
pika_brush_core_eval_transform_symmetry (brush_core, sym, i);
paint_buffer = pika_paint_core_get_paint_buffer (paint_core, drawable,
paint_options,
PIKA_LAYER_MODE_NORMAL,
&coords,
&paint_buffer_x,
&paint_buffer_y,
&paint_width,
&paint_height);
if (! paint_buffer)
continue;
/* DodgeBurn the region */
pika_gegl_dodgeburn (src_buffer,
GEGL_RECTANGLE (paint_buffer_x,
paint_buffer_y,
gegl_buffer_get_width (paint_buffer),
gegl_buffer_get_height (paint_buffer)),
paint_buffer,
GEGL_RECTANGLE (0, 0, 0, 0),
options->exposure / 100.0,
options->type,
options->mode);
if (pika_dynamics_is_output_enabled (dynamics, PIKA_DYNAMICS_OUTPUT_FORCE))
force = pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_FORCE,
&coords,
paint_options,
fade_point);
else
force = paint_options->brush_force;
/* Replace the newly dodgedburned area (paint_area) to the image */
pika_brush_core_replace_canvas (PIKA_BRUSH_CORE (paint_core), drawable,
&coords,
MIN (opacity, PIKA_OPACITY_OPAQUE),
pika_context_get_opacity (context),
pika_paint_options_get_brush_mode (paint_options),
force,
paint_options->application_mode);
}
}

55
app/paint/pikadodgeburn.h Normal file
View File

@ -0,0 +1,55 @@
/* 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/>.
*/
#ifndef __PIKA_DODGE_BURN_H__
#define __PIKA_DODGE_BURN_H__
#include "pikabrushcore.h"
#define PIKA_TYPE_DODGE_BURN (pika_dodge_burn_get_type ())
#define PIKA_DODGE_BURN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_DODGE_BURN, PikaDodgeBurn))
#define PIKA_IS_DODGE_BURN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_DODGE_BURN))
#define PIKA_DODGE_BURN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_DODGEBURN, PikaDodgeBurnClass))
#define PIKA_IS_DODGE_BURN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_DODGE_BURN))
typedef struct _PikaDodgeBurnClass PikaDodgeBurnClass;
struct _PikaDodgeBurn
{
PikaBrushCore parent_instance;
};
struct _PikaDodgeBurnClass
{
PikaBrushCoreClass parent_class;
};
void pika_dodge_burn_register (Pika *pika,
PikaPaintRegisterCallback callback);
GType pika_dodge_burn_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_DODGE_BURN_H__ */

View File

@ -0,0 +1,150 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikaconfig/pikaconfig.h"
#include "paint-types.h"
#include "pikadodgeburnoptions.h"
#include "pika-intl.h"
#define DODGE_BURN_DEFAULT_TYPE PIKA_DODGE_BURN_TYPE_DODGE
#define DODGE_BURN_DEFAULT_MODE PIKA_TRANSFER_MIDTONES
#define DODGE_BURN_DEFAULT_EXPOSURE 50.0
enum
{
PROP_0,
PROP_TYPE,
PROP_MODE,
PROP_EXPOSURE
};
static void pika_dodge_burn_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_dodge_burn_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
G_DEFINE_TYPE (PikaDodgeBurnOptions, pika_dodge_burn_options,
PIKA_TYPE_PAINT_OPTIONS)
static void
pika_dodge_burn_options_class_init (PikaDodgeBurnOptionsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = pika_dodge_burn_options_set_property;
object_class->get_property = pika_dodge_burn_options_get_property;
PIKA_CONFIG_PROP_ENUM (object_class, PROP_TYPE,
"type",
_("Type"),
NULL,
PIKA_TYPE_DODGE_BURN_TYPE,
DODGE_BURN_DEFAULT_TYPE,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_ENUM (object_class, PROP_MODE,
"mode",
_("Range"),
NULL,
PIKA_TYPE_TRANSFER_MODE,
DODGE_BURN_DEFAULT_MODE,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_EXPOSURE,
"exposure",
_("Exposure"),
NULL,
0.0, 100.0, DODGE_BURN_DEFAULT_EXPOSURE,
PIKA_PARAM_STATIC_STRINGS);
}
static void
pika_dodge_burn_options_init (PikaDodgeBurnOptions *options)
{
}
static void
pika_dodge_burn_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaDodgeBurnOptions *options = PIKA_DODGE_BURN_OPTIONS (object);
switch (property_id)
{
case PROP_TYPE:
options->type = g_value_get_enum (value);
break;
case PROP_MODE:
options->mode = g_value_get_enum (value);
break;
case PROP_EXPOSURE:
options->exposure = g_value_get_double (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_dodge_burn_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaDodgeBurnOptions *options = PIKA_DODGE_BURN_OPTIONS (object);
switch (property_id)
{
case PROP_TYPE:
g_value_set_enum (value, options->type);
break;
case PROP_MODE:
g_value_set_enum (value, options->mode);
break;
case PROP_EXPOSURE:
g_value_set_double (value, options->exposure);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}

View File

@ -0,0 +1,57 @@
/* 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/>.
*/
#ifndef __PIKA_DODGE_BURN_OPTIONS_H__
#define __PIKA_DODGE_BURN_OPTIONS_H__
#include "pikapaintoptions.h"
#define PIKA_TYPE_DODGE_BURN_OPTIONS (pika_dodge_burn_options_get_type ())
#define PIKA_DODGE_BURN_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_DODGE_BURN_OPTIONS, PikaDodgeBurnOptions))
#define PIKA_DODGE_BURN_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_DODGE_BURN_OPTIONS, PikaDodgeBurnOptionsClass))
#define PIKA_IS_DODGE_BURN_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_DODGE_BURN_OPTIONS))
#define PIKA_IS_DODGE_BURN_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_DODGE_BURN_OPTIONS))
#define PIKA_DODGE_BURN_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_DODGE_BURN_OPTIONS, PikaDodgeBurnOptionsClass))
typedef struct _PikaDodgeBurnOptionsClass PikaDodgeBurnOptionsClass;
struct _PikaDodgeBurnOptions
{
PikaPaintOptions parent_instance;
PikaDodgeBurnType type;
PikaTransferMode mode; /*highlights, midtones, shadows*/
gdouble exposure;
};
struct _PikaDodgeBurnOptionsClass
{
PikaPaintOptionsClass parent_class;
};
GType pika_dodge_burn_options_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_DODGE_BURN_OPTIONS_H__ */

134
app/paint/pikaeraser.c Normal file
View File

@ -0,0 +1,134 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "paint-types.h"
#include "gegl/pika-gegl-utils.h"
#include "core/pika.h"
#include "core/pika-palettes.h"
#include "core/pikadrawable.h"
#include "core/pikadynamics.h"
#include "core/pikaimage.h"
#include "core/pikapickable.h"
#include "core/pikasymmetry.h"
#include "pikaeraser.h"
#include "pikaeraseroptions.h"
#include "pika-intl.h"
static gboolean pika_eraser_get_color_history_color (PikaPaintbrush *paintbrush,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaRGB *color);
static void pika_eraser_get_paint_params (PikaPaintbrush *paintbrush,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
gdouble grad_point,
PikaLayerMode *paint_mode,
PikaPaintApplicationMode *paint_appl_mode,
const PikaTempBuf **paint_pixmap,
PikaRGB *paint_color);
G_DEFINE_TYPE (PikaEraser, pika_eraser, PIKA_TYPE_PAINTBRUSH)
void
pika_eraser_register (Pika *pika,
PikaPaintRegisterCallback callback)
{
(* callback) (pika,
PIKA_TYPE_ERASER,
PIKA_TYPE_ERASER_OPTIONS,
"pika-eraser",
_("Eraser"),
"pika-tool-eraser");
}
static void
pika_eraser_class_init (PikaEraserClass *klass)
{
PikaPaintbrushClass *paintbrush_class = PIKA_PAINTBRUSH_CLASS (klass);
paintbrush_class->get_color_history_color = pika_eraser_get_color_history_color;
paintbrush_class->get_paint_params = pika_eraser_get_paint_params;
}
static void
pika_eraser_init (PikaEraser *eraser)
{
}
static gboolean
pika_eraser_get_color_history_color (PikaPaintbrush *paintbrush,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaRGB *color)
{
/* Erasing on a drawable without alpha is equivalent to
* drawing with background color. So let's save history.
*/
if (! pika_drawable_has_alpha (drawable))
{
PikaContext *context = PIKA_CONTEXT (paint_options);
pika_context_get_background (context, color);
return TRUE;
}
return FALSE;
}
static void
pika_eraser_get_paint_params (PikaPaintbrush *paintbrush,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
gdouble grad_point,
PikaLayerMode *paint_mode,
PikaPaintApplicationMode *paint_appl_mode,
const PikaTempBuf **paint_pixmap,
PikaRGB *paint_color)
{
PikaEraserOptions *options = PIKA_ERASER_OPTIONS (paint_options);
PikaContext *context = PIKA_CONTEXT (paint_options);
pika_context_get_background (context, paint_color);
pika_pickable_srgb_to_image_color (PIKA_PICKABLE (drawable),
paint_color, paint_color);
if (options->anti_erase)
*paint_mode = PIKA_LAYER_MODE_ANTI_ERASE;
else if (pika_drawable_has_alpha (drawable))
*paint_mode = PIKA_LAYER_MODE_ERASE;
else
*paint_mode = PIKA_LAYER_MODE_NORMAL_LEGACY;
}

56
app/paint/pikaeraser.h Normal file
View File

@ -0,0 +1,56 @@
/* 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/>.
*/
#ifndef __PIKA_ERASER_H__
#define __PIKA_ERASER_H__
#include "pikapaintbrush.h"
#define PIKA_TYPE_ERASER (pika_eraser_get_type ())
#define PIKA_ERASER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_ERASER, PikaEraser))
#define PIKA_ERASER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_ERASER, PikaEraserClass))
#define PIKA_IS_ERASER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_ERASER))
#define PIKA_IS_ERASER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_ERASER))
#define PIKA_ERASER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_ERASER, PikaEraserClass))
typedef struct _PikaEraserClass PikaEraserClass;
struct _PikaEraser
{
PikaPaintbrush parent_instance;
};
struct _PikaEraserClass
{
PikaPaintbrushClass parent_class;
};
void pika_eraser_register (Pika *pika,
PikaPaintRegisterCallback callback);
GType pika_eraser_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_ERASER_H__ */

View File

@ -0,0 +1,118 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikaconfig/pikaconfig.h"
#include "paint-types.h"
#include "pikaeraseroptions.h"
#include "pika-intl.h"
#define ERASER_DEFAULT_ANTI_ERASE FALSE
enum
{
PROP_0,
PROP_ANTI_ERASE
};
static void pika_eraser_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_eraser_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
G_DEFINE_TYPE (PikaEraserOptions, pika_eraser_options,
PIKA_TYPE_PAINT_OPTIONS)
static void
pika_eraser_options_class_init (PikaEraserOptionsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = pika_eraser_options_set_property;
object_class->get_property = pika_eraser_options_get_property;
PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_ANTI_ERASE,
"anti-erase",
_("Anti erase"),
NULL,
ERASER_DEFAULT_ANTI_ERASE,
PIKA_PARAM_STATIC_STRINGS);
}
static void
pika_eraser_options_init (PikaEraserOptions *options)
{
}
static void
pika_eraser_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaEraserOptions *options = PIKA_ERASER_OPTIONS (object);
switch (property_id)
{
case PROP_ANTI_ERASE:
options->anti_erase = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_eraser_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaEraserOptions *options = PIKA_ERASER_OPTIONS (object);
switch (property_id)
{
case PROP_ANTI_ERASE:
g_value_set_boolean (value, options->anti_erase);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}

View File

@ -0,0 +1,55 @@
/* 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/>.
*/
#ifndef __PIKA_ERASER_OPTIONS_H__
#define __PIKA_ERASER_OPTIONS_H__
#include "pikapaintoptions.h"
#define PIKA_TYPE_ERASER_OPTIONS (pika_eraser_options_get_type ())
#define PIKA_ERASER_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_ERASER_OPTIONS, PikaEraserOptions))
#define PIKA_ERASER_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_ERASER_OPTIONS, PikaEraserOptionsClass))
#define PIKA_IS_ERASER_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_ERASER_OPTIONS))
#define PIKA_IS_ERASER_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_ERASER_OPTIONS))
#define PIKA_ERASER_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_ERASER_OPTIONS, PikaEraserOptionsClass))
typedef struct _PikaEraserOptionsClass PikaEraserOptionsClass;
struct _PikaEraserOptions
{
PikaPaintOptions parent_instance;
gboolean anti_erase;
};
struct _PikaEraserOptionsClass
{
PikaPaintOptionsClass parent_class;
};
GType pika_eraser_options_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_ERASER_OPTIONS_H__ */

646
app/paint/pikaheal.c Normal file
View File

@ -0,0 +1,646 @@
/* 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
*
* pikaheal.c
* Copyright (C) Jean-Yves Couleaud <cjyves@free.fr>
* Copyright (C) 2013 Loren Merritt
*
* 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 <stdint.h>
#include <string.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikamath/pikamath.h"
#include "paint-types.h"
#include "gegl/pika-gegl-apply-operation.h"
#include "gegl/pika-gegl-loops.h"
#include "core/pikabrush.h"
#include "core/pikadrawable.h"
#include "core/pikadynamics.h"
#include "core/pikaerror.h"
#include "core/pikaimage.h"
#include "core/pikapickable.h"
#include "core/pikatempbuf.h"
#include "pikaheal.h"
#include "pikasourceoptions.h"
#include "pika-intl.h"
/* NOTES
*
* The method used here is similar to the lighting invariant correction
* method but slightly different: we do not divide the RGB components,
* but subtract them I2 = I0 - I1, where I0 is the sample image to be
* corrected, I1 is the reference pattern. Then we solve DeltaI=0
* (Laplace) with I2 Dirichlet conditions at the borders of the
* mask. The solver is a red/black checker Gauss-Seidel with over-relaxation.
* It could benefit from a multi-grid evaluation of an initial solution
* before the main iteration loop.
*
* I reduced the convergence criteria to 0.1% (0.001) as we are
* dealing here with RGB integer components, more is overkill.
*
* Jean-Yves Couleaud cjyves@free.fr
*/
static gboolean pika_heal_start (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
GError **error);
static GeglBuffer * pika_heal_get_paint_buffer (PikaPaintCore *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 void pika_heal_motion (PikaSourceCore *source_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
GeglNode *op,
gdouble opacity,
PikaPickable *src_pickable,
GeglBuffer *src_buffer,
GeglRectangle *src_rect,
gint src_offset_x,
gint src_offset_y,
GeglBuffer *paint_buffer,
gint paint_buffer_x,
gint paint_buffer_y,
gint paint_area_offset_x,
gint paint_area_offset_y,
gint paint_area_width,
gint paint_area_height);
G_DEFINE_TYPE (PikaHeal, pika_heal, PIKA_TYPE_SOURCE_CORE)
#define parent_class pika_heal_parent_class
void
pika_heal_register (Pika *pika,
PikaPaintRegisterCallback callback)
{
(* callback) (pika,
PIKA_TYPE_HEAL,
PIKA_TYPE_SOURCE_OPTIONS,
"pika-heal",
_("Healing"),
"pika-tool-heal");
}
static void
pika_heal_class_init (PikaHealClass *klass)
{
PikaPaintCoreClass *paint_core_class = PIKA_PAINT_CORE_CLASS (klass);
PikaSourceCoreClass *source_core_class = PIKA_SOURCE_CORE_CLASS (klass);
paint_core_class->start = pika_heal_start;
paint_core_class->get_paint_buffer = pika_heal_get_paint_buffer;
source_core_class->motion = pika_heal_motion;
}
static void
pika_heal_init (PikaHeal *heal)
{
}
static gboolean
pika_heal_start (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
GError **error)
{
PikaSourceCore *source_core = PIKA_SOURCE_CORE (paint_core);
if (! PIKA_PAINT_CORE_CLASS (parent_class)->start (paint_core, drawables,
paint_options, coords,
error))
{
return FALSE;
}
if (! source_core->set_source && pika_drawable_is_indexed (drawables->data))
{
g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
_("Healing does not operate on indexed layers."));
return FALSE;
}
return TRUE;
}
static GeglBuffer *
pika_heal_get_paint_buffer (PikaPaintCore *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)
{
return PIKA_PAINT_CORE_CLASS (parent_class)->get_paint_buffer (core,
drawable,
paint_options,
PIKA_LAYER_MODE_NORMAL,
coords,
paint_buffer_x,
paint_buffer_y,
paint_width,
paint_height);
}
/* Subtract bottom from top and store in result as a float
*/
static void
pika_heal_sub (GeglBuffer *top_buffer,
const GeglRectangle *top_rect,
GeglBuffer *bottom_buffer,
const GeglRectangle *bottom_rect,
GeglBuffer *result_buffer,
const GeglRectangle *result_rect)
{
GeglBufferIterator *iter;
const Babl *format = gegl_buffer_get_format (top_buffer);
gint n_components = babl_format_get_n_components (format);
if (n_components == 2)
format = babl_format ("Y'A float");
else if (n_components == 4)
format = babl_format ("R'G'B'A float");
else
g_return_if_reached ();
iter = gegl_buffer_iterator_new (top_buffer, top_rect, 0, format,
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 3);
gegl_buffer_iterator_add (iter, bottom_buffer, bottom_rect, 0, format,
GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
gegl_buffer_iterator_add (iter, result_buffer, result_rect, 0,
babl_format_n (babl_type ("float"), n_components),
GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
while (gegl_buffer_iterator_next (iter))
{
gfloat *t = iter->items[0].data;
gfloat *b = iter->items[1].data;
gfloat *r = iter->items[2].data;
gint length = iter->length * n_components;
while (length--)
*r++ = *t++ - *b++;
}
}
/* Add first to second and store in result
*/
static void
pika_heal_add (GeglBuffer *first_buffer,
const GeglRectangle *first_rect,
GeglBuffer *second_buffer,
const GeglRectangle *second_rect,
GeglBuffer *result_buffer,
const GeglRectangle *result_rect)
{
GeglBufferIterator *iter;
const Babl *format = gegl_buffer_get_format (result_buffer);
gint n_components = babl_format_get_n_components (format);
if (n_components == 2)
format = babl_format ("Y'A float");
else if (n_components == 4)
format = babl_format ("R'G'B'A float");
else
g_return_if_reached ();
iter = gegl_buffer_iterator_new (first_buffer, first_rect, 0,
babl_format_n (babl_type ("float"),
n_components),
GEGL_ACCESS_READ, GEGL_ABYSS_NONE, 3);
gegl_buffer_iterator_add (iter, second_buffer, second_rect, 0, format,
GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
gegl_buffer_iterator_add (iter, result_buffer, result_rect, 0, format,
GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
while (gegl_buffer_iterator_next (iter))
{
gfloat *f = iter->items[0].data;
gfloat *s = iter->items[1].data;
gfloat *r = iter->items[2].data;
gint length = iter->length * n_components;
while (length--)
*r++ = *f++ + *s++;
}
}
#if defined(__SSE__) && defined(__GNUC__) && __GNUC__ >= 4
static float
pika_heal_laplace_iteration_sse (gfloat *pixels,
gfloat *Adiag,
gint *Aidx,
gfloat w,
gint nmask)
{
typedef float v4sf __attribute__((vector_size(16)));
gint i;
v4sf wv = { w, w, w, w };
v4sf err = { 0, 0, 0, 0 };
union { v4sf v; float f[4]; } erru;
#define Xv(j) (*(v4sf*)&pixels[Aidx[i * 5 + j]])
for (i = 0; i < nmask; i++)
{
v4sf a = { Adiag[i], Adiag[i], Adiag[i], Adiag[i] };
v4sf diff = a * Xv(0) - wv * (Xv(1) + Xv(2) + Xv(3) + Xv(4));
Xv(0) -= diff;
err += diff * diff;
}
erru.v = err;
return erru.f[0] + erru.f[1] + erru.f[2] + erru.f[3];
}
#endif
/* Perform one iteration of Gauss-Seidel, and return the sum squared residual.
*/
static float
pika_heal_laplace_iteration (gfloat *pixels,
gfloat *Adiag,
gint *Aidx,
gfloat w,
gint nmask,
gint depth)
{
gint i, k;
gfloat err = 0;
#if defined(__SSE__) && defined(__GNUC__) && __GNUC__ >= 4
if (depth == 4)
return pika_heal_laplace_iteration_sse (pixels, Adiag, Aidx, w, nmask);
#endif
for (i = 0; i < nmask; i++)
{
gint j0 = Aidx[i * 5 + 0];
gint j1 = Aidx[i * 5 + 1];
gint j2 = Aidx[i * 5 + 2];
gint j3 = Aidx[i * 5 + 3];
gint j4 = Aidx[i * 5 + 4];
gfloat a = Adiag[i];
for (k = 0; k < depth; k++)
{
gfloat diff = (a * pixels[j0 + k] -
w * (pixels[j1 + k] +
pixels[j2 + k] +
pixels[j3 + k] +
pixels[j4 + k]));
pixels[j0 + k] -= diff;
err += diff * diff;
}
}
return err;
}
/* Solve the laplace equation for pixels and store the result in-place.
*/
static void
pika_heal_laplace_loop (gfloat *pixels,
gint height,
gint depth,
gint width,
guchar *mask)
{
/* Tolerate a total deviation-from-smoothness of 0.1 LSBs at 8bit depth. */
#define EPSILON (0.1/255)
#define MAX_ITER 500
gint i, j, iter, parity, nmask, zero;
gfloat *Adiag;
gint *Aidx;
gfloat w;
Adiag = g_new (gfloat, width * height);
Aidx = g_new (gint, 5 * width * height);
/* All off-diagonal elements of A are either -1 or 0. We could store it as a
* general-purpose sparse matrix, but that adds some unnecessary overhead to
* the inner loop. Instead, assume exactly 4 off-diagonal elements in each
* row, all of which have value -1. Any row that in fact wants less than 4
* coefs can put them in a dummy column to be multiplied by an empty pixel.
*/
zero = depth * width * height;
memset (pixels + zero, 0, depth * sizeof (gfloat));
/* Construct the system of equations.
* Arrange Aidx in checkerboard order, so that a single linear pass over that
* array results updating all of the red cells and then all of the black cells.
*/
nmask = 0;
for (parity = 0; parity < 2; parity++)
for (i = 0; i < height; i++)
for (j = (i&1)^parity; j < width; j+=2)
if (mask[j + i * width])
{
#define A_NEIGHBOR(o,di,dj) \
if ((dj<0 && j==0) || (dj>0 && j==width-1) || (di<0 && i==0) || (di>0 && i==height-1)) \
Aidx[o + nmask * 5] = zero; \
else \
Aidx[o + nmask * 5] = ((i + di) * width + (j + dj)) * depth;
/* Omit Dirichlet conditions for any neighbors off the
* edge of the canvas.
*/
Adiag[nmask] = 4 - (i==0) - (j==0) - (i==height-1) - (j==width-1);
A_NEIGHBOR (0, 0, 0);
A_NEIGHBOR (1, 0, 1);
A_NEIGHBOR (2, 1, 0);
A_NEIGHBOR (3, 0, -1);
A_NEIGHBOR (4, -1, 0);
nmask++;
}
/* Empirically optimal over-relaxation factor. (Benchmarked on
* round brushes, at least. I don't know whether aspect ratio
* affects it.)
*/
w = 2.0 - 1.0 / (0.1575 * sqrt (nmask) + 0.8);
w *= 0.25;
for (i = 0; i < nmask; i++)
Adiag[i] *= w;
/* Gauss-Seidel with successive over-relaxation */
for (iter = 0; iter < MAX_ITER; iter++)
{
gfloat err = pika_heal_laplace_iteration (pixels, Adiag, Aidx,
w, nmask, depth);
if (err < EPSILON * EPSILON * w * w)
break;
}
g_free (Adiag);
g_free (Aidx);
}
/* Original Algorithm Design:
*
* T. Georgiev, "Photoshop Healing Brush: a Tool for Seamless Cloning
* http://www.tgeorgiev.net/Photoshop_Healing.pdf
*/
static void
pika_heal (GeglBuffer *src_buffer,
const GeglRectangle *src_rect,
GeglBuffer *dest_buffer,
const GeglRectangle *dest_rect,
GeglBuffer *mask_buffer,
const GeglRectangle *mask_rect)
{
const Babl *src_format;
const Babl *dest_format;
gint src_components;
gint dest_components;
gint width;
gint height;
gfloat *diff, *diff_alloc;
GeglBuffer *diff_buffer;
guchar *mask;
src_format = gegl_buffer_get_format (src_buffer);
dest_format = gegl_buffer_get_format (dest_buffer);
src_components = babl_format_get_n_components (src_format);
dest_components = babl_format_get_n_components (dest_format);
width = gegl_buffer_get_width (src_buffer);
height = gegl_buffer_get_height (src_buffer);
g_return_if_fail (src_components == dest_components);
diff_alloc = g_new (gfloat, 4 + (width * height + 1) * src_components);
diff = (gfloat*)(((uintptr_t)diff_alloc + 15) & ~15);
diff_buffer =
gegl_buffer_linear_new_from_data (diff,
babl_format_n (babl_type ("float"),
src_components),
GEGL_RECTANGLE (0, 0, width, height),
GEGL_AUTO_ROWSTRIDE,
(GDestroyNotify) g_free, diff_alloc);
/* subtract pattern from image and store the result as a float in diff */
pika_heal_sub (dest_buffer, dest_rect,
src_buffer, src_rect,
diff_buffer, GEGL_RECTANGLE (0, 0, width, height));
mask = g_new (guchar, mask_rect->width * mask_rect->height);
gegl_buffer_get (mask_buffer, mask_rect, 1.0, babl_format ("Y u8"),
mask, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
pika_heal_laplace_loop (diff, height, src_components, width, mask);
g_free (mask);
/* add solution to original image and store in dest */
pika_heal_add (diff_buffer, GEGL_RECTANGLE (0, 0, width, height),
src_buffer, src_rect,
dest_buffer, dest_rect);
g_object_unref (diff_buffer);
}
static void
pika_heal_motion (PikaSourceCore *source_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
GeglNode *op,
gdouble opacity,
PikaPickable *src_pickable,
GeglBuffer *src_buffer,
GeglRectangle *src_rect,
gint src_offset_x,
gint src_offset_y,
GeglBuffer *paint_buffer,
gint paint_buffer_x,
gint paint_buffer_y,
gint paint_area_offset_x,
gint paint_area_offset_y,
gint paint_area_width,
gint paint_area_height)
{
PikaPaintCore *paint_core = PIKA_PAINT_CORE (source_core);
PikaContext *context = PIKA_CONTEXT (paint_options);
PikaSourceOptions *src_options = PIKA_SOURCE_OPTIONS (paint_options);
PikaDynamics *dynamics = PIKA_BRUSH_CORE (paint_core)->dynamics;
PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable));
GeglBuffer *src_copy;
GeglBuffer *mask_buffer;
PikaPickable *dest_pickable;
const PikaTempBuf *mask_buf;
gdouble fade_point;
gdouble force;
gint mask_off_x;
gint mask_off_y;
gint dest_pickable_off_x;
gint dest_pickable_off_y;
fade_point = pika_paint_options_get_fade (paint_options, image,
paint_core->pixel_dist);
if (pika_dynamics_is_output_enabled (dynamics, PIKA_DYNAMICS_OUTPUT_FORCE))
force = pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_FORCE,
coords,
paint_options,
fade_point);
else
force = paint_options->brush_force;
mask_buf = pika_brush_core_get_brush_mask (PIKA_BRUSH_CORE (source_core),
coords,
PIKA_BRUSH_HARD,
force);
if (! mask_buf)
return;
/* check that all buffers are of the same size */
if (src_rect->width != gegl_buffer_get_width (paint_buffer) ||
src_rect->height != gegl_buffer_get_height (paint_buffer))
{
/* this generally means that the source point has hit the edge
* of the layer, so it is not an error and we should not
* complain, just don't do anything
*/
return;
}
/* heal should work in perceptual space, use R'G'B' instead of RGB */
src_copy = gegl_buffer_new (GEGL_RECTANGLE (paint_area_offset_x,
paint_area_offset_y,
src_rect->width,
src_rect->height),
babl_format ("R'G'B'A float"));
if (! op)
{
pika_gegl_buffer_copy (src_buffer, src_rect, GEGL_ABYSS_NONE,
src_copy, gegl_buffer_get_extent (src_copy));
}
else
{
pika_gegl_apply_operation (src_buffer, NULL, NULL, op,
src_copy, gegl_buffer_get_extent (src_copy),
FALSE);
}
if (src_options->sample_merged)
{
dest_pickable = PIKA_PICKABLE (image);
pika_item_get_offset (PIKA_ITEM (drawable),
&dest_pickable_off_x,
&dest_pickable_off_y);
}
else
{
dest_pickable = PIKA_PICKABLE (drawable);
dest_pickable_off_x = 0;
dest_pickable_off_y = 0;
}
pika_gegl_buffer_copy (pika_pickable_get_buffer (dest_pickable),
GEGL_RECTANGLE (paint_buffer_x + dest_pickable_off_x,
paint_buffer_y + dest_pickable_off_y,
gegl_buffer_get_width (paint_buffer),
gegl_buffer_get_height (paint_buffer)),
GEGL_ABYSS_NONE,
paint_buffer,
GEGL_RECTANGLE (paint_area_offset_x,
paint_area_offset_y,
paint_area_width,
paint_area_height));
mask_buffer = pika_temp_buf_create_buffer ((PikaTempBuf *) mask_buf);
/* find the offset of the brush mask's rect */
{
gint x = (gint) floor (coords->x) - (gegl_buffer_get_width (mask_buffer) >> 1);
gint y = (gint) floor (coords->y) - (gegl_buffer_get_height (mask_buffer) >> 1);
mask_off_x = (x < 0) ? -x : 0;
mask_off_y = (y < 0) ? -y : 0;
}
pika_heal (src_copy, gegl_buffer_get_extent (src_copy),
paint_buffer,
GEGL_RECTANGLE (paint_area_offset_x,
paint_area_offset_y,
paint_area_width,
paint_area_height),
mask_buffer,
GEGL_RECTANGLE (mask_off_x, mask_off_y,
paint_area_width,
paint_area_height));
g_object_unref (src_copy);
g_object_unref (mask_buffer);
/* replace the canvas with our healed data */
pika_brush_core_replace_canvas (PIKA_BRUSH_CORE (paint_core), drawable,
coords,
MIN (opacity, PIKA_OPACITY_OPAQUE),
pika_context_get_opacity (context),
pika_paint_options_get_brush_mode (paint_options),
force,
PIKA_PAINT_INCREMENTAL);
}

56
app/paint/pikaheal.h Normal file
View File

@ -0,0 +1,56 @@
/* 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/>.
*/
#ifndef __PIKA_HEAL_H__
#define __PIKA_HEAL_H__
#include "pikasourcecore.h"
#define PIKA_TYPE_HEAL (pika_heal_get_type ())
#define PIKA_HEAL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_HEAL, PikaHeal))
#define PIKA_HEAL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_HEAL, PikaHealClass))
#define PIKA_IS_HEAL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_HEAL))
#define PIKA_IS_HEAL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_HEAL))
#define PIKA_HEAL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_HEAL, PikaHealClass))
typedef struct _PikaHealClass PikaHealClass;
struct _PikaHeal
{
PikaSourceCore parent_instance;
};
struct _PikaHealClass
{
PikaSourceCoreClass parent_class;
};
void pika_heal_register (Pika *pika,
PikaPaintRegisterCallback callback);
GType pika_heal_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_HEAL_H__ */

879
app/paint/pikaink-blob.c Normal file
View File

@ -0,0 +1,879 @@
/* 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-1999 Spencer Kimball and Peter Mattis
*
* pikaink-blob.c: routines for manipulating scan converted convex polygons.
* Copyright 1998-1999, Owen Taylor <otaylor@gtk.org>
*
* 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 <glib-object.h>
#include "libpikamath/pikamath.h"
#include "paint-types.h"
#include "pikaink-blob.h"
typedef enum
{
EDGE_NONE = 0,
EDGE_LEFT = 1 << 0,
EDGE_RIGHT = 1 << 1
} EdgeType;
/* local function prototypes */
static PikaBlob * pika_blob_new (gint y,
gint height);
static void pika_blob_fill (PikaBlob *b,
EdgeType *present);
static void pika_blob_make_convex (PikaBlob *b,
EdgeType *present);
#if 0
static void pika_blob_line_add_pixel (PikaBlob *b,
gint x,
gint y);
static void pika_blob_line (PikaBlob *b,
gint x0,
gint y0,
gint x1,
gint y1);
#endif
/* public functions */
/* Return blob for the given (convex) polygon
*/
PikaBlob *
pika_blob_polygon (PikaBlobPoint *points,
gint n_points)
{
PikaBlob *result;
EdgeType *present;
gint i;
gint im1;
gint ip1;
gint ymin, ymax;
ymax = points[0].y;
ymin = points[0].y;
for (i = 1; i < n_points; i++)
{
if (points[i].y > ymax)
ymax = points[i].y;
if (points[i].y < ymin)
ymin = points[i].y;
}
result = pika_blob_new (ymin, ymax - ymin + 1);
present = g_new0 (EdgeType, result->height);
im1 = n_points - 1;
i = 0;
ip1 = 1;
for (; i < n_points ; i++)
{
gint sides = 0;
gint j = points[i].y - ymin;
if (points[i].y < points[im1].y)
sides |= EDGE_RIGHT;
else if (points[i].y > points[im1].y)
sides |= EDGE_LEFT;
if (points[ip1].y < points[i].y)
sides |= EDGE_RIGHT;
else if (points[ip1].y > points[i].y)
sides |= EDGE_LEFT;
if (sides & EDGE_RIGHT)
{
if (present[j] & EDGE_RIGHT)
{
result->data[j].right = MAX (result->data[j].right, points[i].x);
}
else
{
present[j] |= EDGE_RIGHT;
result->data[j].right = points[i].x;
}
}
if (sides & EDGE_LEFT)
{
if (present[j] & EDGE_LEFT)
{
result->data[j].left = MIN (result->data[j].left, points[i].x);
}
else
{
present[j] |= EDGE_LEFT;
result->data[j].left = points[i].x;
}
}
im1 = i;
ip1++;
if (ip1 == n_points)
ip1 = 0;
}
pika_blob_fill (result, present);
g_free (present);
return result;
}
/* Scan convert a square specified by _offsets_ of major and minor
* axes, and by center into a blob
*/
PikaBlob *
pika_blob_square (gdouble xc,
gdouble yc,
gdouble xp,
gdouble yp,
gdouble xq,
gdouble yq)
{
PikaBlobPoint points[4];
/* Make sure we order points ccw */
if (xp * yq - xq * yp < 0)
{
xq = -xq;
yq = -yq;
}
points[0].x = xc + xp + xq;
points[0].y = yc + yp + yq;
points[1].x = xc + xp - xq;
points[1].y = yc + yp - yq;
points[2].x = xc - xp - xq;
points[2].y = yc - yp - yq;
points[3].x = xc - xp + xq;
points[3].y = yc - yp + yq;
return pika_blob_polygon (points, 4);
}
/* Scan convert a diamond specified by _offsets_ of major and minor
* axes, and by center into a blob
*/
PikaBlob *
pika_blob_diamond (gdouble xc,
gdouble yc,
gdouble xp,
gdouble yp,
gdouble xq,
gdouble yq)
{
PikaBlobPoint points[4];
/* Make sure we order points ccw */
if (xp * yq - xq * yp < 0)
{
xq = -xq;
yq = -yq;
}
points[0].x = xc + xp;
points[0].y = yc + yp;
points[1].x = xc - xq;
points[1].y = yc - yq;
points[2].x = xc - xp;
points[2].y = yc - yp;
points[3].x = xc + xq;
points[3].y = yc + yq;
return pika_blob_polygon (points, 4);
}
#define TABLE_SIZE 256
#define ELLIPSE_SHIFT 2
#define TABLE_SHIFT 12
#define TOTAL_SHIFT (ELLIPSE_SHIFT + TABLE_SHIFT)
/*
* The choose of this values limits the maximal image_size to
* 16384 x 16384 pixels. The values will overflow as soon as
* x or y > INT_MAX / (1 << (ELLIPSE_SHIFT + TABLE_SHIFT)) / SUBSAMPLE
*
* Alternatively the code could be change the code as follows:
*
* xc_base = floor (xc)
* xc_shift = 0.5 + (xc - xc_base) * (1 << TOTAL_SHIFT);
*
* gint x = xc_base + (xc_shift + c * xp_shift + s * xq_shift +
* (1 << (TOTAL_SHIFT - 1))) >> TOTAL_SHIFT;
*
* which would change the limit from the image to the ellipse size
*
* Update: this change was done, and now there apparently is a limit
* on the ellipse size. I'm too lazy to fully understand what's going
* on here and simply leave this comment here for
* documentation. --Mitch
*/
static gboolean trig_initialized = FALSE;
static gint trig_table[TABLE_SIZE];
/* Scan convert an ellipse specified by _offsets_ of major and
* minor axes, and by center into a blob
*/
PikaBlob *
pika_blob_ellipse (gdouble xc,
gdouble yc,
gdouble xp,
gdouble yp,
gdouble xq,
gdouble yq)
{
PikaBlob *result;
EdgeType *present;
gint i;
gdouble r1, r2;
gint maxy, miny;
gint step;
gdouble max_radius;
gint xc_shift, yc_shift;
gint xp_shift, yp_shift;
gint xq_shift, yq_shift;
gint xc_base, yc_base;
if (! trig_initialized)
{
trig_initialized = TRUE;
for (i = 0; i < 256; i++)
trig_table[i] = 0.5 + sin (i * (G_PI / 128.0)) * (1 << TABLE_SHIFT);
}
/* Make sure we traverse ellipse in ccw direction */
if (xp * yq - xq * yp < 0)
{
xq = -xq;
yq = -yq;
}
/* Compute bounds as if we were drawing a rectangle */
maxy = ceil (yc + fabs (yp) + fabs (yq));
miny = floor (yc - fabs (yp) - fabs (yq));
result = pika_blob_new (miny, maxy - miny + 1);
present = g_new0 (EdgeType, result->height);
xc_base = floor (xc);
yc_base = floor (yc);
/* Figure out a step that will draw most of the points */
r1 = sqrt (xp * xp + yp * yp);
r2 = sqrt (xq * xq + yq * yq);
max_radius = MAX (r1, r2);
step = TABLE_SIZE;
while (step > 1 && (TABLE_SIZE / step < 4 * max_radius))
step >>= 1;
/* Fill in the edge points */
xc_shift = 0.5 + (xc - xc_base) * (1 << TOTAL_SHIFT);
yc_shift = 0.5 + (yc - yc_base) * (1 << TOTAL_SHIFT);
xp_shift = 0.5 + xp * (1 << ELLIPSE_SHIFT);
yp_shift = 0.5 + yp * (1 << ELLIPSE_SHIFT);
xq_shift = 0.5 + xq * (1 << ELLIPSE_SHIFT);
yq_shift = 0.5 + yq * (1 << ELLIPSE_SHIFT);
for (i = 0 ; i < TABLE_SIZE ; i += step)
{
gint s = trig_table[i];
gint c = trig_table[(TABLE_SIZE + TABLE_SIZE / 4 - i) % TABLE_SIZE];
gint x = ((xc_shift + c * xp_shift + s * xq_shift +
(1 << (TOTAL_SHIFT - 1))) >> TOTAL_SHIFT) + xc_base;
gint y = (((yc_shift + c * yp_shift + s * yq_shift +
(1 << (TOTAL_SHIFT - 1))) >> TOTAL_SHIFT)) + yc_base
- result->y;
gint dydi = c * yq_shift - s * yp_shift;
if (dydi <= 0) /* left edge */
{
if (present[y] & EDGE_LEFT)
{
result->data[y].left = MIN (result->data[y].left, x);
}
else
{
present[y] |= EDGE_LEFT;
result->data[y].left = x;
}
}
if (dydi >= 0) /* right edge */
{
if (present[y] & EDGE_RIGHT)
{
result->data[y].right = MAX (result->data[y].right, x);
}
else
{
present[y] |= EDGE_RIGHT;
result->data[y].right = x;
}
}
}
/* Now fill in missing points */
pika_blob_fill (result, present);
g_free (present);
return result;
}
void
pika_blob_bounds (PikaBlob *b,
gint *x,
gint *y,
gint *width,
gint *height)
{
gint i;
gint x0, x1, y0, y1;
i = 0;
while (i < b->height && b->data[i].left > b->data[i].right)
i++;
if (i < b->height)
{
y0 = b->y + i;
x0 = b->data[i].left;
x1 = b->data[i].right + 1;
while (i < b->height && b->data[i].left <= b->data[i].right)
{
x0 = MIN (b->data[i].left, x0);
x1 = MAX (b->data[i].right + 1, x1);
i++;
}
y1 = b->y + i;
}
else
{
x0 = y0 = 0;
x1 = y1 = 0;
}
*x = x0;
*y = y0;
*width = x1 - x0;
*height = y1 - y0;
}
PikaBlob *
pika_blob_convex_union (PikaBlob *b1,
PikaBlob *b2)
{
PikaBlob *result;
gint y;
gint i, j;
EdgeType *present;
/* Create the storage for the result */
y = MIN (b1->y, b2->y);
result = pika_blob_new (y, MAX (b1->y + b1->height, b2->y + b2->height)-y);
if (result->height == 0)
return result;
present = g_new0 (EdgeType, result->height);
/* Initialize spans from original objects */
for (i = 0, j = b1->y-y; i < b1->height; i++, j++)
{
if (b1->data[i].right >= b1->data[i].left)
{
present[j] = EDGE_LEFT | EDGE_RIGHT;
result->data[j].left = b1->data[i].left;
result->data[j].right = b1->data[i].right;
}
}
for (i = 0, j = b2->y - y; i < b2->height; i++, j++)
{
if (b2->data[i].right >= b2->data[i].left)
{
if (present[j])
{
if (result->data[j].left > b2->data[i].left)
result->data[j].left = b2->data[i].left;
if (result->data[j].right < b2->data[i].right)
result->data[j].right = b2->data[i].right;
}
else
{
present[j] = EDGE_LEFT | EDGE_RIGHT;
result->data[j].left = b2->data[i].left;
result->data[j].right = b2->data[i].right;
}
}
}
pika_blob_make_convex (result, present);
g_free (present);
return result;
}
PikaBlob *
pika_blob_duplicate (PikaBlob *b)
{
g_return_val_if_fail (b != NULL, NULL);
return g_memdup2 (b, sizeof (PikaBlob) + sizeof (PikaBlobSpan) * (b->height - 1));
}
#if 0
void
pika_blob_dump (PikaBlob *b)
{
gint i,j;
for (i = 0; i < b->height; i++)
{
for (j = 0; j < b->data[i].left; j++)
putchar (' ');
for (j = b->data[i].left; j <= b->data[i].right; j++)
putchar ('*');
putchar ('\n');
}
}
#endif
/* private functions */
static PikaBlob *
pika_blob_new (gint y,
gint height)
{
PikaBlob *result;
result = g_malloc (sizeof (PikaBlob) + sizeof (PikaBlobSpan) * (height - 1));
result->y = y;
result->height = height;
return result;
}
static void
pika_blob_fill (PikaBlob *b,
EdgeType *present)
{
gint start;
gint x1, x2, i1, i2;
gint i;
/* Mark empty lines at top and bottom as unused */
start = 0;
while (! present[start])
{
b->data[start].left = 0;
b->data[start].right = -1;
start++;
}
if (present[start] != (EDGE_RIGHT | EDGE_LEFT))
{
if (present[start] == EDGE_RIGHT)
b->data[start].left = b->data[start].right;
else
b->data[start].right = b->data[start].left;
present[start] = EDGE_RIGHT | EDGE_LEFT;
}
for (i = b->height - 1; ! present[i]; i--)
{
b->data[i].left = 0;
b->data[i].right = -1;
}
if (present[i] != (EDGE_RIGHT | EDGE_LEFT))
{
if (present[i] == EDGE_RIGHT)
b->data[i].left = b->data[i].right;
else
b->data[i].right = b->data[i].left;
present[i] = EDGE_RIGHT | EDGE_LEFT;
}
/* Restore missing edges */
/* We fill only interior regions of convex hull, as if we were
* filling polygons. But since we draw ellipses with nearest points,
* not interior points, maybe it would look better if we did the
* same here. Probably not a big deal either way after anti-aliasing
*/
/* left edge */
for (i1 = start; i1 < b->height - 2; i1++)
{
/* Find empty gaps */
if (! (present[i1 + 1] & EDGE_LEFT))
{
gint increment; /* fractional part */
gint denom; /* denominator of fraction */
gint step; /* integral step */
gint frac; /* fractional step */
gint reverse;
/* find bottom of gap */
i2 = i1 + 2;
while (i2 < b->height && ! (present[i2] & EDGE_LEFT))
i2++;
if (i2 < b->height)
{
denom = i2 - i1;
x1 = b->data[i1].left;
x2 = b->data[i2].left;
step = (x2 - x1) / denom;
frac = x2 - x1 - step * denom;
if (frac < 0)
{
frac = -frac;
reverse = 1;
}
else
reverse = 0;
increment = 0;
for (i = i1 + 1; i < i2; i++)
{
x1 += step;
increment += frac;
if (increment >= denom)
{
increment -= denom;
x1 += reverse ? -1 : 1;
}
if (increment == 0 || reverse)
b->data[i].left = x1;
else
b->data[i].left = x1 + 1;
}
}
i1 = i2 - 1; /* advance to next possibility */
}
}
/* right edge */
for (i1 = start; i1 < b->height - 2; i1++)
{
/* Find empty gaps */
if (! (present[i1 + 1] & EDGE_RIGHT))
{
gint increment; /* fractional part */
gint denom; /* denominator of fraction */
gint step; /* integral step */
gint frac; /* fractional step */
gint reverse;
/* find bottom of gap */
i2 = i1 + 2;
while (i2 < b->height && ! (present[i2] & EDGE_RIGHT))
i2++;
if (i2 < b->height)
{
denom = i2 - i1;
x1 = b->data[i1].right;
x2 = b->data[i2].right;
step = (x2 - x1) / denom;
frac = x2 - x1 - step * denom;
if (frac < 0)
{
frac = -frac;
reverse = 1;
}
else
reverse = 0;
increment = 0;
for (i = i1 + 1; i<i2; i++)
{
x1 += step;
increment += frac;
if (increment >= denom)
{
increment -= denom;
x1 += reverse ? -1 : 1;
}
if (reverse && increment != 0)
b->data[i].right = x1 - 1;
else
b->data[i].right = x1;
}
}
i1 = i2 - 1; /* advance to next possibility */
}
}
}
static void
pika_blob_make_convex (PikaBlob *b,
EdgeType *present)
{
gint x1, x2, y1, y2, i1, i2;
gint i;
gint start;
/* Walk through edges, deleting points that aren't on convex hull */
start = 0;
while (! present[start])
start++;
/* left edge */
i1 = start - 1;
i2 = start;
x1 = b->data[start].left - b->data[start].right;
y1 = 0;
for (i = start + 1; i < b->height; i++)
{
if (! (present[i] & EDGE_LEFT))
continue;
x2 = b->data[i].left - b->data[i2].left;
y2 = i - i2;
while (x2 * y1 - x1 * y2 < 0) /* clockwise rotation */
{
present[i2] &= ~EDGE_LEFT;
i2 = i1;
while ((--i1) >= start && (! (present[i1] & EDGE_LEFT)));
if (i1 < start)
{
x1 = b->data[start].left - b->data[start].right;
y1 = 0;
}
else
{
x1 = b->data[i2].left - b->data[i1].left;
y1 = i2 - i1;
}
x2 = b->data[i].left - b->data[i2].left;
y2 = i - i2;
}
x1 = x2;
y1 = y2;
i1 = i2;
i2 = i;
}
/* Right edge */
i1 = start -1;
i2 = start;
x1 = b->data[start].right - b->data[start].left;
y1 = 0;
for (i = start + 1; i < b->height; i++)
{
if (! (present[i] & EDGE_RIGHT))
continue;
x2 = b->data[i].right - b->data[i2].right;
y2 = i - i2;
while (x2 * y1 - x1 * y2 > 0) /* counter-clockwise rotation */
{
present[i2] &= ~EDGE_RIGHT;
i2 = i1;
while ((--i1) >= start && (! (present[i1] & EDGE_RIGHT)));
if (i1 < start)
{
x1 = b->data[start].right - b->data[start].left;
y1 = 0;
}
else
{
x1 = b->data[i2].right - b->data[i1].right;
y1 = i2 - i1;
}
x2 = b->data[i].right - b->data[i2].right;
y2 = i - i2;
}
x1 = x2;
y1 = y2;
i1 = i2;
i2 = i;
}
pika_blob_fill (b, present);
}
#if 0
static void
pika_blob_line_add_pixel (PikaBlob *b,
gint x,
gint y)
{
if (b->data[y - b->y].left > b->data[y - b->y].right)
{
b->data[y - b->y].left = b->data[y - b->y].right = x;
}
else
{
b->data[y - b->y].left = MIN (b->data[y - b->y].left, x);
b->data[y - b->y].right = MAX (b->data[y - b->y].right, x);
}
}
static void
pika_blob_line (PikaBlob *b,
gint x0,
gint y0,
gint x1,
gint y1)
{
gint dx, dy, d;
gint incrE, incrNE;
gint x, y;
gint xstep = 1;
gint ystep = 1;
dx = x1 - x0;
dy = y1 - y0;
if (dx < 0)
{
dx = -dx;
xstep = -1;
}
if (dy < 0)
{
dy = -dy;
ystep = -1;
}
/* for (y = y0; y != y1 + ystep ; y += ystep)
{
b->data[y-b->y].left = 0;
b->data[y-b->y].right = -1;
}*/
x = x0;
y = y0;
if (dy < dx)
{
d = 2 * dy - dx; /* initial value of d */
incrE = 2 * dy; /* increment used for move to E */
incrNE = 2 * (dy - dx); /* increment used for move to NE */
pika_blob_line_add_pixel (b, x, y);
while (x != x1)
{
if (d <= 0)
{
d += incrE;
x += xstep;
}
else
{
d += incrNE;
x += xstep;
y += ystep;
}
pika_blob_line_add_pixel (b, x, y);
}
}
else
{
d = 2 * dx - dy; /* initial value of d */
incrE = 2 * dx; /* increment used for move to E */
incrNE = 2 * (dx - dy); /* increment used for move to NE */
pika_blob_line_add_pixel (b, x, y);
while (y != y1)
{
if (d <= 0)
{
d += incrE;
y += ystep;
}
else
{
d += incrNE;
x += xstep;
y += ystep;
}
pika_blob_line_add_pixel (b, x, y);
}
}
}
#endif

91
app/paint/pikaink-blob.h Normal file
View File

@ -0,0 +1,91 @@
/* 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-1999 Spencer Kimball and Peter Mattis
*
* pikaink-blob.h: routines for manipulating scan converted convex polygons.
* Copyright 1998, Owen Taylor <otaylor@gtk.org>
*
* 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/>.
*
*/
#ifndef __PIKA_INK_BLOB_H__
#define __PIKA_INK_BLOB_H__
typedef struct _PikaBlobPoint PikaBlobPoint;
typedef struct _PikaBlobSpan PikaBlobSpan;
typedef struct _PikaBlob PikaBlob;
typedef PikaBlob * (* PikaBlobFunc) (gdouble xc,
gdouble yc,
gdouble xp,
gdouble yp,
gdouble xq,
gdouble yq);
struct _PikaBlobPoint
{
gint x;
gint y;
};
struct _PikaBlobSpan
{
gint left;
gint right;
};
struct _PikaBlob
{
gint y;
gint height;
PikaBlobSpan data[1];
};
PikaBlob * pika_blob_polygon (PikaBlobPoint *points,
gint n_points);
PikaBlob * pika_blob_square (gdouble xc,
gdouble yc,
gdouble xp,
gdouble yp,
gdouble xq,
gdouble yq);
PikaBlob * pika_blob_diamond (gdouble xc,
gdouble yc,
gdouble xp,
gdouble yp,
gdouble xq,
gdouble yq);
PikaBlob * pika_blob_ellipse (gdouble xc,
gdouble yc,
gdouble xp,
gdouble yp,
gdouble xq,
gdouble yq);
void pika_blob_bounds (PikaBlob *b,
gint *x,
gint *y,
gint *width,
gint *height);
PikaBlob * pika_blob_convex_union (PikaBlob *b1,
PikaBlob *b2);
PikaBlob * pika_blob_duplicate (PikaBlob *b);
#endif /* __PIKA_INK_BLOB_H__ */

800
app/paint/pikaink.c Normal file
View File

@ -0,0 +1,800 @@
/* 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;
pika_blob_bounds (ink->cur_blob, &x, &y, &width, &height);
dwidth = pika_item_get_width (PIKA_ITEM (drawable));
dheight = pika_item_get_height (PIKA_ITEM (drawable));
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 *blob_unions = NULL;
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);
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, 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);
blob_unions = g_list_prepend (blob_unions, 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));
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 (blob_unions, g_free);
g_list_free (blobs_to_render);
}
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);
}
}
}

62
app/paint/pikaink.h Normal file
View File

@ -0,0 +1,62 @@
/* 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/>.
*/
#ifndef __PIKA_INK_H__
#define __PIKA_INK_H__
#include "pikapaintcore.h"
#include "pikaink-blob.h"
#define PIKA_TYPE_INK (pika_ink_get_type ())
#define PIKA_INK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_INK, PikaInk))
#define PIKA_INK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_INK, PikaInkClass))
#define PIKA_IS_INK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_INK))
#define PIKA_IS_INK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_INK))
#define PIKA_INK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_INK, PikaInkClass))
typedef struct _PikaInkClass PikaInkClass;
struct _PikaInk
{
PikaPaintCore parent_instance;
GList *start_blobs; /* starting blobs per stroke (for undo) */
PikaBlob *cur_blob; /* current blob */
GList *last_blobs; /* blobs for last stroke positions */
};
struct _PikaInkClass
{
PikaPaintCoreClass parent_class;
};
void pika_ink_register (Pika *pika,
PikaPaintRegisterCallback callback);
GType pika_ink_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_INK_H__ */

213
app/paint/pikainkoptions.c Normal file
View File

@ -0,0 +1,213 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikaconfig/pikaconfig.h"
#include "paint-types.h"
#include "core/pika.h"
#include "core/pikadrawable.h"
#include "core/pikapaintinfo.h"
#include "pikainkoptions.h"
#include "pikaink-blob.h"
#include "pika-intl.h"
enum
{
PROP_0,
PROP_SIZE,
PROP_TILT_ANGLE,
PROP_SIZE_SENSITIVITY,
PROP_VEL_SENSITIVITY,
PROP_TILT_SENSITIVITY,
PROP_BLOB_TYPE,
PROP_BLOB_ASPECT,
PROP_BLOB_ANGLE
};
static void pika_ink_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_ink_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
G_DEFINE_TYPE (PikaInkOptions, pika_ink_options, PIKA_TYPE_PAINT_OPTIONS)
static void
pika_ink_options_class_init (PikaInkOptionsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = pika_ink_options_set_property;
object_class->get_property = pika_ink_options_get_property;
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_SIZE,
"size",
_("Size"),
_("Ink Blob Size"),
0.0, 200.0, 16.0,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_TILT_ANGLE,
"tilt-angle",
_("Angle"),
NULL,
-90.0, 90.0, 0.0,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_SIZE_SENSITIVITY,
"size-sensitivity",
_("Size"),
NULL,
0.0, 1.0, 1.0,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_VEL_SENSITIVITY,
"vel-sensitivity",
_("Speed"),
NULL,
0.0, 1.0, 0.8,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_TILT_SENSITIVITY,
"tilt-sensitivity",
_("Tilt"),
NULL,
0.0, 1.0, 0.4,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_ENUM (object_class, PROP_BLOB_TYPE,
"blob-type",
_("Shape"),
NULL,
PIKA_TYPE_INK_BLOB_TYPE,
PIKA_INK_BLOB_TYPE_CIRCLE,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_BLOB_ASPECT,
"blob-aspect",
_("Aspect ratio"),
_("Ink Blob Aspect Ratio"),
1.0, 10.0, 1.0,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_BLOB_ANGLE,
"blob-angle",
_("Angle"),
_("Ink Blob Angle"),
-G_PI, G_PI, 0.0,
PIKA_PARAM_STATIC_STRINGS);
}
static void
pika_ink_options_init (PikaInkOptions *options)
{
}
static void
pika_ink_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaInkOptions *options = PIKA_INK_OPTIONS (object);
switch (property_id)
{
case PROP_SIZE:
options->size = g_value_get_double (value);
break;
case PROP_TILT_ANGLE:
options->tilt_angle = g_value_get_double (value);
break;
case PROP_SIZE_SENSITIVITY:
options->size_sensitivity = g_value_get_double (value);
break;
case PROP_VEL_SENSITIVITY:
options->vel_sensitivity = g_value_get_double (value);
break;
case PROP_TILT_SENSITIVITY:
options->tilt_sensitivity = g_value_get_double (value);
break;
case PROP_BLOB_TYPE:
options->blob_type = g_value_get_enum (value);
break;
case PROP_BLOB_ASPECT:
options->blob_aspect = g_value_get_double (value);
break;
case PROP_BLOB_ANGLE:
options->blob_angle = g_value_get_double (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_ink_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaInkOptions *options = PIKA_INK_OPTIONS (object);
switch (property_id)
{
case PROP_SIZE:
g_value_set_double (value, options->size);
break;
case PROP_TILT_ANGLE:
g_value_set_double (value, options->tilt_angle);
break;
case PROP_SIZE_SENSITIVITY:
g_value_set_double (value, options->size_sensitivity);
break;
case PROP_VEL_SENSITIVITY:
g_value_set_double (value, options->vel_sensitivity);
break;
case PROP_TILT_SENSITIVITY:
g_value_set_double (value, options->tilt_sensitivity);
break;
case PROP_BLOB_TYPE:
g_value_set_enum (value, options->blob_type);
break;
case PROP_BLOB_ASPECT:
g_value_set_double (value, options->blob_aspect);
break;
case PROP_BLOB_ANGLE:
g_value_set_double (value, options->blob_angle);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}

View File

@ -0,0 +1,64 @@
/* 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/>.
*/
#ifndef __PIKA_INK_OPTIONS_H__
#define __PIKA_INK_OPTIONS_H__
#include "pikapaintoptions.h"
#define PIKA_TYPE_INK_OPTIONS (pika_ink_options_get_type ())
#define PIKA_INK_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_INK_OPTIONS, PikaInkOptions))
#define PIKA_INK_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_INK_OPTIONS, PikaInkOptionsClass))
#define PIKA_IS_INK_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_INK_OPTIONS))
#define PIKA_IS_INK_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_INK_OPTIONS))
#define PIKA_INK_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_INK_OPTIONS, PikaInkOptionsClass))
typedef struct _PikaInkOptionsClass PikaInkOptionsClass;
struct _PikaInkOptions
{
PikaPaintOptions parent_instance;
gdouble size;
gdouble tilt_angle;
gdouble size_sensitivity;
gdouble vel_sensitivity;
gdouble tilt_sensitivity;
PikaInkBlobType blob_type;
gdouble blob_aspect;
gdouble blob_angle;
};
struct _PikaInkOptionsClass
{
PikaPaintOptionsClass parent_instance;
};
GType pika_ink_options_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_INK_OPTIONS_H__ */

129
app/paint/pikainkundo.c Normal file
View File

@ -0,0 +1,129 @@
/* 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 "paint-types.h"
#include "pikaink.h"
#include "pikaink-blob.h"
#include "pikainkundo.h"
static void pika_ink_undo_constructed (GObject *object);
static void pika_ink_undo_pop (PikaUndo *undo,
PikaUndoMode undo_mode,
PikaUndoAccumulator *accum);
static void pika_ink_undo_free (PikaUndo *undo,
PikaUndoMode undo_mode);
G_DEFINE_TYPE (PikaInkUndo, pika_ink_undo, PIKA_TYPE_PAINT_CORE_UNDO)
#define parent_class pika_ink_undo_parent_class
static void
pika_ink_undo_class_init (PikaInkUndoClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
PikaUndoClass *undo_class = PIKA_UNDO_CLASS (klass);
object_class->constructed = pika_ink_undo_constructed;
undo_class->pop = pika_ink_undo_pop;
undo_class->free = pika_ink_undo_free;
}
static void
pika_ink_undo_init (PikaInkUndo *undo)
{
undo->last_blobs = NULL;
}
static void
pika_ink_undo_constructed (GObject *object)
{
PikaInkUndo *ink_undo = PIKA_INK_UNDO (object);
PikaInk *ink;
G_OBJECT_CLASS (parent_class)->constructed (object);
pika_assert (PIKA_IS_INK (PIKA_PAINT_CORE_UNDO (ink_undo)->paint_core));
ink = PIKA_INK (PIKA_PAINT_CORE_UNDO (ink_undo)->paint_core);
if (ink->start_blobs)
{
gint i;
PikaBlob *blob;
for (i = 0; i < g_list_length (ink->start_blobs); i++)
{
blob = g_list_nth_data (ink->start_blobs, i);
ink_undo->last_blobs = g_list_prepend (ink_undo->last_blobs,
pika_blob_duplicate (blob));
}
ink_undo->last_blobs = g_list_reverse (ink_undo->last_blobs);
}
}
static void
pika_ink_undo_pop (PikaUndo *undo,
PikaUndoMode undo_mode,
PikaUndoAccumulator *accum)
{
PikaInkUndo *ink_undo = PIKA_INK_UNDO (undo);
PIKA_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
if (PIKA_PAINT_CORE_UNDO (ink_undo)->paint_core)
{
PikaInk *ink = PIKA_INK (PIKA_PAINT_CORE_UNDO (ink_undo)->paint_core);
GList *tmp_blobs;
tmp_blobs = ink->last_blobs;
ink->last_blobs = ink_undo->last_blobs;
ink_undo->last_blobs = tmp_blobs;
}
}
static void
pika_ink_undo_free (PikaUndo *undo,
PikaUndoMode undo_mode)
{
PikaInkUndo *ink_undo = PIKA_INK_UNDO (undo);
if (ink_undo->last_blobs)
{
g_list_free_full (ink_undo->last_blobs, g_free);
ink_undo->last_blobs = NULL;
}
PIKA_UNDO_CLASS (parent_class)->free (undo, undo_mode);
}

56
app/paint/pikainkundo.h Normal file
View File

@ -0,0 +1,56 @@
/* 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/>.
*/
#ifndef __PIKA_INK_UNDO_H__
#define __PIKA_INK_UNDO_H__
#include "pikapaintcoreundo.h"
#define PIKA_TYPE_INK_UNDO (pika_ink_undo_get_type ())
#define PIKA_INK_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_INK_UNDO, PikaInkUndo))
#define PIKA_INK_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_INK_UNDO, PikaInkUndoClass))
#define PIKA_IS_INK_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_INK_UNDO))
#define PIKA_IS_INK_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_INK_UNDO))
#define PIKA_INK_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_INK_UNDO, PikaInkUndoClass))
typedef struct _PikaInkUndo PikaInkUndo;
typedef struct _PikaInkUndoClass PikaInkUndoClass;
struct _PikaInkUndo
{
PikaPaintCoreUndo parent_instance;
GList *last_blobs;
};
struct _PikaInkUndoClass
{
PikaPaintCoreUndoClass parent_class;
};
GType pika_ink_undo_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_INK_UNDO_H__ */

434
app/paint/pikamybrushcore.c Normal file
View File

@ -0,0 +1,434 @@
/* 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 <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include <mypaint-brush.h>
#include "libpikamath/pikamath.h"
#include "libpikacolor/pikacolor.h"
#include "paint-types.h"
#include "gegl/pika-gegl-utils.h"
#include "core/pika.h"
#include "core/pika-palettes.h"
#include "core/pikadrawable.h"
#include "core/pikaerror.h"
#include "core/pikamybrush.h"
#include "core/pikapickable.h"
#include "core/pikasymmetry.h"
#include "pikamybrushcore.h"
#include "pikamybrushsurface.h"
#include "pikamybrushoptions.h"
#include "pika-intl.h"
struct _PikaMybrushCorePrivate
{
PikaMybrush *mybrush;
PikaMybrushSurface *surface;
GList *brushes;
gboolean synthetic;
gint64 last_time;
};
/* local function prototypes */
static void pika_mybrush_core_finalize (GObject *object);
static gboolean pika_mybrush_core_start (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
GError **error);
static void pika_mybrush_core_interpolate (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
guint32 time);
static void pika_mybrush_core_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time);
static void pika_mybrush_core_motion (PikaPaintCore *paint_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
guint32 time);
static void pika_mybrush_core_create_brushes (PikaMybrushCore *mybrush,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym);
G_DEFINE_TYPE_WITH_PRIVATE (PikaMybrushCore, pika_mybrush_core,
PIKA_TYPE_PAINT_CORE)
#define parent_class pika_mybrush_core_parent_class
void
pika_mybrush_core_register (Pika *pika,
PikaPaintRegisterCallback callback)
{
(* callback) (pika,
PIKA_TYPE_MYBRUSH_CORE,
PIKA_TYPE_MYBRUSH_OPTIONS,
"pika-mybrush",
_("Mybrush"),
"pika-tool-mypaint-brush");
}
static void
pika_mybrush_core_class_init (PikaMybrushCoreClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
PikaPaintCoreClass *paint_core_class = PIKA_PAINT_CORE_CLASS (klass);
object_class->finalize = pika_mybrush_core_finalize;
paint_core_class->start = pika_mybrush_core_start;
paint_core_class->paint = pika_mybrush_core_paint;
paint_core_class->interpolate = pika_mybrush_core_interpolate;
}
static void
pika_mybrush_core_init (PikaMybrushCore *mybrush)
{
mybrush->private = pika_mybrush_core_get_instance_private (mybrush);
}
static void
pika_mybrush_core_finalize (GObject *object)
{
PikaMybrushCore *core = PIKA_MYBRUSH_CORE (object);
if (core->private->brushes)
{
g_list_free_full (core->private->brushes,
(GDestroyNotify) mypaint_brush_unref);
core->private->brushes = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
pika_mybrush_core_start (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
GError **error)
{
PikaMybrushCore *core = PIKA_MYBRUSH_CORE (paint_core);
PikaContext *context = PIKA_CONTEXT (paint_options);
core->private->mybrush = pika_context_get_mybrush (context);
if (! core->private->mybrush)
{
g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
_("No MyPaint brushes available for use with this tool."));
return FALSE;
}
return TRUE;
}
static void
pika_mybrush_core_interpolate (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
guint32 time)
{
PikaMybrushCore *mybrush = PIKA_MYBRUSH_CORE (paint_core);
/* If this is the first motion the brush has received then
* we're being asked to draw a synthetic stroke in line mode
*/
if (mybrush->private->last_time < 0)
{
PikaCoords saved_coords = paint_core->cur_coords;
paint_core->cur_coords = paint_core->last_coords;
mybrush->private->synthetic = TRUE;
pika_paint_core_paint (paint_core, drawables, paint_options,
PIKA_PAINT_STATE_MOTION, time);
paint_core->cur_coords = saved_coords;
}
pika_paint_core_paint (paint_core, drawables, paint_options,
PIKA_PAINT_STATE_MOTION, time);
paint_core->last_coords = paint_core->cur_coords;
}
static void
pika_mybrush_core_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time)
{
PikaMybrushCore *mybrush = PIKA_MYBRUSH_CORE (paint_core);
PikaContext *context = PIKA_CONTEXT (paint_options);
gint offset_x;
gint offset_y;
PikaRGB fg;
g_return_if_fail (g_list_length (drawables) == 1);
switch (paint_state)
{
case PIKA_PAINT_STATE_INIT:
pika_context_get_foreground (context, &fg);
pika_palettes_add_color_history (context->pika, &fg);
pika_symmetry_set_stateful (sym, TRUE);
pika_item_get_offset (drawables->data, &offset_x, &offset_y);
mybrush->private->surface =
pika_mypaint_surface_new (pika_drawable_get_buffer (drawables->data),
pika_drawable_get_active_mask (drawables->data),
paint_core->mask_buffer,
-offset_x, -offset_y,
PIKA_MYBRUSH_OPTIONS (paint_options));
pika_mybrush_core_create_brushes (mybrush, drawables->data, paint_options, sym);
mybrush->private->last_time = -1;
mybrush->private->synthetic = FALSE;
break;
case PIKA_PAINT_STATE_MOTION:
pika_mybrush_core_motion (paint_core, drawables->data, paint_options,
sym, time);
break;
case PIKA_PAINT_STATE_FINISH:
pika_symmetry_set_stateful (sym, FALSE);
mypaint_surface_unref ((MyPaintSurface *) mybrush->private->surface);
mybrush->private->surface = NULL;
g_list_free_full (mybrush->private->brushes,
(GDestroyNotify) mypaint_brush_unref);
mybrush->private->brushes = NULL;
break;
}
}
static void
pika_mybrush_core_motion (PikaPaintCore *paint_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
guint32 time)
{
PikaMybrushCore *mybrush = PIKA_MYBRUSH_CORE (paint_core);
MyPaintRectangle rect;
PikaCoords coords;
GList *iter;
gdouble dt = 0.0;
gint off_x, off_y;
gint n_strokes;
gint i;
pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y);
n_strokes = pika_symmetry_get_size (sym);
coords = *(pika_symmetry_get_origin (sym));
coords.x -= off_x;
coords.y -= off_y;
pika_symmetry_set_origin (sym, drawable, &coords);
/* The number of strokes may change during a motion, depending on
* the type of symmetry. When that happens, reset the brushes.
*/
if (g_list_length (mybrush->private->brushes) != n_strokes)
{
pika_mybrush_core_create_brushes (mybrush, drawable, paint_options, sym);
}
mypaint_surface_begin_atomic ((MyPaintSurface *) mybrush->private->surface);
if (mybrush->private->last_time < 0)
{
/* First motion, so we need zero pressure events to start the strokes */
for (iter = mybrush->private->brushes, i = 0;
iter;
iter = g_list_next (iter), i++)
{
MyPaintBrush *brush = iter->data;
coords = *(pika_symmetry_get_coords (sym, i));
mypaint_brush_stroke_to (brush,
(MyPaintSurface *) mybrush->private->surface,
coords.x,
coords.y,
0.0f,
coords.xtilt,
coords.ytilt,
1.0f /* Pretend the cursor hasn't moved in a while */);
}
dt = 0.015;
}
else if (mybrush->private->synthetic)
{
PikaVector2 v = { paint_core->cur_coords.x - paint_core->last_coords.x,
paint_core->cur_coords.y - paint_core->last_coords.y };
dt = 0.0005 * pika_vector2_length_val (v);
}
else
{
dt = (time - mybrush->private->last_time) * 0.001;
}
for (iter = mybrush->private->brushes, i = 0;
iter;
iter = g_list_next (iter), i++)
{
MyPaintBrush *brush = iter->data;
PikaCoords coords = *(pika_symmetry_get_coords (sym, i));
gdouble pressure = coords.pressure;
mypaint_brush_stroke_to (brush,
(MyPaintSurface *) mybrush->private->surface,
coords.x,
coords.y,
pressure,
coords.xtilt,
coords.ytilt,
dt);
}
mybrush->private->last_time = time;
mypaint_surface_end_atomic ((MyPaintSurface *) mybrush->private->surface,
&rect);
if (rect.width > 0 && rect.height > 0)
{
paint_core->x1 = MIN (paint_core->x1, rect.x);
paint_core->y1 = MIN (paint_core->y1, rect.y);
paint_core->x2 = MAX (paint_core->x2, rect.x + rect.width);
paint_core->y2 = MAX (paint_core->y2, rect.y + rect.height);
pika_drawable_update (drawable, rect.x, rect.y, rect.width, rect.height);
}
}
static void
pika_mybrush_core_create_brushes (PikaMybrushCore *mybrush,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym)
{
PikaMybrushOptions *options = PIKA_MYBRUSH_OPTIONS (paint_options);
PikaContext *context = PIKA_CONTEXT (paint_options);
PikaRGB fg;
PikaHSV hsv;
gint n_strokes;
gint i;
if (mybrush->private->brushes)
{
g_list_free_full (mybrush->private->brushes,
(GDestroyNotify) mypaint_brush_unref);
mybrush->private->brushes = NULL;
}
if (options->eraser)
pika_context_get_background (context, &fg);
else
pika_context_get_foreground (context, &fg);
pika_pickable_srgb_to_image_color (PIKA_PICKABLE (drawable),
&fg, &fg);
pika_rgb_to_hsv (&fg, &hsv);
n_strokes = pika_symmetry_get_size (sym);
for (i = 0; i < n_strokes; i++)
{
MyPaintBrush *brush = mypaint_brush_new ();
const gchar *brush_data;
mypaint_brush_from_defaults (brush);
brush_data = pika_mybrush_get_brush_json (mybrush->private->mybrush);
if (brush_data)
mypaint_brush_from_string (brush, brush_data);
if (! mypaint_brush_get_base_value (brush,
MYPAINT_BRUSH_SETTING_RESTORE_COLOR))
{
mypaint_brush_set_base_value (brush,
MYPAINT_BRUSH_SETTING_COLOR_H,
hsv.h);
mypaint_brush_set_base_value (brush,
MYPAINT_BRUSH_SETTING_COLOR_S,
hsv.s);
mypaint_brush_set_base_value (brush,
MYPAINT_BRUSH_SETTING_COLOR_V,
hsv.v);
}
mypaint_brush_set_base_value (brush,
MYPAINT_BRUSH_SETTING_RADIUS_LOGARITHMIC,
options->radius);
mypaint_brush_set_base_value (brush,
MYPAINT_BRUSH_SETTING_OPAQUE,
options->opaque *
pika_context_get_opacity (context));
mypaint_brush_set_base_value (brush,
MYPAINT_BRUSH_SETTING_HARDNESS,
options->hardness);
mypaint_brush_set_base_value (brush,
MYPAINT_BRUSH_SETTING_ERASER,
(options->eraser &&
pika_drawable_has_alpha (drawable)) ?
1.0f : 0.0f);
mypaint_brush_new_stroke (brush);
mybrush->private->brushes = g_list_prepend (mybrush->private->brushes,
brush);
}
mybrush->private->brushes = g_list_reverse (mybrush->private->brushes);
}

View File

@ -0,0 +1,59 @@
/* 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/>.
*/
#ifndef __PIKA_MYBRUSH_CORE_H__
#define __PIKA_MYBRUSH_CORE_H__
#include "pikapaintcore.h"
#define PIKA_TYPE_MYBRUSH_CORE (pika_mybrush_core_get_type ())
#define PIKA_MYBRUSH_CORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_MYBRUSH_CORE, PikaMybrushCore))
#define PIKA_MYBRUSH_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_MYBRUSH_CORE, PikaMybrushCoreClass))
#define PIKA_IS_MYBRUSH_CORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_MYBRUSH_CORE))
#define PIKA_IS_MYBRUSH_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_MYBRUSH_CORE))
#define PIKA_MYBRUSH_CORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_MYBRUSH_CORE, PikaMybrushCoreClass))
typedef struct _PikaMybrushCorePrivate PikaMybrushCorePrivate;
typedef struct _PikaMybrushCoreClass PikaMybrushCoreClass;
struct _PikaMybrushCore
{
PikaPaintCore parent_instance;
PikaMybrushCorePrivate *private;
};
struct _PikaMybrushCoreClass
{
PikaPaintCoreClass parent_class;
};
void pika_mybrush_core_register (Pika *pika,
PikaPaintRegisterCallback callback);
GType pika_mybrush_core_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_MYBRUSH_CORE_H__ */

View File

@ -0,0 +1,224 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikaconfig/pikaconfig.h"
#include "paint-types.h"
#include "core/pika.h"
#include "core/pikadrawable.h"
#include "core/pikamybrush.h"
#include "core/pikapaintinfo.h"
#include "pikamybrushoptions.h"
#include "pika-intl.h"
enum
{
PROP_0,
PROP_RADIUS,
PROP_OPAQUE,
PROP_HARDNESS,
PROP_ERASER,
PROP_NO_ERASING
};
static void pika_mybrush_options_config_iface_init (PikaConfigInterface *config_iface);
static void pika_mybrush_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_mybrush_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void pika_mybrush_options_mybrush_changed (PikaContext *context,
PikaMybrush *brush);
static void pika_mybrush_options_reset (PikaConfig *config);
G_DEFINE_TYPE_WITH_CODE (PikaMybrushOptions, pika_mybrush_options,
PIKA_TYPE_PAINT_OPTIONS,
G_IMPLEMENT_INTERFACE (PIKA_TYPE_CONFIG,
pika_mybrush_options_config_iface_init))
static PikaConfigInterface *parent_config_iface = NULL;
static void
pika_mybrush_options_class_init (PikaMybrushOptionsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
PikaContextClass *context_class = PIKA_CONTEXT_CLASS (klass);
object_class->set_property = pika_mybrush_options_set_property;
object_class->get_property = pika_mybrush_options_get_property;
context_class->mybrush_changed = pika_mybrush_options_mybrush_changed;
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_RADIUS,
"radius",
_("Radius"),
NULL,
-2.0, 6.0, 1.0,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_OPAQUE,
"opaque",
_("Base Opacity"),
NULL,
0.0, 2.0, 1.0,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_HARDNESS,
"hardness",
_("Hardness"),
NULL,
0.0, 1.0, 1.0,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_ERASER,
"eraser",
_("Erase with this brush"),
NULL,
FALSE,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_NO_ERASING,
"no-erasing",
_("No erasing effect"),
_("Never decrease alpha of existing pixels"),
FALSE,
PIKA_PARAM_STATIC_STRINGS);
}
static void
pika_mybrush_options_config_iface_init (PikaConfigInterface *config_iface)
{
parent_config_iface = g_type_interface_peek_parent (config_iface);
config_iface->reset = pika_mybrush_options_reset;
}
static void
pika_mybrush_options_init (PikaMybrushOptions *options)
{
}
static void
pika_mybrush_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaMybrushOptions *options = PIKA_MYBRUSH_OPTIONS (object);
switch (property_id)
{
case PROP_RADIUS:
options->radius = g_value_get_double (value);
break;
case PROP_HARDNESS:
options->hardness = g_value_get_double (value);
break;
case PROP_OPAQUE:
options->opaque = g_value_get_double (value);
break;
case PROP_ERASER:
options->eraser = g_value_get_boolean (value);
break;
case PROP_NO_ERASING:
options->no_erasing = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_mybrush_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaMybrushOptions *options = PIKA_MYBRUSH_OPTIONS (object);
switch (property_id)
{
case PROP_RADIUS:
g_value_set_double (value, options->radius);
break;
case PROP_OPAQUE:
g_value_set_double (value, options->opaque);
break;
case PROP_HARDNESS:
g_value_set_double (value, options->hardness);
break;
case PROP_ERASER:
g_value_set_boolean (value, options->eraser);
break;
case PROP_NO_ERASING:
g_value_set_boolean (value, options->no_erasing);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_mybrush_options_mybrush_changed (PikaContext *context,
PikaMybrush *brush)
{
if (brush)
g_object_set (context,
"radius", pika_mybrush_get_radius (brush),
"opaque", pika_mybrush_get_opaque (brush),
"hardness", pika_mybrush_get_hardness (brush),
"eraser", pika_mybrush_get_is_eraser (brush),
NULL);
}
static void
pika_mybrush_options_reset (PikaConfig *config)
{
PikaContext *context = PIKA_CONTEXT (config);
PikaMybrush *brush = pika_context_get_mybrush (context);
parent_config_iface->reset (config);
pika_mybrush_options_mybrush_changed (context, brush);
}

View File

@ -0,0 +1,59 @@
/* 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/>.
*/
#ifndef __PIKA_MYBRUSH_OPTIONS_H__
#define __PIKA_MYBRUSH_OPTIONS_H__
#include "pikapaintoptions.h"
#define PIKA_TYPE_MYBRUSH_OPTIONS (pika_mybrush_options_get_type ())
#define PIKA_MYBRUSH_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_MYBRUSH_OPTIONS, PikaMybrushOptions))
#define PIKA_MYBRUSH_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_MYBRUSH_OPTIONS, PikaMybrushOptionsClass))
#define PIKA_IS_MYBRUSH_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_MYBRUSH_OPTIONS))
#define PIKA_IS_MYBRUSH_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_MYBRUSH_OPTIONS))
#define PIKA_MYBRUSH_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_MYBRUSH_OPTIONS, PikaMybrushOptionsClass))
typedef struct _PikaMybrushOptionsClass PikaMybrushOptionsClass;
struct _PikaMybrushOptions
{
PikaPaintOptions parent_instance;
gdouble radius;
gdouble opaque;
gdouble hardness;
gboolean eraser;
gboolean no_erasing;
};
struct _PikaMybrushOptionsClass
{
PikaPaintOptionsClass parent_instance;
};
GType pika_mybrush_options_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_MYBRUSH_OPTIONS_H__ */

View File

@ -0,0 +1,565 @@
/* 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 <gegl.h>
#include <mypaint-surface.h>
#include "paint-types.h"
#include "libpikamath/pikamath.h"
#include <cairo.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "libpikacolor/pikacolor.h"
#include "pikamybrushoptions.h"
#include "pikamybrushsurface.h"
struct _PikaMybrushSurface
{
MyPaintSurface surface;
GeglBuffer *buffer;
GeglBuffer *paint_mask;
gint paint_mask_x;
gint paint_mask_y;
GeglRectangle dirty;
PikaComponentMask component_mask;
PikaMybrushOptions *options;
};
/* --- Taken from mypaint-tiled-surface.c --- */
static inline float
calculate_rr (int xp,
int yp,
float x,
float y,
float aspect_ratio,
float sn,
float cs,
float one_over_radius2)
{
/* code duplication, see brush::count_dabs_to() */
const float yy = (yp + 0.5f - y);
const float xx = (xp + 0.5f - x);
const float yyr=(yy*cs-xx*sn)*aspect_ratio;
const float xxr=yy*sn+xx*cs;
const float rr = (yyr*yyr + xxr*xxr) * one_over_radius2;
/* rr is in range 0.0..1.0*sqrt(2) */
return rr;
}
static inline float
calculate_r_sample (float x,
float y,
float aspect_ratio,
float sn,
float cs)
{
const float yyr=(y*cs-x*sn)*aspect_ratio;
const float xxr=y*sn+x*cs;
const float r = (yyr*yyr + xxr*xxr);
return r;
}
static inline float
sign_point_in_line (float px,
float py,
float vx,
float vy)
{
return (px - vx) * (-vy) - (vx) * (py - vy);
}
static inline void
closest_point_to_line (float lx,
float ly,
float px,
float py,
float *ox,
float *oy)
{
const float l2 = lx*lx + ly*ly;
const float ltp_dot = px*lx + py*ly;
const float t = ltp_dot / l2;
*ox = lx * t;
*oy = ly * t;
}
/* This works by taking the visibility at the nearest point
* and dividing by 1.0 + delta.
*
* - nearest point: point where the dab has more influence
* - farthest point: point at a fixed distance away from
* the nearest point
* - delta: how much occluded is the farthest point relative
* to the nearest point
*/
static inline float
calculate_rr_antialiased (int xp,
int yp,
float x,
float y,
float aspect_ratio,
float sn,
float cs,
float one_over_radius2,
float r_aa_start)
{
/* calculate pixel position and borders in a way
* that the dab's center is always at zero */
float pixel_right = x - (float)xp;
float pixel_bottom = y - (float)yp;
float pixel_center_x = pixel_right - 0.5f;
float pixel_center_y = pixel_bottom - 0.5f;
float pixel_left = pixel_right - 1.0f;
float pixel_top = pixel_bottom - 1.0f;
float nearest_x, nearest_y; /* nearest to origin, but still inside pixel */
float farthest_x, farthest_y; /* farthest from origin, but still inside pixel */
float r_near, r_far, rr_near, rr_far;
float center_sign, rad_area_1, visibilityNear, delta, delta2;
/* Dab's center is inside pixel? */
if( pixel_left<0 && pixel_right>0 &&
pixel_top<0 && pixel_bottom>0 )
{
nearest_x = 0;
nearest_y = 0;
r_near = rr_near = 0;
}
else
{
closest_point_to_line( cs, sn, pixel_center_x, pixel_center_y, &nearest_x, &nearest_y );
nearest_x = CLAMP( nearest_x, pixel_left, pixel_right );
nearest_y = CLAMP( nearest_y, pixel_top, pixel_bottom );
/* XXX: precision of "nearest" values could be improved
* by intersecting the line that goes from nearest_x/Y to 0
* with the pixel's borders here, however the improvements
* would probably not justify the perdormance cost.
*/
r_near = calculate_r_sample( nearest_x, nearest_y, aspect_ratio, sn, cs );
rr_near = r_near * one_over_radius2;
}
/* out of dab's reach? */
if( rr_near > 1.0f )
return rr_near;
/* check on which side of the dab's line is the pixel center */
center_sign = sign_point_in_line( pixel_center_x, pixel_center_y, cs, -sn );
/* radius of a circle with area=1
* A = pi * r * r
* r = sqrt(1/pi)
*/
rad_area_1 = sqrtf( 1.0f / M_PI );
/* center is below dab */
if( center_sign < 0 )
{
farthest_x = nearest_x - sn*rad_area_1;
farthest_y = nearest_y + cs*rad_area_1;
}
/* above dab */
else
{
farthest_x = nearest_x + sn*rad_area_1;
farthest_y = nearest_y - cs*rad_area_1;
}
r_far = calculate_r_sample( farthest_x, farthest_y, aspect_ratio, sn, cs );
rr_far = r_far * one_over_radius2;
/* check if we can skip heavier AA */
if( r_far < r_aa_start )
return (rr_far+rr_near) * 0.5f;
/* calculate AA approximate */
visibilityNear = 1.0f - rr_near;
delta = rr_far - rr_near;
delta2 = 1.0f + delta;
visibilityNear /= delta2;
return 1.0f - visibilityNear;
}
/* -- end mypaint code */
static inline float
calculate_alpha_for_rr (float rr,
float hardness,
float slope1,
float slope2)
{
if (rr > 1.0f)
return 0.0f;
else if (rr <= hardness)
return 1.0f + rr * slope1;
else
return rr * slope2 - slope2;
}
static GeglRectangle
calculate_dab_roi (float x,
float y,
float radius)
{
int x0 = floor (x - radius);
int x1 = ceil (x + radius);
int y0 = floor (y - radius);
int y1 = ceil (y + radius);
return *GEGL_RECTANGLE (x0, y0, x1 - x0, y1 - y0);
}
static void
pika_mypaint_surface_get_color (MyPaintSurface *base_surface,
float x,
float y,
float radius,
float *color_r,
float *color_g,
float *color_b,
float *color_a)
{
PikaMybrushSurface *surface = (PikaMybrushSurface *)base_surface;
GeglRectangle dabRect;
if (radius < 1.0f)
radius = 1.0f;
dabRect = calculate_dab_roi (x, y, radius);
*color_r = 0.0f;
*color_g = 0.0f;
*color_b = 0.0f;
*color_a = 0.0f;
if (dabRect.width > 0 || dabRect.height > 0)
{
const float one_over_radius2 = 1.0f / (radius * radius);
float sum_weight = 0.0f;
float sum_r = 0.0f;
float sum_g = 0.0f;
float sum_b = 0.0f;
float sum_a = 0.0f;
/* Read in clamp mode to avoid transparency bleeding in at the edges */
GeglBufferIterator *iter = gegl_buffer_iterator_new (surface->buffer, &dabRect, 0,
babl_format ("R'aG'aB'aA float"),
GEGL_BUFFER_READ,
GEGL_ABYSS_CLAMP, 2);
if (surface->paint_mask)
{
GeglRectangle mask_roi = dabRect;
mask_roi.x -= surface->paint_mask_x;
mask_roi.y -= surface->paint_mask_y;
gegl_buffer_iterator_add (iter, surface->paint_mask, &mask_roi, 0,
babl_format ("Y float"),
GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
}
while (gegl_buffer_iterator_next (iter))
{
float *pixel = (float *)iter->items[0].data;
float *mask;
int iy, ix;
if (surface->paint_mask)
mask = iter->items[1].data;
else
mask = NULL;
for (iy = iter->items[0].roi.y; iy < iter->items[0].roi.y + iter->items[0].roi.height; iy++)
{
float yy = (iy + 0.5f - y);
for (ix = iter->items[0].roi.x; ix < iter->items[0].roi.x + iter->items[0].roi.width; ix++)
{
/* pixel_weight == a standard dab with hardness = 0.5, aspect_ratio = 1.0, and angle = 0.0 */
float xx = (ix + 0.5f - x);
float rr = (yy * yy + xx * xx) * one_over_radius2;
float pixel_weight = 0.0f;
if (rr <= 1.0f)
pixel_weight = 1.0f - rr;
if (mask)
pixel_weight *= *mask;
sum_r += pixel_weight * pixel[RED];
sum_g += pixel_weight * pixel[GREEN];
sum_b += pixel_weight * pixel[BLUE];
sum_a += pixel_weight * pixel[ALPHA];
sum_weight += pixel_weight;
pixel += 4;
if (mask)
mask += 1;
}
}
}
if (sum_a > 0.0f && sum_weight > 0.0f)
{
sum_r /= sum_weight;
sum_g /= sum_weight;
sum_b /= sum_weight;
sum_a /= sum_weight;
sum_r /= sum_a;
sum_g /= sum_a;
sum_b /= sum_a;
/* FIXME: Clamping is wrong because GEGL allows alpha > 1, this should probably re-multipy things */
*color_r = CLAMP(sum_r, 0.0f, 1.0f);
*color_g = CLAMP(sum_g, 0.0f, 1.0f);
*color_b = CLAMP(sum_b, 0.0f, 1.0f);
*color_a = CLAMP(sum_a, 0.0f, 1.0f);
}
}
}
static int
pika_mypaint_surface_draw_dab (MyPaintSurface *base_surface,
float x,
float y,
float radius,
float color_r,
float color_g,
float color_b,
float opaque,
float hardness,
float color_a,
float aspect_ratio,
float angle,
float lock_alpha,
float colorize)
{
PikaMybrushSurface *surface = (PikaMybrushSurface *)base_surface;
GeglBufferIterator *iter;
GeglRectangle dabRect;
PikaComponentMask component_mask = surface->component_mask;
const float one_over_radius2 = 1.0f / (radius * radius);
const double angle_rad = angle / 360 * 2 * M_PI;
const float cs = cos(angle_rad);
const float sn = sin(angle_rad);
float normal_mode;
float segment1_slope;
float segment2_slope;
float r_aa_start;
hardness = CLAMP (hardness, 0.0f, 1.0f);
segment1_slope = -(1.0f / hardness - 1.0f);
segment2_slope = -hardness / (1.0f - hardness);
aspect_ratio = MAX (1.0f, aspect_ratio);
r_aa_start = radius - 1.0f;
r_aa_start = MAX (r_aa_start, 0);
r_aa_start = (r_aa_start * r_aa_start) / aspect_ratio;
normal_mode = opaque * (1.0f - colorize);
colorize = opaque * colorize;
/* FIXME: This should use the real matrix values to trim aspect_ratio dabs */
dabRect = calculate_dab_roi (x, y, radius);
gegl_rectangle_intersect (&dabRect, &dabRect, gegl_buffer_get_extent (surface->buffer));
if (dabRect.width <= 0 || dabRect.height <= 0)
return 0;
gegl_rectangle_bounding_box (&surface->dirty, &surface->dirty, &dabRect);
iter = gegl_buffer_iterator_new (surface->buffer, &dabRect, 0,
babl_format ("R'G'B'A float"),
GEGL_BUFFER_READWRITE,
GEGL_ABYSS_NONE, 2);
if (surface->paint_mask)
{
GeglRectangle mask_roi = dabRect;
mask_roi.x -= surface->paint_mask_x;
mask_roi.y -= surface->paint_mask_y;
gegl_buffer_iterator_add (iter, surface->paint_mask, &mask_roi, 0,
babl_format ("Y float"),
GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
}
while (gegl_buffer_iterator_next (iter))
{
float *pixel = (float *)iter->items[0].data;
float *mask;
int iy, ix;
if (surface->paint_mask)
mask = iter->items[1].data;
else
mask = NULL;
for (iy = iter->items[0].roi.y; iy < iter->items[0].roi.y + iter->items[0].roi.height; iy++)
{
for (ix = iter->items[0].roi.x; ix < iter->items[0].roi.x + iter->items[0].roi.width; ix++)
{
float rr, base_alpha, alpha, dst_alpha, r, g, b, a;
if (radius < 3.0f)
rr = calculate_rr_antialiased (ix, iy, x, y, aspect_ratio, sn, cs, one_over_radius2, r_aa_start);
else
rr = calculate_rr (ix, iy, x, y, aspect_ratio, sn, cs, one_over_radius2);
base_alpha = calculate_alpha_for_rr (rr, hardness, segment1_slope, segment2_slope);
alpha = base_alpha * normal_mode;
if (mask)
alpha *= *mask;
dst_alpha = pixel[ALPHA];
/* a = alpha * color_a + dst_alpha * (1.0f - alpha);
* which converts to: */
a = alpha * (color_a - dst_alpha) + dst_alpha;
r = pixel[RED];
g = pixel[GREEN];
b = pixel[BLUE];
if (a > 0.0f)
{
/* By definition the ratio between each color[] and pixel[] component in a non-pre-multipled blend always sums to 1.0f.
* Originally this would have been "(color[n] * alpha * color_a + pixel[n] * dst_alpha * (1.0f - alpha)) / a",
* instead we only calculate the cheaper term. */
float src_term = (alpha * color_a) / a;
float dst_term = 1.0f - src_term;
r = color_r * src_term + r * dst_term;
g = color_g * src_term + g * dst_term;
b = color_b * src_term + b * dst_term;
}
if (colorize > 0.0f && base_alpha > 0.0f)
{
alpha = base_alpha * colorize;
a = alpha + dst_alpha - alpha * dst_alpha;
if (a > 0.0f)
{
PikaHSL pixel_hsl, out_hsl;
PikaRGB pixel_rgb = {color_r, color_g, color_b};
PikaRGB out_rgb = {r, g, b};
float src_term = alpha / a;
float dst_term = 1.0f - src_term;
pika_rgb_to_hsl (&pixel_rgb, &pixel_hsl);
pika_rgb_to_hsl (&out_rgb, &out_hsl);
out_hsl.h = pixel_hsl.h;
out_hsl.s = pixel_hsl.s;
pika_hsl_to_rgb (&out_hsl, &out_rgb);
r = (float)out_rgb.r * src_term + r * dst_term;
g = (float)out_rgb.g * src_term + g * dst_term;
b = (float)out_rgb.b * src_term + b * dst_term;
}
}
if (surface->options->no_erasing)
a = MAX (a, pixel[ALPHA]);
if (component_mask != PIKA_COMPONENT_MASK_ALL)
{
if (component_mask & PIKA_COMPONENT_MASK_RED)
pixel[RED] = r;
if (component_mask & PIKA_COMPONENT_MASK_GREEN)
pixel[GREEN] = g;
if (component_mask & PIKA_COMPONENT_MASK_BLUE)
pixel[BLUE] = b;
if (component_mask & PIKA_COMPONENT_MASK_ALPHA)
pixel[ALPHA] = a;
}
else
{
pixel[RED] = r;
pixel[GREEN] = g;
pixel[BLUE] = b;
pixel[ALPHA] = a;
}
pixel += 4;
if (mask)
mask += 1;
}
}
}
return 1;
}
static void
pika_mypaint_surface_begin_atomic (MyPaintSurface *base_surface)
{
}
static void
pika_mypaint_surface_end_atomic (MyPaintSurface *base_surface,
MyPaintRectangle *roi)
{
PikaMybrushSurface *surface = (PikaMybrushSurface *)base_surface;
roi->x = surface->dirty.x;
roi->y = surface->dirty.y;
roi->width = surface->dirty.width;
roi->height = surface->dirty.height;
surface->dirty = *GEGL_RECTANGLE (0, 0, 0, 0);
}
static void
pika_mypaint_surface_destroy (MyPaintSurface *base_surface)
{
PikaMybrushSurface *surface = (PikaMybrushSurface *)base_surface;
g_clear_object (&surface->buffer);
g_clear_object (&surface->paint_mask);
g_free (surface);
}
PikaMybrushSurface *
pika_mypaint_surface_new (GeglBuffer *buffer,
PikaComponentMask component_mask,
GeglBuffer *paint_mask,
gint paint_mask_x,
gint paint_mask_y,
PikaMybrushOptions *options)
{
PikaMybrushSurface *surface = g_malloc0 (sizeof (PikaMybrushSurface));
mypaint_surface_init ((MyPaintSurface *)surface);
surface->surface.get_color = pika_mypaint_surface_get_color;
surface->surface.draw_dab = pika_mypaint_surface_draw_dab;
surface->surface.begin_atomic = pika_mypaint_surface_begin_atomic;
surface->surface.end_atomic = pika_mypaint_surface_end_atomic;
surface->surface.destroy = pika_mypaint_surface_destroy;
surface->component_mask = component_mask;
surface->options = options;
surface->buffer = g_object_ref (buffer);
if (paint_mask)
surface->paint_mask = g_object_ref (paint_mask);
surface->paint_mask_x = paint_mask_x;
surface->paint_mask_y = paint_mask_y;
surface->dirty = *GEGL_RECTANGLE (0, 0, 0, 0);
return surface;
}

View File

@ -0,0 +1,37 @@
/* 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/>.
*/
#ifndef __PIKA_MYBRUSH_SURFACE_H__
#define __PIKA_MYBRUSH_SURFACE_H__
typedef struct _PikaMybrushSurface PikaMybrushSurface;
PikaMybrushSurface *
pika_mypaint_surface_new (GeglBuffer *buffer,
PikaComponentMask component_mask,
GeglBuffer *paint_mask,
gint paint_mask_x,
gint paint_mask_y,
PikaMybrushOptions *options);
#endif /* __PIKA_MYBRUSH_SURFACE_H__ */

389
app/paint/pikapaintbrush.c Normal file
View File

@ -0,0 +1,389 @@
/* 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 <cairo.h>
#include <gegl.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "libpikacolor/pikacolor.h"
#include "libpikamath/pikamath.h"
#include "libpikabase/pikabase.h"
#include "paint-types.h"
#include "gegl/pika-gegl-utils.h"
#include "core/pika.h"
#include "core/pika-palettes.h"
#include "core/pikabrush.h"
#include "core/pikadrawable.h"
#include "core/pikadynamics.h"
#include "core/pikagradient.h"
#include "core/pikaimage.h"
#include "core/pikapickable.h"
#include "core/pikasymmetry.h"
#include "core/pikatempbuf.h"
#include "pikapaintbrush.h"
#include "pikapaintoptions.h"
#include "pika-intl.h"
static void pika_paintbrush_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time);
static gboolean pika_paintbrush_real_get_color_history_color (PikaPaintbrush *paintbrush,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaRGB *color);
static void pika_paintbrush_real_get_paint_params (PikaPaintbrush *paintbrush,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
gdouble grad_point,
PikaLayerMode *paint_mode,
PikaPaintApplicationMode *paint_appl_mode,
const PikaTempBuf **paint_pixmap,
PikaRGB *paint_color);
G_DEFINE_TYPE (PikaPaintbrush, pika_paintbrush, PIKA_TYPE_BRUSH_CORE)
void
pika_paintbrush_register (Pika *pika,
PikaPaintRegisterCallback callback)
{
(* callback) (pika,
PIKA_TYPE_PAINTBRUSH,
PIKA_TYPE_PAINT_OPTIONS,
"pika-paintbrush",
_("Paintbrush"),
"pika-tool-paintbrush");
}
static void
pika_paintbrush_class_init (PikaPaintbrushClass *klass)
{
PikaPaintCoreClass *paint_core_class = PIKA_PAINT_CORE_CLASS (klass);
PikaBrushCoreClass *brush_core_class = PIKA_BRUSH_CORE_CLASS (klass);
paint_core_class->paint = pika_paintbrush_paint;
brush_core_class->handles_changing_brush = TRUE;
klass->get_color_history_color = pika_paintbrush_real_get_color_history_color;
klass->get_paint_params = pika_paintbrush_real_get_paint_params;
}
static void
pika_paintbrush_init (PikaPaintbrush *paintbrush)
{
}
static void
pika_paintbrush_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time)
{
PikaPaintbrush *paintbrush = PIKA_PAINTBRUSH (paint_core);
g_return_if_fail (g_list_length (drawables) == 1);
switch (paint_state)
{
case PIKA_PAINT_STATE_INIT:
{
PikaRGB color;
for (GList *iter = drawables; iter; iter = iter->next)
if (PIKA_PAINTBRUSH_GET_CLASS (paintbrush)->get_color_history_color &&
PIKA_PAINTBRUSH_GET_CLASS (paintbrush)->get_color_history_color (paintbrush,
iter->data,
paint_options,
&color))
{
PikaContext *context = PIKA_CONTEXT (paint_options);
pika_palettes_add_color_history (context->pika, &color);
}
}
break;
case PIKA_PAINT_STATE_MOTION:
for (GList *iter = drawables; iter; iter = iter->next)
_pika_paintbrush_motion (paint_core, iter->data, paint_options,
sym, PIKA_OPACITY_OPAQUE);
break;
case PIKA_PAINT_STATE_FINISH:
g_clear_weak_pointer (&paintbrush->paint_buffer);
g_clear_pointer (&paintbrush->paint_pixmap, pika_temp_buf_unref);
break;
}
}
static gboolean
pika_paintbrush_real_get_color_history_color (PikaPaintbrush *paintbrush,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaRGB *color)
{
PikaContext *context = PIKA_CONTEXT (paint_options);
PikaBrushCore *brush_core = PIKA_BRUSH_CORE (paintbrush);
PikaDynamics *dynamics = pika_context_get_dynamics (context);
/* We don't save gradient color history and pixmap brushes
* have no color to save.
*/
if (pika_dynamics_is_output_enabled (dynamics, PIKA_DYNAMICS_OUTPUT_COLOR) ||
(brush_core->brush && pika_brush_get_pixmap (brush_core->brush)))
{
return FALSE;
}
pika_context_get_foreground (context, color);
return TRUE;
}
static void
pika_paintbrush_real_get_paint_params (PikaPaintbrush *paintbrush,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
gdouble grad_point,
PikaLayerMode *paint_mode,
PikaPaintApplicationMode *paint_appl_mode,
const PikaTempBuf **paint_pixmap,
PikaRGB *paint_color)
{
PikaPaintCore *paint_core = PIKA_PAINT_CORE (paintbrush);
PikaBrushCore *brush_core = PIKA_BRUSH_CORE (paintbrush);
PikaContext *context = PIKA_CONTEXT (paint_options);
PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable));
*paint_mode = pika_context_get_paint_mode (context);
if (pika_paint_options_get_gradient_color (paint_options, image,
grad_point,
paint_core->pixel_dist,
paint_color))
{
/* optionally take the color from the current gradient */
pika_pickable_srgb_to_image_color (PIKA_PICKABLE (drawable),
paint_color, paint_color);
*paint_appl_mode = PIKA_PAINT_INCREMENTAL;
}
else if (brush_core->brush && pika_brush_get_pixmap (brush_core->brush))
{
/* otherwise check if the brush has a pixmap and use that to
* color the area
*/
*paint_pixmap = pika_brush_core_get_brush_pixmap (brush_core);
*paint_appl_mode = PIKA_PAINT_INCREMENTAL;
}
else
{
/* otherwise fill the area with the foreground color */
pika_context_get_foreground (context, paint_color);
pika_pickable_srgb_to_image_color (PIKA_PICKABLE (drawable),
paint_color, paint_color);
}
}
void
_pika_paintbrush_motion (PikaPaintCore *paint_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
gdouble opacity)
{
PikaBrushCore *brush_core = PIKA_BRUSH_CORE (paint_core);
PikaPaintbrush *paintbrush = PIKA_PAINTBRUSH (paint_core);
PikaContext *context = PIKA_CONTEXT (paint_options);
PikaDynamics *dynamics = brush_core->dynamics;
PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable));
gdouble fade_point;
gdouble grad_point;
gdouble force;
PikaCoords coords;
gint n_strokes;
gint off_x, off_y;
gint i;
fade_point = pika_paint_options_get_fade (paint_options, image,
paint_core->pixel_dist);
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);
/* Some settings are based on the original stroke. */
opacity *= pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_OPACITY,
&coords,
paint_options,
fade_point);
if (opacity == 0.0)
return;
if (PIKA_BRUSH_CORE_GET_CLASS (brush_core)->handles_transforming_brush)
{
pika_brush_core_eval_transform_dynamics (brush_core,
image,
paint_options,
&coords);
}
grad_point = pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_COLOR,
&coords,
paint_options,
fade_point);
n_strokes = pika_symmetry_get_size (sym);
for (i = 0; i < n_strokes; i++)
{
PikaLayerMode paint_mode;
PikaPaintApplicationMode paint_appl_mode;
GeglBuffer *paint_buffer;
gint paint_buffer_x;
gint paint_buffer_y;
const PikaTempBuf *paint_pixmap = NULL;
PikaRGB paint_color;
gint paint_width, paint_height;
paint_appl_mode = paint_options->application_mode;
PIKA_PAINTBRUSH_GET_CLASS (paintbrush)->get_paint_params (paintbrush,
drawable,
paint_options,
sym,
grad_point,
&paint_mode,
&paint_appl_mode,
&paint_pixmap,
&paint_color);
coords = *(pika_symmetry_get_coords (sym, i));
if (PIKA_BRUSH_CORE_GET_CLASS (brush_core)->handles_transforming_brush)
pika_brush_core_eval_transform_symmetry (brush_core, sym, i);
paint_buffer = pika_paint_core_get_paint_buffer (paint_core, drawable,
paint_options,
paint_mode,
&coords,
&paint_buffer_x,
&paint_buffer_y,
&paint_width,
&paint_height);
if (! paint_buffer)
continue;
if (! paint_pixmap)
{
opacity *= paint_color.a;
pika_rgb_set_alpha (&paint_color, PIKA_OPACITY_OPAQUE);
}
/* fill the paint buffer. we can skip this step when reusing the
* previous paint buffer, if the paint color/pixmap hasn't changed
* (unless using an applicator, which currently modifies the paint buffer
* in-place).
*/
if (paint_core->applicators ||
paint_buffer != paintbrush->paint_buffer ||
paint_pixmap != paintbrush->paint_pixmap ||
(! paint_pixmap && (pika_rgba_distance (&paint_color,
&paintbrush->paint_color))))
{
g_set_weak_pointer (&paintbrush->paint_buffer, paint_buffer);
if (paint_pixmap != paintbrush->paint_pixmap)
{
g_clear_pointer (&paintbrush->paint_pixmap, pika_temp_buf_unref);
if (paint_pixmap)
paintbrush->paint_pixmap = pika_temp_buf_ref (paint_pixmap);
}
paintbrush->paint_color = paint_color;
if (paint_pixmap)
{
pika_brush_core_color_area_with_pixmap (brush_core, drawable,
&coords,
paint_buffer,
paint_buffer_x,
paint_buffer_y,
FALSE);
}
else
{
GeglColor *color;
color = pika_gegl_color_new (&paint_color,
pika_drawable_get_space (drawable));
gegl_buffer_set_color (paint_buffer, NULL, color);
g_object_unref (color);
}
}
if (pika_dynamics_is_output_enabled (dynamics, PIKA_DYNAMICS_OUTPUT_FORCE))
force = pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_FORCE,
&coords,
paint_options,
fade_point);
else
force = paint_options->brush_force;
/* finally, let the brush core paste the colored area on the canvas */
pika_brush_core_paste_canvas (brush_core, drawable,
&coords,
MIN (opacity, PIKA_OPACITY_OPAQUE),
pika_context_get_opacity (context),
paint_mode,
pika_paint_options_get_brush_mode (paint_options),
force,
paint_appl_mode);
}
}

View File

@ -0,0 +1,84 @@
/* 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/>.
*/
#ifndef __PIKA_PAINTBRUSH_H__
#define __PIKA_PAINTBRUSH_H__
#include "pikabrushcore.h"
#define PIKA_TYPE_PAINTBRUSH (pika_paintbrush_get_type ())
#define PIKA_PAINTBRUSH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_PAINTBRUSH, PikaPaintbrush))
#define PIKA_PAINTBRUSH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_PAINTBRUSH, PikaPaintbrushClass))
#define PIKA_IS_PAINTBRUSH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_PAINTBRUSH))
#define PIKA_IS_PAINTBRUSH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_PAINTBRUSH))
#define PIKA_PAINTBRUSH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_PAINTBRUSH, PikaPaintbrushClass))
typedef struct _PikaPaintbrushClass PikaPaintbrushClass;
struct _PikaPaintbrush
{
PikaBrushCore parent_instance;
GeglBuffer *paint_buffer;
const PikaTempBuf *paint_pixmap;
PikaRGB paint_color;
};
struct _PikaPaintbrushClass
{
PikaBrushCoreClass parent_class;
/* virtual functions */
gboolean (* get_color_history_color) (PikaPaintbrush *paintbrush,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaRGB *color);
void (* get_paint_params) (PikaPaintbrush *paintbrush,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
gdouble grad_point,
PikaLayerMode *paint_mode,
PikaPaintApplicationMode *paint_appl_mode,
const PikaTempBuf **paint_pixmap,
PikaRGB *paint_color);
};
void pika_paintbrush_register (Pika *pika,
PikaPaintRegisterCallback callback);
GType pika_paintbrush_get_type (void) G_GNUC_CONST;
/* protected */
void _pika_paintbrush_motion (PikaPaintCore *paint_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
gdouble opacity);
#endif /* __PIKA_PAINTBRUSH_H__ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,74 @@
/* 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) 2013 Daniel Sabo
*
* 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/>.
*/
#ifndef __PIKA_PAINT_CORE_LOOPS_H__
#define __PIKA_PAINT_CORE_LOOPS_H__
typedef enum
{
PIKA_PAINT_CORE_LOOPS_ALGORITHM_NONE = 0,
PIKA_PAINT_CORE_LOOPS_ALGORITHM_COMBINE_PAINT_MASK_TO_CANVAS_BUFFER = 1 << 0,
PIKA_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_PAINT_BUF_ALPHA = 1 << 1,
PIKA_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_PAINT_BUF_ALPHA = 1 << 2,
PIKA_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_COMP_MASK = 1 << 3,
PIKA_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_COMP_MASK = 1 << 4,
PIKA_PAINT_CORE_LOOPS_ALGORITHM_DO_LAYER_BLEND = 1 << 5,
PIKA_PAINT_CORE_LOOPS_ALGORITHM_MASK_COMPONENTS = 1 << 6
} PikaPaintCoreLoopsAlgorithm;
typedef struct
{
GeglBuffer *canvas_buffer;
PikaTempBuf *paint_buf;
gint paint_buf_offset_x;
gint paint_buf_offset_y;
const PikaTempBuf *paint_mask;
gint paint_mask_offset_x;
gint paint_mask_offset_y;
gboolean stipple;
GeglBuffer *src_buffer;
GeglBuffer *dest_buffer;
GeglBuffer *mask_buffer;
gint mask_offset_x;
gint mask_offset_y;
gdouble paint_opacity;
gdouble image_opacity;
PikaLayerMode paint_mode;
PikaComponentMask affect;
} PikaPaintCoreLoopsParams;
void pika_paint_core_loops_process (const PikaPaintCoreLoopsParams *params,
PikaPaintCoreLoopsAlgorithm algorithms);
#endif /* __PIKA_PAINT_CORE_LOOPS_H__ */

View File

@ -0,0 +1,396 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikamath/pikamath.h"
#include "paint-types.h"
#include "core/pikaboundary.h"
#include "core/pikadrawable.h"
#include "core/pikaerror.h"
#include "core/pikacoords.h"
#include "vectors/pikastroke.h"
#include "vectors/pikavectors.h"
#include "pikapaintcore.h"
#include "pikapaintcore-stroke.h"
#include "pikapaintoptions.h"
#include "pika-intl.h"
static void pika_paint_core_stroke_emulate_dynamics (PikaCoords *coords,
gint length);
static const PikaCoords default_coords = PIKA_COORDS_DEFAULT_VALUES;
gboolean
pika_paint_core_stroke (PikaPaintCore *core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaCoords *strokes,
gint n_strokes,
gboolean push_undo,
GError **error)
{
GList *drawables;
gboolean success = FALSE;
g_return_val_if_fail (PIKA_IS_PAINT_CORE (core), FALSE);
g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE);
g_return_val_if_fail (pika_item_is_attached (PIKA_ITEM (drawable)), FALSE);
g_return_val_if_fail (PIKA_IS_PAINT_OPTIONS (paint_options), FALSE);
g_return_val_if_fail (strokes != NULL, FALSE);
g_return_val_if_fail (n_strokes > 0, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
drawables = g_list_prepend (NULL, drawable);
if (pika_paint_core_start (core, drawables, paint_options, &strokes[0],
error))
{
gint i;
core->last_coords = strokes[0];
pika_paint_core_paint (core, drawables, paint_options,
PIKA_PAINT_STATE_INIT, 0);
pika_paint_core_paint (core, drawables, paint_options,
PIKA_PAINT_STATE_MOTION, 0);
for (i = 1; i < n_strokes; i++)
{
pika_paint_core_interpolate (core, drawables, paint_options,
&strokes[i], 0);
}
pika_paint_core_paint (core, drawables, paint_options,
PIKA_PAINT_STATE_FINISH, 0);
pika_paint_core_finish (core, drawables, push_undo);
pika_paint_core_cleanup (core);
success = TRUE;
}
g_list_free (drawables);
return success;
}
gboolean
pika_paint_core_stroke_boundary (PikaPaintCore *core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
gboolean emulate_dynamics,
const PikaBoundSeg *bound_segs,
gint n_bound_segs,
gint offset_x,
gint offset_y,
gboolean push_undo,
GError **error)
{
GList *drawables;
PikaBoundSeg *stroke_segs;
gint n_stroke_segs;
PikaCoords *coords;
gboolean initialized = FALSE;
gint n_coords;
gint seg;
gint s;
g_return_val_if_fail (PIKA_IS_PAINT_CORE (core), FALSE);
g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE);
g_return_val_if_fail (pika_item_is_attached (PIKA_ITEM (drawable)), FALSE);
g_return_val_if_fail (PIKA_IS_PAINT_OPTIONS (paint_options), FALSE);
g_return_val_if_fail (bound_segs != NULL && n_bound_segs > 0, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
stroke_segs = pika_boundary_sort (bound_segs, n_bound_segs,
&n_stroke_segs);
if (n_stroke_segs == 0)
return TRUE;
coords = g_new0 (PikaCoords, n_bound_segs + 4);
seg = 0;
n_coords = 0;
/* we offset all coordinates by 0.5 to align the brush with the path */
coords[n_coords] = default_coords;
coords[n_coords].x = (gdouble) (stroke_segs[0].x1 + offset_x + 0.5);
coords[n_coords].y = (gdouble) (stroke_segs[0].y1 + offset_y + 0.5);
n_coords++;
drawables = g_list_prepend (NULL, drawable);
for (s = 0; s < n_stroke_segs; s++)
{
while (stroke_segs[seg].x1 != -1 ||
stroke_segs[seg].x2 != -1 ||
stroke_segs[seg].y1 != -1 ||
stroke_segs[seg].y2 != -1)
{
coords[n_coords] = default_coords;
coords[n_coords].x = (gdouble) (stroke_segs[seg].x1 + offset_x + 0.5);
coords[n_coords].y = (gdouble) (stroke_segs[seg].y1 + offset_y + 0.5);
n_coords++;
seg++;
}
/* Close the stroke points up */
coords[n_coords] = coords[0];
n_coords++;
if (emulate_dynamics)
pika_paint_core_stroke_emulate_dynamics (coords, n_coords);
if (initialized ||
pika_paint_core_start (core, drawables, paint_options, &coords[0],
error))
{
gint i;
initialized = TRUE;
core->cur_coords = coords[0];
core->last_coords = coords[0];
pika_paint_core_paint (core, drawables, paint_options,
PIKA_PAINT_STATE_INIT, 0);
pika_paint_core_paint (core, drawables, paint_options,
PIKA_PAINT_STATE_MOTION, 0);
for (i = 1; i < n_coords; i++)
{
pika_paint_core_interpolate (core, drawables, paint_options,
&coords[i], 0);
}
pika_paint_core_paint (core, drawables, paint_options,
PIKA_PAINT_STATE_FINISH, 0);
}
else
{
break;
}
n_coords = 0;
seg++;
coords[n_coords] = default_coords;
coords[n_coords].x = (gdouble) (stroke_segs[seg].x1 + offset_x + 0.5);
coords[n_coords].y = (gdouble) (stroke_segs[seg].y1 + offset_y + 0.5);
n_coords++;
}
if (initialized)
{
pika_paint_core_finish (core, drawables, push_undo);
pika_paint_core_cleanup (core);
}
g_list_free (drawables);
g_free (coords);
g_free (stroke_segs);
return initialized;
}
gboolean
pika_paint_core_stroke_vectors (PikaPaintCore *core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
gboolean emulate_dynamics,
PikaVectors *vectors,
gboolean push_undo,
GError **error)
{
GList *drawables;
GList *stroke;
gboolean initialized = FALSE;
gboolean due_to_lack_of_points = FALSE;
gint off_x, off_y;
g_return_val_if_fail (PIKA_IS_PAINT_CORE (core), FALSE);
g_return_val_if_fail (PIKA_IS_DRAWABLE (drawable), FALSE);
g_return_val_if_fail (pika_item_is_attached (PIKA_ITEM (drawable)), FALSE);
g_return_val_if_fail (PIKA_IS_PAINT_OPTIONS (paint_options), FALSE);
g_return_val_if_fail (PIKA_IS_VECTORS (vectors), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
pika_item_get_offset (PIKA_ITEM (vectors), &off_x, &off_y);
drawables = g_list_prepend (NULL, drawable);
for (stroke = vectors->strokes->head;
stroke;
stroke = stroke->next)
{
GArray *coords;
gboolean closed;
coords = pika_stroke_interpolate (PIKA_STROKE (stroke->data),
1.0, &closed);
if (coords && coords->len)
{
gint i;
for (i = 0; i < coords->len; i++)
{
g_array_index (coords, PikaCoords, i).x += off_x;
g_array_index (coords, PikaCoords, i).y += off_y;
}
if (emulate_dynamics)
pika_paint_core_stroke_emulate_dynamics ((PikaCoords *) coords->data,
coords->len);
if (initialized ||
pika_paint_core_start (core, drawables, paint_options,
&g_array_index (coords, PikaCoords, 0),
error))
{
initialized = TRUE;
core->cur_coords = g_array_index (coords, PikaCoords, 0);
core->last_coords = g_array_index (coords, PikaCoords, 0);
pika_paint_core_paint (core, drawables, paint_options,
PIKA_PAINT_STATE_INIT, 0);
pika_paint_core_paint (core, drawables, paint_options,
PIKA_PAINT_STATE_MOTION, 0);
for (i = 1; i < coords->len; i++)
{
pika_paint_core_interpolate (core, drawables, paint_options,
&g_array_index (coords, PikaCoords, i),
0);
}
pika_paint_core_paint (core, drawables, paint_options,
PIKA_PAINT_STATE_FINISH, 0);
}
else
{
if (coords)
g_array_free (coords, TRUE);
break;
}
}
else
{
due_to_lack_of_points = TRUE;
}
if (coords)
g_array_free (coords, TRUE);
}
if (initialized)
{
pika_paint_core_finish (core, drawables, push_undo);
pika_paint_core_cleanup (core);
}
if (! initialized && due_to_lack_of_points && *error == NULL)
{
g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
_("Not enough points to stroke"));
}
g_list_free (drawables);
return initialized;
}
static void
pika_paint_core_stroke_emulate_dynamics (PikaCoords *coords,
gint length)
{
const gint ramp_length = length / 3;
/* Calculate and create pressure ramp parameters */
if (ramp_length > 0)
{
gdouble slope = 1.0 / (gdouble) (ramp_length);
gint i;
/* Calculate pressure start ramp */
for (i = 0; i < ramp_length; i++)
{
coords[i].pressure = i * slope;
}
/* Calculate pressure end ramp */
for (i = length - ramp_length; i < length; i++)
{
coords[i].pressure = 1.0 - (i - (length - ramp_length)) * slope;
}
}
/* Calculate and create velocity ramp parameters */
if (length > 0)
{
gdouble slope = 1.0 / length;
gint i;
/* Calculate velocity end ramp */
for (i = 0; i < length; i++)
{
coords[i].velocity = i * slope;
}
}
if (length > 1)
{
gint i;
/* Fill in direction */
for (i = 1; i < length; i++)
{
coords[i].direction = pika_coords_direction (&coords[i-1], &coords[i]);
}
coords[0].direction = coords[1].direction;
}
}

View File

@ -0,0 +1,52 @@
/* 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/>.
*/
#ifndef __PIKA_PAINT_CORE_STROKE_H__
#define __PIKA_PAINT_CORE_STROKE_H__
gboolean pika_paint_core_stroke (PikaPaintCore *core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaCoords *strokes,
gint n_strokes,
gboolean push_undo,
GError **error);
gboolean pika_paint_core_stroke_boundary (PikaPaintCore *core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
gboolean emulate_dynamics,
const PikaBoundSeg *bound_segs,
gint n_bound_segs,
gint offset_x,
gint offset_y,
gboolean push_undo,
GError **error);
gboolean pika_paint_core_stroke_vectors (PikaPaintCore *core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
gboolean emulate_dynamics,
PikaVectors *vectors,
gboolean push_undo,
GError **error);
#endif /* __PIKA_PAINT_CORE_STROKE_H__ */

1307
app/paint/pikapaintcore.c Normal file

File diff suppressed because it is too large Load Diff

219
app/paint/pikapaintcore.h Normal file
View File

@ -0,0 +1,219 @@
/* 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/>.
*/
#ifndef __PIKA_PAINT_CORE_H__
#define __PIKA_PAINT_CORE_H__
#include "core/pikaobject.h"
#define PIKA_TYPE_PAINT_CORE (pika_paint_core_get_type ())
#define PIKA_PAINT_CORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_PAINT_CORE, PikaPaintCore))
#define PIKA_PAINT_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_PAINT_CORE, PikaPaintCoreClass))
#define PIKA_IS_PAINT_CORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_PAINT_CORE))
#define PIKA_IS_PAINT_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_PAINT_CORE))
#define PIKA_PAINT_CORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_PAINT_CORE, PikaPaintCoreClass))
typedef struct _PikaPaintCoreClass PikaPaintCoreClass;
struct _PikaPaintCore
{
PikaObject parent_instance;
gint ID; /* unique instance ID */
gchar *undo_desc; /* undo description */
gboolean show_all; /* whether working in show-all mode */
PikaCoords start_coords; /* the last stroke's endpoint for undo */
PikaCoords cur_coords; /* current coords */
PikaCoords last_coords; /* last coords */
PikaVector2 last_paint; /* last point that was painted */
gdouble distance; /* distance traveled by brush */
gdouble pixel_dist; /* distance in pixels */
gint x1, y1; /* undo extents in image coords */
gint x2, y2; /* undo extents in image coords */
gboolean use_saved_proj; /* keep the unmodified proj around */
PikaPickable *image_pickable; /* the image pickable */
GHashTable *undo_buffers; /* pixels which have been modified */
GeglBuffer *saved_proj_buffer; /* proj tiles which have been modified */
GeglBuffer *canvas_buffer; /* the buffer to paint the mask to */
GeglBuffer *paint_buffer; /* the buffer to paint pixels to */
gint paint_buffer_x;
gint paint_buffer_y;
GeglBuffer *mask_buffer; /* the target drawable's mask */
GHashTable *applicators;
GArray *stroke_buffer;
};
struct _PikaPaintCoreClass
{
PikaObjectClass parent_class;
/* virtual functions */
gboolean (* start) (PikaPaintCore *core,
GList *drawables,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
GError **error);
gboolean (* pre_paint) (PikaPaintCore *core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaPaintState paint_state,
guint32 time);
void (* paint) (PikaPaintCore *core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time);
void (* post_paint) (PikaPaintCore *core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaPaintState paint_state,
guint32 time);
void (* interpolate) (PikaPaintCore *core,
GList *drawables,
PikaPaintOptions *paint_options,
guint32 time);
GeglBuffer * (* get_paint_buffer) (PikaPaintCore *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);
PikaUndo * (* push_undo) (PikaPaintCore *core,
PikaImage *image,
const gchar *undo_desc);
};
GType pika_paint_core_get_type (void) G_GNUC_CONST;
void pika_paint_core_paint (PikaPaintCore *core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaPaintState state,
guint32 time);
gboolean pika_paint_core_start (PikaPaintCore *core,
GList *drawables,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
GError **error);
void pika_paint_core_finish (PikaPaintCore *core,
GList *drawables,
gboolean push_undo);
void pika_paint_core_cancel (PikaPaintCore *core,
GList *drawables);
void pika_paint_core_cleanup (PikaPaintCore *core);
void pika_paint_core_interpolate (PikaPaintCore *core,
GList *drawables,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
guint32 time);
void pika_paint_core_set_show_all (PikaPaintCore *core,
gboolean show_all);
gboolean pika_paint_core_get_show_all (PikaPaintCore *core);
void pika_paint_core_set_current_coords (PikaPaintCore *core,
const PikaCoords *coords);
void pika_paint_core_get_current_coords (PikaPaintCore *core,
PikaCoords *coords);
void pika_paint_core_set_last_coords (PikaPaintCore *core,
const PikaCoords *coords);
void pika_paint_core_get_last_coords (PikaPaintCore *core,
PikaCoords *coords);
void pika_paint_core_round_line (PikaPaintCore *core,
PikaPaintOptions *options,
gboolean constrain_15_degrees,
gdouble constrain_offset_angle,
gdouble constrain_xres,
gdouble constrain_yres);
/* protected functions */
GeglBuffer * pika_paint_core_get_paint_buffer (PikaPaintCore *core,
PikaDrawable *drawable,
PikaPaintOptions *options,
PikaLayerMode paint_mode,
const PikaCoords *coords,
gint *paint_buffer_x,
gint *paint_buffer_y,
gint *paint_width,
gint *paint_height);
PikaPickable * pika_paint_core_get_image_pickable (PikaPaintCore *core);
GeglBuffer * pika_paint_core_get_orig_image (PikaPaintCore *core,
PikaDrawable *drawable);
GeglBuffer * pika_paint_core_get_orig_proj (PikaPaintCore *core);
void pika_paint_core_paste (PikaPaintCore *core,
const PikaTempBuf *paint_mask,
gint paint_mask_offset_x,
gint paint_mask_offset_y,
PikaDrawable *drawable,
gdouble paint_opacity,
gdouble image_opacity,
PikaLayerMode paint_mode,
PikaPaintApplicationMode mode);
void pika_paint_core_replace (PikaPaintCore *core,
const PikaTempBuf *paint_mask,
gint paint_mask_offset_x,
gint paint_mask_offset_y,
PikaDrawable *drawable,
gdouble paint_opacity,
gdouble image_opacity,
PikaPaintApplicationMode mode);
void pika_paint_core_smooth_coords (PikaPaintCore *core,
PikaPaintOptions *paint_options,
PikaCoords *coords);
#endif /* __PIKA_PAINT_CORE_H__ */

View File

@ -0,0 +1,171 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "paint-types.h"
#include "pikapaintcore.h"
#include "pikapaintcoreundo.h"
enum
{
PROP_0,
PROP_PAINT_CORE
};
static void pika_paint_core_undo_constructed (GObject *object);
static void pika_paint_core_undo_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_paint_core_undo_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void pika_paint_core_undo_pop (PikaUndo *undo,
PikaUndoMode undo_mode,
PikaUndoAccumulator *accum);
static void pika_paint_core_undo_free (PikaUndo *undo,
PikaUndoMode undo_mode);
G_DEFINE_TYPE (PikaPaintCoreUndo, pika_paint_core_undo, PIKA_TYPE_UNDO)
#define parent_class pika_paint_core_undo_parent_class
static void
pika_paint_core_undo_class_init (PikaPaintCoreUndoClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
PikaUndoClass *undo_class = PIKA_UNDO_CLASS (klass);
object_class->constructed = pika_paint_core_undo_constructed;
object_class->set_property = pika_paint_core_undo_set_property;
object_class->get_property = pika_paint_core_undo_get_property;
undo_class->pop = pika_paint_core_undo_pop;
undo_class->free = pika_paint_core_undo_free;
g_object_class_install_property (object_class, PROP_PAINT_CORE,
g_param_spec_object ("paint-core", NULL, NULL,
PIKA_TYPE_PAINT_CORE,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
}
static void
pika_paint_core_undo_init (PikaPaintCoreUndo *undo)
{
}
static void
pika_paint_core_undo_constructed (GObject *object)
{
PikaPaintCoreUndo *paint_core_undo = PIKA_PAINT_CORE_UNDO (object);
G_OBJECT_CLASS (parent_class)->constructed (object);
pika_assert (PIKA_IS_PAINT_CORE (paint_core_undo->paint_core));
paint_core_undo->last_coords = paint_core_undo->paint_core->start_coords;
}
static void
pika_paint_core_undo_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaPaintCoreUndo *paint_core_undo = PIKA_PAINT_CORE_UNDO (object);
switch (property_id)
{
case PROP_PAINT_CORE:
g_set_weak_pointer (&paint_core_undo->paint_core,
g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_paint_core_undo_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaPaintCoreUndo *paint_core_undo = PIKA_PAINT_CORE_UNDO (object);
switch (property_id)
{
case PROP_PAINT_CORE:
g_value_set_object (value, paint_core_undo->paint_core);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_paint_core_undo_pop (PikaUndo *undo,
PikaUndoMode undo_mode,
PikaUndoAccumulator *accum)
{
PikaPaintCoreUndo *paint_core_undo = PIKA_PAINT_CORE_UNDO (undo);
PIKA_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
/* only pop if the core still exists */
if (paint_core_undo->paint_core)
{
PikaCoords tmp_coords;
tmp_coords = paint_core_undo->paint_core->last_coords;
paint_core_undo->paint_core->last_coords = paint_core_undo->last_coords;
paint_core_undo->last_coords = tmp_coords;
}
}
static void
pika_paint_core_undo_free (PikaUndo *undo,
PikaUndoMode undo_mode)
{
PikaPaintCoreUndo *paint_core_undo = PIKA_PAINT_CORE_UNDO (undo);
g_clear_weak_pointer (&paint_core_undo->paint_core);
PIKA_UNDO_CLASS (parent_class)->free (undo, undo_mode);
}

View File

@ -0,0 +1,57 @@
/* 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/>.
*/
#ifndef __PIKA_PAINT_CORE_UNDO_H__
#define __PIKA_PAINT_CORE_UNDO_H__
#include "core/pikaundo.h"
#define PIKA_TYPE_PAINT_CORE_UNDO (pika_paint_core_undo_get_type ())
#define PIKA_PAINT_CORE_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_PAINT_CORE_UNDO, PikaPaintCoreUndo))
#define PIKA_PAINT_CORE_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_PAINT_CORE_UNDO, PikaPaintCoreUndoClass))
#define PIKA_IS_PAINT_CORE_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_PAINT_CORE_UNDO))
#define PIKA_IS_PAINT_CORE_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_PAINT_CORE_UNDO))
#define PIKA_PAINT_CORE_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_PAINT_CORE_UNDO, PikaPaintCoreUndoClass))
typedef struct _PikaPaintCoreUndo PikaPaintCoreUndo;
typedef struct _PikaPaintCoreUndoClass PikaPaintCoreUndoClass;
struct _PikaPaintCoreUndo
{
PikaUndo parent_instance;
PikaPaintCore *paint_core;
PikaCoords last_coords;
};
struct _PikaPaintCoreUndoClass
{
PikaUndoClass parent_class;
};
GType pika_paint_core_undo_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_PAINT_CORE_UNDO_H__ */

1299
app/paint/pikapaintoptions.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,184 @@
/* 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-1999 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/>.
*/
#ifndef __PIKA_PAINT_OPTIONS_H__
#define __PIKA_PAINT_OPTIONS_H__
#include "core/pikatooloptions.h"
#define PIKA_PAINT_OPTIONS_CONTEXT_MASK PIKA_CONTEXT_PROP_MASK_FOREGROUND | \
PIKA_CONTEXT_PROP_MASK_BACKGROUND | \
PIKA_CONTEXT_PROP_MASK_OPACITY | \
PIKA_CONTEXT_PROP_MASK_PAINT_MODE | \
PIKA_CONTEXT_PROP_MASK_BRUSH | \
PIKA_CONTEXT_PROP_MASK_DYNAMICS | \
PIKA_CONTEXT_PROP_MASK_PALETTE
typedef struct _PikaJitterOptions PikaJitterOptions;
typedef struct _PikaFadeOptions PikaFadeOptions;
typedef struct _PikaGradientPaintOptions PikaGradientPaintOptions;
typedef struct _PikaSmoothingOptions PikaSmoothingOptions;
struct _PikaJitterOptions
{
gboolean use_jitter;
gdouble jitter_amount;
};
struct _PikaFadeOptions
{
gboolean fade_reverse;
gdouble fade_length;
PikaUnit fade_unit;
PikaRepeatMode fade_repeat;
};
struct _PikaGradientPaintOptions
{
gboolean gradient_reverse;
PikaGradientBlendColorSpace gradient_blend_color_space;
PikaRepeatMode gradient_repeat; /* only used by gradient tool */
};
struct _PikaSmoothingOptions
{
gboolean use_smoothing;
gint smoothing_quality;
gdouble smoothing_factor;
};
#define PIKA_TYPE_PAINT_OPTIONS (pika_paint_options_get_type ())
#define PIKA_PAINT_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_PAINT_OPTIONS, PikaPaintOptions))
#define PIKA_PAINT_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_PAINT_OPTIONS, PikaPaintOptionsClass))
#define PIKA_IS_PAINT_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_PAINT_OPTIONS))
#define PIKA_IS_PAINT_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_PAINT_OPTIONS))
#define PIKA_PAINT_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_PAINT_OPTIONS, PikaPaintOptionsClass))
typedef struct _PikaPaintOptionsClass PikaPaintOptionsClass;
struct _PikaPaintOptions
{
PikaToolOptions parent_instance;
PikaPaintInfo *paint_info;
gboolean use_applicator;
PikaBrush *brush; /* weak-refed storage for the GUI */
gdouble brush_size;
gdouble brush_angle;
gdouble brush_aspect_ratio;
gdouble brush_spacing;
gdouble brush_hardness;
gdouble brush_force;
gboolean brush_link_size;
gboolean brush_link_aspect_ratio;
gboolean brush_link_angle;
gboolean brush_link_spacing;
gboolean brush_link_hardness;
gboolean brush_lock_to_view;
PikaPaintApplicationMode application_mode;
PikaPaintApplicationMode application_mode_save;
gboolean hard;
PikaJitterOptions *jitter_options;
gboolean dynamics_enabled;
PikaFadeOptions *fade_options;
PikaGradientPaintOptions *gradient_options;
PikaSmoothingOptions *smoothing_options;
PikaViewType brush_view_type;
PikaViewSize brush_view_size;
PikaViewType dynamics_view_type;
PikaViewSize dynamics_view_size;
PikaViewType pattern_view_type;
PikaViewSize pattern_view_size;
PikaViewType gradient_view_type;
PikaViewSize gradient_view_size;
};
struct _PikaPaintOptionsClass
{
PikaToolOptionsClass parent_instance;
};
GType pika_paint_options_get_type (void) G_GNUC_CONST;
PikaPaintOptions *
pika_paint_options_new (PikaPaintInfo *paint_info);
gdouble pika_paint_options_get_fade (PikaPaintOptions *options,
PikaImage *image,
gdouble pixel_dist);
gdouble pika_paint_options_get_jitter (PikaPaintOptions *options,
PikaImage *image);
gboolean pika_paint_options_get_gradient_color (PikaPaintOptions *options,
PikaImage *image,
gdouble grad_point,
gdouble pixel_dist,
PikaRGB *color);
PikaBrushApplicationMode
pika_paint_options_get_brush_mode (PikaPaintOptions *options);
void pika_paint_options_set_default_brush_size
(PikaPaintOptions *options,
PikaBrush *brush);
void pika_paint_options_set_default_brush_angle
(PikaPaintOptions *options,
PikaBrush *brush);
void pika_paint_options_set_default_brush_aspect_ratio
(PikaPaintOptions *options,
PikaBrush *brush);
void pika_paint_options_set_default_brush_spacing
(PikaPaintOptions *options,
PikaBrush *brush);
void pika_paint_options_set_default_brush_hardness
(PikaPaintOptions *options,
PikaBrush *brush);
gboolean pika_paint_options_are_dynamics_enabled (PikaPaintOptions *paint_options);
void pika_paint_options_enable_dynamics (PikaPaintOptions *paint_options,
gboolean enable);
gboolean pika_paint_options_is_prop (const gchar *prop_name,
PikaContextPropMask prop_mask);
void pika_paint_options_copy_props (PikaPaintOptions *src,
PikaPaintOptions *dest,
PikaContextPropMask prop_mask);
#endif /* __PIKA_PAINT_OPTIONS_H__ */

58
app/paint/pikapencil.c Normal file
View File

@ -0,0 +1,58 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "paint-types.h"
#include "pikapencil.h"
#include "pikapenciloptions.h"
#include "pika-intl.h"
G_DEFINE_TYPE (PikaPencil, pika_pencil, PIKA_TYPE_PAINTBRUSH)
void
pika_pencil_register (Pika *pika,
PikaPaintRegisterCallback callback)
{
(* callback) (pika,
PIKA_TYPE_PENCIL,
PIKA_TYPE_PENCIL_OPTIONS,
"pika-pencil",
_("Pencil"),
"pika-tool-pencil");
}
static void
pika_pencil_class_init (PikaPencilClass *klass)
{
}
static void
pika_pencil_init (PikaPencil *pencil)
{
}

56
app/paint/pikapencil.h Normal file
View File

@ -0,0 +1,56 @@
/* 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/>.
*/
#ifndef __PIKA_PENCIL_H__
#define __PIKA_PENCIL_H__
#include "pikapaintbrush.h"
#define PIKA_TYPE_PENCIL (pika_pencil_get_type ())
#define PIKA_PENCIL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_PENCIL, PikaPencil))
#define PIKA_PENCIL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_PENCIL, PikaPencilClass))
#define PIKA_IS_PENCIL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_PENCIL))
#define PIKA_IS_PENCIL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_PENCIL))
#define PIKA_PENCIL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_PENCIL, PikaPencilClass))
typedef struct _PikaPencilClass PikaPencilClass;
struct _PikaPencil
{
PikaPaintbrush parent_instance;
};
struct _PikaPencilClass
{
PikaPaintbrushClass parent_class;
};
void pika_pencil_register (Pika *pika,
PikaPaintRegisterCallback callback);
GType pika_pencil_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_PENCIL_H__ */

View File

@ -0,0 +1,115 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikaconfig/pikaconfig.h"
#include "paint-types.h"
#include "pikapenciloptions.h"
#define PENCIL_DEFAULT_HARD TRUE
enum
{
PROP_0,
PROP_HARD
};
static void pika_pencil_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_pencil_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
G_DEFINE_TYPE (PikaPencilOptions, pika_pencil_options,
PIKA_TYPE_PAINT_OPTIONS)
static void
pika_pencil_options_class_init (PikaPencilOptionsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = pika_pencil_options_set_property;
object_class->get_property = pika_pencil_options_get_property;
PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_HARD,
"hard",
NULL, NULL,
PENCIL_DEFAULT_HARD,
PIKA_PARAM_STATIC_STRINGS);
}
static void
pika_pencil_options_init (PikaPencilOptions *options)
{
}
static void
pika_pencil_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaPaintOptions *options = PIKA_PAINT_OPTIONS (object);
switch (property_id)
{
case PROP_HARD:
options->hard = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_pencil_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaPaintOptions *options = PIKA_PAINT_OPTIONS (object);
switch (property_id)
{
case PROP_HARD:
g_value_set_boolean (value, options->hard);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}

View File

@ -0,0 +1,53 @@
/* 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/>.
*/
#ifndef __PIKA_PENCIL_OPTIONS_H__
#define __PIKA_PENCIL_OPTIONS_H__
#include "pikapaintoptions.h"
#define PIKA_TYPE_PENCIL_OPTIONS (pika_pencil_options_get_type ())
#define PIKA_PENCIL_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_PENCIL_OPTIONS, PikaPencilOptions))
#define PIKA_PENCIL_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_PENCIL_OPTIONS, PikaPencilOptionsClass))
#define PIKA_IS_PENCIL_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_PENCIL_OPTIONS))
#define PIKA_IS_PENCIL_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_PENCIL_OPTIONS))
#define PIKA_PENCIL_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_PENCIL_OPTIONS, PikaPencilOptionsClass))
typedef struct _PikaPencilOptionsClass PikaPencilOptionsClass;
struct _PikaPencilOptions
{
PikaPaintOptions parent_instance;
};
struct _PikaPencilOptionsClass
{
PikaPaintOptionsClass parent_class;
};
GType pika_pencil_options_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_PENCIL_OPTIONS_H__ */

View File

@ -0,0 +1,595 @@
/* 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 <stdlib.h>
#include <string.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikamath/pikamath.h"
#include "paint-types.h"
#include "gegl/pika-gegl-nodes.h"
#include "gegl/pika-gegl-utils.h"
#include "core/pika.h"
#include "core/pika-utils.h"
#include "core/pikacontainer.h"
#include "core/pikadrawable.h"
#include "core/pikaerror.h"
#include "core/pikaimage.h"
#include "core/pikaimage-new.h"
#include "core/pikapattern.h"
#include "core/pikapickable.h"
#include "core/pikasymmetry.h"
#include "pikaperspectiveclone.h"
#include "pikaperspectivecloneoptions.h"
#include "pika-intl.h"
static void pika_perspective_clone_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time);
static gboolean pika_perspective_clone_use_source (PikaSourceCore *source_core,
PikaSourceOptions *options);
static GeglBuffer * pika_perspective_clone_get_source (PikaSourceCore *source_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
gboolean self_drawable,
PikaPickable *src_pickable,
gint src_offset_x,
gint src_offset_y,
GeglBuffer *paint_buffer,
gint paint_buffer_x,
gint paint_buffer_y,
gint *paint_area_offset_x,
gint *paint_area_offset_y,
gint *paint_area_width,
gint *paint_area_height,
GeglRectangle *src_rect);
static void pika_perspective_clone_get_matrix (PikaPerspectiveClone *clone,
PikaMatrix3 *matrix);
G_DEFINE_TYPE (PikaPerspectiveClone, pika_perspective_clone,
PIKA_TYPE_CLONE)
#define parent_class pika_perspective_clone_parent_class
void
pika_perspective_clone_register (Pika *pika,
PikaPaintRegisterCallback callback)
{
(* callback) (pika,
PIKA_TYPE_PERSPECTIVE_CLONE,
PIKA_TYPE_PERSPECTIVE_CLONE_OPTIONS,
"pika-perspective-clone",
_("Perspective Clone"),
"pika-tool-perspective-clone");
}
static void
pika_perspective_clone_class_init (PikaPerspectiveCloneClass *klass)
{
PikaPaintCoreClass *paint_core_class = PIKA_PAINT_CORE_CLASS (klass);
PikaSourceCoreClass *source_core_class = PIKA_SOURCE_CORE_CLASS (klass);
paint_core_class->paint = pika_perspective_clone_paint;
source_core_class->use_source = pika_perspective_clone_use_source;
source_core_class->get_source = pika_perspective_clone_get_source;
}
static void
pika_perspective_clone_init (PikaPerspectiveClone *clone)
{
clone->src_x_fv = 0.0; /* source coords in front_view perspective */
clone->src_y_fv = 0.0;
clone->dest_x_fv = 0.0; /* destination coords in front_view perspective */
clone->dest_y_fv = 0.0;
pika_matrix3_identity (&clone->transform);
pika_matrix3_identity (&clone->transform_inv);
}
static void
pika_perspective_clone_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time)
{
PikaSourceCore *source_core = PIKA_SOURCE_CORE (paint_core);
PikaPerspectiveClone *clone = PIKA_PERSPECTIVE_CLONE (paint_core);
PikaContext *context = PIKA_CONTEXT (paint_options);
PikaCloneOptions *clone_options = PIKA_CLONE_OPTIONS (paint_options);
PikaSourceOptions *options = PIKA_SOURCE_OPTIONS (paint_options);
const PikaCoords *coords;
gboolean sample_merged;
/* The source is based on the original stroke */
coords = pika_symmetry_get_origin (sym);
sample_merged = options->sample_merged && (g_list_length (drawables) > 1);
switch (paint_state)
{
case PIKA_PAINT_STATE_INIT:
if (source_core->set_source)
{
gint src_x = floor (coords->x);
gint src_y = floor (coords->y);
g_object_set (options,
"src-drawables", drawables,
"src-x", src_x,
"src-y", src_y,
NULL);
/* get source coordinates in front view perspective */
pika_matrix3_transform_point (&clone->transform_inv,
src_x,
src_y,
&clone->src_x_fv,
&clone->src_y_fv);
source_core->first_stroke = TRUE;
}
else
{
PikaImage *del_image = NULL;
GeglBuffer *orig_buffer = NULL;
GeglNode *tile = NULL;
if (options->align_mode == PIKA_SOURCE_ALIGN_NO)
{
g_object_get (options,
"src-x", &source_core->orig_src_x,
"src-y", &source_core->orig_src_y,
NULL);
source_core->first_stroke = TRUE;
}
clone->node = gegl_node_new ();
g_object_set (clone->node,
"cache-policy", GEGL_CACHE_POLICY_NEVER,
NULL);
switch (clone_options->clone_type)
{
case PIKA_CLONE_IMAGE:
{
PikaPickable *src_pickable = NULL;
PikaImage *src_image;
PikaImage *dest_image;
/* If the source image is different from the
* destination, then we should copy straight from the
* source image to the canvas.
* Otherwise, we need a call to get_orig_image to make sure
* we get a copy of the unblemished (offset) image
*/
src_image = pika_pickable_get_image (options->src_drawables->data);
if (sample_merged)
src_pickable = PIKA_PICKABLE (src_image);
dest_image = pika_item_get_image (PIKA_ITEM (drawables->data));
if ((sample_merged &&
(src_image != dest_image)) ||
(! sample_merged &&
g_list_length (options->src_drawables) == 1 &&
g_list_length (drawables) == 1 &&
(options->src_drawables != drawables->data)))
{
if (! sample_merged)
src_pickable = PIKA_PICKABLE (options->src_drawables->data);
orig_buffer = pika_pickable_get_buffer (src_pickable);
}
else
{
if (sample_merged && paint_core->use_saved_proj)
orig_buffer = pika_paint_core_get_orig_proj (paint_core);
else
orig_buffer = pika_paint_core_get_orig_image (paint_core, drawables->data);
}
if (! orig_buffer)
{
/* A composited image of the drawables */
del_image = pika_image_new_from_drawables (src_image->pika, drawables,
FALSE, FALSE);
pika_container_remove (src_image->pika->images, PIKA_OBJECT (del_image));
src_pickable = PIKA_PICKABLE (del_image);
pika_pickable_flush (src_pickable);
orig_buffer = pika_pickable_get_buffer (src_pickable);
}
}
break;
case PIKA_CLONE_PATTERN:
{
PikaPattern *pattern = pika_context_get_pattern (context);
orig_buffer = pika_pattern_create_buffer (pattern);
tile = gegl_node_new_child (clone->node,
"operation", "gegl:tile",
NULL);
clone->crop = gegl_node_new_child (clone->node,
"operation", "gegl:crop",
NULL);
}
break;
}
clone->src_node = gegl_node_new_child (clone->node,
"operation", "gegl:buffer-source",
"buffer", orig_buffer,
NULL);
clone->transform_node =
gegl_node_new_child (clone->node,
"operation", "gegl:transform",
"sampler", PIKA_INTERPOLATION_LINEAR,
NULL);
clone->dest_node =
gegl_node_new_child (clone->node,
"operation", "gegl:write-buffer",
NULL);
if (tile)
{
gegl_node_link_many (clone->src_node,
tile,
clone->crop,
clone->transform_node,
clone->dest_node,
NULL);
g_object_unref (orig_buffer);
}
else
{
gegl_node_link_many (clone->src_node,
clone->transform_node,
clone->dest_node,
NULL);
}
g_clear_object (&del_image);
}
break;
case PIKA_PAINT_STATE_MOTION:
if (source_core->set_source)
{
gint src_x = floor (coords->x);
gint src_y = floor (coords->y);
/* If the control key is down, move the src target and return */
g_object_set (options,
"src-drawables", drawables,
"src-x", src_x,
"src-y", src_y,
NULL);
/* get source coordinates in front view perspective */
pika_matrix3_transform_point (&clone->transform_inv,
src_x,
src_y,
&clone->src_x_fv,
&clone->src_y_fv);
source_core->first_stroke = TRUE;
}
else
{
/* otherwise, update the target */
gint src_x;
gint src_y;
gint dest_x;
gint dest_y;
gint n_strokes;
gint i;
g_object_get (options,
"src-x", &src_x,
"src-y", &src_y,
NULL);
n_strokes = pika_symmetry_get_size (sym);
for (i = 0; i < n_strokes; i++)
{
coords = pika_symmetry_get_coords (sym, i);
dest_x = floor (coords->x);
dest_y = floor (coords->y);
if (options->align_mode == PIKA_SOURCE_ALIGN_REGISTERED)
{
source_core->offset_x = 0;
source_core->offset_y = 0;
}
else if (options->align_mode == PIKA_SOURCE_ALIGN_FIXED)
{
source_core->offset_x = src_x - dest_x;
source_core->offset_y = src_y - dest_y;
}
else if (source_core->first_stroke)
{
source_core->offset_x = src_x - dest_x;
source_core->offset_y = src_y - dest_y;
/* get destination coordinates in front view perspective */
pika_matrix3_transform_point (&clone->transform_inv,
dest_x,
dest_y,
&clone->dest_x_fv,
&clone->dest_y_fv);
source_core->first_stroke = FALSE;
}
}
for (GList *iter = drawables; iter; iter = iter->next)
pika_source_core_motion (source_core, iter->data, paint_options,
(g_list_length (drawables) > 1), sym);
}
break;
case PIKA_PAINT_STATE_FINISH:
g_clear_object (&clone->node);
clone->crop = NULL;
clone->transform_node = NULL;
clone->dest_node = NULL;
break;
default:
break;
}
}
static gboolean
pika_perspective_clone_use_source (PikaSourceCore *source_core,
PikaSourceOptions *options)
{
return TRUE;
}
static GeglBuffer *
pika_perspective_clone_get_source (PikaSourceCore *source_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
gboolean self_drawable,
PikaPickable *src_pickable,
gint src_offset_x,
gint src_offset_y,
GeglBuffer *paint_buffer,
gint paint_buffer_x,
gint paint_buffer_y,
gint *paint_area_offset_x,
gint *paint_area_offset_y,
gint *paint_area_width,
gint *paint_area_height,
GeglRectangle *src_rect)
{
PikaPerspectiveClone *clone = PIKA_PERSPECTIVE_CLONE (source_core);
PikaCloneOptions *clone_options = PIKA_CLONE_OPTIONS (paint_options);
GeglBuffer *src_buffer;
GeglBuffer *dest_buffer;
const Babl *src_format_alpha;
gint x1d, y1d, x2d, y2d;
gdouble x1s, y1s, x2s, y2s, x3s, y3s, x4s, y4s;
gint xmin, ymin, xmax, ymax;
PikaMatrix3 matrix;
PikaMatrix3 gegl_matrix;
if (self_drawable)
src_pickable = PIKA_PICKABLE (drawable);
src_buffer = pika_pickable_get_buffer (src_pickable);
src_format_alpha = pika_pickable_get_format_with_alpha (src_pickable);
/* Destination coordinates that will be painted */
x1d = paint_buffer_x;
y1d = paint_buffer_y;
x2d = paint_buffer_x + gegl_buffer_get_width (paint_buffer);
y2d = paint_buffer_y + gegl_buffer_get_height (paint_buffer);
/* Boundary box for source pixels to copy: Convert all the vertex of
* the box to paint in destination area to its correspondent in
* source area bearing in mind perspective
*/
pika_perspective_clone_get_source_point (clone, x1d, y1d, &x1s, &y1s);
pika_perspective_clone_get_source_point (clone, x1d, y2d, &x2s, &y2s);
pika_perspective_clone_get_source_point (clone, x2d, y1d, &x3s, &y3s);
pika_perspective_clone_get_source_point (clone, x2d, y2d, &x4s, &y4s);
xmin = floor (MIN4 (x1s, x2s, x3s, x4s));
ymin = floor (MIN4 (y1s, y2s, y3s, y4s));
xmax = ceil (MAX4 (x1s, x2s, x3s, x4s));
ymax = ceil (MAX4 (y1s, y2s, y3s, y4s));
switch (clone_options->clone_type)
{
case PIKA_CLONE_IMAGE:
if (! pika_rectangle_intersect (xmin, ymin,
xmax - xmin, ymax - ymin,
gegl_buffer_get_x (src_buffer),
gegl_buffer_get_y (src_buffer),
gegl_buffer_get_width (src_buffer),
gegl_buffer_get_height (src_buffer),
NULL, NULL, NULL, NULL))
{
/* if the source area is completely out of the image */
return NULL;
}
else
{
gegl_node_set (clone->src_node,
"buffer", src_buffer,
NULL);
}
break;
case PIKA_CLONE_PATTERN:
gegl_node_set (clone->crop,
"x", (gdouble) xmin,
"y", (gdouble) ymin,
"width", (gdouble) xmax - xmin,
"height", (gdouble) ymax - ymin,
NULL);
break;
}
dest_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, x2d - x1d, y2d - y1d),
src_format_alpha);
pika_perspective_clone_get_matrix (clone, &matrix);
pika_matrix3_identity (&gegl_matrix);
pika_matrix3_mult (&matrix, &gegl_matrix);
pika_matrix3_translate (&gegl_matrix, -x1d, -y1d);
pika_gegl_node_set_matrix (clone->transform_node, &gegl_matrix);
gegl_node_set (clone->dest_node,
"buffer", dest_buffer,
NULL);
gegl_node_blit (clone->dest_node, 1.0,
GEGL_RECTANGLE (0, 0, x2d - x1d, y2d - y1d),
NULL, NULL, 0, GEGL_BLIT_DEFAULT);
*src_rect = *GEGL_RECTANGLE (0, 0, x2d - x1d, y2d - y1d);
return dest_buffer;
}
/* public functions */
void
pika_perspective_clone_set_transform (PikaPerspectiveClone *clone,
PikaMatrix3 *transform)
{
g_return_if_fail (PIKA_IS_PERSPECTIVE_CLONE (clone));
g_return_if_fail (transform != NULL);
clone->transform = *transform;
clone->transform_inv = clone->transform;
pika_matrix3_invert (&clone->transform_inv);
#if 0
g_printerr ("%f\t%f\t%f\n%f\t%f\t%f\n%f\t%f\t%f\n\n",
clone->transform.coeff[0][0],
clone->transform.coeff[0][1],
clone->transform.coeff[0][2],
clone->transform.coeff[1][0],
clone->transform.coeff[1][1],
clone->transform.coeff[1][2],
clone->transform.coeff[2][0],
clone->transform.coeff[2][1],
clone->transform.coeff[2][2]);
#endif
}
void
pika_perspective_clone_get_source_point (PikaPerspectiveClone *clone,
gdouble x,
gdouble y,
gdouble *newx,
gdouble *newy)
{
gdouble temp_x, temp_y;
g_return_if_fail (PIKA_IS_PERSPECTIVE_CLONE (clone));
g_return_if_fail (newx != NULL);
g_return_if_fail (newy != NULL);
pika_matrix3_transform_point (&clone->transform_inv,
x, y, &temp_x, &temp_y);
#if 0
/* Get the offset of each pixel in destination area from the
* destination pixel in front view perspective
*/
offset_x_fv = temp_x - clone->dest_x_fv;
offset_y_fv = temp_y - clone->dest_y_fv;
/* Get the source pixel in front view perspective */
temp_x = offset_x_fv + clone->src_x_fv;
temp_y = offset_y_fv + clone->src_y_fv;
#endif
temp_x += clone->src_x_fv - clone->dest_x_fv;
temp_y += clone->src_y_fv - clone->dest_y_fv;
/* Convert the source pixel to perspective view */
pika_matrix3_transform_point (&clone->transform,
temp_x, temp_y, newx, newy);
}
/* private functions */
static void
pika_perspective_clone_get_matrix (PikaPerspectiveClone *clone,
PikaMatrix3 *matrix)
{
PikaMatrix3 temp;
pika_matrix3_identity (&temp);
pika_matrix3_translate (&temp,
clone->dest_x_fv - clone->src_x_fv,
clone->dest_y_fv - clone->src_y_fv);
*matrix = clone->transform_inv;
pika_matrix3_mult (&temp, matrix);
pika_matrix3_mult (&clone->transform, matrix);
}

View File

@ -0,0 +1,79 @@
/* 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/>.
*/
#ifndef __PIKA_PERSPECTIVE_CLONE_H__
#define __PIKA_PERSPECTIVE_CLONE_H__
#include "pikaclone.h"
#define PIKA_TYPE_PERSPECTIVE_CLONE (pika_perspective_clone_get_type ())
#define PIKA_PERSPECTIVE_CLONE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_PERSPECTIVE_CLONE, PikaPerspectiveClone))
#define PIKA_PERSPECTIVE_CLONE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_PERSPECTIVE_CLONE, PikaPerspectiveCloneClass))
#define PIKA_IS_PERSPECTIVE_CLONE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_PERSPECTIVE_CLONE))
#define PIKA_IS_PERSPECTIVE_CLONE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_PERSPECTIVE_CLONE))
#define PIKA_PERSPECTIVE_CLONE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_PERSPECTIVE_CLONE, PikaPerspectiveCloneClass))
typedef struct _PikaPerspectiveCloneClass PikaPerspectiveCloneClass;
struct _PikaPerspectiveClone
{
PikaClone parent_instance;
gdouble src_x_fv; /* source coords in front_view perspective */
gdouble src_y_fv;
gdouble dest_x_fv; /* destination coords in front_view perspective */
gdouble dest_y_fv;
PikaMatrix3 transform;
PikaMatrix3 transform_inv;
GeglNode *node;
GeglNode *crop;
GeglNode *transform_node;
GeglNode *src_node;
GeglNode *dest_node;
};
struct _PikaPerspectiveCloneClass
{
PikaCloneClass parent_class;
};
void pika_perspective_clone_register (Pika *pika,
PikaPaintRegisterCallback callback);
GType pika_perspective_clone_get_type (void) G_GNUC_CONST;
void pika_perspective_clone_set_transform (PikaPerspectiveClone *clone,
PikaMatrix3 *transform);
void pika_perspective_clone_get_source_point (PikaPerspectiveClone *clone,
gdouble x,
gdouble y,
gdouble *newx,
gdouble *newy);
#endif /* __PIKA_PERSPECTIVE_CLONE_H__ */

View File

@ -0,0 +1,117 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikaconfig/pikaconfig.h"
#include "paint-types.h"
#include "pikaperspectivecloneoptions.h"
#include "pika-intl.h"
enum
{
PROP_0,
PROP_CLONE_MODE
};
static void pika_perspective_clone_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_perspective_clone_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
G_DEFINE_TYPE (PikaPerspectiveCloneOptions, pika_perspective_clone_options,
PIKA_TYPE_CLONE_OPTIONS)
static void
pika_perspective_clone_options_class_init (PikaPerspectiveCloneOptionsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = pika_perspective_clone_options_set_property;
object_class->get_property = pika_perspective_clone_options_get_property;
PIKA_CONFIG_PROP_ENUM (object_class, PROP_CLONE_MODE,
"clone-mode",
NULL, NULL,
PIKA_TYPE_PERSPECTIVE_CLONE_MODE,
PIKA_PERSPECTIVE_CLONE_MODE_ADJUST,
PIKA_PARAM_STATIC_STRINGS);
}
static void
pika_perspective_clone_options_init (PikaPerspectiveCloneOptions *options)
{
}
static void
pika_perspective_clone_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaPerspectiveCloneOptions *options = PIKA_PERSPECTIVE_CLONE_OPTIONS (object);
switch (property_id)
{
case PROP_CLONE_MODE:
options->clone_mode = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_perspective_clone_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaPerspectiveCloneOptions *options = PIKA_PERSPECTIVE_CLONE_OPTIONS (object);
switch (property_id)
{
case PROP_CLONE_MODE:
g_value_set_enum (value, options->clone_mode);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}

View File

@ -0,0 +1,55 @@
/* 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/>.
*/
#ifndef __PIKA_PERSPECTIVE_CLONE_OPTIONS_H__
#define __PIKA_PERSPECTIVE_CLONE_OPTIONS_H__
#include "pikacloneoptions.h"
#define PIKA_TYPE_PERSPECTIVE_CLONE_OPTIONS (pika_perspective_clone_options_get_type ())
#define PIKA_PERSPECTIVE_CLONE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_PERSPECTIVE_CLONE_OPTIONS, PikaPerspectiveCloneOptions))
#define PIKA_PERSPECTIVE_CLONE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_PERSPECTIVE_CLONE_OPTIONS, PikaPerspectiveCloneOptionsClass))
#define PIKA_IS_PERSPECTIVE_CLONE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_PERSPECTIVE_CLONE_OPTIONS))
#define PIKA_IS_PERSPECTIVE_CLONE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_PERSPECTIVE_CLONE_OPTIONS))
#define PIKA_PERSPECTIVE_CLONE_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_PERSPECTIVE_CLONE_OPTIONS, PikaPerspectiveCloneOptionsClass))
typedef struct _PikaPerspectiveCloneOptionsClass PikaPerspectiveCloneOptionsClass;
struct _PikaPerspectiveCloneOptions
{
PikaCloneOptions paint_instance;
PikaPerspectiveCloneMode clone_mode;
};
struct _PikaPerspectiveCloneOptionsClass
{
PikaCloneOptionsClass parent_class;
};
GType pika_perspective_clone_options_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_PERSPECTIVE_CLONE_OPTIONS_H__ */

596
app/paint/pikasmudge.c Normal file
View File

@ -0,0 +1,596 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikamath/pikamath.h"
#include "paint-types.h"
#include "gegl/pika-gegl-loops.h"
#include "gegl/pika-gegl-utils.h"
#include "core/pika-palettes.h"
#include "core/pikabrush.h"
#include "core/pikabrush-header.h"
#include "core/pikadrawable.h"
#include "core/pikadynamics.h"
#include "core/pikaimage.h"
#include "core/pikapickable.h"
#include "core/pikasymmetry.h"
#include "core/pikatempbuf.h"
#include "pikasmudge.h"
#include "pikasmudgeoptions.h"
#include "pika-intl.h"
static void pika_smudge_finalize (GObject *object);
static void pika_smudge_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time);
static gboolean pika_smudge_start (PikaPaintCore *paint_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym);
static void pika_smudge_motion (PikaPaintCore *paint_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym);
static void pika_smudge_accumulator_coords (PikaPaintCore *paint_core,
const PikaCoords *coords,
gint stroke,
gint *x,
gint *y);
static void pika_smudge_accumulator_size (PikaPaintOptions *paint_options,
const PikaCoords *coords,
gint *accumulator_size);
G_DEFINE_TYPE (PikaSmudge, pika_smudge, PIKA_TYPE_BRUSH_CORE)
#define parent_class pika_smudge_parent_class
void
pika_smudge_register (Pika *pika,
PikaPaintRegisterCallback callback)
{
(* callback) (pika,
PIKA_TYPE_SMUDGE,
PIKA_TYPE_SMUDGE_OPTIONS,
"pika-smudge",
_("Smudge"),
"pika-tool-smudge");
}
static void
pika_smudge_class_init (PikaSmudgeClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
PikaPaintCoreClass *paint_core_class = PIKA_PAINT_CORE_CLASS (klass);
PikaBrushCoreClass *brush_core_class = PIKA_BRUSH_CORE_CLASS (klass);
object_class->finalize = pika_smudge_finalize;
paint_core_class->paint = pika_smudge_paint;
brush_core_class->handles_changing_brush = TRUE;
brush_core_class->handles_transforming_brush = TRUE;
brush_core_class->handles_dynamic_transforming_brush = TRUE;
}
static void
pika_smudge_init (PikaSmudge *smudge)
{
}
static void
pika_smudge_finalize (GObject *object)
{
PikaSmudge *smudge = PIKA_SMUDGE (object);
if (smudge->accum_buffers)
{
GList *iter;
for (iter = smudge->accum_buffers; iter; iter = g_list_next (iter))
{
if (iter->data)
g_object_unref (iter->data);
}
g_list_free (smudge->accum_buffers);
smudge->accum_buffers = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
pika_smudge_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time)
{
PikaSmudge *smudge = PIKA_SMUDGE (paint_core);
g_return_if_fail (g_list_length (drawables) == 1);
switch (paint_state)
{
case PIKA_PAINT_STATE_INIT:
{
PikaContext *context = PIKA_CONTEXT (paint_options);
PikaSmudgeOptions *options = PIKA_SMUDGE_OPTIONS (paint_options);
PikaBrushCore *brush_core = PIKA_BRUSH_CORE (paint_core);
PikaDynamics *dynamics = pika_context_get_dynamics (context);
/* Don't add to color history when
* 1. pure smudging (flow=0)
* 2. color is from gradient or pixmap brushes
*/
if (options->flow > 0.0 &&
! pika_dynamics_is_output_enabled (dynamics, PIKA_DYNAMICS_OUTPUT_COLOR) &&
! (brush_core->brush && pika_brush_get_pixmap (brush_core->brush)))
{
PikaRGB foreground;
pika_context_get_foreground (context, &foreground);
pika_palettes_add_color_history (context->pika, &foreground);
}
}
break;
case PIKA_PAINT_STATE_MOTION:
/* initialization fails if the user starts outside the drawable */
if (! smudge->initialized)
smudge->initialized = pika_smudge_start (paint_core, drawables->data,
paint_options, sym);
if (smudge->initialized)
pika_smudge_motion (paint_core, drawables->data, paint_options, sym);
break;
case PIKA_PAINT_STATE_FINISH:
if (smudge->accum_buffers)
{
GList *iter;
for (iter = smudge->accum_buffers; iter; iter = g_list_next (iter))
{
if (iter->data)
g_object_unref (iter->data);
}
g_list_free (smudge->accum_buffers);
smudge->accum_buffers = NULL;
}
smudge->initialized = FALSE;
break;
default:
break;
}
}
static gboolean
pika_smudge_start (PikaPaintCore *paint_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym)
{
PikaSmudge *smudge = PIKA_SMUDGE (paint_core);
PikaBrushCore *brush_core = PIKA_BRUSH_CORE (paint_core);
PikaSmudgeOptions *options = PIKA_SMUDGE_OPTIONS (paint_options);
PikaPickable *dest_pickable;
GeglBuffer *pickable_buffer;
GeglBuffer *paint_buffer;
PikaCoords coords;
gint dest_pickable_off_x;
gint dest_pickable_off_y;
gint paint_buffer_x;
gint paint_buffer_y;
gint accum_size;
gint n_strokes;
gint i;
gint x, y;
gint off_x, off_y;
coords = *(pika_symmetry_get_origin (sym));
pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y);
coords.x -= off_x;
coords.y -= off_y;
pika_brush_core_eval_transform_dynamics (brush_core,
pika_item_get_image (PIKA_ITEM (drawable)),
paint_options,
&coords);
if (options->sample_merged)
{
dest_pickable = pika_paint_core_get_image_pickable (paint_core);
pika_item_get_offset (PIKA_ITEM (drawable),
&dest_pickable_off_x,
&dest_pickable_off_y);
}
else
{
dest_pickable = PIKA_PICKABLE (drawable);
dest_pickable_off_x = 0;
dest_pickable_off_y = 0;
}
pickable_buffer = pika_pickable_get_buffer (dest_pickable);
n_strokes = pika_symmetry_get_size (sym);
for (i = 0; i < n_strokes; i++)
{
GeglBuffer *accum_buffer;
coords = *(pika_symmetry_get_coords (sym, i));
coords.x -= off_x;
coords.y -= off_y;
pika_smudge_accumulator_size (paint_options, &coords, &accum_size);
/* Allocate the accumulation buffer */
accum_buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0,
accum_size,
accum_size),
babl_format ("RGBA float"));
smudge->accum_buffers = g_list_prepend (smudge->accum_buffers,
accum_buffer);
/* adjust the x and y coordinates to the upper left corner of the
* accumulator
*/
paint_buffer = pika_paint_core_get_paint_buffer (paint_core, drawable,
paint_options,
PIKA_LAYER_MODE_NORMAL,
&coords,
&paint_buffer_x,
&paint_buffer_y,
NULL, NULL);
if (! paint_buffer)
continue;
pika_smudge_accumulator_coords (paint_core, &coords, 0, &x, &y);
/* If clipped, prefill the smudge buffer with the color at the
* brush position.
*/
if (x != paint_buffer_x ||
y != paint_buffer_y ||
accum_size != gegl_buffer_get_width (paint_buffer) ||
accum_size != gegl_buffer_get_height (paint_buffer))
{
gfloat pixel[4];
GeglColor *color;
gint pick_x;
gint pick_y;
pick_x = CLAMP ((gint) coords.x + dest_pickable_off_x,
0,
gegl_buffer_get_width (pickable_buffer) - 1);
pick_y = CLAMP ((gint) coords.y + dest_pickable_off_y,
0,
gegl_buffer_get_height (pickable_buffer) - 1);
pika_pickable_get_pixel_at (dest_pickable,
pick_x, pick_y,
babl_format ("RGBA float"),
pixel);
color = gegl_color_new (NULL);
gegl_color_set_pixel (color, babl_format ("RGBA float"), pixel);
gegl_buffer_set_color (accum_buffer, NULL, color);
g_object_unref (color);
}
/* copy the region under the original painthit. */
pika_gegl_buffer_copy
(pickable_buffer,
GEGL_RECTANGLE (paint_buffer_x + dest_pickable_off_x,
paint_buffer_y + dest_pickable_off_y,
gegl_buffer_get_width (paint_buffer),
gegl_buffer_get_height (paint_buffer)),
GEGL_ABYSS_NONE,
accum_buffer,
GEGL_RECTANGLE (paint_buffer_x - x,
paint_buffer_y - y,
0, 0));
}
smudge->accum_buffers = g_list_reverse (smudge->accum_buffers);
return TRUE;
}
static void
pika_smudge_motion (PikaPaintCore *paint_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
PikaSymmetry *sym)
{
PikaSmudge *smudge = PIKA_SMUDGE (paint_core);
PikaBrushCore *brush_core = PIKA_BRUSH_CORE (paint_core);
PikaSmudgeOptions *options = PIKA_SMUDGE_OPTIONS (paint_options);
PikaContext *context = PIKA_CONTEXT (paint_options);
PikaDynamics *dynamics = PIKA_BRUSH_CORE (paint_core)->dynamics;
PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable));
PikaPickable *dest_pickable;
GeglBuffer *paint_buffer;
gint dest_pickable_off_x;
gint dest_pickable_off_y;
gint paint_buffer_x;
gint paint_buffer_y;
gint paint_buffer_width;
gint paint_buffer_height;
/* brush dynamics */
gdouble fade_point;
gdouble opacity;
gdouble rate;
gdouble flow;
gdouble grad_point;
/* brush color */
PikaRGB brush_color;
PikaRGB *brush_color_ptr; /* whether use single color or pixmap */
/* accum buffer */
gint x, y;
GeglBuffer *accum_buffer;
/* other variables */
gdouble force;
PikaCoords coords;
gint off_x, off_y;
gint paint_width, paint_height;
gint n_strokes;
gint i;
if (options->sample_merged)
{
dest_pickable = pika_paint_core_get_image_pickable (paint_core);
pika_item_get_offset (PIKA_ITEM (drawable),
&dest_pickable_off_x,
&dest_pickable_off_y);
}
else
{
dest_pickable = PIKA_PICKABLE (drawable);
dest_pickable_off_x = 0;
dest_pickable_off_y = 0;
}
fade_point = pika_paint_options_get_fade (paint_options, image,
paint_core->pixel_dist);
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);
opacity = pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_OPACITY,
&coords,
paint_options,
fade_point);
if (opacity == 0.0)
return;
pika_brush_core_eval_transform_dynamics (brush_core,
image,
paint_options,
&coords);
/* Get brush dynamic values other than opacity */
rate = ((options->rate / 100.0) *
pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_RATE,
&coords,
paint_options,
fade_point));
flow = ((options->flow / 100.0) *
pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_FLOW,
&coords,
paint_options,
fade_point));
grad_point = pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_COLOR,
&coords,
paint_options,
fade_point);
/* Get current gradient color, brush pixmap, or foreground color */
brush_color_ptr = &brush_color;
if (pika_paint_options_get_gradient_color (paint_options, image,
grad_point,
paint_core->pixel_dist,
&brush_color))
{
/* No more processing needed */
}
else if (brush_core->brush && pika_brush_get_pixmap (brush_core->brush))
{
brush_color_ptr = NULL;
}
else
{
pika_context_get_foreground (context, &brush_color);
}
/* Convert to linear RGBA */
if (brush_color_ptr)
pika_pickable_rgb_to_pixel (dest_pickable,
&brush_color,
babl_format ("RGBA double"),
&brush_color);
n_strokes = pika_symmetry_get_size (sym);
for (i = 0; i < n_strokes; i++)
{
coords = *(pika_symmetry_get_coords (sym, i));
pika_brush_core_eval_transform_symmetry (brush_core, sym, i);
paint_buffer = pika_paint_core_get_paint_buffer (paint_core, drawable,
paint_options,
PIKA_LAYER_MODE_NORMAL,
&coords,
&paint_buffer_x,
&paint_buffer_y,
&paint_width,
&paint_height);
if (! paint_buffer)
continue;
paint_buffer_width = gegl_buffer_get_width (paint_buffer);
paint_buffer_height = gegl_buffer_get_height (paint_buffer);
/* Get the unclipped accumulator coordinates */
pika_smudge_accumulator_coords (paint_core, &coords, i, &x, &y);
accum_buffer = g_list_nth_data (smudge->accum_buffers, i);
/* Old smudge tool:
* Smudge uses the buffer Accum.
* For each successive painthit Accum is built like this
* Accum = rate*Accum + (1-rate)*I.
* where I is the pixels under the current painthit.
* Then the paint area (paint_area) is built as
* (Accum,1) (if no alpha),
*/
/* 2017/4/22: New smudge painting tool:
* Accum=rate*Accum + (1-rate)*I
* if brush_color_ptr!=NULL
* Paint=(1-flow)*Accum + flow*BrushColor
* else, draw brush pixmap on the paint_buffer and
* Paint=(1-flow)*Accum + flow*Paint
*
* For non-pixmap brushes, calculate blending in
* pika_gegl_smudge_with_paint() instead of calling
* gegl_buffer_set_color() to reduce gegl's internal processing.
*/
if (! brush_color_ptr && flow > 0.0)
{
pika_brush_core_color_area_with_pixmap (brush_core, drawable,
&coords,
paint_buffer,
paint_buffer_x,
paint_buffer_y,
TRUE);
}
pika_gegl_smudge_with_paint (accum_buffer,
GEGL_RECTANGLE (paint_buffer_x - x,
paint_buffer_y - y,
paint_buffer_width,
paint_buffer_height),
pika_pickable_get_buffer (dest_pickable),
GEGL_RECTANGLE (paint_buffer_x +
dest_pickable_off_x,
paint_buffer_y +
dest_pickable_off_y,
paint_buffer_width,
paint_buffer_height),
brush_color_ptr,
paint_buffer,
options->no_erasing,
flow,
rate);
if (pika_dynamics_is_output_enabled (dynamics, PIKA_DYNAMICS_OUTPUT_FORCE))
force = pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_FORCE,
&coords,
paint_options,
fade_point);
else
force = paint_options->brush_force;
pika_brush_core_replace_canvas (PIKA_BRUSH_CORE (paint_core), drawable,
&coords,
MIN (opacity, PIKA_OPACITY_OPAQUE),
pika_context_get_opacity (context),
pika_paint_options_get_brush_mode (paint_options),
force,
PIKA_PAINT_INCREMENTAL);
}
}
static void
pika_smudge_accumulator_coords (PikaPaintCore *paint_core,
const PikaCoords *coords,
gint stroke,
gint *x,
gint *y)
{
PikaSmudge *smudge = PIKA_SMUDGE (paint_core);
GeglBuffer *accum_buffer;
accum_buffer = g_list_nth_data (smudge->accum_buffers, stroke);
*x = (gint) coords->x - gegl_buffer_get_width (accum_buffer) / 2;
*y = (gint) coords->y - gegl_buffer_get_height (accum_buffer) / 2;
}
static void
pika_smudge_accumulator_size (PikaPaintOptions *paint_options,
const PikaCoords *coords,
gint *accumulator_size)
{
gdouble max_view_scale = 1.0;
gdouble max_brush_size;
if (paint_options->brush_lock_to_view)
max_view_scale = MAX (coords->xscale, coords->yscale);
max_brush_size = MIN (paint_options->brush_size / max_view_scale,
PIKA_BRUSH_MAX_SIZE);
/* Note: the max brush mask size plus a border of 1 pixel and a
* little headroom
*/
*accumulator_size = ceil (sqrt (2 * SQR (max_brush_size + 1)) + 2);
}

59
app/paint/pikasmudge.h Normal file
View File

@ -0,0 +1,59 @@
/* 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/>.
*/
#ifndef __PIKA_SMUDGE_H__
#define __PIKA_SMUDGE_H__
#include "pikabrushcore.h"
#define PIKA_TYPE_SMUDGE (pika_smudge_get_type ())
#define PIKA_SMUDGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_SMUDGE, PikaSmudge))
#define PIKA_SMUDGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_SMUDGE, PikaSmudgeClass))
#define PIKA_IS_SMUDGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_SMUDGE))
#define PIKA_IS_SMUDGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_SMUDGE))
#define PIKA_SMUDGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_SMUDGE, PikaSmudgeClass))
typedef struct _PikaSmudgeClass PikaSmudgeClass;
struct _PikaSmudge
{
PikaBrushCore parent_instance;
gboolean initialized;
GList *accum_buffers;
};
struct _PikaSmudgeClass
{
PikaBrushCoreClass parent_class;
};
void pika_smudge_register (Pika *pika,
PikaPaintRegisterCallback callback);
GType pika_smudge_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_SMUDGE_H__ */

View File

@ -0,0 +1,164 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikaconfig/pikaconfig.h"
#include "paint-types.h"
#include "pikasmudgeoptions.h"
#include "pika-intl.h"
#define SMUDGE_DEFAULT_RATE 50.0
#define SMUDGE_DEFAULT_FLOW 0.0
#define SMUDGE_DEFAULT_NO_ERASING FALSE
enum
{
PROP_0,
PROP_RATE,
PROP_FLOW,
PROP_NO_ERASING,
PROP_SAMPLE_MERGED
};
static void pika_smudge_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_smudge_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
G_DEFINE_TYPE (PikaSmudgeOptions, pika_smudge_options,
PIKA_TYPE_PAINT_OPTIONS)
static void
pika_smudge_options_class_init (PikaSmudgeOptionsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = pika_smudge_options_set_property;
object_class->get_property = pika_smudge_options_get_property;
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_RATE,
"rate",
C_("smudge-tool", "Rate"),
_("The strength of smudging"),
0.0, 100.0, SMUDGE_DEFAULT_RATE,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_DOUBLE (object_class, PROP_FLOW,
"flow",
C_("smudge-tool", "Flow"),
_("The amount of brush color to blend"),
0.0, 100.0, SMUDGE_DEFAULT_FLOW,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_NO_ERASING,
"no-erasing",
C_("smudge-tool", "No erasing effect"),
_("Never decrease alpha of existing pixels"),
SMUDGE_DEFAULT_NO_ERASING,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_SAMPLE_MERGED,
"sample-merged",
_("Sample merged"),
NULL,
FALSE,
PIKA_PARAM_STATIC_STRINGS);
}
static void
pika_smudge_options_init (PikaSmudgeOptions *options)
{
}
static void
pika_smudge_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaSmudgeOptions *options = PIKA_SMUDGE_OPTIONS (object);
switch (property_id)
{
case PROP_RATE:
options->rate = g_value_get_double (value);
break;
case PROP_FLOW:
options->flow = g_value_get_double (value);
break;
case PROP_NO_ERASING:
options->no_erasing = g_value_get_boolean (value);
break;
case PROP_SAMPLE_MERGED:
options->sample_merged = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_smudge_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaSmudgeOptions *options = PIKA_SMUDGE_OPTIONS (object);
switch (property_id)
{
case PROP_RATE:
g_value_set_double (value, options->rate);
break;
case PROP_FLOW:
g_value_set_double (value, options->flow);
break;
case PROP_NO_ERASING:
g_value_set_boolean (value, options->no_erasing);
break;
case PROP_SAMPLE_MERGED:
g_value_set_boolean (value, options->sample_merged);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}

View File

@ -0,0 +1,58 @@
/* 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/>.
*/
#ifndef __PIKA_SMUDGE_OPTIONS_H__
#define __PIKA_SMUDGE_OPTIONS_H__
#include "pikapaintoptions.h"
#define PIKA_TYPE_SMUDGE_OPTIONS (pika_smudge_options_get_type ())
#define PIKA_SMUDGE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_SMUDGE_OPTIONS, PikaSmudgeOptions))
#define PIKA_SMUDGE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_SMUDGE_OPTIONS, PikaSmudgeOptionsClass))
#define PIKA_IS_SMUDGE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_SMUDGE_OPTIONS))
#define PIKA_IS_SMUDGE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_SMUDGE_OPTIONS))
#define PIKA_SMUDGE_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_SMUDGE_OPTIONS, PikaSmudgeOptionsClass))
typedef struct _PikaSmudgeOptionsClass PikaSmudgeOptionsClass;
struct _PikaSmudgeOptions
{
PikaPaintOptions parent_instance;
gdouble rate;
gdouble flow;
gboolean no_erasing;
gboolean sample_merged;
};
struct _PikaSmudgeOptionsClass
{
PikaPaintOptionsClass parent_class;
};
GType pika_smudge_options_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_SMUDGE_OPTIONS_H__ */

620
app/paint/pikasourcecore.c Normal file
View File

@ -0,0 +1,620 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikamath/pikamath.h"
#include "paint-types.h"
#include "gegl/pika-gegl-utils.h"
#include "core/pika.h"
#include "core/pikacontainer.h"
#include "core/pikadrawable.h"
#include "core/pikadynamics.h"
#include "core/pikaerror.h"
#include "core/pikaimage.h"
#include "core/pikaimage-new.h"
#include "core/pikapickable.h"
#include "core/pikasymmetry.h"
#include "pikasourcecore.h"
#include "pikasourceoptions.h"
#include "pika-intl.h"
enum
{
PROP_0,
};
static gboolean pika_source_core_start (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
GError **error);
static void pika_source_core_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time);
#if 0
static void pika_source_core_motion (PikaSourceCore *source_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
gboolean self_drawable,
PikaSymmetry *sym);
#endif
static gboolean pika_source_core_real_use_source (PikaSourceCore *source_core,
PikaSourceOptions *options);
static GeglBuffer *
pika_source_core_real_get_source (PikaSourceCore *source_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
gboolean self_drawable,
PikaPickable *src_pickable,
gint src_offset_x,
gint src_offset_y,
GeglBuffer *paint_buffer,
gint paint_buffer_x,
gint paint_buffer_y,
gint *paint_area_offset_x,
gint *paint_area_offset_y,
gint *paint_area_width,
gint *paint_area_height,
GeglRectangle *src_rect);
G_DEFINE_TYPE (PikaSourceCore, pika_source_core, PIKA_TYPE_BRUSH_CORE)
#define parent_class pika_source_core_parent_class
static void
pika_source_core_class_init (PikaSourceCoreClass *klass)
{
PikaPaintCoreClass *paint_core_class = PIKA_PAINT_CORE_CLASS (klass);
PikaBrushCoreClass *brush_core_class = PIKA_BRUSH_CORE_CLASS (klass);
paint_core_class->start = pika_source_core_start;
paint_core_class->paint = pika_source_core_paint;
brush_core_class->handles_changing_brush = TRUE;
klass->use_source = pika_source_core_real_use_source;
klass->get_source = pika_source_core_real_get_source;
klass->motion = NULL;
}
static void
pika_source_core_init (PikaSourceCore *source_core)
{
source_core->set_source = FALSE;
source_core->orig_src_x = 0;
source_core->orig_src_y = 0;
source_core->offset_x = 0;
source_core->offset_y = 0;
source_core->first_stroke = TRUE;
}
static gboolean
pika_source_core_start (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
GError **error)
{
PikaSourceCore *source_core = PIKA_SOURCE_CORE (paint_core);
PikaSourceOptions *options = PIKA_SOURCE_OPTIONS (paint_options);
if (! PIKA_PAINT_CORE_CLASS (parent_class)->start (paint_core, drawables,
paint_options, coords,
error))
{
return FALSE;
}
paint_core->use_saved_proj = FALSE;
if (! source_core->set_source &&
pika_source_core_use_source (source_core, options))
{
if (! options->src_drawables)
{
g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
_("Set a source image first."));
return FALSE;
}
else if (options->align_mode == PIKA_SOURCE_ALIGN_REGISTERED &&
g_list_length (drawables) > 1)
{
g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED,
_("\"Registered\" alignment cannot paint on multiple drawables."));
return FALSE;
}
if (options->sample_merged &&
g_list_length (drawables) == 1 &&
pika_item_get_image (PIKA_ITEM (options->src_drawables->data)) ==
pika_item_get_image (PIKA_ITEM (drawables->data)))
{
paint_core->use_saved_proj = TRUE;
}
}
return TRUE;
}
static void
pika_source_core_paint (PikaPaintCore *paint_core,
GList *drawables,
PikaPaintOptions *paint_options,
PikaSymmetry *sym,
PikaPaintState paint_state,
guint32 time)
{
PikaSourceCore *source_core = PIKA_SOURCE_CORE (paint_core);
PikaSourceOptions *options = PIKA_SOURCE_OPTIONS (paint_options);
const PikaCoords *coords;
/* The source is based on the original stroke */
coords = pika_symmetry_get_origin (sym);
switch (paint_state)
{
case PIKA_PAINT_STATE_INIT:
if (source_core->set_source)
{
g_object_set (options,
"src-drawables", drawables,
"src-x", (gint) floor (coords->x),
"src-y", (gint) floor (coords->y),
NULL);
/* FIXME(?): subpixel source sampling */
source_core->first_stroke = TRUE;
}
else if (options->align_mode == PIKA_SOURCE_ALIGN_NO)
{
g_object_get (options,
"src-x", &source_core->orig_src_x,
"src-y", &source_core->orig_src_y,
NULL);
source_core->first_stroke = TRUE;
}
break;
case PIKA_PAINT_STATE_MOTION:
if (source_core->set_source)
{
/* If the control key is down, move the src target and return */
g_object_set (options,
"src-drawables", drawables,
"src-x", (gint) floor (coords->x),
"src-y", (gint) floor (coords->y),
NULL);
source_core->first_stroke = TRUE;
}
else
{
/* otherwise, update the target */
gint src_x;
gint src_y;
gint dest_x;
gint dest_y;
dest_x = floor (coords->x);
dest_y = floor (coords->y);
g_object_get (options,
"src-x", &src_x,
"src-y", &src_y,
NULL);
if (options->align_mode == PIKA_SOURCE_ALIGN_REGISTERED)
{
source_core->offset_x = 0;
source_core->offset_y = 0;
}
else if (options->align_mode == PIKA_SOURCE_ALIGN_FIXED)
{
source_core->offset_x = src_x - dest_x;
source_core->offset_y = src_y - dest_y;
}
else if (source_core->first_stroke)
{
source_core->offset_x = src_x - dest_x;
source_core->offset_y = src_y - dest_y;
source_core->first_stroke = FALSE;
}
g_object_set (options,
"src-x", dest_x + source_core->offset_x,
"src-y", dest_y + source_core->offset_y,
NULL);
for (GList *iter = drawables; iter; iter = iter->next)
pika_source_core_motion (source_core, iter->data,
paint_options,
(g_list_length (drawables) > 1),
sym);
}
break;
case PIKA_PAINT_STATE_FINISH:
if (options->align_mode == PIKA_SOURCE_ALIGN_NO &&
! source_core->first_stroke)
{
g_object_set (options,
"src-x", source_core->orig_src_x,
"src-y", source_core->orig_src_y,
NULL);
}
break;
default:
break;
}
}
void
pika_source_core_motion (PikaSourceCore *source_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
gboolean self_drawable,
PikaSymmetry *sym)
{
PikaPaintCore *paint_core = PIKA_PAINT_CORE (source_core);
PikaBrushCore *brush_core = PIKA_BRUSH_CORE (source_core);
PikaSourceOptions *options = PIKA_SOURCE_OPTIONS (paint_options);
PikaDynamics *dynamics = PIKA_BRUSH_CORE (paint_core)->dynamics;
PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable));
PikaPickable *src_pickable = NULL;
GeglBuffer *src_buffer = NULL;
GeglRectangle src_rect;
gint base_src_offset_x;
gint base_src_offset_y;
gint src_offset_x;
gint src_offset_y;
GeglBuffer *paint_buffer;
gint paint_buffer_x;
gint paint_buffer_y;
gint paint_area_offset_x;
gint paint_area_offset_y;
gint paint_area_width;
gint paint_area_height;
gdouble fade_point;
gdouble opacity;
PikaLayerMode paint_mode;
GeglNode *op;
PikaCoords origin;
PikaCoords coords;
gint src_off_x = 0;
gint src_off_y = 0;
gint src_x, src_y;
gint off_x, off_y;
gint n_strokes;
gint i;
fade_point = pika_paint_options_get_fade (paint_options, image,
paint_core->pixel_dist);
origin = *(pika_symmetry_get_origin (sym));
pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y);
coords = origin;
coords.x -= off_x;
coords.y -= off_y;
pika_symmetry_set_origin (sym, drawable, &coords);
/* Some settings are based on the original stroke. */
opacity = pika_dynamics_get_linear_value (dynamics,
PIKA_DYNAMICS_OUTPUT_OPACITY,
&origin,
paint_options,
fade_point);
if (opacity == 0.0)
return;
base_src_offset_x = source_core->offset_x;
base_src_offset_y = source_core->offset_y;
if (pika_source_core_use_source (source_core, options))
{
if (self_drawable)
{
src_pickable = PIKA_PICKABLE (drawable);
}
else if (options->sample_merged)
{
PikaImage *src_image;
src_image = pika_pickable_get_image (options->src_drawables->data);
if (! pika_paint_core_get_show_all (paint_core))
{
src_pickable = PIKA_PICKABLE (src_image);
}
else
{
src_pickable = PIKA_PICKABLE (pika_image_get_projection (src_image));
}
}
else
{
src_pickable = options->src_pickable;
}
if (PIKA_IS_ITEM (src_pickable))
pika_item_get_offset (PIKA_ITEM (src_pickable),
&src_off_x, &src_off_y);
}
g_object_get (options,
"src-x", &src_x,
"src-y", &src_y,
NULL);
pika_brush_core_eval_transform_dynamics (brush_core,
image,
paint_options,
&origin);
paint_mode = pika_context_get_paint_mode (PIKA_CONTEXT (paint_options));
n_strokes = pika_symmetry_get_size (sym);
for (i = 0; i < n_strokes; i++)
{
coords = *(pika_symmetry_get_coords (sym, i));
pika_brush_core_eval_transform_symmetry (brush_core, sym, i);
paint_buffer = pika_paint_core_get_paint_buffer (paint_core, drawable,
paint_options,
paint_mode,
&coords,
&paint_buffer_x,
&paint_buffer_y,
NULL, NULL);
if (! paint_buffer)
continue;
paint_area_offset_x = 0;
paint_area_offset_y = 0;
paint_area_width = gegl_buffer_get_width (paint_buffer);
paint_area_height = gegl_buffer_get_height (paint_buffer);
src_offset_x = base_src_offset_x;
src_offset_y = base_src_offset_y;
if (pika_source_core_use_source (source_core, options))
{
/* When using a source, use the same for every stroke. */
src_offset_x += floor (origin.x) - floor (coords.x) - src_off_x;
src_offset_y += floor (origin.y) - floor (coords.y) - src_off_y;
src_buffer =
PIKA_SOURCE_CORE_GET_CLASS (source_core)->get_source (source_core,
drawable,
paint_options,
self_drawable,
src_pickable,
src_offset_x,
src_offset_y,
paint_buffer,
paint_buffer_x,
paint_buffer_y,
&paint_area_offset_x,
&paint_area_offset_y,
&paint_area_width,
&paint_area_height,
&src_rect);
if (! src_buffer)
continue;
}
/* Set the paint buffer to transparent */
gegl_buffer_clear (paint_buffer, NULL);
op = pika_symmetry_get_operation (sym, i);
if (op)
{
GeglNode *node;
GeglNode *input;
GeglNode *translate_before;
GeglNode *translate_after;
GeglNode *output;
node = gegl_node_new ();
input = gegl_node_get_input_proxy (node, "input");
translate_before = gegl_node_new_child (
node,
"operation", "gegl:translate",
"x", -((gdouble) src_x + 0.5),
"y", -((gdouble) src_y + 0.5),
NULL);
gegl_node_add_child (node, op);
translate_after = gegl_node_new_child (
node,
"operation", "gegl:translate",
"x", ((gdouble) src_x + 0.5) +
(paint_area_offset_x - src_rect.x),
"y", ((gdouble) src_y + 0.5) +
(paint_area_offset_y - src_rect.y),
NULL);
output = gegl_node_get_output_proxy (node, "output");
gegl_node_link_many (input,
translate_before,
op,
translate_after,
output,
NULL);
g_object_unref (op);
op = node;
}
PIKA_SOURCE_CORE_GET_CLASS (source_core)->motion (source_core,
drawable,
paint_options,
&coords,
op,
opacity,
src_pickable,
src_buffer,
&src_rect,
src_offset_x,
src_offset_y,
paint_buffer,
paint_buffer_x,
paint_buffer_y,
paint_area_offset_x,
paint_area_offset_y,
paint_area_width,
paint_area_height);
g_clear_object (&op);
g_clear_object (&src_buffer);
}
}
gboolean
pika_source_core_use_source (PikaSourceCore *source_core,
PikaSourceOptions *options)
{
return PIKA_SOURCE_CORE_GET_CLASS (source_core)->use_source (source_core,
options);
}
static gboolean
pika_source_core_real_use_source (PikaSourceCore *source_core,
PikaSourceOptions *options)
{
return TRUE;
}
static GeglBuffer *
pika_source_core_real_get_source (PikaSourceCore *source_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
gboolean self_drawable,
PikaPickable *src_pickable,
gint src_offset_x,
gint src_offset_y,
GeglBuffer *paint_buffer,
gint paint_buffer_x,
gint paint_buffer_y,
gint *paint_area_offset_x,
gint *paint_area_offset_y,
gint *paint_area_width,
gint *paint_area_height,
GeglRectangle *src_rect)
{
PikaSourceOptions *options = PIKA_SOURCE_OPTIONS (paint_options);
PikaImage *image = pika_item_get_image (PIKA_ITEM (drawable));
PikaImage *src_image = pika_pickable_get_image (src_pickable);
GeglBuffer *src_buffer = pika_pickable_get_buffer (src_pickable);
GeglBuffer *dest_buffer;
gboolean sample_merged;
gint x, y;
gint width, height;
/* As a special case, we bypass sample merged value when we request
* each drawable to be its own source.
*/
sample_merged = options->sample_merged && (! self_drawable);
if (! pika_rectangle_intersect (paint_buffer_x + src_offset_x,
paint_buffer_y + src_offset_y,
gegl_buffer_get_width (paint_buffer),
gegl_buffer_get_height (paint_buffer),
gegl_buffer_get_x (src_buffer),
gegl_buffer_get_y (src_buffer),
gegl_buffer_get_width (src_buffer),
gegl_buffer_get_height (src_buffer),
&x, &y,
&width, &height))
{
return FALSE;
}
/* If the source image is different from the destination,
* then we should copy straight from the source image
* to the canvas.
* Otherwise, we need a call to get_orig_image to make sure
* we get a copy of the unblemished (offset) image
*/
if (( sample_merged && (src_image != image)) ||
(! sample_merged && (g_list_length (options->src_drawables) != 1 ||
options->src_drawables->data != drawable)))
{
dest_buffer = src_buffer;
}
else
{
/* get the original image */
if (sample_merged)
dest_buffer = pika_paint_core_get_orig_proj (PIKA_PAINT_CORE (source_core));
else
dest_buffer = pika_paint_core_get_orig_image (PIKA_PAINT_CORE (source_core),
drawable);
}
*paint_area_offset_x = x - (paint_buffer_x + src_offset_x);
*paint_area_offset_y = y - (paint_buffer_y + src_offset_y);
*paint_area_width = width;
*paint_area_height = height;
*src_rect = *GEGL_RECTANGLE (x, y, width, height);
return g_object_ref (dest_buffer);
}

112
app/paint/pikasourcecore.h Normal file
View File

@ -0,0 +1,112 @@
/* 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/>.
*/
#ifndef __PIKA_SOURCE_CORE_H__
#define __PIKA_SOURCE_CORE_H__
#include "pikabrushcore.h"
#define PIKA_TYPE_SOURCE_CORE (pika_source_core_get_type ())
#define PIKA_SOURCE_CORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_SOURCE_CORE, PikaSourceCore))
#define PIKA_SOURCE_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_SOURCE_CORE, PikaSourceCoreClass))
#define PIKA_IS_SOURCE_CORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_SOURCE_CORE))
#define PIKA_IS_SOURCE_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_SOURCE_CORE))
#define PIKA_SOURCE_CORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_SOURCE_CORE, PikaSourceCoreClass))
typedef struct _PikaSourceCoreClass PikaSourceCoreClass;
struct _PikaSourceCore
{
PikaBrushCore parent_instance;
gboolean set_source;
gint orig_src_x;
gint orig_src_y;
gint offset_x;
gint offset_y;
gboolean first_stroke;
};
struct _PikaSourceCoreClass
{
PikaBrushCoreClass parent_class;
gboolean (* use_source) (PikaSourceCore *source_core,
PikaSourceOptions *options);
GeglBuffer * (* get_source) (PikaSourceCore *source_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
gboolean self_drawable,
PikaPickable *src_pickable,
gint src_offset_x,
gint src_offset_y,
GeglBuffer *paint_buffer,
gint paint_buffer_x,
gint paint_buffer_y,
/* offsets *into* the paint_buffer: */
gint *paint_area_offset_x,
gint *paint_area_offset_y,
gint *paint_area_width,
gint *paint_area_height,
GeglRectangle *src_rect);
void (* motion) (PikaSourceCore *source_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
const PikaCoords *coords,
GeglNode *op,
gdouble opacity,
PikaPickable *src_pickable,
GeglBuffer *src_buffer,
GeglRectangle *src_rect,
gint src_offset_x,
gint src_offset_y,
GeglBuffer *paint_buffer,
gint paint_buffer_x,
gint paint_buffer_y,
/* offsets *into* the paint_buffer: */
gint paint_area_offset_x,
gint paint_area_offset_y,
gint paint_area_width,
gint paint_area_height);
};
GType pika_source_core_get_type (void) G_GNUC_CONST;
gboolean pika_source_core_use_source (PikaSourceCore *source_core,
PikaSourceOptions *options);
/* TEMP HACK */
void pika_source_core_motion (PikaSourceCore *source_core,
PikaDrawable *drawable,
PikaPaintOptions *paint_options,
gboolean self_drawable,
PikaSymmetry *sym);
#endif /* __PIKA_SOURCE_CORE_H__ */

View File

@ -0,0 +1,297 @@
/* 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 <gdk-pixbuf/gdk-pixbuf.h>
#include <gegl.h>
#include "libpikabase/pikabase.h"
#include "libpikaconfig/pikaconfig.h"
#include "paint-types.h"
#include "core/pika.h"
#include "core/pikacontainer.h"
#include "core/pikaimage.h"
#include "core/pikaimage-new.h"
#include "core/pikaitem.h"
#include "core/pikapickable.h"
#include "pikasourceoptions.h"
#include "pika-intl.h"
enum
{
PROP_0,
PROP_SRC_DRAWABLES,
PROP_SRC_X,
PROP_SRC_Y,
PROP_ALIGN_MODE,
PROP_SAMPLE_MERGED
};
static void pika_source_options_finalize (GObject *object);
static void pika_source_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_source_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void
pika_source_options_set_src_drawables (PikaSourceOptions *options,
GList *drawables);
static void
pika_source_options_src_drawable_removed (PikaDrawable *drawable,
PikaSourceOptions *options);
static void pika_source_options_make_pickable (PikaSourceOptions *options);
G_DEFINE_TYPE (PikaSourceOptions, pika_source_options, PIKA_TYPE_PAINT_OPTIONS)
#define parent_class pika_source_options_parent_class
static void
pika_source_options_class_init (PikaSourceOptionsClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = pika_source_options_finalize;
object_class->set_property = pika_source_options_set_property;
object_class->get_property = pika_source_options_get_property;
g_object_class_install_property (object_class, PROP_SRC_DRAWABLES,
g_param_spec_pointer ("src-drawables",
NULL, NULL,
PIKA_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_SRC_X,
g_param_spec_int ("src-x",
NULL, NULL,
G_MININT, G_MAXINT, 0,
PIKA_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_SRC_Y,
g_param_spec_int ("src-y",
NULL, NULL,
G_MININT, G_MAXINT, 0,
PIKA_PARAM_READWRITE));
PIKA_CONFIG_PROP_ENUM (object_class, PROP_ALIGN_MODE,
"align-mode",
_("Alignment"),
NULL,
PIKA_TYPE_SOURCE_ALIGN_MODE,
PIKA_SOURCE_ALIGN_NO,
PIKA_PARAM_STATIC_STRINGS);
PIKA_CONFIG_PROP_BOOLEAN (object_class, PROP_SAMPLE_MERGED,
"sample-merged",
_("Sample merged"),
NULL,
FALSE,
PIKA_PARAM_STATIC_STRINGS);
}
static void
pika_source_options_init (PikaSourceOptions *options)
{
options->src_drawables = NULL;
}
static void
pika_source_options_finalize (GObject *object)
{
PikaSourceOptions *options = PIKA_SOURCE_OPTIONS (object);
pika_source_options_set_src_drawables (options, NULL);
g_clear_object (&options->src_image);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
pika_source_options_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaSourceOptions *options = PIKA_SOURCE_OPTIONS (object);
switch (property_id)
{
case PROP_SRC_DRAWABLES:
pika_source_options_set_src_drawables (options,
g_value_get_pointer (value));
break;
case PROP_SRC_X:
options->src_x = g_value_get_int (value);
break;
case PROP_SRC_Y:
options->src_y = g_value_get_int (value);
break;
case PROP_ALIGN_MODE:
options->align_mode = g_value_get_enum (value);
break;
case PROP_SAMPLE_MERGED:
options->sample_merged = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_source_options_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaSourceOptions *options = PIKA_SOURCE_OPTIONS (object);
switch (property_id)
{
case PROP_SRC_DRAWABLES:
g_value_set_pointer (value, options->src_drawables);
break;
case PROP_SRC_X:
g_value_set_int (value, options->src_x);
break;
case PROP_SRC_Y:
g_value_set_int (value, options->src_y);
break;
case PROP_ALIGN_MODE:
g_value_set_enum (value, options->align_mode);
break;
case PROP_SAMPLE_MERGED:
g_value_set_boolean (value, options->sample_merged);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_source_options_set_src_drawables (PikaSourceOptions *options,
GList *drawables)
{
PikaImage *image = NULL;
GList *iter;
if (g_list_length (options->src_drawables) == g_list_length (drawables))
{
GList *iter2;
for (iter = options->src_drawables, iter2 = drawables;
iter; iter = iter->next, iter2 = iter2->next)
{
if (iter->data != iter2->data)
break;
}
if (iter == NULL)
return;
}
for (GList *iter = drawables; iter; iter = iter->next)
{
/* Make sure all drawables are from the same image. */
if (image == NULL)
image = pika_item_get_image (PIKA_ITEM (iter->data));
else
g_return_if_fail (image == pika_item_get_image (PIKA_ITEM (iter->data)));
}
if (options->src_drawables)
{
for (GList *iter = options->src_drawables; iter; iter = iter->next)
g_signal_handlers_disconnect_by_func (iter->data,
pika_source_options_src_drawable_removed,
options);
g_list_free (options->src_drawables);
}
options->src_drawables = g_list_copy (drawables);
if (options->src_drawables)
{
for (GList *iter = options->src_drawables; iter; iter = iter->next)
g_signal_connect (iter->data, "removed",
G_CALLBACK (pika_source_options_src_drawable_removed),
options);
}
pika_source_options_make_pickable (options);
g_object_notify (G_OBJECT (options), "src-drawables");
}
static void
pika_source_options_src_drawable_removed (PikaDrawable *drawable,
PikaSourceOptions *options)
{
options->src_drawables = g_list_remove (options->src_drawables, drawable);
g_signal_handlers_disconnect_by_func (drawable,
pika_source_options_src_drawable_removed,
options);
pika_source_options_make_pickable (options);
g_object_notify (G_OBJECT (options), "src-drawables");
}
static void
pika_source_options_make_pickable (PikaSourceOptions *options)
{
g_clear_object (&options->src_image);
options->src_pickable = NULL;
if (options->src_drawables)
{
PikaImage *image;
image = pika_item_get_image (PIKA_ITEM (options->src_drawables->data));
if (g_list_length (options->src_drawables) > 1)
{
/* A composited projection of src_drawables as if they were on
* their own in the image. Some kind of sample_merged limited
* to these drawables.
*/
options->src_image = pika_image_new_from_drawables (image->pika, options->src_drawables,
FALSE, FALSE);
pika_container_remove (image->pika->images, PIKA_OBJECT (options->src_image));
options->src_pickable = PIKA_PICKABLE (options->src_image);
pika_pickable_flush (options->src_pickable);
}
else
{
options->src_pickable = PIKA_PICKABLE (options->src_drawables->data);
}
}
}

View File

@ -0,0 +1,64 @@
/* 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/>.
*/
#ifndef __PIKA_SOURCE_OPTIONS_H__
#define __PIKA_SOURCE_OPTIONS_H__
#include "pikapaintoptions.h"
#define PIKA_TYPE_SOURCE_OPTIONS (pika_source_options_get_type ())
#define PIKA_SOURCE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_SOURCE_OPTIONS, PikaSourceOptions))
#define PIKA_SOURCE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_SOURCE_OPTIONS, PikaSourceOptionsClass))
#define PIKA_IS_SOURCE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_SOURCE_OPTIONS))
#define PIKA_IS_SOURCE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_SOURCE_OPTIONS))
#define PIKA_SOURCE_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_SOURCE_OPTIONS, PikaSourceOptionsClass))
typedef struct _PikaSourceOptionsClass PikaSourceOptionsClass;
struct _PikaSourceOptions
{
PikaPaintOptions parent_instance;
GList *src_drawables;
gint src_x;
gint src_y;
/* The pickable to use when not in sample merged mode. */
PikaPickable *src_pickable;
PikaImage *src_image;
PikaSourceAlignMode align_mode;
gboolean sample_merged;
};
struct _PikaSourceOptionsClass
{
PikaPaintOptionsClass parent_class;
};
GType pika_source_options_get_type (void) G_GNUC_CONST;
#endif /* __PIKA_SOURCE_OPTIONS_H__ */