Initial checkin of Pika from heckimp
This commit is contained in:
68
app/paint/meson.build
Normal file
68
app/paint/meson.build
Normal 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
105
app/paint/paint-enums.c
Normal 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
89
app/paint/paint-enums.h
Normal 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
80
app/paint/paint-types.h
Normal 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
144
app/paint/pika-paint.c
Normal 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
30
app/paint/pika-paint.h
Normal 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
275
app/paint/pikaairbrush.c
Normal 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
68
app/paint/pikaairbrush.h
Normal 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__ */
|
160
app/paint/pikaairbrushoptions.c
Normal file
160
app/paint/pikaairbrushoptions.c
Normal 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;
|
||||
}
|
||||
}
|
57
app/paint/pikaairbrushoptions.h
Normal file
57
app/paint/pikaairbrushoptions.h
Normal 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__ */
|
116
app/paint/pikabrushcore-kernels.h
Normal file
116
app/paint/pikabrushcore-kernels.h
Normal 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__ */
|
651
app/paint/pikabrushcore-loops.cc
Normal file
651
app/paint/pikabrushcore-loops.cc
Normal 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;
|
||||
}
|
41
app/paint/pikabrushcore-loops.h
Normal file
41
app/paint/pikabrushcore-loops.h
Normal 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
1364
app/paint/pikabrushcore.c
Normal file
File diff suppressed because it is too large
Load Diff
156
app/paint/pikabrushcore.h
Normal file
156
app/paint/pikabrushcore.h
Normal 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
260
app/paint/pikaclone.c
Normal 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
56
app/paint/pikaclone.h
Normal 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__ */
|
119
app/paint/pikacloneoptions.c
Normal file
119
app/paint/pikacloneoptions.c
Normal 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;
|
||||
}
|
||||
}
|
55
app/paint/pikacloneoptions.h
Normal file
55
app/paint/pikacloneoptions.h
Normal 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
290
app/paint/pikaconvolve.c
Normal 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
58
app/paint/pikaconvolve.h
Normal 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__ */
|
134
app/paint/pikaconvolveoptions.c
Normal file
134
app/paint/pikaconvolveoptions.c
Normal 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;
|
||||
}
|
||||
}
|
56
app/paint/pikaconvolveoptions.h
Normal file
56
app/paint/pikaconvolveoptions.h
Normal 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
214
app/paint/pikadodgeburn.c
Normal 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
55
app/paint/pikadodgeburn.h
Normal 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__ */
|
150
app/paint/pikadodgeburnoptions.c
Normal file
150
app/paint/pikadodgeburnoptions.c
Normal 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;
|
||||
}
|
||||
}
|
57
app/paint/pikadodgeburnoptions.h
Normal file
57
app/paint/pikadodgeburnoptions.h
Normal 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
134
app/paint/pikaeraser.c
Normal 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
56
app/paint/pikaeraser.h
Normal 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__ */
|
118
app/paint/pikaeraseroptions.c
Normal file
118
app/paint/pikaeraseroptions.c
Normal 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;
|
||||
}
|
||||
}
|
55
app/paint/pikaeraseroptions.h
Normal file
55
app/paint/pikaeraseroptions.h
Normal 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
646
app/paint/pikaheal.c
Normal 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
56
app/paint/pikaheal.h
Normal 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
879
app/paint/pikaink-blob.c
Normal 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
91
app/paint/pikaink-blob.h
Normal 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
800
app/paint/pikaink.c
Normal 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
62
app/paint/pikaink.h
Normal 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
213
app/paint/pikainkoptions.c
Normal 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;
|
||||
}
|
||||
}
|
64
app/paint/pikainkoptions.h
Normal file
64
app/paint/pikainkoptions.h
Normal 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
129
app/paint/pikainkundo.c
Normal 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
56
app/paint/pikainkundo.h
Normal 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
434
app/paint/pikamybrushcore.c
Normal 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);
|
||||
}
|
59
app/paint/pikamybrushcore.h
Normal file
59
app/paint/pikamybrushcore.h
Normal 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__ */
|
224
app/paint/pikamybrushoptions.c
Normal file
224
app/paint/pikamybrushoptions.c
Normal 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);
|
||||
}
|
59
app/paint/pikamybrushoptions.h
Normal file
59
app/paint/pikamybrushoptions.h
Normal 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__ */
|
565
app/paint/pikamybrushsurface.c
Normal file
565
app/paint/pikamybrushsurface.c
Normal 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;
|
||||
}
|
37
app/paint/pikamybrushsurface.h
Normal file
37
app/paint/pikamybrushsurface.h
Normal 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
389
app/paint/pikapaintbrush.c
Normal 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);
|
||||
}
|
||||
}
|
84
app/paint/pikapaintbrush.h
Normal file
84
app/paint/pikapaintbrush.h
Normal 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__ */
|
2269
app/paint/pikapaintcore-loops.cc
Normal file
2269
app/paint/pikapaintcore-loops.cc
Normal file
File diff suppressed because it is too large
Load Diff
74
app/paint/pikapaintcore-loops.h
Normal file
74
app/paint/pikapaintcore-loops.h
Normal 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__ */
|
396
app/paint/pikapaintcore-stroke.c
Normal file
396
app/paint/pikapaintcore-stroke.c
Normal 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;
|
||||
}
|
||||
}
|
52
app/paint/pikapaintcore-stroke.h
Normal file
52
app/paint/pikapaintcore-stroke.h
Normal 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
1307
app/paint/pikapaintcore.c
Normal file
File diff suppressed because it is too large
Load Diff
219
app/paint/pikapaintcore.h
Normal file
219
app/paint/pikapaintcore.h
Normal 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__ */
|
171
app/paint/pikapaintcoreundo.c
Normal file
171
app/paint/pikapaintcoreundo.c
Normal 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);
|
||||
}
|
57
app/paint/pikapaintcoreundo.h
Normal file
57
app/paint/pikapaintcoreundo.h
Normal 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
1299
app/paint/pikapaintoptions.c
Normal file
File diff suppressed because it is too large
Load Diff
184
app/paint/pikapaintoptions.h
Normal file
184
app/paint/pikapaintoptions.h
Normal 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
58
app/paint/pikapencil.c
Normal 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
56
app/paint/pikapencil.h
Normal 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__ */
|
115
app/paint/pikapenciloptions.c
Normal file
115
app/paint/pikapenciloptions.c
Normal 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;
|
||||
}
|
||||
}
|
53
app/paint/pikapenciloptions.h
Normal file
53
app/paint/pikapenciloptions.h
Normal 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__ */
|
595
app/paint/pikaperspectiveclone.c
Normal file
595
app/paint/pikaperspectiveclone.c
Normal 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);
|
||||
}
|
79
app/paint/pikaperspectiveclone.h
Normal file
79
app/paint/pikaperspectiveclone.h
Normal 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__ */
|
117
app/paint/pikaperspectivecloneoptions.c
Normal file
117
app/paint/pikaperspectivecloneoptions.c
Normal 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;
|
||||
}
|
||||
}
|
55
app/paint/pikaperspectivecloneoptions.h
Normal file
55
app/paint/pikaperspectivecloneoptions.h
Normal 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
596
app/paint/pikasmudge.c
Normal 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
59
app/paint/pikasmudge.h
Normal 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__ */
|
164
app/paint/pikasmudgeoptions.c
Normal file
164
app/paint/pikasmudgeoptions.c
Normal 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;
|
||||
}
|
||||
}
|
58
app/paint/pikasmudgeoptions.h
Normal file
58
app/paint/pikasmudgeoptions.h
Normal 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
620
app/paint/pikasourcecore.c
Normal 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
112
app/paint/pikasourcecore.h
Normal 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__ */
|
297
app/paint/pikasourceoptions.c
Normal file
297
app/paint/pikasourceoptions.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
64
app/paint/pikasourceoptions.h
Normal file
64
app/paint/pikasourceoptions.h
Normal 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__ */
|
Reference in New Issue
Block a user