summaryrefslogtreecommitdiffstats
path: root/app/paint
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--app/paint/Makefile.am125
-rw-r--r--app/paint/Makefile.in1208
-rw-r--r--app/paint/gimp-paint.c140
-rw-r--r--app/paint/gimp-paint.h26
-rw-r--r--app/paint/gimpairbrush.c266
-rw-r--r--app/paint/gimpairbrush.h64
-rw-r--r--app/paint/gimpairbrushoptions.c155
-rw-r--r--app/paint/gimpairbrushoptions.h53
-rw-r--r--app/paint/gimpbrushcore-kernels.h116
-rw-r--r--app/paint/gimpbrushcore-loops.cc647
-rw-r--r--app/paint/gimpbrushcore-loops.h37
-rw-r--r--app/paint/gimpbrushcore.c1344
-rw-r--r--app/paint/gimpbrushcore.h152
-rw-r--r--app/paint/gimpclone.c256
-rw-r--r--app/paint/gimpclone.h52
-rw-r--r--app/paint/gimpcloneoptions.c114
-rw-r--r--app/paint/gimpcloneoptions.h51
-rw-r--r--app/paint/gimpconvolve.c277
-rw-r--r--app/paint/gimpconvolve.h54
-rw-r--r--app/paint/gimpconvolveoptions.c129
-rw-r--r--app/paint/gimpconvolveoptions.h52
-rw-r--r--app/paint/gimpdodgeburn.c201
-rw-r--r--app/paint/gimpdodgeburn.h51
-rw-r--r--app/paint/gimpdodgeburnoptions.c145
-rw-r--r--app/paint/gimpdodgeburnoptions.h53
-rw-r--r--app/paint/gimperaser.c130
-rw-r--r--app/paint/gimperaser.h52
-rw-r--r--app/paint/gimperaseroptions.c113
-rw-r--r--app/paint/gimperaseroptions.h51
-rw-r--r--app/paint/gimpheal.c642
-rw-r--r--app/paint/gimpheal.h52
-rw-r--r--app/paint/gimpink-blob.c875
-rw-r--r--app/paint/gimpink-blob.h87
-rw-r--r--app/paint/gimpink.c785
-rw-r--r--app/paint/gimpink.h58
-rw-r--r--app/paint/gimpinkoptions.c208
-rw-r--r--app/paint/gimpinkoptions.h60
-rw-r--r--app/paint/gimpinkundo.c125
-rw-r--r--app/paint/gimpinkundo.h52
-rw-r--r--app/paint/gimpmybrushcore.c421
-rw-r--r--app/paint/gimpmybrushcore.h55
-rw-r--r--app/paint/gimpmybrushoptions.c219
-rw-r--r--app/paint/gimpmybrushoptions.h55
-rw-r--r--app/paint/gimpmybrushsurface.c560
-rw-r--r--app/paint/gimpmybrushsurface.h33
-rw-r--r--app/paint/gimppaintbrush.c395
-rw-r--r--app/paint/gimppaintbrush.h80
-rw-r--r--app/paint/gimppaintcore-loops.cc2268
-rw-r--r--app/paint/gimppaintcore-loops.h70
-rw-r--r--app/paint/gimppaintcore-stroke.c388
-rw-r--r--app/paint/gimppaintcore-stroke.h48
-rw-r--r--app/paint/gimppaintcore.c1242
-rw-r--r--app/paint/gimppaintcore.h216
-rw-r--r--app/paint/gimppaintcoreundo.c172
-rw-r--r--app/paint/gimppaintcoreundo.h53
-rw-r--r--app/paint/gimppaintoptions.c1272
-rw-r--r--app/paint/gimppaintoptions.h176
-rw-r--r--app/paint/gimppencil.c54
-rw-r--r--app/paint/gimppencil.h52
-rw-r--r--app/paint/gimppenciloptions.c110
-rw-r--r--app/paint/gimppenciloptions.h49
-rw-r--r--app/paint/gimpperspectiveclone.c537
-rw-r--r--app/paint/gimpperspectiveclone.h74
-rw-r--r--app/paint/gimpperspectivecloneoptions.c112
-rw-r--r--app/paint/gimpperspectivecloneoptions.h51
-rw-r--r--app/paint/gimpsmudge.c575
-rw-r--r--app/paint/gimpsmudge.h55
-rw-r--r--app/paint/gimpsmudgeoptions.c159
-rw-r--r--app/paint/gimpsmudgeoptions.h54
-rw-r--r--app/paint/gimpsourcecore.c679
-rw-r--r--app/paint/gimpsourcecore.h110
-rw-r--r--app/paint/gimpsourceoptions.c124
-rw-r--r--app/paint/gimpsourceoptions.h52
-rw-r--r--app/paint/paint-enums.c104
-rw-r--r--app/paint/paint-enums.h85
-rw-r--r--app/paint/paint-types.h76
76 files changed, 19893 insertions, 0 deletions
diff --git a/app/paint/Makefile.am b/app/paint/Makefile.am
new file mode 100644
index 0000000..1cc625e
--- /dev/null
+++ b/app/paint/Makefile.am
@@ -0,0 +1,125 @@
+## Process this file with automake to produce Makefile.in
+
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"Gimp-Paint\" \
+ -I$(top_builddir) \
+ -I$(top_srcdir) \
+ -I$(top_builddir)/app \
+ -I$(top_srcdir)/app \
+ $(CAIRO_CFLAGS) \
+ $(GEGL_CFLAGS) \
+ $(GDK_PIXBUF_CFLAGS) \
+ $(LIBMYPAINT_CFLAGS) \
+ -I$(includedir)
+
+noinst_LIBRARIES = libapppaint.a
+
+libapppaint_a_sources = \
+ paint-enums.h \
+ paint-types.h \
+ gimp-paint.c \
+ gimp-paint.h \
+ gimpairbrush.c \
+ gimpairbrush.h \
+ gimpairbrushoptions.c \
+ gimpairbrushoptions.h \
+ gimpbrushcore.c \
+ gimpbrushcore.h \
+ gimpbrushcore-loops.cc \
+ gimpbrushcore-loops.h \
+ gimpbrushcore-kernels.h \
+ gimpclone.c \
+ gimpclone.h \
+ gimpcloneoptions.c \
+ gimpcloneoptions.h \
+ gimpconvolve.c \
+ gimpconvolve.h \
+ gimpconvolveoptions.c \
+ gimpconvolveoptions.h \
+ gimpdodgeburn.c \
+ gimpdodgeburn.h \
+ gimpdodgeburnoptions.c \
+ gimpdodgeburnoptions.h \
+ gimperaser.c \
+ gimperaser.h \
+ gimperaseroptions.c \
+ gimperaseroptions.h \
+ gimpheal.c \
+ gimpheal.h \
+ gimpink.c \
+ gimpink.h \
+ gimpink-blob.c \
+ gimpink-blob.h \
+ gimpinkoptions.c \
+ gimpinkoptions.h \
+ gimpinkundo.c \
+ gimpinkundo.h \
+ gimpmybrushcore.c \
+ gimpmybrushcore.h \
+ gimpmybrushoptions.c \
+ gimpmybrushoptions.h \
+ gimpmybrushsurface.c \
+ gimpmybrushsurface.h \
+ gimppaintcore.c \
+ gimppaintcore.h \
+ gimppaintcore-loops.cc \
+ gimppaintcore-loops.h \
+ gimppaintcore-stroke.c \
+ gimppaintcore-stroke.h \
+ gimppaintcoreundo.c \
+ gimppaintcoreundo.h \
+ gimppaintoptions.c \
+ gimppaintoptions.h \
+ gimppencil.c \
+ gimppencil.h \
+ gimppenciloptions.c \
+ gimppenciloptions.h \
+ gimppaintbrush.c \
+ gimppaintbrush.h \
+ gimpperspectiveclone.c \
+ gimpperspectiveclone.h \
+ gimpperspectivecloneoptions.c \
+ gimpperspectivecloneoptions.h \
+ gimpsmudge.c \
+ gimpsmudge.h \
+ gimpsmudgeoptions.c \
+ gimpsmudgeoptions.h \
+ gimpsourcecore.c \
+ gimpsourcecore.h \
+ gimpsourceoptions.c \
+ gimpsourceoptions.h
+
+libapppaint_a_built_sources = paint-enums.c
+
+libapppaint_a_SOURCES = $(libapppaint_a_built_sources) $(libapppaint_a_sources)
+
+#
+# rules to generate built sources
+#
+# setup autogeneration dependencies
+gen_sources = xgen-pec
+CLEANFILES = $(gen_sources)
+
+xgen-pec: $(srcdir)/paint-enums.h $(GIMP_MKENUMS) Makefile.am
+ $(AM_V_GEN) $(GIMP_MKENUMS) \
+ --fhead "#include \"config.h\"\n#include <gio/gio.h>\n#include \"libgimpbase/gimpbase.h\"\n#include \"paint-enums.h\"\n#include \"gimp-intl.h\"" \
+ --fprod "\n/* enumerations from \"@basename@\" */" \
+ --vhead "GType\n@enum_name@_get_type (void)\n{\n static const G@Type@Value values[] =\n {" \
+ --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
+ --vtail " { 0, NULL, NULL }\n };\n" \
+ --dhead " static const Gimp@Type@Desc descs[] =\n {" \
+ --dprod " { @VALUENAME@, @valuedesc@, @valuehelp@ },@if ('@valueabbrev@' ne 'NULL')@\n /* Translators: this is an abbreviated version of @valueudesc@.\n Keep it short. */\n { @VALUENAME@, @valueabbrev@, NULL },@endif@" \
+ --dtail " { 0, NULL, NULL }\n };\n\n static GType type = 0;\n\n if (G_UNLIKELY (! type))\n {\n type = g_@type@_register_static (\"@EnumName@\", values);\n gimp_type_set_translation_context (type, \"@enumnick@\");\n gimp_@type@_set_value_descriptions (type, descs);\n }\n\n return type;\n}\n" \
+ $< > $@
+
+# copy the generated enum file back to the source directory only if it's
+# changed; otherwise, only update its timestamp, so that the recipe isn't
+# executed again on the next build, however, allow this to (harmlessly) fail,
+# to support building from a read-only source tree.
+$(srcdir)/paint-enums.c: xgen-pec
+ $(AM_V_GEN) if ! cmp -s $< $@; then \
+ cp $< $@; \
+ else \
+ touch $@ 2> /dev/null \
+ || true; \
+ fi
diff --git a/app/paint/Makefile.in b/app/paint/Makefile.in
new file mode 100644
index 0000000..7cdaca4
--- /dev/null
+++ b/app/paint/Makefile.in
@@ -0,0 +1,1208 @@
+# Makefile.in generated by automake 1.16.3 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2020 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+subdir = app/paint
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \
+ $(top_srcdir)/m4macros/alsa.m4 \
+ $(top_srcdir)/m4macros/ax_compare_version.m4 \
+ $(top_srcdir)/m4macros/ax_cxx_compile_stdcxx.m4 \
+ $(top_srcdir)/m4macros/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4macros/ax_prog_cc_for_build.m4 \
+ $(top_srcdir)/m4macros/ax_prog_perl_version.m4 \
+ $(top_srcdir)/m4macros/detectcflags.m4 \
+ $(top_srcdir)/m4macros/pythondev.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_@AM_V@)
+am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@)
+am__v_AR_0 = @echo " AR " $@;
+am__v_AR_1 =
+libapppaint_a_AR = $(AR) $(ARFLAGS)
+libapppaint_a_LIBADD =
+am__objects_1 = paint-enums.$(OBJEXT)
+am__objects_2 = gimp-paint.$(OBJEXT) gimpairbrush.$(OBJEXT) \
+ gimpairbrushoptions.$(OBJEXT) gimpbrushcore.$(OBJEXT) \
+ gimpbrushcore-loops.$(OBJEXT) gimpclone.$(OBJEXT) \
+ gimpcloneoptions.$(OBJEXT) gimpconvolve.$(OBJEXT) \
+ gimpconvolveoptions.$(OBJEXT) gimpdodgeburn.$(OBJEXT) \
+ gimpdodgeburnoptions.$(OBJEXT) gimperaser.$(OBJEXT) \
+ gimperaseroptions.$(OBJEXT) gimpheal.$(OBJEXT) \
+ gimpink.$(OBJEXT) gimpink-blob.$(OBJEXT) \
+ gimpinkoptions.$(OBJEXT) gimpinkundo.$(OBJEXT) \
+ gimpmybrushcore.$(OBJEXT) gimpmybrushoptions.$(OBJEXT) \
+ gimpmybrushsurface.$(OBJEXT) gimppaintcore.$(OBJEXT) \
+ gimppaintcore-loops.$(OBJEXT) gimppaintcore-stroke.$(OBJEXT) \
+ gimppaintcoreundo.$(OBJEXT) gimppaintoptions.$(OBJEXT) \
+ gimppencil.$(OBJEXT) gimppenciloptions.$(OBJEXT) \
+ gimppaintbrush.$(OBJEXT) gimpperspectiveclone.$(OBJEXT) \
+ gimpperspectivecloneoptions.$(OBJEXT) gimpsmudge.$(OBJEXT) \
+ gimpsmudgeoptions.$(OBJEXT) gimpsourcecore.$(OBJEXT) \
+ gimpsourceoptions.$(OBJEXT)
+am_libapppaint_a_OBJECTS = $(am__objects_1) $(am__objects_2)
+libapppaint_a_OBJECTS = $(am_libapppaint_a_OBJECTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/gimp-paint.Po \
+ ./$(DEPDIR)/gimpairbrush.Po ./$(DEPDIR)/gimpairbrushoptions.Po \
+ ./$(DEPDIR)/gimpbrushcore-loops.Po \
+ ./$(DEPDIR)/gimpbrushcore.Po ./$(DEPDIR)/gimpclone.Po \
+ ./$(DEPDIR)/gimpcloneoptions.Po ./$(DEPDIR)/gimpconvolve.Po \
+ ./$(DEPDIR)/gimpconvolveoptions.Po \
+ ./$(DEPDIR)/gimpdodgeburn.Po \
+ ./$(DEPDIR)/gimpdodgeburnoptions.Po ./$(DEPDIR)/gimperaser.Po \
+ ./$(DEPDIR)/gimperaseroptions.Po ./$(DEPDIR)/gimpheal.Po \
+ ./$(DEPDIR)/gimpink-blob.Po ./$(DEPDIR)/gimpink.Po \
+ ./$(DEPDIR)/gimpinkoptions.Po ./$(DEPDIR)/gimpinkundo.Po \
+ ./$(DEPDIR)/gimpmybrushcore.Po \
+ ./$(DEPDIR)/gimpmybrushoptions.Po \
+ ./$(DEPDIR)/gimpmybrushsurface.Po \
+ ./$(DEPDIR)/gimppaintbrush.Po \
+ ./$(DEPDIR)/gimppaintcore-loops.Po \
+ ./$(DEPDIR)/gimppaintcore-stroke.Po \
+ ./$(DEPDIR)/gimppaintcore.Po ./$(DEPDIR)/gimppaintcoreundo.Po \
+ ./$(DEPDIR)/gimppaintoptions.Po ./$(DEPDIR)/gimppencil.Po \
+ ./$(DEPDIR)/gimppenciloptions.Po \
+ ./$(DEPDIR)/gimpperspectiveclone.Po \
+ ./$(DEPDIR)/gimpperspectivecloneoptions.Po \
+ ./$(DEPDIR)/gimpsmudge.Po ./$(DEPDIR)/gimpsmudgeoptions.Po \
+ ./$(DEPDIR)/gimpsourcecore.Po ./$(DEPDIR)/gimpsourceoptions.Po \
+ ./$(DEPDIR)/paint-enums.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \
+ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS)
+LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CXXFLAGS) $(CXXFLAGS)
+AM_V_CXX = $(am__v_CXX_@AM_V@)
+am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@)
+am__v_CXX_0 = @echo " CXX " $@;
+am__v_CXX_1 =
+CXXLD = $(CXX)
+CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CXXLD = $(am__v_CXXLD_@AM_V@)
+am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@)
+am__v_CXXLD_0 = @echo " CXXLD " $@;
+am__v_CXXLD_1 =
+SOURCES = $(libapppaint_a_SOURCES)
+DIST_SOURCES = $(libapppaint_a_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+AA_LIBS = @AA_LIBS@
+ACLOCAL = @ACLOCAL@
+ALLOCA = @ALLOCA@
+ALL_LINGUAS = @ALL_LINGUAS@
+ALSA_CFLAGS = @ALSA_CFLAGS@
+ALSA_LIBS = @ALSA_LIBS@
+ALTIVEC_EXTRA_CFLAGS = @ALTIVEC_EXTRA_CFLAGS@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APPSTREAM_UTIL = @APPSTREAM_UTIL@
+AR = @AR@
+AS = @AS@
+ATK_CFLAGS = @ATK_CFLAGS@
+ATK_LIBS = @ATK_LIBS@
+ATK_REQUIRED_VERSION = @ATK_REQUIRED_VERSION@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BABL_CFLAGS = @BABL_CFLAGS@
+BABL_LIBS = @BABL_LIBS@
+BABL_REQUIRED_VERSION = @BABL_REQUIRED_VERSION@
+BUG_REPORT_URL = @BUG_REPORT_URL@
+BUILD_EXEEXT = @BUILD_EXEEXT@
+BUILD_OBJEXT = @BUILD_OBJEXT@
+BZIP2_LIBS = @BZIP2_LIBS@
+CAIRO_CFLAGS = @CAIRO_CFLAGS@
+CAIRO_LIBS = @CAIRO_LIBS@
+CAIRO_PDF_CFLAGS = @CAIRO_PDF_CFLAGS@
+CAIRO_PDF_LIBS = @CAIRO_PDF_LIBS@
+CAIRO_PDF_REQUIRED_VERSION = @CAIRO_PDF_REQUIRED_VERSION@
+CAIRO_REQUIRED_VERSION = @CAIRO_REQUIRED_VERSION@
+CATALOGS = @CATALOGS@
+CATOBJEXT = @CATOBJEXT@
+CC = @CC@
+CCAS = @CCAS@
+CCASDEPMODE = @CCASDEPMODE@
+CCASFLAGS = @CCASFLAGS@
+CCDEPMODE = @CCDEPMODE@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+CC_VERSION = @CC_VERSION@
+CFLAGS = @CFLAGS@
+CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@
+CPP_FOR_BUILD = @CPP_FOR_BUILD@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DATADIRNAME = @DATADIRNAME@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DESKTOP_DATADIR = @DESKTOP_DATADIR@
+DESKTOP_FILE_VALIDATE = @DESKTOP_FILE_VALIDATE@
+DLLTOOL = @DLLTOOL@
+DOC_SHOOTER = @DOC_SHOOTER@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILE_AA = @FILE_AA@
+FILE_EXR = @FILE_EXR@
+FILE_HEIF = @FILE_HEIF@
+FILE_JP2_LOAD = @FILE_JP2_LOAD@
+FILE_JPEGXL = @FILE_JPEGXL@
+FILE_MNG = @FILE_MNG@
+FILE_PDF_SAVE = @FILE_PDF_SAVE@
+FILE_PS = @FILE_PS@
+FILE_WMF = @FILE_WMF@
+FILE_XMC = @FILE_XMC@
+FILE_XPM = @FILE_XPM@
+FONTCONFIG_CFLAGS = @FONTCONFIG_CFLAGS@
+FONTCONFIG_LIBS = @FONTCONFIG_LIBS@
+FONTCONFIG_REQUIRED_VERSION = @FONTCONFIG_REQUIRED_VERSION@
+FREETYPE2_REQUIRED_VERSION = @FREETYPE2_REQUIRED_VERSION@
+FREETYPE_CFLAGS = @FREETYPE_CFLAGS@
+FREETYPE_LIBS = @FREETYPE_LIBS@
+GDBUS_CODEGEN = @GDBUS_CODEGEN@
+GDK_PIXBUF_CFLAGS = @GDK_PIXBUF_CFLAGS@
+GDK_PIXBUF_CSOURCE = @GDK_PIXBUF_CSOURCE@
+GDK_PIXBUF_LIBS = @GDK_PIXBUF_LIBS@
+GDK_PIXBUF_REQUIRED_VERSION = @GDK_PIXBUF_REQUIRED_VERSION@
+GEGL = @GEGL@
+GEGL_CFLAGS = @GEGL_CFLAGS@
+GEGL_LIBS = @GEGL_LIBS@
+GEGL_MAJOR_MINOR_VERSION = @GEGL_MAJOR_MINOR_VERSION@
+GEGL_REQUIRED_VERSION = @GEGL_REQUIRED_VERSION@
+GETTEXT_PACKAGE = @GETTEXT_PACKAGE@
+GEXIV2_CFLAGS = @GEXIV2_CFLAGS@
+GEXIV2_LIBS = @GEXIV2_LIBS@
+GEXIV2_REQUIRED_VERSION = @GEXIV2_REQUIRED_VERSION@
+GIMP_API_VERSION = @GIMP_API_VERSION@
+GIMP_APP_VERSION = @GIMP_APP_VERSION@
+GIMP_BINARY_AGE = @GIMP_BINARY_AGE@
+GIMP_COMMAND = @GIMP_COMMAND@
+GIMP_DATA_VERSION = @GIMP_DATA_VERSION@
+GIMP_FULL_NAME = @GIMP_FULL_NAME@
+GIMP_INTERFACE_AGE = @GIMP_INTERFACE_AGE@
+GIMP_MAJOR_VERSION = @GIMP_MAJOR_VERSION@
+GIMP_MICRO_VERSION = @GIMP_MICRO_VERSION@
+GIMP_MINOR_VERSION = @GIMP_MINOR_VERSION@
+GIMP_MKENUMS = @GIMP_MKENUMS@
+GIMP_MODULES = @GIMP_MODULES@
+GIMP_PACKAGE_REVISION = @GIMP_PACKAGE_REVISION@
+GIMP_PKGCONFIG_VERSION = @GIMP_PKGCONFIG_VERSION@
+GIMP_PLUGINS = @GIMP_PLUGINS@
+GIMP_PLUGIN_VERSION = @GIMP_PLUGIN_VERSION@
+GIMP_REAL_VERSION = @GIMP_REAL_VERSION@
+GIMP_RELEASE = @GIMP_RELEASE@
+GIMP_SYSCONF_VERSION = @GIMP_SYSCONF_VERSION@
+GIMP_TOOL_VERSION = @GIMP_TOOL_VERSION@
+GIMP_UNSTABLE = @GIMP_UNSTABLE@
+GIMP_USER_VERSION = @GIMP_USER_VERSION@
+GIMP_VERSION = @GIMP_VERSION@
+GIO_CFLAGS = @GIO_CFLAGS@
+GIO_LIBS = @GIO_LIBS@
+GIO_UNIX_CFLAGS = @GIO_UNIX_CFLAGS@
+GIO_UNIX_LIBS = @GIO_UNIX_LIBS@
+GIO_WINDOWS_CFLAGS = @GIO_WINDOWS_CFLAGS@
+GIO_WINDOWS_LIBS = @GIO_WINDOWS_LIBS@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_COMPILE_RESOURCES = @GLIB_COMPILE_RESOURCES@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GLIB_REQUIRED_VERSION = @GLIB_REQUIRED_VERSION@
+GMODULE_NO_EXPORT_CFLAGS = @GMODULE_NO_EXPORT_CFLAGS@
+GMODULE_NO_EXPORT_LIBS = @GMODULE_NO_EXPORT_LIBS@
+GMOFILES = @GMOFILES@
+GMSGFMT = @GMSGFMT@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GREP = @GREP@
+GS_LIBS = @GS_LIBS@
+GTKDOC_CHECK = @GTKDOC_CHECK@
+GTKDOC_CHECK_PATH = @GTKDOC_CHECK_PATH@
+GTKDOC_DEPS_CFLAGS = @GTKDOC_DEPS_CFLAGS@
+GTKDOC_DEPS_LIBS = @GTKDOC_DEPS_LIBS@
+GTKDOC_MKPDF = @GTKDOC_MKPDF@
+GTKDOC_REBASE = @GTKDOC_REBASE@
+GTK_CFLAGS = @GTK_CFLAGS@
+GTK_LIBS = @GTK_LIBS@
+GTK_MAC_INTEGRATION_CFLAGS = @GTK_MAC_INTEGRATION_CFLAGS@
+GTK_MAC_INTEGRATION_LIBS = @GTK_MAC_INTEGRATION_LIBS@
+GTK_REQUIRED_VERSION = @GTK_REQUIRED_VERSION@
+GTK_UPDATE_ICON_CACHE = @GTK_UPDATE_ICON_CACHE@
+GUDEV_CFLAGS = @GUDEV_CFLAGS@
+GUDEV_LIBS = @GUDEV_LIBS@
+HARFBUZZ_CFLAGS = @HARFBUZZ_CFLAGS@
+HARFBUZZ_LIBS = @HARFBUZZ_LIBS@
+HARFBUZZ_REQUIRED_VERSION = @HARFBUZZ_REQUIRED_VERSION@
+HAVE_CXX14 = @HAVE_CXX14@
+HAVE_FINITE = @HAVE_FINITE@
+HAVE_ISFINITE = @HAVE_ISFINITE@
+HAVE_VFORK = @HAVE_VFORK@
+HOST_GLIB_COMPILE_RESOURCES = @HOST_GLIB_COMPILE_RESOURCES@
+HTML_DIR = @HTML_DIR@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INSTOBJEXT = @INSTOBJEXT@
+INTLLIBS = @INTLLIBS@
+INTLTOOL_EXTRACT = @INTLTOOL_EXTRACT@
+INTLTOOL_MERGE = @INTLTOOL_MERGE@
+INTLTOOL_PERL = @INTLTOOL_PERL@
+INTLTOOL_REQUIRED_VERSION = @INTLTOOL_REQUIRED_VERSION@
+INTLTOOL_UPDATE = @INTLTOOL_UPDATE@
+INTLTOOL_V_MERGE = @INTLTOOL_V_MERGE@
+INTLTOOL_V_MERGE_OPTIONS = @INTLTOOL_V_MERGE_OPTIONS@
+INTLTOOL__v_MERGE_ = @INTLTOOL__v_MERGE_@
+INTLTOOL__v_MERGE_0 = @INTLTOOL__v_MERGE_0@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+ISO_CODES_LOCALEDIR = @ISO_CODES_LOCALEDIR@
+ISO_CODES_LOCATION = @ISO_CODES_LOCATION@
+JPEG_LIBS = @JPEG_LIBS@
+JSON_GLIB_CFLAGS = @JSON_GLIB_CFLAGS@
+JSON_GLIB_LIBS = @JSON_GLIB_LIBS@
+JXL_CFLAGS = @JXL_CFLAGS@
+JXL_LIBS = @JXL_LIBS@
+JXL_THREADS_CFLAGS = @JXL_THREADS_CFLAGS@
+JXL_THREADS_LIBS = @JXL_THREADS_LIBS@
+LCMS_CFLAGS = @LCMS_CFLAGS@
+LCMS_LIBS = @LCMS_LIBS@
+LCMS_REQUIRED_VERSION = @LCMS_REQUIRED_VERSION@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@
+LIBBACKTRACE_LIBS = @LIBBACKTRACE_LIBS@
+LIBHEIF_CFLAGS = @LIBHEIF_CFLAGS@
+LIBHEIF_LIBS = @LIBHEIF_LIBS@
+LIBHEIF_REQUIRED_VERSION = @LIBHEIF_REQUIRED_VERSION@
+LIBJXL_REQUIRED_VERSION = @LIBJXL_REQUIRED_VERSION@
+LIBLZMA_REQUIRED_VERSION = @LIBLZMA_REQUIRED_VERSION@
+LIBMYPAINT_CFLAGS = @LIBMYPAINT_CFLAGS@
+LIBMYPAINT_LIBS = @LIBMYPAINT_LIBS@
+LIBMYPAINT_REQUIRED_VERSION = @LIBMYPAINT_REQUIRED_VERSION@
+LIBOBJS = @LIBOBJS@
+LIBPNG_REQUIRED_VERSION = @LIBPNG_REQUIRED_VERSION@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUNWIND_CFLAGS = @LIBUNWIND_CFLAGS@
+LIBUNWIND_LIBS = @LIBUNWIND_LIBS@
+LIBUNWIND_REQUIRED_VERSION = @LIBUNWIND_REQUIRED_VERSION@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_CURRENT_MINUS_AGE = @LT_CURRENT_MINUS_AGE@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+LT_VERSION_INFO = @LT_VERSION_INFO@
+LZMA_CFLAGS = @LZMA_CFLAGS@
+LZMA_LIBS = @LZMA_LIBS@
+MAIL = @MAIL@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MIME_INFO_CFLAGS = @MIME_INFO_CFLAGS@
+MIME_INFO_LIBS = @MIME_INFO_LIBS@
+MIME_TYPES = @MIME_TYPES@
+MKDIR_P = @MKDIR_P@
+MKINSTALLDIRS = @MKINSTALLDIRS@
+MMX_EXTRA_CFLAGS = @MMX_EXTRA_CFLAGS@
+MNG_CFLAGS = @MNG_CFLAGS@
+MNG_LIBS = @MNG_LIBS@
+MSGFMT = @MSGFMT@
+MSGFMT_OPTS = @MSGFMT_OPTS@
+MSGMERGE = @MSGMERGE@
+MYPAINT_BRUSHES_CFLAGS = @MYPAINT_BRUSHES_CFLAGS@
+MYPAINT_BRUSHES_LIBS = @MYPAINT_BRUSHES_LIBS@
+NATIVE_GLIB_CFLAGS = @NATIVE_GLIB_CFLAGS@
+NATIVE_GLIB_LIBS = @NATIVE_GLIB_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENEXR_CFLAGS = @OPENEXR_CFLAGS@
+OPENEXR_LIBS = @OPENEXR_LIBS@
+OPENEXR_REQUIRED_VERSION = @OPENEXR_REQUIRED_VERSION@
+OPENJPEG_CFLAGS = @OPENJPEG_CFLAGS@
+OPENJPEG_LIBS = @OPENJPEG_LIBS@
+OPENJPEG_REQUIRED_VERSION = @OPENJPEG_REQUIRED_VERSION@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PANGOCAIRO_CFLAGS = @PANGOCAIRO_CFLAGS@
+PANGOCAIRO_LIBS = @PANGOCAIRO_LIBS@
+PANGOCAIRO_REQUIRED_VERSION = @PANGOCAIRO_REQUIRED_VERSION@
+PATHSEP = @PATHSEP@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL = @PERL@
+PERL_REQUIRED_VERSION = @PERL_REQUIRED_VERSION@
+PERL_VERSION = @PERL_VERSION@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PNG_CFLAGS = @PNG_CFLAGS@
+PNG_LIBS = @PNG_LIBS@
+POFILES = @POFILES@
+POPPLER_CFLAGS = @POPPLER_CFLAGS@
+POPPLER_DATA_CFLAGS = @POPPLER_DATA_CFLAGS@
+POPPLER_DATA_LIBS = @POPPLER_DATA_LIBS@
+POPPLER_DATA_REQUIRED_VERSION = @POPPLER_DATA_REQUIRED_VERSION@
+POPPLER_LIBS = @POPPLER_LIBS@
+POPPLER_REQUIRED_VERSION = @POPPLER_REQUIRED_VERSION@
+POSUB = @POSUB@
+PO_IN_DATADIR_FALSE = @PO_IN_DATADIR_FALSE@
+PO_IN_DATADIR_TRUE = @PO_IN_DATADIR_TRUE@
+PYBIN_PATH = @PYBIN_PATH@
+PYCAIRO_CFLAGS = @PYCAIRO_CFLAGS@
+PYCAIRO_LIBS = @PYCAIRO_LIBS@
+PYGIMP_EXTRA_CFLAGS = @PYGIMP_EXTRA_CFLAGS@
+PYGTK_CFLAGS = @PYGTK_CFLAGS@
+PYGTK_CODEGEN = @PYGTK_CODEGEN@
+PYGTK_DEFSDIR = @PYGTK_DEFSDIR@
+PYGTK_LIBS = @PYGTK_LIBS@
+PYLINK_LIBS = @PYLINK_LIBS@
+PYTHON = @PYTHON@
+PYTHON2_REQUIRED_VERSION = @PYTHON2_REQUIRED_VERSION@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_INCLUDES = @PYTHON_INCLUDES@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+RSVG_REQUIRED_VERSION = @RSVG_REQUIRED_VERSION@
+RT_LIBS = @RT_LIBS@
+SCREENSHOT_LIBS = @SCREENSHOT_LIBS@
+SED = @SED@
+SENDMAIL = @SENDMAIL@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SOCKET_LIBS = @SOCKET_LIBS@
+SSE2_EXTRA_CFLAGS = @SSE2_EXTRA_CFLAGS@
+SSE4_1_EXTRA_CFLAGS = @SSE4_1_EXTRA_CFLAGS@
+SSE_EXTRA_CFLAGS = @SSE_EXTRA_CFLAGS@
+STRIP = @STRIP@
+SVG_CFLAGS = @SVG_CFLAGS@
+SVG_LIBS = @SVG_LIBS@
+SYMPREFIX = @SYMPREFIX@
+TIFF_LIBS = @TIFF_LIBS@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+WEBKIT_CFLAGS = @WEBKIT_CFLAGS@
+WEBKIT_LIBS = @WEBKIT_LIBS@
+WEBKIT_REQUIRED_VERSION = @WEBKIT_REQUIRED_VERSION@
+WEBPDEMUX_CFLAGS = @WEBPDEMUX_CFLAGS@
+WEBPDEMUX_LIBS = @WEBPDEMUX_LIBS@
+WEBPMUX_CFLAGS = @WEBPMUX_CFLAGS@
+WEBPMUX_LIBS = @WEBPMUX_LIBS@
+WEBP_CFLAGS = @WEBP_CFLAGS@
+WEBP_LIBS = @WEBP_LIBS@
+WEBP_REQUIRED_VERSION = @WEBP_REQUIRED_VERSION@
+WEB_PAGE = @WEB_PAGE@
+WIN32_LARGE_ADDRESS_AWARE = @WIN32_LARGE_ADDRESS_AWARE@
+WINDRES = @WINDRES@
+WMF_CFLAGS = @WMF_CFLAGS@
+WMF_CONFIG = @WMF_CONFIG@
+WMF_LIBS = @WMF_LIBS@
+WMF_REQUIRED_VERSION = @WMF_REQUIRED_VERSION@
+XDG_EMAIL = @XDG_EMAIL@
+XFIXES_CFLAGS = @XFIXES_CFLAGS@
+XFIXES_LIBS = @XFIXES_LIBS@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_REQUIRED_VERSION = @XGETTEXT_REQUIRED_VERSION@
+XMC_CFLAGS = @XMC_CFLAGS@
+XMC_LIBS = @XMC_LIBS@
+XMKMF = @XMKMF@
+XMLLINT = @XMLLINT@
+XMU_LIBS = @XMU_LIBS@
+XPM_LIBS = @XPM_LIBS@
+XSLTPROC = @XSLTPROC@
+XVFB_RUN = @XVFB_RUN@
+X_CFLAGS = @X_CFLAGS@
+X_EXTRA_LIBS = @X_EXTRA_LIBS@
+X_LIBS = @X_LIBS@
+X_PRE_LIBS = @X_PRE_LIBS@
+Z_LIBS = @Z_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CC_FOR_BUILD = @ac_ct_CC_FOR_BUILD@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+gimpdatadir = @gimpdatadir@
+gimpdir = @gimpdir@
+gimplocaledir = @gimplocaledir@
+gimpplugindir = @gimpplugindir@
+gimpsysconfdir = @gimpsysconfdir@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+intltool__v_merge_options_ = @intltool__v_merge_options_@
+intltool__v_merge_options_0 = @intltool__v_merge_options_0@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+manpage_gimpdir = @manpage_gimpdir@
+mkdir_p = @mkdir_p@
+ms_librarian = @ms_librarian@
+mypaint_brushes_dir = @mypaint_brushes_dir@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"Gimp-Paint\" \
+ -I$(top_builddir) \
+ -I$(top_srcdir) \
+ -I$(top_builddir)/app \
+ -I$(top_srcdir)/app \
+ $(CAIRO_CFLAGS) \
+ $(GEGL_CFLAGS) \
+ $(GDK_PIXBUF_CFLAGS) \
+ $(LIBMYPAINT_CFLAGS) \
+ -I$(includedir)
+
+noinst_LIBRARIES = libapppaint.a
+libapppaint_a_sources = \
+ paint-enums.h \
+ paint-types.h \
+ gimp-paint.c \
+ gimp-paint.h \
+ gimpairbrush.c \
+ gimpairbrush.h \
+ gimpairbrushoptions.c \
+ gimpairbrushoptions.h \
+ gimpbrushcore.c \
+ gimpbrushcore.h \
+ gimpbrushcore-loops.cc \
+ gimpbrushcore-loops.h \
+ gimpbrushcore-kernels.h \
+ gimpclone.c \
+ gimpclone.h \
+ gimpcloneoptions.c \
+ gimpcloneoptions.h \
+ gimpconvolve.c \
+ gimpconvolve.h \
+ gimpconvolveoptions.c \
+ gimpconvolveoptions.h \
+ gimpdodgeburn.c \
+ gimpdodgeburn.h \
+ gimpdodgeburnoptions.c \
+ gimpdodgeburnoptions.h \
+ gimperaser.c \
+ gimperaser.h \
+ gimperaseroptions.c \
+ gimperaseroptions.h \
+ gimpheal.c \
+ gimpheal.h \
+ gimpink.c \
+ gimpink.h \
+ gimpink-blob.c \
+ gimpink-blob.h \
+ gimpinkoptions.c \
+ gimpinkoptions.h \
+ gimpinkundo.c \
+ gimpinkundo.h \
+ gimpmybrushcore.c \
+ gimpmybrushcore.h \
+ gimpmybrushoptions.c \
+ gimpmybrushoptions.h \
+ gimpmybrushsurface.c \
+ gimpmybrushsurface.h \
+ gimppaintcore.c \
+ gimppaintcore.h \
+ gimppaintcore-loops.cc \
+ gimppaintcore-loops.h \
+ gimppaintcore-stroke.c \
+ gimppaintcore-stroke.h \
+ gimppaintcoreundo.c \
+ gimppaintcoreundo.h \
+ gimppaintoptions.c \
+ gimppaintoptions.h \
+ gimppencil.c \
+ gimppencil.h \
+ gimppenciloptions.c \
+ gimppenciloptions.h \
+ gimppaintbrush.c \
+ gimppaintbrush.h \
+ gimpperspectiveclone.c \
+ gimpperspectiveclone.h \
+ gimpperspectivecloneoptions.c \
+ gimpperspectivecloneoptions.h \
+ gimpsmudge.c \
+ gimpsmudge.h \
+ gimpsmudgeoptions.c \
+ gimpsmudgeoptions.h \
+ gimpsourcecore.c \
+ gimpsourcecore.h \
+ gimpsourceoptions.c \
+ gimpsourceoptions.h
+
+libapppaint_a_built_sources = paint-enums.c
+libapppaint_a_SOURCES = $(libapppaint_a_built_sources) $(libapppaint_a_sources)
+
+#
+# rules to generate built sources
+#
+# setup autogeneration dependencies
+gen_sources = xgen-pec
+CLEANFILES = $(gen_sources)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .cc .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu app/paint/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu app/paint/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+libapppaint.a: $(libapppaint_a_OBJECTS) $(libapppaint_a_DEPENDENCIES) $(EXTRA_libapppaint_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libapppaint.a
+ $(AM_V_AR)$(libapppaint_a_AR) libapppaint.a $(libapppaint_a_OBJECTS) $(libapppaint_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libapppaint.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimp-paint.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpairbrush.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpairbrushoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrushcore-loops.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbrushcore.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpclone.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpcloneoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpconvolve.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpconvolveoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdodgeburn.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpdodgeburnoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimperaser.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimperaseroptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpheal.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpink-blob.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpink.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpinkoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpinkundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmybrushcore.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmybrushoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpmybrushsurface.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppaintbrush.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppaintcore-loops.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppaintcore-stroke.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppaintcore.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppaintcoreundo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppaintoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppencil.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimppenciloptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpperspectiveclone.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpperspectivecloneoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsmudge.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsmudgeoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsourcecore.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpsourceoptions.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/paint-enums.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LTCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+.cc.o:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $<
+
+.cc.obj:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.cc.lo:
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(LIBRARIES)
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+ -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES)
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/gimp-paint.Po
+ -rm -f ./$(DEPDIR)/gimpairbrush.Po
+ -rm -f ./$(DEPDIR)/gimpairbrushoptions.Po
+ -rm -f ./$(DEPDIR)/gimpbrushcore-loops.Po
+ -rm -f ./$(DEPDIR)/gimpbrushcore.Po
+ -rm -f ./$(DEPDIR)/gimpclone.Po
+ -rm -f ./$(DEPDIR)/gimpcloneoptions.Po
+ -rm -f ./$(DEPDIR)/gimpconvolve.Po
+ -rm -f ./$(DEPDIR)/gimpconvolveoptions.Po
+ -rm -f ./$(DEPDIR)/gimpdodgeburn.Po
+ -rm -f ./$(DEPDIR)/gimpdodgeburnoptions.Po
+ -rm -f ./$(DEPDIR)/gimperaser.Po
+ -rm -f ./$(DEPDIR)/gimperaseroptions.Po
+ -rm -f ./$(DEPDIR)/gimpheal.Po
+ -rm -f ./$(DEPDIR)/gimpink-blob.Po
+ -rm -f ./$(DEPDIR)/gimpink.Po
+ -rm -f ./$(DEPDIR)/gimpinkoptions.Po
+ -rm -f ./$(DEPDIR)/gimpinkundo.Po
+ -rm -f ./$(DEPDIR)/gimpmybrushcore.Po
+ -rm -f ./$(DEPDIR)/gimpmybrushoptions.Po
+ -rm -f ./$(DEPDIR)/gimpmybrushsurface.Po
+ -rm -f ./$(DEPDIR)/gimppaintbrush.Po
+ -rm -f ./$(DEPDIR)/gimppaintcore-loops.Po
+ -rm -f ./$(DEPDIR)/gimppaintcore-stroke.Po
+ -rm -f ./$(DEPDIR)/gimppaintcore.Po
+ -rm -f ./$(DEPDIR)/gimppaintcoreundo.Po
+ -rm -f ./$(DEPDIR)/gimppaintoptions.Po
+ -rm -f ./$(DEPDIR)/gimppencil.Po
+ -rm -f ./$(DEPDIR)/gimppenciloptions.Po
+ -rm -f ./$(DEPDIR)/gimpperspectiveclone.Po
+ -rm -f ./$(DEPDIR)/gimpperspectivecloneoptions.Po
+ -rm -f ./$(DEPDIR)/gimpsmudge.Po
+ -rm -f ./$(DEPDIR)/gimpsmudgeoptions.Po
+ -rm -f ./$(DEPDIR)/gimpsourcecore.Po
+ -rm -f ./$(DEPDIR)/gimpsourceoptions.Po
+ -rm -f ./$(DEPDIR)/paint-enums.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/gimp-paint.Po
+ -rm -f ./$(DEPDIR)/gimpairbrush.Po
+ -rm -f ./$(DEPDIR)/gimpairbrushoptions.Po
+ -rm -f ./$(DEPDIR)/gimpbrushcore-loops.Po
+ -rm -f ./$(DEPDIR)/gimpbrushcore.Po
+ -rm -f ./$(DEPDIR)/gimpclone.Po
+ -rm -f ./$(DEPDIR)/gimpcloneoptions.Po
+ -rm -f ./$(DEPDIR)/gimpconvolve.Po
+ -rm -f ./$(DEPDIR)/gimpconvolveoptions.Po
+ -rm -f ./$(DEPDIR)/gimpdodgeburn.Po
+ -rm -f ./$(DEPDIR)/gimpdodgeburnoptions.Po
+ -rm -f ./$(DEPDIR)/gimperaser.Po
+ -rm -f ./$(DEPDIR)/gimperaseroptions.Po
+ -rm -f ./$(DEPDIR)/gimpheal.Po
+ -rm -f ./$(DEPDIR)/gimpink-blob.Po
+ -rm -f ./$(DEPDIR)/gimpink.Po
+ -rm -f ./$(DEPDIR)/gimpinkoptions.Po
+ -rm -f ./$(DEPDIR)/gimpinkundo.Po
+ -rm -f ./$(DEPDIR)/gimpmybrushcore.Po
+ -rm -f ./$(DEPDIR)/gimpmybrushoptions.Po
+ -rm -f ./$(DEPDIR)/gimpmybrushsurface.Po
+ -rm -f ./$(DEPDIR)/gimppaintbrush.Po
+ -rm -f ./$(DEPDIR)/gimppaintcore-loops.Po
+ -rm -f ./$(DEPDIR)/gimppaintcore-stroke.Po
+ -rm -f ./$(DEPDIR)/gimppaintcore.Po
+ -rm -f ./$(DEPDIR)/gimppaintcoreundo.Po
+ -rm -f ./$(DEPDIR)/gimppaintoptions.Po
+ -rm -f ./$(DEPDIR)/gimppencil.Po
+ -rm -f ./$(DEPDIR)/gimppenciloptions.Po
+ -rm -f ./$(DEPDIR)/gimpperspectiveclone.Po
+ -rm -f ./$(DEPDIR)/gimpperspectivecloneoptions.Po
+ -rm -f ./$(DEPDIR)/gimpsmudge.Po
+ -rm -f ./$(DEPDIR)/gimpsmudgeoptions.Po
+ -rm -f ./$(DEPDIR)/gimpsourcecore.Po
+ -rm -f ./$(DEPDIR)/gimpsourceoptions.Po
+ -rm -f ./$(DEPDIR)/paint-enums.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLIBRARIES \
+ cscopelist-am ctags ctags-am distclean distclean-compile \
+ distclean-generic distclean-libtool distclean-tags distdir dvi \
+ dvi-am html html-am info info-am install install-am \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+xgen-pec: $(srcdir)/paint-enums.h $(GIMP_MKENUMS) Makefile.am
+ $(AM_V_GEN) $(GIMP_MKENUMS) \
+ --fhead "#include \"config.h\"\n#include <gio/gio.h>\n#include \"libgimpbase/gimpbase.h\"\n#include \"paint-enums.h\"\n#include \"gimp-intl.h\"" \
+ --fprod "\n/* enumerations from \"@basename@\" */" \
+ --vhead "GType\n@enum_name@_get_type (void)\n{\n static const G@Type@Value values[] =\n {" \
+ --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" }," \
+ --vtail " { 0, NULL, NULL }\n };\n" \
+ --dhead " static const Gimp@Type@Desc descs[] =\n {" \
+ --dprod " { @VALUENAME@, @valuedesc@, @valuehelp@ },@if ('@valueabbrev@' ne 'NULL')@\n /* Translators: this is an abbreviated version of @valueudesc@.\n Keep it short. */\n { @VALUENAME@, @valueabbrev@, NULL },@endif@" \
+ --dtail " { 0, NULL, NULL }\n };\n\n static GType type = 0;\n\n if (G_UNLIKELY (! type))\n {\n type = g_@type@_register_static (\"@EnumName@\", values);\n gimp_type_set_translation_context (type, \"@enumnick@\");\n gimp_@type@_set_value_descriptions (type, descs);\n }\n\n return type;\n}\n" \
+ $< > $@
+
+# copy the generated enum file back to the source directory only if it's
+# changed; otherwise, only update its timestamp, so that the recipe isn't
+# executed again on the next build, however, allow this to (harmlessly) fail,
+# to support building from a read-only source tree.
+$(srcdir)/paint-enums.c: xgen-pec
+ $(AM_V_GEN) if ! cmp -s $< $@; then \
+ cp $< $@; \
+ else \
+ touch $@ 2> /dev/null \
+ || true; \
+ fi
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/app/paint/gimp-paint.c b/app/paint/gimp-paint.c
new file mode 100644
index 0000000..f3904a3
--- /dev/null
+++ b/app/paint/gimp-paint.c
@@ -0,0 +1,140 @@
+/* GIMP - The GNU Image Manipulation Program
+ * 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/gimp.h"
+#include "core/gimplist.h"
+#include "core/gimppaintinfo.h"
+
+#include "gimp-paint.h"
+#include "gimpairbrush.h"
+#include "gimpclone.h"
+#include "gimpconvolve.h"
+#include "gimpdodgeburn.h"
+#include "gimperaser.h"
+#include "gimpheal.h"
+#include "gimpink.h"
+#include "gimpmybrushcore.h"
+#include "gimppaintoptions.h"
+#include "gimppaintbrush.h"
+#include "gimppencil.h"
+#include "gimpperspectiveclone.h"
+#include "gimpsmudge.h"
+
+
+/* local function prototypes */
+
+static void gimp_paint_register (Gimp *gimp,
+ GType paint_type,
+ GType paint_options_type,
+ const gchar *identifier,
+ const gchar *blurb,
+ const gchar *icon_name);
+
+
+/* public functions */
+
+void
+gimp_paint_init (Gimp *gimp)
+{
+ GimpPaintRegisterFunc register_funcs[] =
+ {
+ gimp_dodge_burn_register,
+ gimp_smudge_register,
+ gimp_convolve_register,
+ gimp_perspective_clone_register,
+ gimp_heal_register,
+ gimp_clone_register,
+ gimp_mybrush_core_register,
+ gimp_ink_register,
+ gimp_airbrush_register,
+ gimp_eraser_register,
+ gimp_paintbrush_register,
+ gimp_pencil_register
+ };
+
+ gint i;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ gimp->paint_info_list = gimp_list_new (GIMP_TYPE_PAINT_INFO, FALSE);
+ gimp_object_set_static_name (GIMP_OBJECT (gimp->paint_info_list),
+ "paint infos");
+
+ gimp_container_freeze (gimp->paint_info_list);
+
+ for (i = 0; i < G_N_ELEMENTS (register_funcs); i++)
+ {
+ register_funcs[i] (gimp, gimp_paint_register);
+ }
+
+ gimp_container_thaw (gimp->paint_info_list);
+}
+
+void
+gimp_paint_exit (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ gimp_paint_info_set_standard (gimp, NULL);
+
+ if (gimp->paint_info_list)
+ {
+ gimp_container_foreach (gimp->paint_info_list,
+ (GFunc) g_object_run_dispose, NULL);
+ g_clear_object (&gimp->paint_info_list);
+ }
+}
+
+
+/* private functions */
+
+static void
+gimp_paint_register (Gimp *gimp,
+ GType paint_type,
+ GType paint_options_type,
+ const gchar *identifier,
+ const gchar *blurb,
+ const gchar *icon_name)
+{
+ GimpPaintInfo *paint_info;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+ g_return_if_fail (g_type_is_a (paint_type, GIMP_TYPE_PAINT_CORE));
+ g_return_if_fail (g_type_is_a (paint_options_type, GIMP_TYPE_PAINT_OPTIONS));
+ g_return_if_fail (identifier != NULL);
+ g_return_if_fail (blurb != NULL);
+
+ paint_info = gimp_paint_info_new (gimp,
+ paint_type,
+ paint_options_type,
+ identifier,
+ blurb,
+ icon_name);
+
+ gimp_container_add (gimp->paint_info_list, GIMP_OBJECT (paint_info));
+ g_object_unref (paint_info);
+
+ if (paint_type == GIMP_TYPE_PAINTBRUSH)
+ gimp_paint_info_set_standard (gimp, paint_info);
+}
diff --git a/app/paint/gimp-paint.h b/app/paint/gimp-paint.h
new file mode 100644
index 0000000..97dee10
--- /dev/null
+++ b/app/paint/gimp-paint.h
@@ -0,0 +1,26 @@
+/* GIMP - The GNU Image Manipulation Program
+ * 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 __GIMP_PAINT_H__
+#define __GIMP_PAINT_H__
+
+
+void gimp_paint_init (Gimp *gimp);
+void gimp_paint_exit (Gimp *gimp);
+
+
+#endif /* __GIMP_PAINT_H__ */
diff --git a/app/paint/gimpairbrush.c b/app/paint/gimpairbrush.c
new file mode 100644
index 0000000..b4bf5b4
--- /dev/null
+++ b/app/paint/gimpairbrush.c
@@ -0,0 +1,266 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "paint-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpbrush.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdynamics.h"
+#include "core/gimpgradient.h"
+#include "core/gimpimage.h"
+#include "core/gimpsymmetry.h"
+
+#include "gimpairbrush.h"
+#include "gimpairbrushoptions.h"
+
+#include "gimp-intl.h"
+
+
+#define STAMP_MAX_FPS 60
+
+
+enum
+{
+ STAMP,
+ LAST_SIGNAL
+};
+
+
+static void gimp_airbrush_finalize (GObject *object);
+
+static void gimp_airbrush_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time);
+static void gimp_airbrush_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym);
+
+static gboolean gimp_airbrush_timeout (gpointer data);
+
+
+G_DEFINE_TYPE (GimpAirbrush, gimp_airbrush, GIMP_TYPE_PAINTBRUSH)
+
+#define parent_class gimp_airbrush_parent_class
+
+static guint airbrush_signals[LAST_SIGNAL] = { 0 };
+
+
+void
+gimp_airbrush_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback)
+{
+ (* callback) (gimp,
+ GIMP_TYPE_AIRBRUSH,
+ GIMP_TYPE_AIRBRUSH_OPTIONS,
+ "gimp-airbrush",
+ _("Airbrush"),
+ "gimp-tool-airbrush");
+}
+
+static void
+gimp_airbrush_class_init (GimpAirbrushClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
+
+ object_class->finalize = gimp_airbrush_finalize;
+
+ paint_core_class->paint = gimp_airbrush_paint;
+
+ airbrush_signals[STAMP] =
+ g_signal_new ("stamp",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpAirbrushClass, stamp),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+gimp_airbrush_init (GimpAirbrush *airbrush)
+{
+}
+
+static void
+gimp_airbrush_finalize (GObject *object)
+{
+ GimpAirbrush *airbrush = GIMP_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
+gimp_airbrush_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time)
+{
+ GimpAirbrush *airbrush = GIMP_AIRBRUSH (paint_core);
+ GimpAirbrushOptions *options = GIMP_AIRBRUSH_OPTIONS (paint_options);
+ GimpDynamics *dynamics = GIMP_BRUSH_CORE (paint_core)->dynamics;
+
+ if (airbrush->timeout_id)
+ {
+ g_source_remove (airbrush->timeout_id);
+ airbrush->timeout_id = 0;
+ }
+
+ switch (paint_state)
+ {
+ case GIMP_PAINT_STATE_INIT:
+ GIMP_PAINT_CORE_CLASS (parent_class)->paint (paint_core, drawable,
+ paint_options,
+ sym,
+ paint_state, time);
+ break;
+
+ case GIMP_PAINT_STATE_MOTION:
+ gimp_airbrush_motion (paint_core, drawable, paint_options, sym);
+
+ if ((options->rate != 0.0) && ! options->motion_only)
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ gdouble fade_point;
+ gdouble dynamic_rate;
+ gint timeout;
+ GimpCoords *coords;
+
+ fade_point = gimp_paint_options_get_fade (paint_options, image,
+ paint_core->pixel_dist);
+
+ airbrush->drawable = drawable;
+ airbrush->paint_options = paint_options;
+
+ if (airbrush->sym)
+ g_object_unref (airbrush->sym);
+ airbrush->sym = g_object_ref (sym);
+
+ /* Base our timeout on the original stroke. */
+ coords = gimp_symmetry_get_origin (sym);
+
+ airbrush->coords = *coords;
+
+ dynamic_rate = gimp_dynamics_get_linear_value (dynamics,
+ GIMP_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,
+ gimp_airbrush_timeout,
+ airbrush, NULL);
+ }
+ break;
+
+ case GIMP_PAINT_STATE_FINISH:
+ GIMP_PAINT_CORE_CLASS (parent_class)->paint (paint_core, drawable,
+ paint_options,
+ sym,
+ paint_state, time);
+
+ g_clear_object (&airbrush->sym);
+ break;
+ }
+}
+
+static void
+gimp_airbrush_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym)
+
+{
+ GimpAirbrushOptions *options = GIMP_AIRBRUSH_OPTIONS (paint_options);
+ GimpDynamics *dynamics = GIMP_BRUSH_CORE (paint_core)->dynamics;
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ gdouble opacity;
+ gdouble fade_point;
+ GimpCoords *coords;
+
+ fade_point = gimp_paint_options_get_fade (paint_options, image,
+ paint_core->pixel_dist);
+
+ coords = gimp_symmetry_get_origin (sym);
+
+ opacity = (options->flow / 100.0 *
+ gimp_dynamics_get_linear_value (dynamics,
+ GIMP_DYNAMICS_OUTPUT_FLOW,
+ coords,
+ paint_options,
+ fade_point));
+
+ _gimp_paintbrush_motion (paint_core, drawable, paint_options,
+ sym, opacity);
+}
+
+static gboolean
+gimp_airbrush_timeout (gpointer data)
+{
+ GimpAirbrush *airbrush = GIMP_AIRBRUSH (data);
+
+ airbrush->timeout_id = 0;
+
+ g_signal_emit (airbrush, airbrush_signals[STAMP], 0);
+
+ return G_SOURCE_REMOVE;
+}
+
+
+/* public functions */
+
+
+void
+gimp_airbrush_stamp (GimpAirbrush *airbrush)
+{
+ g_return_if_fail (GIMP_IS_AIRBRUSH (airbrush));
+
+ gimp_symmetry_set_origin (airbrush->sym,
+ airbrush->drawable, &airbrush->coords);
+
+ gimp_airbrush_paint (GIMP_PAINT_CORE (airbrush),
+ airbrush->drawable,
+ airbrush->paint_options,
+ airbrush->sym,
+ GIMP_PAINT_STATE_MOTION, 0);
+
+ gimp_symmetry_clear_origin (airbrush->sym);
+}
diff --git a/app/paint/gimpairbrush.h b/app/paint/gimpairbrush.h
new file mode 100644
index 0000000..23b2b85
--- /dev/null
+++ b/app/paint/gimpairbrush.h
@@ -0,0 +1,64 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_AIRBRUSH_H__
+#define __GIMP_AIRBRUSH_H__
+
+
+#include "gimppaintbrush.h"
+
+
+#define GIMP_TYPE_AIRBRUSH (gimp_airbrush_get_type ())
+#define GIMP_AIRBRUSH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_AIRBRUSH, GimpAirbrush))
+#define GIMP_AIRBRUSH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_AIRBRUSH, GimpAirbrushClass))
+#define GIMP_IS_AIRBRUSH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_AIRBRUSH))
+#define GIMP_IS_AIRBRUSH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_AIRBRUSH))
+#define GIMP_AIRBRUSH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_AIRBRUSH, GimpAirbrushClass))
+
+
+typedef struct _GimpAirbrushClass GimpAirbrushClass;
+
+struct _GimpAirbrush
+{
+ GimpPaintbrush parent_instance;
+
+ guint timeout_id;
+
+ GimpSymmetry *sym;
+ GimpDrawable *drawable;
+ GimpPaintOptions *paint_options;
+ GimpCoords coords;
+};
+
+struct _GimpAirbrushClass
+{
+ GimpPaintbrushClass parent_class;
+
+ /* signals */
+ void (* stamp) (GimpAirbrush *airbrush);
+};
+
+
+void gimp_airbrush_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback);
+
+GType gimp_airbrush_get_type (void) G_GNUC_CONST;
+
+void gimp_airbrush_stamp (GimpAirbrush *airbrush);
+
+
+#endif /* __GIMP_AIRBRUSH_H__ */
diff --git a/app/paint/gimpairbrushoptions.c b/app/paint/gimpairbrushoptions.c
new file mode 100644
index 0000000..37c7e78
--- /dev/null
+++ b/app/paint/gimpairbrushoptions.c
@@ -0,0 +1,155 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "paint-types.h"
+
+#include "gimpairbrushoptions.h"
+
+#include "gimp-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 gimp_airbrush_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_airbrush_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpAirbrushOptions, gimp_airbrush_options,
+ GIMP_TYPE_PAINT_OPTIONS)
+
+
+static void
+gimp_airbrush_options_class_init (GimpAirbrushOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_airbrush_options_set_property;
+ object_class->get_property = gimp_airbrush_options_get_property;
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_RATE,
+ "rate",
+ C_("airbrush-tool", "Rate"),
+ NULL,
+ 0.0, 100.0, AIRBRUSH_DEFAULT_RATE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_MOTION_ONLY,
+ "motion-only",
+ _("Motion only"),
+ NULL,
+ AIRBRUSH_DEFAULT_MOTION_ONLY,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_FLOW,
+ "flow",
+ _("Flow"),
+ NULL,
+ 0.0, 100.0, AIRBRUSH_DEFAULT_FLOW,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ /* backwads-compadibility prop for flow fomerly known as pressure */
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_PRESSURE,
+ "pressure",
+ NULL, NULL,
+ 0.0, 100.0, AIRBRUSH_DEFAULT_FLOW,
+ GIMP_CONFIG_PARAM_IGNORE);
+}
+
+static void
+gimp_airbrush_options_init (GimpAirbrushOptions *options)
+{
+}
+
+static void
+gimp_airbrush_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpAirbrushOptions *options = GIMP_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
+gimp_airbrush_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpAirbrushOptions *options = GIMP_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;
+ }
+}
diff --git a/app/paint/gimpairbrushoptions.h b/app/paint/gimpairbrushoptions.h
new file mode 100644
index 0000000..d7d8bcd
--- /dev/null
+++ b/app/paint/gimpairbrushoptions.h
@@ -0,0 +1,53 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_AIRBRUSH_OPTIONS_H__
+#define __GIMP_AIRBRUSH_OPTIONS_H__
+
+
+#include "gimppaintoptions.h"
+
+
+#define GIMP_TYPE_AIRBRUSH_OPTIONS (gimp_airbrush_options_get_type ())
+#define GIMP_AIRBRUSH_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_AIRBRUSH_OPTIONS, GimpAirbrushOptions))
+#define GIMP_AIRBRUSH_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_AIRBRUSH_OPTIONS, GimpAirbrushOptionsClass))
+#define GIMP_IS_AIRBRUSH_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_AIRBRUSH_OPTIONS))
+#define GIMP_IS_AIRBRUSH_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_AIRBRUSH_OPTIONS))
+#define GIMP_AIRBRUSH_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_AIRBRUSH_OPTIONS, GimpAirbrushOptionsClass))
+
+
+typedef struct _GimpAirbrushOptionsClass GimpAirbrushOptionsClass;
+
+struct _GimpAirbrushOptions
+{
+ GimpPaintOptions parent_instance;
+
+ gdouble rate;
+ gboolean motion_only;
+ gdouble flow;
+};
+
+struct _GimpAirbrushOptionsClass
+{
+ GimpPaintOptionsClass parent_class;
+};
+
+
+GType gimp_airbrush_options_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_AIRBRUSH_OPTIONS_H__ */
diff --git a/app/paint/gimpbrushcore-kernels.h b/app/paint/gimpbrushcore-kernels.h
new file mode 100644
index 0000000..ea5eb45
--- /dev/null
+++ b/app/paint/gimpbrushcore-kernels.h
@@ -0,0 +1,116 @@
+/* gimpbrushcore-kernels.h
+ *
+ * This file was generated using kernelgen as found in the tools dir.
+ * (threshold = 0.25)
+ */
+
+#ifndef __GIMP_BRUSH_CORE_KERNELS_H__
+#define __GIMP_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 /* __GIMP_BRUSH_CORE_KERNELS_H__ */
diff --git a/app/paint/gimpbrushcore-loops.cc b/app/paint/gimpbrushcore-loops.cc
new file mode 100644
index 0000000..31b4afb
--- /dev/null
+++ b/app/paint/gimpbrushcore-loops.cc
@@ -0,0 +1,647 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpmath/gimpmath.h"
+
+extern "C"
+{
+
+#include "paint-types.h"
+
+#include "core/gimptempbuf.h"
+
+#include "gimpbrushcore.h"
+#include "gimpbrushcore-loops.h"
+
+} /* extern "C" */
+
+#include "gimpbrushcore-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 (GimpTempBuf *buf,
+ gint top,
+ gint bottom,
+ gint left,
+ gint right)
+{
+ guchar *data;
+ const Babl *format = gimp_temp_buf_get_format (buf);
+ gint bpp = babl_format_get_bytes_per_pixel (format);
+ gint width = gimp_temp_buf_get_width (buf);
+ gint height = gimp_temp_buf_get_height (buf);
+
+ if (top + bottom >= height || left + right >= width)
+ {
+ gimp_temp_buf_data_clear (buf);
+
+ return;
+ }
+
+ data = gimp_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 = gimp_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 = gimp_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
+gimp_brush_core_subsample_mask_impl (const GimpTempBuf *mask,
+ GimpTempBuf *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 = gimp_temp_buf_get_width (mask);
+ gint mask_height = gimp_temp_buf_get_height (mask);
+ gint dest_width = gimp_temp_buf_get_width (dest);
+ gint dest_height = gimp_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 *) gimp_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 *) gimp_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 *) gimp_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 GimpTempBuf *
+gimp_brush_core_subsample_mask (GimpBrushCore *core,
+ const GimpTempBuf *mask,
+ gdouble x,
+ gdouble y)
+{
+ GimpTempBuf *dest;
+ const Babl *mask_format;
+ gdouble left;
+ gint index1;
+ gint index2;
+ gint dest_offset_x = 0;
+ gint dest_offset_y = 0;
+ gint mask_width = gimp_temp_buf_get_width (mask);
+ gint mask_height = gimp_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], gimp_temp_buf_unref);
+
+ core->last_subsample_brush_mask = mask;
+ core->subsample_cache_invalid = FALSE;
+ }
+
+ mask_format = gimp_temp_buf_get_format (mask);
+
+ dest = gimp_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"))
+ {
+ gimp_brush_core_subsample_mask_impl<guchar> (mask, dest,
+ dest_offset_x, dest_offset_y,
+ index1, index2);
+ }
+ else if (mask_format == babl_format ("Y float"))
+ {
+ gimp_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
+gimp_brush_core_pressurize_mask_impl (const GimpTempBuf *mask,
+ GimpTempBuf *dest,
+ Pressure pressure)
+{
+ gegl_parallel_distribute_range (
+ gimp_temp_buf_get_width (mask) * gimp_temp_buf_get_height (mask),
+ PIXELS_PER_THREAD,
+ [=] (gint offset, gint size)
+ {
+ const T *m;
+ T *d;
+ gint i;
+
+ m = (const T *) gimp_temp_buf_get_data (mask) + offset;
+ d = ( T *) gimp_temp_buf_get_data (dest) + offset;
+
+ for (i = 0; i < size; i++)
+ *d++ = pressure (*m++);
+ });
+}
+
+/* #define FANCY_PRESSURE */
+
+const GimpTempBuf *
+gimp_brush_core_pressurize_mask (GimpBrushCore *core,
+ const GimpTempBuf *brush_mask,
+ gdouble x,
+ gdouble y,
+ gdouble pressure)
+{
+ const GimpTempBuf *subsample_mask;
+ const Babl *subsample_mask_format;
+
+ /* Get the raw subsampled mask */
+ subsample_mask = gimp_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, gimp_temp_buf_unref);
+
+ subsample_mask_format = gimp_temp_buf_get_format (subsample_mask);
+
+ core->pressure_brush =
+ gimp_temp_buf_new (gimp_temp_buf_get_width (brush_mask) + 2,
+ gimp_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"))
+ {
+ gimp_brush_core_pressurize_mask_impl<guchar> (subsample_mask,
+ core->pressure_brush,
+ CachedPressure<guchar> (
+ Pressure (pressure)));
+ }
+ else if (subsample_mask_format == babl_format ("Y float"))
+ {
+ gimp_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
+gimp_brush_core_solidify_mask_impl (const GimpTempBuf *mask,
+ GimpTempBuf *dest,
+ gint dest_offset_x,
+ gint dest_offset_y)
+{
+ gint mask_width = gimp_temp_buf_get_width (mask);
+ gint mask_height = gimp_temp_buf_get_height (mask);
+ gint dest_width = gimp_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 *) gimp_temp_buf_get_data (mask) +
+ area->y * mask_width + area->x;
+ d = ((gfloat *) gimp_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 GimpTempBuf *
+gimp_brush_core_solidify_mask (GimpBrushCore *core,
+ const GimpTempBuf *brush_mask,
+ gdouble x,
+ gdouble y)
+{
+ GimpTempBuf *dest;
+ const Babl *brush_mask_format;
+ gint dest_offset_x = 0;
+ gint dest_offset_y = 0;
+ gint brush_mask_width = gimp_temp_buf_get_width (brush_mask);
+ gint brush_mask_height = gimp_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], gimp_temp_buf_unref);
+
+ core->last_solid_brush_mask = brush_mask;
+ core->solid_cache_invalid = FALSE;
+ }
+
+ brush_mask_format = gimp_temp_buf_get_format (brush_mask);
+
+ dest = gimp_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"))
+ {
+ gimp_brush_core_solidify_mask_impl<guchar> (brush_mask, dest,
+ dest_offset_x, dest_offset_y);
+ }
+ else if (brush_mask_format == babl_format ("Y float"))
+ {
+ gimp_brush_core_solidify_mask_impl<gfloat> (brush_mask, dest,
+ dest_offset_x, dest_offset_y);
+ }
+ else
+ {
+ g_warn_if_reached ();
+ }
+
+ return dest;
+}
diff --git a/app/paint/gimpbrushcore-loops.h b/app/paint/gimpbrushcore-loops.h
new file mode 100644
index 0000000..6fde397
--- /dev/null
+++ b/app/paint/gimpbrushcore-loops.h
@@ -0,0 +1,37 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_CORE_LOOPS_H__
+#define __GIMP_BRUSH_CORE_LOOPS_H__
+
+
+const GimpTempBuf * gimp_brush_core_subsample_mask (GimpBrushCore *core,
+ const GimpTempBuf *mask,
+ gdouble x,
+ gdouble y);
+const GimpTempBuf * gimp_brush_core_pressurize_mask (GimpBrushCore *core,
+ const GimpTempBuf *brush_mask,
+ gdouble x,
+ gdouble y,
+ gdouble pressure);
+const GimpTempBuf * gimp_brush_core_solidify_mask (GimpBrushCore *core,
+ const GimpTempBuf *brush_mask,
+ gdouble x,
+ gdouble y);
+
+
+#endif /* __GIMP_BRUSH_CORE_LOOPS_H__ */
diff --git a/app/paint/gimpbrushcore.c b/app/paint/gimpbrushcore.c
new file mode 100644
index 0000000..da4f26b
--- /dev/null
+++ b/app/paint/gimpbrushcore.c
@@ -0,0 +1,1344 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "paint-types.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-loops.h"
+
+#include "core/gimpbrush-header.h"
+#include "core/gimpbrushgenerated.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdynamics.h"
+#include "core/gimpdynamicsoutput.h"
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimpmarshal.h"
+#include "core/gimpsymmetry.h"
+#include "core/gimptempbuf.h"
+
+#include "gimpbrushcore.h"
+#include "gimpbrushcore-loops.h"
+#include "gimpbrushcore-kernels.h"
+
+#include "gimppaintoptions.h"
+
+#include "gimp-intl.h"
+
+
+#define EPSILON 0.00001
+
+enum
+{
+ SET_BRUSH,
+ SET_DYNAMICS,
+ LAST_SIGNAL
+};
+
+
+/* local function prototypes */
+
+static void gimp_brush_core_finalize (GObject *object);
+
+static gboolean gimp_brush_core_start (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GError **error);
+static gboolean gimp_brush_core_pre_paint (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpPaintState paint_state,
+ guint32 time);
+static void gimp_brush_core_post_paint (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpPaintState paint_state,
+ guint32 time);
+static void gimp_brush_core_interpolate (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ guint32 time);
+
+static GeglBuffer * gimp_brush_core_get_paint_buffer(GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpLayerMode paint_mode,
+ const GimpCoords *coords,
+ gint *paint_buffer_x,
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height);
+
+static void gimp_brush_core_real_set_brush (GimpBrushCore *core,
+ GimpBrush *brush);
+static void gimp_brush_core_real_set_dynamics (GimpBrushCore *core,
+ GimpDynamics *dynamics);
+
+static gdouble gimp_brush_core_get_angle (GimpBrushCore *core);
+static gboolean gimp_brush_core_get_reflect (GimpBrushCore *core);
+
+static const GimpTempBuf *
+ gimp_brush_core_transform_mask (GimpBrushCore *core,
+ GimpBrush *brush);
+
+static void gimp_brush_core_invalidate_cache (GimpBrush *brush,
+ GimpBrushCore *core);
+
+
+G_DEFINE_TYPE (GimpBrushCore, gimp_brush_core, GIMP_TYPE_PAINT_CORE)
+
+#define parent_class gimp_brush_core_parent_class
+
+static guint core_signals[LAST_SIGNAL] = { 0, };
+
+
+static void
+gimp_brush_core_class_init (GimpBrushCoreClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
+
+ core_signals[SET_BRUSH] =
+ g_signal_new ("set-brush",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpBrushCoreClass, set_brush),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_BRUSH);
+
+ core_signals[SET_DYNAMICS] =
+ g_signal_new ("set-dynamics",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GimpBrushCoreClass, set_dynamics),
+ NULL, NULL,
+ gimp_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ GIMP_TYPE_DYNAMICS);
+
+ object_class->finalize = gimp_brush_core_finalize;
+
+ paint_core_class->start = gimp_brush_core_start;
+ paint_core_class->pre_paint = gimp_brush_core_pre_paint;
+ paint_core_class->post_paint = gimp_brush_core_post_paint;
+ paint_core_class->interpolate = gimp_brush_core_interpolate;
+ paint_core_class->get_paint_buffer = gimp_brush_core_get_paint_buffer;
+
+ klass->handles_changing_brush = FALSE;
+ klass->handles_transforming_brush = TRUE;
+ klass->handles_dynamic_transforming_brush = TRUE;
+
+ klass->set_brush = gimp_brush_core_real_set_brush;
+ klass->set_dynamics = gimp_brush_core_real_set_dynamics;
+}
+
+static void
+gimp_brush_core_init (GimpBrushCore *core)
+{
+ gint i, j;
+
+ core->main_brush = NULL;
+ core->brush = NULL;
+ core->dynamics = NULL;
+ core->spacing = 1.0;
+ core->scale = 1.0;
+ core->angle = 0.0;
+ core->reflect = FALSE;
+ core->hardness = 1.0;
+ core->aspect_ratio = 0.0;
+
+ core->symmetry_angle = 0.0;
+ core->symmetry_reflect = FALSE;
+
+ core->pressure_brush = NULL;
+
+ core->last_solid_brush_mask = NULL;
+ core->solid_cache_invalid = FALSE;
+
+ core->transform_brush = NULL;
+ core->transform_pixmap = NULL;
+
+ core->last_subsample_brush_mask = NULL;
+ core->subsample_cache_invalid = FALSE;
+
+ core->rand = g_rand_new ();
+
+ for (i = 0; i < BRUSH_CORE_SOLID_SUBSAMPLE; i++)
+ {
+ for (j = 0; j < BRUSH_CORE_SOLID_SUBSAMPLE; j++)
+ {
+ core->solid_brushes[i][j] = NULL;
+ }
+ }
+
+ for (i = 0; i < BRUSH_CORE_JITTER_LUTSIZE - 1; ++i)
+ {
+ core->jitter_lut_y[i] = cos (gimp_deg_to_rad (i * 360 /
+ BRUSH_CORE_JITTER_LUTSIZE));
+ core->jitter_lut_x[i] = sin (gimp_deg_to_rad (i * 360 /
+ BRUSH_CORE_JITTER_LUTSIZE));
+ }
+
+ gimp_assert (BRUSH_CORE_SUBSAMPLE == KERNEL_SUBSAMPLE);
+
+ for (i = 0; i < KERNEL_SUBSAMPLE + 1; i++)
+ {
+ for (j = 0; j < KERNEL_SUBSAMPLE + 1; j++)
+ {
+ core->subsample_brushes[i][j] = NULL;
+ }
+ }
+}
+
+static void
+gimp_brush_core_finalize (GObject *object)
+{
+ GimpBrushCore *core = GIMP_BRUSH_CORE (object);
+ gint i, j;
+
+ g_clear_pointer (&core->pressure_brush, gimp_temp_buf_unref);
+
+ for (i = 0; i < BRUSH_CORE_SOLID_SUBSAMPLE; i++)
+ for (j = 0; j < BRUSH_CORE_SOLID_SUBSAMPLE; j++)
+ g_clear_pointer (&core->solid_brushes[i][j], gimp_temp_buf_unref);
+
+ g_clear_pointer (&core->rand, g_rand_free);
+
+ for (i = 0; i < KERNEL_SUBSAMPLE + 1; i++)
+ for (j = 0; j < KERNEL_SUBSAMPLE + 1; j++)
+ g_clear_pointer (&core->subsample_brushes[i][j], gimp_temp_buf_unref);
+
+ if (core->main_brush)
+ {
+ g_signal_handlers_disconnect_by_func (core->main_brush,
+ gimp_brush_core_invalidate_cache,
+ core);
+ gimp_brush_end_use (core->main_brush);
+ g_clear_object (&core->main_brush);
+ }
+
+ g_clear_object (&core->dynamics);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+gimp_brush_core_pre_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpPaintState paint_state,
+ guint32 time)
+{
+ GimpBrushCore *core = GIMP_BRUSH_CORE (paint_core);
+
+ if (paint_state == GIMP_PAINT_STATE_MOTION)
+ {
+ GimpCoords last_coords;
+ GimpCoords current_coords;
+ gdouble scale;
+
+ gimp_paint_core_get_last_coords (paint_core, &last_coords);
+ gimp_paint_core_get_current_coords (paint_core, &current_coords);
+
+ /* If we current point == last point, check if the brush
+ * wants to be painted in that case. (Direction dependent
+ * pixmap brush pipes don't, as they don't know which
+ * pixmap to select.)
+ */
+ if (last_coords.x == current_coords.x &&
+ last_coords.y == current_coords.y &&
+ ! gimp_brush_want_null_motion (core->main_brush,
+ &last_coords,
+ &current_coords))
+ {
+ return FALSE;
+ }
+ /*No drawing anything if the scale is too small*/
+ if (GIMP_BRUSH_CORE_GET_CLASS (core)->handles_transforming_brush)
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ gdouble fade_point;
+
+ if (GIMP_BRUSH_CORE_GET_CLASS (core)->handles_dynamic_transforming_brush)
+ {
+ gdouble width;
+ gdouble height;
+
+ fade_point = gimp_paint_options_get_fade (paint_options, image,
+ paint_core->pixel_dist);
+ width = gimp_brush_get_width (core->main_brush);
+ height = gimp_brush_get_height (core->main_brush);
+
+ scale = paint_options->brush_size /
+ MAX (width, height) *
+ gimp_dynamics_get_linear_value (core->dynamics,
+ GIMP_DYNAMICS_OUTPUT_SIZE,
+ &current_coords,
+ paint_options,
+ fade_point);
+
+ if (paint_options->brush_lock_to_view &&
+ MAX (current_coords.xscale, current_coords.yscale) > 0)
+ {
+ scale /= MAX (current_coords.xscale, current_coords.yscale);
+
+ /* Cap transform result for brushes or OOM can occur */
+ if ((scale * MAX (width, height)) > GIMP_BRUSH_MAX_SIZE)
+ {
+ scale = GIMP_BRUSH_MAX_SIZE / MAX (width, height);
+ }
+ }
+
+ if (scale < 0.0000001)
+ return FALSE;
+ }
+ }
+
+ if (GIMP_BRUSH_CORE_GET_CLASS (paint_core)->handles_changing_brush)
+ {
+ core->brush = gimp_brush_select_brush (core->main_brush,
+ &last_coords,
+ &current_coords);
+ }
+ if ((! GIMP_IS_BRUSH_GENERATED(core->main_brush)) &&
+ (paint_options->brush_hardness != gimp_brush_get_blur_hardness(core->main_brush)))
+ {
+ gimp_brush_flush_blur_caches(core->main_brush);
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_brush_core_post_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpPaintState paint_state,
+ guint32 time)
+{
+ GimpBrushCore *core = GIMP_BRUSH_CORE (paint_core);
+
+ if (paint_state == GIMP_PAINT_STATE_MOTION)
+ {
+ core->brush = core->main_brush;
+ }
+}
+
+static gboolean
+gimp_brush_core_start (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GError **error)
+{
+ GimpBrushCore *core = GIMP_BRUSH_CORE (paint_core);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+
+ gimp_brush_core_set_brush (core, gimp_context_get_brush (context));
+
+ gimp_brush_core_set_dynamics (core, gimp_context_get_dynamics (context));
+
+ if (! core->main_brush)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("No brushes available for use with this tool."));
+ return FALSE;
+ }
+
+ if (! core->dynamics)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("No paint dynamics available for use with this tool."));
+ return FALSE;
+ }
+
+ if (GIMP_BRUSH_CORE_GET_CLASS (core)->handles_transforming_brush)
+ {
+ gimp_brush_core_eval_transform_dynamics (core,
+ drawable,
+ paint_options,
+ coords);
+
+ gimp_brush_core_eval_transform_symmetry (core, NULL, 0);
+ }
+
+ core->spacing = paint_options->brush_spacing;
+
+ core->brush = core->main_brush;
+
+ core->jitter =
+ gimp_paint_options_get_jitter (paint_options,
+ gimp_item_get_image (GIMP_ITEM (drawable)));
+
+ return TRUE;
+}
+
+/**
+ * gimp_avoid_exact_integer
+ * @x: points to a gdouble
+ *
+ * Adjusts *x such that it is not too close to an integer. This is used
+ * for decision algorithms that would be vulnerable to rounding glitches
+ * if exact integers were input.
+ *
+ * Side effects: Changes the value of *x
+ **/
+static void
+gimp_avoid_exact_integer (gdouble *x)
+{
+ const gdouble integral = floor (*x);
+ const gdouble fractional = *x - integral;
+
+ if (fractional < EPSILON)
+ {
+ *x = integral + EPSILON;
+ }
+ else if (fractional > (1 -EPSILON))
+ {
+ *x = integral + (1 - EPSILON);
+ }
+}
+
+static void
+gimp_brush_core_interpolate (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ guint32 time)
+{
+ GimpBrushCore *core = GIMP_BRUSH_CORE (paint_core);
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ GimpDynamicsOutput *spacing_output;
+ GimpCoords last_coords;
+ GimpCoords current_coords;
+ GimpVector2 delta_vec;
+ gdouble delta_pressure;
+ gdouble delta_xtilt, delta_ytilt;
+ gdouble delta_wheel;
+ gdouble delta_velocity;
+ gdouble temp_direction;
+ GimpVector2 temp_vec;
+ gint n, num_points;
+ gdouble t0, dt, tn;
+ gdouble st_factor, st_offset;
+ gdouble initial;
+ gdouble dist;
+ gdouble total;
+ gdouble pixel_dist;
+ gdouble pixel_initial;
+ gdouble xd, yd;
+ gdouble mag;
+ gdouble dyn_spacing = core->spacing;
+ gdouble fade_point;
+ gboolean use_dyn_spacing;
+
+ g_return_if_fail (GIMP_IS_BRUSH (core->brush));
+
+ gimp_paint_core_get_last_coords (paint_core, &last_coords);
+ gimp_paint_core_get_current_coords (paint_core, &current_coords);
+
+ gimp_avoid_exact_integer (&last_coords.x);
+ gimp_avoid_exact_integer (&last_coords.y);
+ gimp_avoid_exact_integer (&current_coords.x);
+ gimp_avoid_exact_integer (&current_coords.y);
+
+ delta_vec.x = current_coords.x - last_coords.x;
+ delta_vec.y = current_coords.y - last_coords.y;
+ delta_pressure = current_coords.pressure - last_coords.pressure;
+ delta_xtilt = current_coords.xtilt - last_coords.xtilt;
+ delta_ytilt = current_coords.ytilt - last_coords.ytilt;
+ delta_wheel = current_coords.wheel - last_coords.wheel;
+ delta_velocity = current_coords.velocity - last_coords.velocity;
+ temp_direction = current_coords.direction;
+
+ /* return if there has been no motion */
+ if (! delta_vec.x &&
+ ! delta_vec.y &&
+ ! delta_pressure &&
+ ! delta_xtilt &&
+ ! delta_ytilt &&
+ ! delta_wheel &&
+ ! delta_velocity)
+ return;
+
+ pixel_dist = gimp_vector2_length (&delta_vec);
+ pixel_initial = paint_core->pixel_dist;
+
+ /* Zero sized brushes are unfit for interpolate, so we just let
+ * paint core fail on its own
+ */
+ if (core->scale == 0.0)
+ {
+ gimp_paint_core_set_last_coords (paint_core, &current_coords);
+
+ gimp_paint_core_paint (paint_core, drawable, paint_options,
+ GIMP_PAINT_STATE_MOTION, time);
+
+ paint_core->pixel_dist = pixel_initial + pixel_dist; /* Don't forget to update pixel distance*/
+
+ return;
+ }
+
+ /* Handle dynamic spacing */
+ spacing_output = gimp_dynamics_get_output (core->dynamics,
+ GIMP_DYNAMICS_OUTPUT_SPACING);
+
+ fade_point = gimp_paint_options_get_fade (paint_options, image,
+ paint_core->pixel_dist);
+
+ use_dyn_spacing = gimp_dynamics_output_is_enabled (spacing_output);
+
+ if (use_dyn_spacing)
+ {
+ dyn_spacing = gimp_dynamics_output_get_linear_value (spacing_output,
+ &current_coords,
+ paint_options,
+ fade_point);
+
+ /* Dynamic spacing assumes that the value set in core is the min
+ * value and the max is full 200% spacing. This approach differs
+ * from the usual factor from user input approach because making
+ * spacing smaller than the nominal value is unlikely and
+ * spacing has a hard defined max.
+ */
+ dyn_spacing = (core->spacing +
+ ((2.0 - core->spacing) * (1.0 - dyn_spacing)));
+
+ /* Limiting spacing to minimum 1% */
+ dyn_spacing = MAX (core->spacing, dyn_spacing);
+ }
+
+ /* calculate the distance traveled in the coordinate space of the brush */
+ temp_vec = gimp_brush_get_x_axis (core->brush);
+ gimp_vector2_mul (&temp_vec, core->scale);
+ gimp_vector2_rotate (&temp_vec, core->angle * G_PI * 2);
+
+ mag = gimp_vector2_length (&temp_vec);
+ xd = gimp_vector2_inner_product (&delta_vec, &temp_vec) / (mag * mag);
+
+ temp_vec = gimp_brush_get_y_axis (core->brush);
+ gimp_vector2_mul (&temp_vec, core->scale);
+ gimp_vector2_rotate (&temp_vec, core->angle * G_PI * 2);
+
+ mag = gimp_vector2_length (&temp_vec);
+ yd = gimp_vector2_inner_product (&delta_vec, &temp_vec) / (mag * mag);
+
+ dist = 0.5 * sqrt (xd * xd + yd * yd);
+ total = dist + paint_core->distance;
+ initial = paint_core->distance;
+
+
+ if (delta_vec.x * delta_vec.x > delta_vec.y * delta_vec.y)
+ {
+ st_factor = delta_vec.x;
+ st_offset = last_coords.x - 0.5;
+ }
+ else
+ {
+ st_factor = delta_vec.y;
+ st_offset = last_coords.y - 0.5;
+ }
+
+ if (use_dyn_spacing)
+ {
+ gint s0;
+
+ num_points = dist / dyn_spacing;
+
+ s0 = (gint) floor (st_offset + 0.5);
+ t0 = (s0 - st_offset) / st_factor;
+ dt = dyn_spacing / dist;
+
+ if (num_points == 0)
+ return;
+ }
+ else if (fabs (st_factor) > dist / core->spacing)
+ {
+ /* The stripe principle leads to brush positions that are spaced
+ * *closer* than the official brush spacing. Use the official
+ * spacing instead. This is the common case when the brush spacing
+ * is large.
+ * The net effect is then to put a lower bound on the spacing, but
+ * one that varies with the slope of the line. This is suppose to
+ * make thin lines (say, with a 1x1 brush) prettier while leaving
+ * lines with larger brush spacing as they used to look in 1.2.x.
+ */
+
+ dt = core->spacing / dist;
+ n = (gint) (initial / core->spacing + 1.0 + EPSILON);
+ t0 = (n * core->spacing - initial) / dist;
+ num_points = 1 + (gint) floor ((1 + EPSILON - t0) / dt);
+
+ /* if we arnt going to paint anything this time and the brush
+ * has only moved on one axis return without updating the brush
+ * position, distance etc. so that we can more accurately space
+ * brush strokes when curves are supplied to us in single pixel
+ * chunks.
+ */
+
+ if (num_points == 0 && (delta_vec.x == 0 || delta_vec.y == 0))
+ return;
+ }
+ else if (fabs (st_factor) < EPSILON)
+ {
+ /* Hm, we've hardly moved at all. Don't draw anything, but reset the
+ * old coordinates and hope we've gone longer the next time...
+ */
+ current_coords.x = last_coords.x;
+ current_coords.y = last_coords.y;
+
+ gimp_paint_core_set_current_coords (paint_core, &current_coords);
+
+ /* ... but go along with the current pressure, tilt and wheel */
+ return;
+ }
+ else
+ {
+ gint direction = st_factor > 0 ? 1 : -1;
+ gint x, y;
+ gint s0, sn;
+
+ /* Choose the first and last stripe to paint.
+ * FIRST PRIORITY is to avoid gaps painting with a 1x1 aliasing
+ * brush when a horizontalish line segment follows a verticalish
+ * one or vice versa - no matter what the angle between the two
+ * lines is. This will also limit the local thinning that a 1x1
+ * subsampled brush may suffer in the same situation.
+ * SECOND PRIORITY is to avoid making free-hand drawings
+ * unpleasantly fat by plotting redundant points.
+ * These are achieved by the following rules, but it is a little
+ * tricky to see just why. Do not change this algorithm unless you
+ * are sure you know what you're doing!
+ */
+
+ /* Basic case: round the beginning and ending point to nearest
+ * stripe center.
+ */
+ s0 = (gint) floor (st_offset + 0.5);
+ sn = (gint) floor (st_offset + st_factor + 0.5);
+
+ t0 = (s0 - st_offset) / st_factor;
+ tn = (sn - st_offset) / st_factor;
+
+ x = (gint) floor (last_coords.x + t0 * delta_vec.x);
+ y = (gint) floor (last_coords.y + t0 * delta_vec.y);
+
+ if (t0 < 0.0 && !( x == (gint) floor (last_coords.x) &&
+ y == (gint) floor (last_coords.y) ))
+ {
+ /* Exception A: If the first stripe's brush position is
+ * EXTRApolated into a different pixel square than the
+ * ideal starting point, don't plot it.
+ */
+ s0 += direction;
+ }
+ else if (x == (gint) floor (paint_core->last_paint.x) &&
+ y == (gint) floor (paint_core->last_paint.y))
+ {
+ /* Exception B: If first stripe's brush position is within the
+ * same pixel square as the last plot of the previous line,
+ * don't plot it either.
+ */
+ s0 += direction;
+ }
+
+ x = (gint) floor (last_coords.x + tn * delta_vec.x);
+ y = (gint) floor (last_coords.y + tn * delta_vec.y);
+
+ if (tn > 1.0 && !( x == (gint) floor (current_coords.x) &&
+ y == (gint) floor (current_coords.y)))
+ {
+ /* Exception C: If the last stripe's brush position is
+ * EXTRApolated into a different pixel square than the
+ * ideal ending point, don't plot it.
+ */
+ sn -= direction;
+ }
+
+ t0 = (s0 - st_offset) / st_factor;
+ tn = (sn - st_offset) / st_factor;
+ dt = direction * 1.0 / st_factor;
+ num_points = 1 + direction * (sn - s0);
+
+ if (num_points >= 1)
+ {
+ /* Hack the reported total distance such that it looks to the
+ * next line as if the the last pixel plotted were at an integer
+ * multiple of the brush spacing. This helps prevent artifacts
+ * for connected lines when the brush spacing is such that some
+ * slopes will use the stripe regime and other slopes will use
+ * the nominal brush spacing.
+ */
+
+ if (tn < 1)
+ total = initial + tn * dist;
+
+ total = core->spacing * (gint) (total / core->spacing + 0.5);
+ total += (1.0 - tn) * dist;
+ }
+ }
+
+ for (n = 0; n < num_points; n++)
+ {
+ gdouble t = t0 + n * dt;
+ gdouble p = (gdouble) n / num_points;
+
+ current_coords.x = last_coords.x + t * delta_vec.x;
+ current_coords.y = last_coords.y + t * delta_vec.y;
+ current_coords.pressure = last_coords.pressure + p * delta_pressure;
+ current_coords.xtilt = last_coords.xtilt + p * delta_xtilt;
+ current_coords.ytilt = last_coords.ytilt + p * delta_ytilt;
+ current_coords.wheel = last_coords.wheel + p * delta_wheel;
+ current_coords.velocity = last_coords.velocity + p * delta_velocity;
+ current_coords.direction = temp_direction;
+ current_coords.xscale = last_coords.xscale;
+ current_coords.yscale = last_coords.yscale;
+ current_coords.angle = last_coords.angle;
+ current_coords.reflect = last_coords.reflect;
+
+ if (core->jitter > 0.0)
+ {
+ GimpVector2 x_axis;
+ GimpVector2 y_axis;
+ gdouble dyn_jitter;
+ gdouble jitter_dist;
+ gint32 jitter_angle;
+
+ x_axis = gimp_brush_get_x_axis (core->brush);
+ y_axis = gimp_brush_get_y_axis (core->brush);
+
+ dyn_jitter = (core->jitter *
+ gimp_dynamics_get_linear_value (core->dynamics,
+ GIMP_DYNAMICS_OUTPUT_JITTER,
+ &current_coords,
+ paint_options,
+ fade_point));
+
+ jitter_dist = g_rand_double_range (core->rand, 0, dyn_jitter);
+ jitter_angle = g_rand_int_range (core->rand,
+ 0, BRUSH_CORE_JITTER_LUTSIZE);
+
+ current_coords.x +=
+ (x_axis.x + y_axis.x) *
+ jitter_dist * core->jitter_lut_x[jitter_angle] * core->scale;
+
+ current_coords.y +=
+ (y_axis.y + x_axis.y) *
+ jitter_dist * core->jitter_lut_y[jitter_angle] * core->scale;
+ }
+
+ gimp_paint_core_set_current_coords (paint_core, &current_coords);
+
+ paint_core->distance = initial + t * dist;
+ paint_core->pixel_dist = pixel_initial + t * pixel_dist;
+
+ gimp_paint_core_paint (paint_core, drawable, paint_options,
+ GIMP_PAINT_STATE_MOTION, time);
+ }
+
+ current_coords.x = last_coords.x + delta_vec.x;
+ current_coords.y = last_coords.y + delta_vec.y;
+ current_coords.pressure = last_coords.pressure + delta_pressure;
+ current_coords.xtilt = last_coords.xtilt + delta_xtilt;
+ current_coords.ytilt = last_coords.ytilt + delta_ytilt;
+ current_coords.wheel = last_coords.wheel + delta_wheel;
+ current_coords.velocity = last_coords.velocity + delta_velocity;
+ current_coords.xscale = last_coords.xscale;
+ current_coords.yscale = last_coords.yscale;
+ current_coords.angle = last_coords.angle;
+ current_coords.reflect = last_coords.reflect;
+
+ gimp_paint_core_set_current_coords (paint_core, &current_coords);
+ gimp_paint_core_set_last_coords (paint_core, &current_coords);
+
+ paint_core->distance = total;
+ paint_core->pixel_dist = pixel_initial + pixel_dist;
+}
+
+static GeglBuffer *
+gimp_brush_core_get_paint_buffer (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpLayerMode paint_mode,
+ const GimpCoords *coords,
+ gint *paint_buffer_x,
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height)
+{
+ GimpBrushCore *core = GIMP_BRUSH_CORE (paint_core);
+ gint x, y;
+ gint x1, y1, x2, y2;
+ gint drawable_width, drawable_height;
+ gint brush_width, brush_height;
+
+ gimp_brush_transform_size (core->brush,
+ core->scale, core->aspect_ratio,
+ gimp_brush_core_get_angle (core),
+ gimp_brush_core_get_reflect (core),
+ &brush_width, &brush_height);
+
+ if (paint_width)
+ *paint_width = brush_width;
+ if (paint_height)
+ *paint_height = brush_height;
+
+ /* adjust the x and y coordinates to the upper left corner of the brush */
+ x = (gint) floor (coords->x) - (brush_width / 2);
+ y = (gint) floor (coords->y) - (brush_height / 2);
+
+ drawable_width = gimp_item_get_width (GIMP_ITEM (drawable));
+ drawable_height = gimp_item_get_height (GIMP_ITEM (drawable));
+
+ x1 = CLAMP (x - 1, 0, drawable_width);
+ y1 = CLAMP (y - 1, 0, drawable_height);
+ x2 = CLAMP (x + brush_width + 1, 0, drawable_width);
+ y2 = CLAMP (y + brush_height + 1, 0, drawable_height);
+
+ /* configure the canvas buffer */
+ if ((x2 - x1) && (y2 - y1))
+ {
+ GimpTempBuf *temp_buf;
+ const Babl *format;
+ GimpLayerCompositeMode composite_mode;
+
+ composite_mode = gimp_layer_mode_get_paint_composite_mode (paint_mode);
+
+ format = gimp_layer_mode_get_format (paint_mode,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ composite_mode,
+ gimp_drawable_get_format (drawable));
+
+ if (paint_core->paint_buffer &&
+ gegl_buffer_get_width (paint_core->paint_buffer) == (x2 - x1) &&
+ gegl_buffer_get_height (paint_core->paint_buffer) == (y2 - y1) &&
+ gegl_buffer_get_format (paint_core->paint_buffer) == format)
+ {
+ *paint_buffer_x = x1;
+ *paint_buffer_y = y1;
+
+ return paint_core->paint_buffer;
+ }
+
+ g_clear_object (&paint_core->paint_buffer);
+
+ temp_buf = gimp_temp_buf_new ((x2 - x1), (y2 - y1),
+ format);
+
+ *paint_buffer_x = x1;
+ *paint_buffer_y = y1;
+
+ paint_core->paint_buffer = gimp_temp_buf_create_buffer (temp_buf);
+
+ gimp_temp_buf_unref (temp_buf);
+
+ return paint_core->paint_buffer;
+ }
+
+ return NULL;
+}
+
+static void
+gimp_brush_core_real_set_brush (GimpBrushCore *core,
+ GimpBrush *brush)
+{
+ if (brush == core->main_brush)
+ return;
+
+ if (core->main_brush)
+ {
+ g_signal_handlers_disconnect_by_func (core->main_brush,
+ gimp_brush_core_invalidate_cache,
+ core);
+ gimp_brush_end_use (core->main_brush);
+ }
+
+ g_set_object (&core->main_brush, brush);
+
+ if (core->main_brush)
+ {
+ gimp_brush_begin_use (core->main_brush);
+ g_signal_connect (core->main_brush, "invalidate-preview",
+ G_CALLBACK (gimp_brush_core_invalidate_cache),
+ core);
+ }
+}
+
+static void
+gimp_brush_core_real_set_dynamics (GimpBrushCore *core,
+ GimpDynamics *dynamics)
+{
+ g_set_object (&core->dynamics, dynamics);
+}
+
+void
+gimp_brush_core_set_brush (GimpBrushCore *core,
+ GimpBrush *brush)
+{
+ g_return_if_fail (GIMP_IS_BRUSH_CORE (core));
+ g_return_if_fail (brush == NULL || GIMP_IS_BRUSH (brush));
+
+ if (brush != core->main_brush)
+ g_signal_emit (core, core_signals[SET_BRUSH], 0, brush);
+}
+
+void
+gimp_brush_core_set_dynamics (GimpBrushCore *core,
+ GimpDynamics *dynamics)
+{
+ g_return_if_fail (GIMP_IS_BRUSH_CORE (core));
+ g_return_if_fail (dynamics == NULL || GIMP_IS_DYNAMICS (dynamics));
+
+ if (dynamics != core->dynamics)
+ g_signal_emit (core, core_signals[SET_DYNAMICS], 0, dynamics);
+}
+
+void
+gimp_brush_core_paste_canvas (GimpBrushCore *core,
+ GimpDrawable *drawable,
+ const GimpCoords *coords,
+ gdouble brush_opacity,
+ gdouble image_opacity,
+ GimpLayerMode paint_mode,
+ GimpBrushApplicationMode brush_hardness,
+ gdouble dynamic_force,
+ GimpPaintApplicationMode mode)
+{
+ const GimpTempBuf *brush_mask;
+
+ brush_mask = gimp_brush_core_get_brush_mask (core, coords,
+ brush_hardness,
+ dynamic_force);
+
+ if (brush_mask)
+ {
+ GimpPaintCore *paint_core = GIMP_PAINT_CORE (core);
+ gint x;
+ gint y;
+ gint off_x;
+ gint off_y;
+
+ x = (gint) floor (coords->x) - (gimp_temp_buf_get_width (brush_mask) >> 1);
+ y = (gint) floor (coords->y) - (gimp_temp_buf_get_height (brush_mask) >> 1);
+
+ off_x = (x < 0) ? -x : 0;
+ off_y = (y < 0) ? -y : 0;
+
+ gimp_paint_core_paste (paint_core, brush_mask,
+ off_x, off_y,
+ drawable,
+ brush_opacity,
+ image_opacity,
+ paint_mode,
+ mode);
+ }
+}
+
+/* Similar to gimp_brush_core_paste_canvas, but replaces the alpha channel
+ * rather than using it to composite (i.e. transparent over opaque
+ * becomes transparent rather than opauqe.
+ */
+void
+gimp_brush_core_replace_canvas (GimpBrushCore *core,
+ GimpDrawable *drawable,
+ const GimpCoords *coords,
+ gdouble brush_opacity,
+ gdouble image_opacity,
+ GimpBrushApplicationMode brush_hardness,
+ gdouble dynamic_force,
+ GimpPaintApplicationMode mode)
+{
+ const GimpTempBuf *brush_mask;
+
+ brush_mask = gimp_brush_core_get_brush_mask (core, coords,
+ brush_hardness,
+ dynamic_force);
+
+ if (brush_mask)
+ {
+ GimpPaintCore *paint_core = GIMP_PAINT_CORE (core);
+ gint x;
+ gint y;
+ gint off_x;
+ gint off_y;
+
+ x = (gint) floor (coords->x) - (gimp_temp_buf_get_width (brush_mask) >> 1);
+ y = (gint) floor (coords->y) - (gimp_temp_buf_get_height (brush_mask) >> 1);
+
+ off_x = (x < 0) ? -x : 0;
+ off_y = (y < 0) ? -y : 0;
+
+ gimp_paint_core_replace (paint_core, brush_mask,
+ off_x, off_y,
+ drawable,
+ brush_opacity,
+ image_opacity,
+ mode);
+ }
+}
+
+
+static void
+gimp_brush_core_invalidate_cache (GimpBrush *brush,
+ GimpBrushCore *core)
+{
+ /* Make sure we don't cache data for a brush that has changed */
+
+ core->subsample_cache_invalid = TRUE;
+ core->solid_cache_invalid = TRUE;
+
+ /* Notify of the brush change */
+
+ g_signal_emit (core, core_signals[SET_BRUSH], 0, brush);
+}
+
+
+/************************************************************
+ * LOCAL FUNCTION DEFINITIONS *
+ ************************************************************/
+
+static gdouble
+gimp_brush_core_get_angle (GimpBrushCore *core)
+{
+ gdouble angle = core->angle;
+
+ if (core->reflect)
+ angle -= core->symmetry_angle;
+ else
+ angle += core->symmetry_angle;
+
+ angle = fmod (angle, 1.0);
+
+ if (angle < 0.0)
+ angle += 1.0;
+
+ return angle;
+}
+
+static gboolean
+gimp_brush_core_get_reflect (GimpBrushCore *core)
+{
+ return core->reflect ^ core->symmetry_reflect;
+}
+
+static const GimpTempBuf *
+gimp_brush_core_transform_mask (GimpBrushCore *core,
+ GimpBrush *brush)
+{
+ const GimpTempBuf *mask;
+
+ if (core->scale <= 0.0)
+ return NULL;
+
+ mask = gimp_brush_transform_mask (brush,
+ core->scale,
+ core->aspect_ratio,
+ gimp_brush_core_get_angle (core),
+ gimp_brush_core_get_reflect (core),
+ core->hardness);
+
+ if (mask == core->transform_brush)
+ return mask;
+
+ core->transform_brush = mask;
+ core->subsample_cache_invalid = TRUE;
+ core->solid_cache_invalid = TRUE;
+
+ return core->transform_brush;
+}
+
+const GimpTempBuf *
+gimp_brush_core_get_brush_mask (GimpBrushCore *core,
+ const GimpCoords *coords,
+ GimpBrushApplicationMode brush_hardness,
+ gdouble dynamic_force)
+{
+ const GimpTempBuf *mask;
+
+ if (dynamic_force <= 0.0)
+ return NULL;
+
+ mask = gimp_brush_core_transform_mask (core, core->brush);
+
+ if (! mask)
+ return NULL;
+
+ switch (brush_hardness)
+ {
+ case GIMP_BRUSH_SOFT:
+ return gimp_brush_core_subsample_mask (core, mask,
+ coords->x,
+ coords->y);
+ break;
+
+ case GIMP_BRUSH_HARD:
+ return gimp_brush_core_solidify_mask (core, mask,
+ coords->x,
+ coords->y);
+ break;
+
+ case GIMP_BRUSH_PRESSURE:
+ return gimp_brush_core_pressurize_mask (core, mask,
+ coords->x,
+ coords->y,
+ dynamic_force);
+ break;
+ }
+
+ g_return_val_if_reached (NULL);
+}
+
+const GimpTempBuf *
+gimp_brush_core_get_brush_pixmap (GimpBrushCore *core)
+{
+ const GimpTempBuf *pixmap;
+
+ if (core->scale <= 0.0)
+ return NULL;
+
+ pixmap = gimp_brush_transform_pixmap (core->brush,
+ core->scale,
+ core->aspect_ratio,
+ gimp_brush_core_get_angle (core),
+ gimp_brush_core_get_reflect (core),
+ core->hardness);
+
+ if (pixmap == core->transform_pixmap)
+ return pixmap;
+
+ core->transform_pixmap = pixmap;
+ core->subsample_cache_invalid = TRUE;
+
+ return core->transform_pixmap;
+}
+
+void
+gimp_brush_core_eval_transform_dynamics (GimpBrushCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords)
+{
+ if (core->main_brush)
+ {
+ gdouble max_side;
+
+ max_side = MAX (gimp_brush_get_width (core->main_brush),
+ gimp_brush_get_height (core->main_brush));
+
+ core->scale = paint_options->brush_size / max_side;
+
+ if (paint_options->brush_lock_to_view &&
+ MAX (coords->xscale, coords->yscale) > 0)
+ {
+ core->scale /= MAX (coords->xscale, coords->yscale);
+
+ /* Cap transform result for brushes or OOM can occur */
+ if ((core->scale * max_side) > GIMP_BRUSH_MAX_SIZE)
+ {
+ core->scale = GIMP_BRUSH_MAX_SIZE / max_side;
+ }
+ }
+ }
+ else
+ core->scale = -1;
+
+ core->aspect_ratio = paint_options->brush_aspect_ratio;
+ core->angle = paint_options->brush_angle;
+ core->reflect = FALSE;
+ core->hardness = paint_options->brush_hardness;
+
+ if (paint_options->brush_lock_to_view)
+ {
+ core->angle += coords->angle;
+ core->reflect = coords->reflect;
+ }
+
+ if (! GIMP_IS_DYNAMICS (core->dynamics))
+ return;
+
+ if (GIMP_BRUSH_CORE_GET_CLASS (core)->handles_dynamic_transforming_brush)
+ {
+ gdouble fade_point = 1.0;
+
+ if (drawable)
+ {
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ GimpPaintCore *paint_core = GIMP_PAINT_CORE (core);
+
+ fade_point = gimp_paint_options_get_fade (paint_options, image,
+ paint_core->pixel_dist);
+ }
+
+ core->scale *= gimp_dynamics_get_linear_value (core->dynamics,
+ GIMP_DYNAMICS_OUTPUT_SIZE,
+ coords,
+ paint_options,
+ fade_point);
+
+ core->angle += gimp_dynamics_get_angular_value (core->dynamics,
+ GIMP_DYNAMICS_OUTPUT_ANGLE,
+ coords,
+ paint_options,
+ fade_point);
+
+ core->hardness *= gimp_dynamics_get_linear_value (core->dynamics,
+ GIMP_DYNAMICS_OUTPUT_HARDNESS,
+ coords,
+ paint_options,
+ fade_point);
+
+ if (gimp_dynamics_is_output_enabled (core->dynamics,
+ GIMP_DYNAMICS_OUTPUT_ASPECT_RATIO))
+ {
+ gdouble dyn_aspect;
+
+ dyn_aspect = gimp_dynamics_get_aspect_value (core->dynamics,
+ GIMP_DYNAMICS_OUTPUT_ASPECT_RATIO,
+ coords,
+ paint_options,
+ fade_point);
+
+ /* Zero aspect ratio is special cased to half of all ar range,
+ * to force dynamics to have any effect. Forcing to full results
+ * in disappearing stamp if applied to maximum.
+ */
+ if (core->aspect_ratio == 0.0)
+ core->aspect_ratio = 10.0 * dyn_aspect;
+ else
+ core->aspect_ratio *= dyn_aspect;
+ }
+ }
+}
+
+void
+gimp_brush_core_eval_transform_symmetry (GimpBrushCore *core,
+ GimpSymmetry *symmetry,
+ gint stroke)
+{
+ g_return_if_fail (GIMP_IS_BRUSH_CORE (core));
+ g_return_if_fail (symmetry == NULL || GIMP_IS_SYMMETRY (symmetry));
+
+ core->symmetry_angle = 0.0;
+ core->symmetry_reflect = FALSE;
+
+ if (symmetry)
+ {
+ gimp_symmetry_get_transform (symmetry,
+ stroke,
+ &core->symmetry_angle,
+ &core->symmetry_reflect);
+
+ core->symmetry_angle /= 360.0;
+ }
+}
+
+void
+gimp_brush_core_color_area_with_pixmap (GimpBrushCore *core,
+ GimpDrawable *drawable,
+ const GimpCoords *coords,
+ GeglBuffer *area,
+ gint area_x,
+ gint area_y,
+ gboolean apply_mask)
+{
+ const GimpTempBuf *pixmap;
+ GeglBuffer *pixmap_buffer;
+ const GimpTempBuf *mask;
+ GeglBuffer *mask_buffer;
+ gint area_width;
+ gint area_height;
+ gint ul_x;
+ gint ul_y;
+ gint offset_x;
+ gint offset_y;
+
+ g_return_if_fail (GIMP_IS_BRUSH (core->brush));
+ g_return_if_fail (gimp_brush_get_pixmap (core->brush) != NULL);
+
+ /* scale the brush */
+ pixmap = gimp_brush_core_get_brush_pixmap (core);
+
+ if (! pixmap)
+ return;
+
+ if (apply_mask)
+ mask = gimp_brush_core_transform_mask (core, core->brush);
+ else
+ mask = NULL;
+
+ /* Calculate upper left corner of brush as in
+ * gimp_paint_core_get_paint_area. Ugly to have to do this here, too.
+ */
+ ul_x = (gint) floor (coords->x) - (gimp_temp_buf_get_width (pixmap) >> 1);
+ ul_y = (gint) floor (coords->y) - (gimp_temp_buf_get_height (pixmap) >> 1);
+
+ /* Not sure why this is necessary, but empirically the code does
+ * not work without it for even-sided brushes. See bug #166622.
+ */
+ if (gimp_temp_buf_get_width (pixmap) % 2 == 0)
+ ul_x += ROUND (coords->x) - floor (coords->x);
+ if (gimp_temp_buf_get_height (pixmap) % 2 == 0)
+ ul_y += ROUND (coords->y) - floor (coords->y);
+
+ offset_x = area_x - ul_x;
+ offset_y = area_y - ul_y;
+
+ area_width = gegl_buffer_get_width (area);
+ area_height = gegl_buffer_get_height (area);
+
+ pixmap_buffer = gimp_temp_buf_create_buffer (pixmap);
+
+ gimp_gegl_buffer_copy (pixmap_buffer,
+ GEGL_RECTANGLE (offset_x, offset_y,
+ area_width, area_height),
+ GEGL_ABYSS_NONE,
+ area,
+ GEGL_RECTANGLE (0, 0,
+ area_width, area_height));
+
+ g_object_unref (pixmap_buffer);
+
+ if (mask)
+ {
+ mask_buffer = gimp_temp_buf_create_buffer (mask);
+
+ gimp_gegl_apply_mask (mask_buffer,
+ GEGL_RECTANGLE (offset_x, offset_y,
+ area_width, area_height),
+ area,
+ GEGL_RECTANGLE (0, 0,
+ area_width, area_height),
+ 1.0);
+
+ g_object_unref (mask_buffer);
+ }
+}
diff --git a/app/paint/gimpbrushcore.h b/app/paint/gimpbrushcore.h
new file mode 100644
index 0000000..3ba4a41
--- /dev/null
+++ b/app/paint/gimpbrushcore.h
@@ -0,0 +1,152 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_BRUSH_CORE_H__
+#define __GIMP_BRUSH_CORE_H__
+
+
+#include "gimppaintcore.h"
+
+
+#define BRUSH_CORE_SUBSAMPLE 4
+#define BRUSH_CORE_SOLID_SUBSAMPLE 2
+#define BRUSH_CORE_JITTER_LUTSIZE 360
+
+
+#define GIMP_TYPE_BRUSH_CORE (gimp_brush_core_get_type ())
+#define GIMP_BRUSH_CORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BRUSH_CORE, GimpBrushCore))
+#define GIMP_BRUSH_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BRUSH_CORE, GimpBrushCoreClass))
+#define GIMP_IS_BRUSH_CORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BRUSH_CORE))
+#define GIMP_IS_BRUSH_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BRUSH_CORE))
+#define GIMP_BRUSH_CORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BRUSH_CORE, GimpBrushCoreClass))
+
+
+typedef struct _GimpBrushCoreClass GimpBrushCoreClass;
+
+struct _GimpBrushCore
+{
+ GimpPaintCore parent_instance;
+
+ GimpBrush *main_brush;
+ GimpBrush *brush;
+ GimpDynamics *dynamics;
+ gdouble spacing;
+ gdouble scale;
+ gdouble aspect_ratio;
+ gdouble angle;
+ gboolean reflect;
+ gdouble hardness;
+
+ gdouble symmetry_angle;
+ gboolean symmetry_reflect;
+
+ /* brush buffers */
+ GimpTempBuf *pressure_brush;
+
+ GimpTempBuf *solid_brushes[BRUSH_CORE_SOLID_SUBSAMPLE][BRUSH_CORE_SOLID_SUBSAMPLE];
+ const GimpTempBuf *last_solid_brush_mask;
+ gboolean solid_cache_invalid;
+
+ const GimpTempBuf *transform_brush;
+ const GimpTempBuf *transform_pixmap;
+
+ GimpTempBuf *subsample_brushes[BRUSH_CORE_SUBSAMPLE + 1][BRUSH_CORE_SUBSAMPLE + 1];
+ const GimpTempBuf *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 _GimpBrushCoreClass
+{
+ GimpPaintCoreClass 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) (GimpBrushCore *core,
+ GimpBrush *brush);
+ void (* set_dynamics) (GimpBrushCore *core,
+ GimpDynamics *brush);
+};
+
+
+GType gimp_brush_core_get_type (void) G_GNUC_CONST;
+
+void gimp_brush_core_set_brush (GimpBrushCore *core,
+ GimpBrush *brush);
+
+void gimp_brush_core_set_dynamics (GimpBrushCore *core,
+ GimpDynamics *dynamics);
+
+void gimp_brush_core_paste_canvas (GimpBrushCore *core,
+ GimpDrawable *drawable,
+ const GimpCoords *coords,
+ gdouble brush_opacity,
+ gdouble image_opacity,
+ GimpLayerMode paint_mode,
+ GimpBrushApplicationMode brush_hardness,
+ gdouble dynamic_hardness,
+ GimpPaintApplicationMode mode);
+void gimp_brush_core_replace_canvas (GimpBrushCore *core,
+ GimpDrawable *drawable,
+ const GimpCoords *coords,
+ gdouble brush_opacity,
+ gdouble image_opacity,
+ GimpBrushApplicationMode brush_hardness,
+ gdouble dynamic_hardness,
+ GimpPaintApplicationMode mode);
+
+void gimp_brush_core_color_area_with_pixmap
+ (GimpBrushCore *core,
+ GimpDrawable *drawable,
+ const GimpCoords *coords,
+ GeglBuffer *area,
+ gint area_x,
+ gint area_y,
+ gboolean apply_mask);
+
+const GimpTempBuf * gimp_brush_core_get_brush_mask
+ (GimpBrushCore *core,
+ const GimpCoords *coords,
+ GimpBrushApplicationMode brush_hardness,
+ gdouble dynamic_hardness);
+const GimpTempBuf * gimp_brush_core_get_brush_pixmap
+ (GimpBrushCore *core);
+
+void gimp_brush_core_eval_transform_dynamics
+ (GimpBrushCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords);
+void gimp_brush_core_eval_transform_symmetry
+ (GimpBrushCore *core,
+ GimpSymmetry *symmetry,
+ gint stroke);
+
+
+#endif /* __GIMP_BRUSH_CORE_H__ */
diff --git a/app/paint/gimpclone.c b/app/paint/gimpclone.c
new file mode 100644
index 0000000..9c807f4
--- /dev/null
+++ b/app/paint/gimpclone.c
@@ -0,0 +1,256 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "paint-types.h"
+
+#include "gegl/gimp-gegl-apply-operation.h"
+#include "gegl/gimp-gegl-loops.h"
+
+#include "core/gimp.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdynamics.h"
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimppattern.h"
+#include "core/gimppickable.h"
+#include "core/gimpsymmetry.h"
+
+#include "gimpclone.h"
+#include "gimpcloneoptions.h"
+
+#include "gimp-intl.h"
+
+
+static gboolean gimp_clone_start (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GError **error);
+
+static void gimp_clone_motion (GimpSourceCore *source_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GeglNode *op,
+ gdouble opacity,
+ GimpPickable *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 gimp_clone_use_source (GimpSourceCore *source_core,
+ GimpSourceOptions *options);
+
+
+G_DEFINE_TYPE (GimpClone, gimp_clone, GIMP_TYPE_SOURCE_CORE)
+
+#define parent_class gimp_clone_parent_class
+
+
+void
+gimp_clone_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback)
+{
+ (* callback) (gimp,
+ GIMP_TYPE_CLONE,
+ GIMP_TYPE_CLONE_OPTIONS,
+ "gimp-clone",
+ _("Clone"),
+ "gimp-tool-clone");
+}
+
+static void
+gimp_clone_class_init (GimpCloneClass *klass)
+{
+ GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
+ GimpSourceCoreClass *source_core_class = GIMP_SOURCE_CORE_CLASS (klass);
+
+ paint_core_class->start = gimp_clone_start;
+
+ source_core_class->use_source = gimp_clone_use_source;
+ source_core_class->motion = gimp_clone_motion;
+}
+
+static void
+gimp_clone_init (GimpClone *clone)
+{
+}
+
+static gboolean
+gimp_clone_start (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GError **error)
+{
+ GimpCloneOptions *options = GIMP_CLONE_OPTIONS (paint_options);
+
+ if (! GIMP_PAINT_CORE_CLASS (parent_class)->start (paint_core, drawable,
+ paint_options, coords,
+ error))
+ {
+ return FALSE;
+ }
+
+ if (options->clone_type == GIMP_CLONE_PATTERN)
+ {
+ if (! gimp_context_get_pattern (GIMP_CONTEXT (options)))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("No patterns available for use with this tool."));
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_clone_motion (GimpSourceCore *source_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GeglNode *op,
+ gdouble opacity,
+ GimpPickable *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)
+{
+ GimpPaintCore *paint_core = GIMP_PAINT_CORE (source_core);
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (source_core);
+ GimpCloneOptions *options = GIMP_CLONE_OPTIONS (paint_options);
+ GimpSourceOptions *source_options = GIMP_SOURCE_OPTIONS (paint_options);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpDynamics *dynamics = brush_core->dynamics;
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ gdouble fade_point;
+ gdouble force;
+
+ if (gimp_source_core_use_source (source_core, source_options))
+ {
+ if (! op)
+ {
+ gimp_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
+ {
+ gimp_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 == GIMP_CLONE_PATTERN)
+ {
+ GimpPattern *pattern = gimp_context_get_pattern (context);
+ GeglBuffer *src_buffer = gimp_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 = gimp_paint_options_get_fade (paint_options, image,
+ paint_core->pixel_dist);
+
+ if (gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_FORCE))
+ force = gimp_dynamics_get_linear_value (dynamics,
+ GIMP_DYNAMICS_OUTPUT_FORCE,
+ coords,
+ paint_options,
+ fade_point);
+ else
+ force = paint_options->brush_force;
+
+ gimp_brush_core_paste_canvas (GIMP_BRUSH_CORE (paint_core), drawable,
+ coords,
+ MIN (opacity, GIMP_OPACITY_OPAQUE),
+ gimp_context_get_opacity (context),
+ gimp_context_get_paint_mode (context),
+ gimp_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 ==
+ GIMP_SOURCE_ALIGN_FIXED ?
+ GIMP_PAINT_INCREMENTAL : GIMP_PAINT_CONSTANT);
+}
+
+static gboolean
+gimp_clone_use_source (GimpSourceCore *source_core,
+ GimpSourceOptions *options)
+{
+ return GIMP_CLONE_OPTIONS (options)->clone_type == GIMP_CLONE_IMAGE;
+}
diff --git a/app/paint/gimpclone.h b/app/paint/gimpclone.h
new file mode 100644
index 0000000..dced05b
--- /dev/null
+++ b/app/paint/gimpclone.h
@@ -0,0 +1,52 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CLONE_H__
+#define __GIMP_CLONE_H__
+
+
+#include "gimpsourcecore.h"
+
+
+#define GIMP_TYPE_CLONE (gimp_clone_get_type ())
+#define GIMP_CLONE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CLONE, GimpClone))
+#define GIMP_CLONE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CLONE, GimpCloneClass))
+#define GIMP_IS_CLONE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CLONE))
+#define GIMP_IS_CLONE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CLONE))
+#define GIMP_CLONE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CLONE, GimpCloneClass))
+
+
+typedef struct _GimpCloneClass GimpCloneClass;
+
+struct _GimpClone
+{
+ GimpSourceCore parent_instance;
+};
+
+struct _GimpCloneClass
+{
+ GimpSourceCoreClass parent_class;
+};
+
+
+void gimp_clone_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback);
+
+GType gimp_clone_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_CLONE_H__ */
diff --git a/app/paint/gimpcloneoptions.c b/app/paint/gimpcloneoptions.c
new file mode 100644
index 0000000..4f398d6
--- /dev/null
+++ b/app/paint/gimpcloneoptions.c
@@ -0,0 +1,114 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "paint-types.h"
+
+#include "gimpcloneoptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_CLONE_TYPE
+};
+
+
+static void gimp_clone_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_clone_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpCloneOptions, gimp_clone_options, GIMP_TYPE_SOURCE_OPTIONS)
+
+#define parent_class gimp_clone_options_parent_class
+
+
+static void
+gimp_clone_options_class_init (GimpCloneOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_clone_options_set_property;
+ object_class->get_property = gimp_clone_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_CLONE_TYPE,
+ "clone-type",
+ _("Source"),
+ NULL,
+ GIMP_TYPE_CLONE_TYPE,
+ GIMP_CLONE_IMAGE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_clone_options_init (GimpCloneOptions *options)
+{
+}
+
+static void
+gimp_clone_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCloneOptions *options = GIMP_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
+gimp_clone_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCloneOptions *options = GIMP_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;
+ }
+}
diff --git a/app/paint/gimpcloneoptions.h b/app/paint/gimpcloneoptions.h
new file mode 100644
index 0000000..b71f217
--- /dev/null
+++ b/app/paint/gimpcloneoptions.h
@@ -0,0 +1,51 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CLONE_OPTIONS_H__
+#define __GIMP_CLONE_OPTIONS_H__
+
+
+#include "gimpsourceoptions.h"
+
+
+#define GIMP_TYPE_CLONE_OPTIONS (gimp_clone_options_get_type ())
+#define GIMP_CLONE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CLONE_OPTIONS, GimpCloneOptions))
+#define GIMP_CLONE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CLONE_OPTIONS, GimpCloneOptionsClass))
+#define GIMP_IS_CLONE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CLONE_OPTIONS))
+#define GIMP_IS_CLONE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CLONE_OPTIONS))
+#define GIMP_CLONE_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CLONE_OPTIONS, GimpCloneOptionsClass))
+
+
+typedef struct _GimpCloneOptionsClass GimpCloneOptionsClass;
+
+struct _GimpCloneOptions
+{
+ GimpSourceOptions parent_instance;
+
+ GimpCloneType clone_type;
+};
+
+struct _GimpCloneOptionsClass
+{
+ GimpSourceOptionsClass parent_class;
+};
+
+
+GType gimp_clone_options_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_CLONE_OPTIONS_H__ */
diff --git a/app/paint/gimpconvolve.c b/app/paint/gimpconvolve.c
new file mode 100644
index 0000000..f2eea47
--- /dev/null
+++ b/app/paint/gimpconvolve.c
@@ -0,0 +1,277 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "paint-types.h"
+
+#include "gegl/gimp-gegl-loops.h"
+
+#include "core/gimp.h"
+#include "core/gimpbrush.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdynamics.h"
+#include "core/gimpimage.h"
+#include "core/gimppickable.h"
+#include "core/gimpsymmetry.h"
+#include "core/gimptempbuf.h"
+
+#include "gimpconvolve.h"
+#include "gimpconvolveoptions.h"
+
+#include "gimp-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 gimp_convolve_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time);
+static void gimp_convolve_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym);
+
+static void gimp_convolve_calculate_matrix (GimpConvolve *convolve,
+ GimpConvolveType type,
+ gint radius_x,
+ gint radius_y,
+ gdouble rate);
+static gdouble gimp_convolve_sum_matrix (const gfloat *matrix);
+
+
+G_DEFINE_TYPE (GimpConvolve, gimp_convolve, GIMP_TYPE_BRUSH_CORE)
+
+
+void
+gimp_convolve_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback)
+{
+ (* callback) (gimp,
+ GIMP_TYPE_CONVOLVE,
+ GIMP_TYPE_CONVOLVE_OPTIONS,
+ "gimp-convolve",
+ _("Convolve"),
+ "gimp-tool-blur");
+}
+
+static void
+gimp_convolve_class_init (GimpConvolveClass *klass)
+{
+ GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
+
+ paint_core_class->paint = gimp_convolve_paint;
+}
+
+static void
+gimp_convolve_init (GimpConvolve *convolve)
+{
+ gint i;
+
+ for (i = 0; i < 9; i++)
+ convolve->matrix[i] = 1.0;
+
+ convolve->matrix_divisor = 9.0;
+}
+
+static void
+gimp_convolve_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time)
+{
+ switch (paint_state)
+ {
+ case GIMP_PAINT_STATE_MOTION:
+ gimp_convolve_motion (paint_core, drawable, paint_options, sym);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+gimp_convolve_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym)
+{
+ GimpConvolve *convolve = GIMP_CONVOLVE (paint_core);
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paint_core);
+ GimpConvolveOptions *options = GIMP_CONVOLVE_OPTIONS (paint_options);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpDynamics *dynamics = GIMP_BRUSH_CORE (paint_core)->dynamics;
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ GeglBuffer *paint_buffer;
+ gint paint_buffer_x;
+ gint paint_buffer_y;
+ GimpTempBuf *temp_buf;
+ GeglBuffer *convolve_buffer;
+ gdouble fade_point;
+ gdouble opacity;
+ gdouble rate;
+ const GimpCoords *coords;
+ gint paint_width, paint_height;
+ gint n_strokes;
+ gint i;
+
+ fade_point = gimp_paint_options_get_fade (paint_options, image,
+ paint_core->pixel_dist);
+
+ coords = gimp_symmetry_get_origin (sym);
+ opacity = gimp_dynamics_get_linear_value (dynamics,
+ GIMP_DYNAMICS_OUTPUT_OPACITY,
+ coords,
+ paint_options,
+ fade_point);
+ if (opacity == 0.0)
+ return;
+
+ gimp_brush_core_eval_transform_dynamics (GIMP_BRUSH_CORE (paint_core),
+ drawable,
+ paint_options,
+ coords);
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; i++)
+ {
+ coords = gimp_symmetry_get_coords (sym, i);
+
+ gimp_brush_core_eval_transform_symmetry (brush_core, sym, i);
+
+ paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
+ paint_options,
+ GIMP_LAYER_MODE_NORMAL,
+ coords,
+ &paint_buffer_x,
+ &paint_buffer_y,
+ &paint_width,
+ &paint_height);
+ if (! paint_buffer)
+ continue;
+
+ rate = (options->rate *
+ gimp_dynamics_get_linear_value (dynamics,
+ GIMP_DYNAMICS_OUTPUT_RATE,
+ coords,
+ paint_options,
+ fade_point));
+
+ gimp_convolve_calculate_matrix (convolve, options->type,
+ gimp_brush_get_width (brush_core->brush) / 2,
+ gimp_brush_get_height (brush_core->brush) / 2,
+ rate);
+
+ /* need a linear buffer for gimp_gegl_convolve() */
+ temp_buf = gimp_temp_buf_new (gegl_buffer_get_width (paint_buffer),
+ gegl_buffer_get_height (paint_buffer),
+ gegl_buffer_get_format (paint_buffer));
+ convolve_buffer = gimp_temp_buf_create_buffer (temp_buf);
+ gimp_temp_buf_unref (temp_buf);
+
+ gimp_gegl_buffer_copy (
+ gimp_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));
+
+ gimp_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,
+ GIMP_NORMAL_CONVOL, TRUE);
+
+ g_object_unref (convolve_buffer);
+
+ gimp_brush_core_replace_canvas (brush_core, drawable,
+ coords,
+ MIN (opacity, GIMP_OPACITY_OPAQUE),
+ gimp_context_get_opacity (context),
+ gimp_paint_options_get_brush_mode (paint_options),
+ 1.0,
+ GIMP_PAINT_INCREMENTAL);
+ }
+}
+
+static void
+gimp_convolve_calculate_matrix (GimpConvolve *convolve,
+ GimpConvolveType 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 GIMP_CONVOLVE_BLUR:
+ convolve->matrix[4] = MIN_BLUR + percent * (MAX_BLUR - MIN_BLUR);
+ break;
+
+ case GIMP_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 = gimp_convolve_sum_matrix (convolve->matrix);
+}
+
+static gdouble
+gimp_convolve_sum_matrix (const gfloat *matrix)
+{
+ gdouble sum = 0.0;
+ gint i;
+
+ for (i = 0; i < 9; i++)
+ sum += matrix[i];
+
+ return sum;
+}
diff --git a/app/paint/gimpconvolve.h b/app/paint/gimpconvolve.h
new file mode 100644
index 0000000..1aecf03
--- /dev/null
+++ b/app/paint/gimpconvolve.h
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CONVOLVE_H__
+#define __GIMP_CONVOLVE_H__
+
+
+#include "gimpbrushcore.h"
+
+
+#define GIMP_TYPE_CONVOLVE (gimp_convolve_get_type ())
+#define GIMP_CONVOLVE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONVOLVE, GimpConvolve))
+#define GIMP_CONVOLVE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONVOLVE, GimpConvolveClass))
+#define GIMP_IS_CONVOLVE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONVOLVE))
+#define GIMP_IS_CONVOLVE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONVOLVE))
+#define GIMP_CONVOLVE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONVOLVE, GimpConvolveClass))
+
+
+typedef struct _GimpConvolveClass GimpConvolveClass;
+
+struct _GimpConvolve
+{
+ GimpBrushCore parent_instance;
+ gfloat matrix[9];
+ gfloat matrix_divisor;
+};
+
+struct _GimpConvolveClass
+{
+ GimpBrushCoreClass parent_class;
+};
+
+
+void gimp_convolve_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback);
+
+GType gimp_convolve_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_CONVOLVE_H__ */
diff --git a/app/paint/gimpconvolveoptions.c b/app/paint/gimpconvolveoptions.c
new file mode 100644
index 0000000..dffec68
--- /dev/null
+++ b/app/paint/gimpconvolveoptions.c
@@ -0,0 +1,129 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "paint-types.h"
+
+#include "gimpconvolveoptions.h"
+
+#include "gimp-intl.h"
+
+
+#define DEFAULT_CONVOLVE_TYPE GIMP_CONVOLVE_BLUR
+#define DEFAULT_CONVOLVE_RATE 50.0
+
+
+enum
+{
+ PROP_0,
+ PROP_TYPE,
+ PROP_RATE
+};
+
+
+static void gimp_convolve_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_convolve_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpConvolveOptions, gimp_convolve_options,
+ GIMP_TYPE_PAINT_OPTIONS)
+
+
+static void
+gimp_convolve_options_class_init (GimpConvolveOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_convolve_options_set_property;
+ object_class->get_property = gimp_convolve_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_TYPE,
+ "type",
+ _("Convolve Type"),
+ NULL,
+ GIMP_TYPE_CONVOLVE_TYPE,
+ DEFAULT_CONVOLVE_TYPE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_RATE,
+ "rate",
+ C_("convolve-tool", "Rate"),
+ NULL,
+ 0.0, 100.0, DEFAULT_CONVOLVE_RATE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_convolve_options_init (GimpConvolveOptions *options)
+{
+}
+
+static void
+gimp_convolve_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpConvolveOptions *options = GIMP_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
+gimp_convolve_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpConvolveOptions *options = GIMP_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;
+ }
+}
diff --git a/app/paint/gimpconvolveoptions.h b/app/paint/gimpconvolveoptions.h
new file mode 100644
index 0000000..62bd99b
--- /dev/null
+++ b/app/paint/gimpconvolveoptions.h
@@ -0,0 +1,52 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_CONVOLVE_OPTIONS_H__
+#define __GIMP_CONVOLVE_OPTIONS_H__
+
+
+#include "gimppaintoptions.h"
+
+
+#define GIMP_TYPE_CONVOLVE_OPTIONS (gimp_convolve_options_get_type ())
+#define GIMP_CONVOLVE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_CONVOLVE_OPTIONS, GimpConvolveOptions))
+#define GIMP_CONVOLVE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_CONVOLVE_OPTIONS, GimpConvolveOptionsClass))
+#define GIMP_IS_CONVOLVE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_CONVOLVE_OPTIONS))
+#define GIMP_IS_CONVOLVE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_CONVOLVE_OPTIONS))
+#define GIMP_CONVOLVE_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_CONVOLVE_OPTIONS, GimpConvolveOptionsClass))
+
+
+typedef struct _GimpConvolveOptionsClass GimpConvolveOptionsClass;
+
+struct _GimpConvolveOptions
+{
+ GimpPaintOptions parent_instance;
+
+ GimpConvolveType type;
+ gdouble rate;
+};
+
+struct _GimpConvolveOptionsClass
+{
+ GimpPaintOptionsClass parent_class;
+};
+
+
+GType gimp_convolve_options_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_CONVOLVE_OPTIONS_H__ */
diff --git a/app/paint/gimpdodgeburn.c b/app/paint/gimpdodgeburn.c
new file mode 100644
index 0000000..cdfc176
--- /dev/null
+++ b/app/paint/gimpdodgeburn.c
@@ -0,0 +1,201 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "paint-types.h"
+
+#include "gegl/gimp-gegl-loops.h"
+
+#include "core/gimp.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdynamics.h"
+#include "core/gimpimage.h"
+#include "core/gimpsymmetry.h"
+
+#include "gimpdodgeburn.h"
+#include "gimpdodgeburnoptions.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_dodge_burn_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time);
+static void gimp_dodge_burn_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym);
+
+
+G_DEFINE_TYPE (GimpDodgeBurn, gimp_dodge_burn, GIMP_TYPE_BRUSH_CORE)
+
+#define parent_class gimp_dodge_burn_parent_class
+
+
+void
+gimp_dodge_burn_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback)
+{
+ (* callback) (gimp,
+ GIMP_TYPE_DODGE_BURN,
+ GIMP_TYPE_DODGE_BURN_OPTIONS,
+ "gimp-dodge-burn",
+ _("Dodge/Burn"),
+ "gimp-tool-dodge");
+}
+
+static void
+gimp_dodge_burn_class_init (GimpDodgeBurnClass *klass)
+{
+ GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
+ GimpBrushCoreClass *brush_core_class = GIMP_BRUSH_CORE_CLASS (klass);
+
+ paint_core_class->paint = gimp_dodge_burn_paint;
+
+ brush_core_class->handles_changing_brush = TRUE;
+}
+
+static void
+gimp_dodge_burn_init (GimpDodgeBurn *dodgeburn)
+{
+}
+
+static void
+gimp_dodge_burn_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time)
+{
+ switch (paint_state)
+ {
+ case GIMP_PAINT_STATE_INIT:
+ break;
+
+ case GIMP_PAINT_STATE_MOTION:
+ gimp_dodge_burn_motion (paint_core, drawable, paint_options, sym);
+ break;
+
+ case GIMP_PAINT_STATE_FINISH:
+ break;
+ }
+}
+
+static void
+gimp_dodge_burn_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym)
+{
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paint_core);
+ GimpDodgeBurnOptions *options = GIMP_DODGE_BURN_OPTIONS (paint_options);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpDynamics *dynamics = GIMP_BRUSH_CORE (paint_core)->dynamics;
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ GeglBuffer *src_buffer;
+ GeglBuffer *paint_buffer;
+ gint paint_buffer_x;
+ gint paint_buffer_y;
+ gdouble fade_point;
+ gdouble opacity;
+ gdouble force;
+ const GimpCoords *coords;
+ gint paint_width, paint_height;
+ gint n_strokes;
+ gint i;
+
+ fade_point = gimp_paint_options_get_fade (paint_options, image,
+ paint_core->pixel_dist);
+
+ coords = gimp_symmetry_get_origin (sym);
+ opacity = gimp_dynamics_get_linear_value (dynamics,
+ GIMP_DYNAMICS_OUTPUT_OPACITY,
+ coords,
+ paint_options,
+ fade_point);
+ if (opacity == 0.0)
+ return;
+
+ if (paint_options->application_mode == GIMP_PAINT_CONSTANT)
+ src_buffer = gimp_paint_core_get_orig_image (paint_core);
+ else
+ src_buffer = gimp_drawable_get_buffer (drawable);
+
+ gimp_brush_core_eval_transform_dynamics (brush_core,
+ drawable,
+ paint_options,
+ coords);
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; i++)
+ {
+ coords = gimp_symmetry_get_coords (sym, i);
+
+ gimp_brush_core_eval_transform_symmetry (brush_core, sym, i);
+
+ paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
+ paint_options,
+ GIMP_LAYER_MODE_NORMAL,
+ coords,
+ &paint_buffer_x,
+ &paint_buffer_y,
+ &paint_width,
+ &paint_height);
+ if (! paint_buffer)
+ continue;
+
+ /* DodgeBurn the region */
+ gimp_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 (gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_FORCE))
+ force = gimp_dynamics_get_linear_value (dynamics,
+ GIMP_DYNAMICS_OUTPUT_FORCE,
+ coords,
+ paint_options,
+ fade_point);
+ else
+ force = paint_options->brush_force;
+
+ /* Replace the newly dodgedburned area (paint_area) to the image */
+ gimp_brush_core_replace_canvas (GIMP_BRUSH_CORE (paint_core), drawable,
+ coords,
+ MIN (opacity, GIMP_OPACITY_OPAQUE),
+ gimp_context_get_opacity (context),
+ gimp_paint_options_get_brush_mode (paint_options),
+ force,
+ paint_options->application_mode);
+ }
+}
diff --git a/app/paint/gimpdodgeburn.h b/app/paint/gimpdodgeburn.h
new file mode 100644
index 0000000..859887b
--- /dev/null
+++ b/app/paint/gimpdodgeburn.h
@@ -0,0 +1,51 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DODGE_BURN_H__
+#define __GIMP_DODGE_BURN_H__
+
+
+#include "gimpbrushcore.h"
+
+
+#define GIMP_TYPE_DODGE_BURN (gimp_dodge_burn_get_type ())
+#define GIMP_DODGE_BURN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DODGE_BURN, GimpDodgeBurn))
+#define GIMP_IS_DODGE_BURN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DODGE_BURN))
+#define GIMP_DODGE_BURN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DODGEBURN, GimpDodgeBurnClass))
+#define GIMP_IS_DODGE_BURN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DODGE_BURN))
+
+
+typedef struct _GimpDodgeBurnClass GimpDodgeBurnClass;
+
+struct _GimpDodgeBurn
+{
+ GimpBrushCore parent_instance;
+};
+
+struct _GimpDodgeBurnClass
+{
+ GimpBrushCoreClass parent_class;
+};
+
+
+void gimp_dodge_burn_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback);
+
+GType gimp_dodge_burn_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_DODGE_BURN_H__ */
diff --git a/app/paint/gimpdodgeburnoptions.c b/app/paint/gimpdodgeburnoptions.c
new file mode 100644
index 0000000..b897b7e
--- /dev/null
+++ b/app/paint/gimpdodgeburnoptions.c
@@ -0,0 +1,145 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "paint-types.h"
+
+#include "gimpdodgeburnoptions.h"
+
+#include "gimp-intl.h"
+
+
+#define DODGE_BURN_DEFAULT_TYPE GIMP_DODGE_BURN_TYPE_DODGE
+#define DODGE_BURN_DEFAULT_MODE GIMP_TRANSFER_MIDTONES
+#define DODGE_BURN_DEFAULT_EXPOSURE 50.0
+
+
+enum
+{
+ PROP_0,
+ PROP_TYPE,
+ PROP_MODE,
+ PROP_EXPOSURE
+};
+
+
+static void gimp_dodge_burn_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_dodge_burn_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpDodgeBurnOptions, gimp_dodge_burn_options,
+ GIMP_TYPE_PAINT_OPTIONS)
+
+
+static void
+gimp_dodge_burn_options_class_init (GimpDodgeBurnOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_dodge_burn_options_set_property;
+ object_class->get_property = gimp_dodge_burn_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_TYPE,
+ "type",
+ _("Type"),
+ NULL,
+ GIMP_TYPE_DODGE_BURN_TYPE,
+ DODGE_BURN_DEFAULT_TYPE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_MODE,
+ "mode",
+ _("Range"),
+ NULL,
+ GIMP_TYPE_TRANSFER_MODE,
+ DODGE_BURN_DEFAULT_MODE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_EXPOSURE,
+ "exposure",
+ _("Exposure"),
+ NULL,
+ 0.0, 100.0, DODGE_BURN_DEFAULT_EXPOSURE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_dodge_burn_options_init (GimpDodgeBurnOptions *options)
+{
+}
+
+static void
+gimp_dodge_burn_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDodgeBurnOptions *options = GIMP_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
+gimp_dodge_burn_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpDodgeBurnOptions *options = GIMP_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;
+ }
+}
diff --git a/app/paint/gimpdodgeburnoptions.h b/app/paint/gimpdodgeburnoptions.h
new file mode 100644
index 0000000..02eed8a
--- /dev/null
+++ b/app/paint/gimpdodgeburnoptions.h
@@ -0,0 +1,53 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_DODGE_BURN_OPTIONS_H__
+#define __GIMP_DODGE_BURN_OPTIONS_H__
+
+
+#include "gimppaintoptions.h"
+
+
+#define GIMP_TYPE_DODGE_BURN_OPTIONS (gimp_dodge_burn_options_get_type ())
+#define GIMP_DODGE_BURN_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_DODGE_BURN_OPTIONS, GimpDodgeBurnOptions))
+#define GIMP_DODGE_BURN_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_DODGE_BURN_OPTIONS, GimpDodgeBurnOptionsClass))
+#define GIMP_IS_DODGE_BURN_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_DODGE_BURN_OPTIONS))
+#define GIMP_IS_DODGE_BURN_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_DODGE_BURN_OPTIONS))
+#define GIMP_DODGE_BURN_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_DODGE_BURN_OPTIONS, GimpDodgeBurnOptionsClass))
+
+
+typedef struct _GimpDodgeBurnOptionsClass GimpDodgeBurnOptionsClass;
+
+struct _GimpDodgeBurnOptions
+{
+ GimpPaintOptions parent_instance;
+
+ GimpDodgeBurnType type;
+ GimpTransferMode mode; /*highlights, midtones, shadows*/
+ gdouble exposure;
+};
+
+struct _GimpDodgeBurnOptionsClass
+{
+ GimpPaintOptionsClass parent_class;
+};
+
+
+GType gimp_dodge_burn_options_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_DODGE_BURN_OPTIONS_H__ */
diff --git a/app/paint/gimperaser.c b/app/paint/gimperaser.c
new file mode 100644
index 0000000..68a535e
--- /dev/null
+++ b/app/paint/gimperaser.c
@@ -0,0 +1,130 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "paint-types.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimp.h"
+#include "core/gimp-palettes.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdynamics.h"
+#include "core/gimpimage.h"
+#include "core/gimppickable.h"
+#include "core/gimpsymmetry.h"
+
+#include "gimperaser.h"
+#include "gimperaseroptions.h"
+
+#include "gimp-intl.h"
+
+
+static gboolean gimp_eraser_get_color_history_color (GimpPaintbrush *paintbrush,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpRGB *color);
+static void gimp_eraser_get_paint_params (GimpPaintbrush *paintbrush,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ gdouble grad_point,
+ GimpLayerMode *paint_mode,
+ GimpPaintApplicationMode *paint_appl_mode,
+ const GimpTempBuf **paint_pixmap,
+ GimpRGB *paint_color);
+
+
+G_DEFINE_TYPE (GimpEraser, gimp_eraser, GIMP_TYPE_PAINTBRUSH)
+
+
+void
+gimp_eraser_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback)
+{
+ (* callback) (gimp,
+ GIMP_TYPE_ERASER,
+ GIMP_TYPE_ERASER_OPTIONS,
+ "gimp-eraser",
+ _("Eraser"),
+ "gimp-tool-eraser");
+}
+
+static void
+gimp_eraser_class_init (GimpEraserClass *klass)
+{
+ GimpPaintbrushClass *paintbrush_class = GIMP_PAINTBRUSH_CLASS (klass);
+
+ paintbrush_class->get_color_history_color = gimp_eraser_get_color_history_color;
+ paintbrush_class->get_paint_params = gimp_eraser_get_paint_params;
+}
+
+static void
+gimp_eraser_init (GimpEraser *eraser)
+{
+}
+
+static gboolean
+gimp_eraser_get_color_history_color (GimpPaintbrush *paintbrush,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpRGB *color)
+{
+ /* Erasing on a drawable without alpha is equivalent to
+ * drawing with background color. So let's save history.
+ */
+ if (! gimp_drawable_has_alpha (drawable))
+ {
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+
+ gimp_context_get_background (context, color);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_eraser_get_paint_params (GimpPaintbrush *paintbrush,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ gdouble grad_point,
+ GimpLayerMode *paint_mode,
+ GimpPaintApplicationMode *paint_appl_mode,
+ const GimpTempBuf **paint_pixmap,
+ GimpRGB *paint_color)
+{
+ GimpEraserOptions *options = GIMP_ERASER_OPTIONS (paint_options);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+
+ gimp_context_get_background (context, paint_color);
+ gimp_pickable_srgb_to_image_color (GIMP_PICKABLE (drawable),
+ paint_color, paint_color);
+
+ if (options->anti_erase)
+ *paint_mode = GIMP_LAYER_MODE_ANTI_ERASE;
+ else if (gimp_drawable_has_alpha (drawable))
+ *paint_mode = GIMP_LAYER_MODE_ERASE;
+ else
+ *paint_mode = GIMP_LAYER_MODE_NORMAL_LEGACY;
+}
diff --git a/app/paint/gimperaser.h b/app/paint/gimperaser.h
new file mode 100644
index 0000000..5b04a32
--- /dev/null
+++ b/app/paint/gimperaser.h
@@ -0,0 +1,52 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ERASER_H__
+#define __GIMP_ERASER_H__
+
+
+#include "gimppaintbrush.h"
+
+
+#define GIMP_TYPE_ERASER (gimp_eraser_get_type ())
+#define GIMP_ERASER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ERASER, GimpEraser))
+#define GIMP_ERASER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ERASER, GimpEraserClass))
+#define GIMP_IS_ERASER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ERASER))
+#define GIMP_IS_ERASER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ERASER))
+#define GIMP_ERASER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ERASER, GimpEraserClass))
+
+
+typedef struct _GimpEraserClass GimpEraserClass;
+
+struct _GimpEraser
+{
+ GimpPaintbrush parent_instance;
+};
+
+struct _GimpEraserClass
+{
+ GimpPaintbrushClass parent_class;
+};
+
+
+void gimp_eraser_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback);
+
+GType gimp_eraser_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_ERASER_H__ */
diff --git a/app/paint/gimperaseroptions.c b/app/paint/gimperaseroptions.c
new file mode 100644
index 0000000..0b5a3fc
--- /dev/null
+++ b/app/paint/gimperaseroptions.c
@@ -0,0 +1,113 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "paint-types.h"
+
+#include "gimperaseroptions.h"
+
+#include "gimp-intl.h"
+
+
+#define ERASER_DEFAULT_ANTI_ERASE FALSE
+
+
+enum
+{
+ PROP_0,
+ PROP_ANTI_ERASE
+};
+
+
+static void gimp_eraser_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_eraser_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpEraserOptions, gimp_eraser_options,
+ GIMP_TYPE_PAINT_OPTIONS)
+
+
+static void
+gimp_eraser_options_class_init (GimpEraserOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_eraser_options_set_property;
+ object_class->get_property = gimp_eraser_options_get_property;
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_ANTI_ERASE,
+ "anti-erase",
+ _("Anti erase"),
+ NULL,
+ ERASER_DEFAULT_ANTI_ERASE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_eraser_options_init (GimpEraserOptions *options)
+{
+}
+
+static void
+gimp_eraser_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpEraserOptions *options = GIMP_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
+gimp_eraser_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpEraserOptions *options = GIMP_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;
+ }
+}
diff --git a/app/paint/gimperaseroptions.h b/app/paint/gimperaseroptions.h
new file mode 100644
index 0000000..3551d11
--- /dev/null
+++ b/app/paint/gimperaseroptions.h
@@ -0,0 +1,51 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_ERASER_OPTIONS_H__
+#define __GIMP_ERASER_OPTIONS_H__
+
+
+#include "gimppaintoptions.h"
+
+
+#define GIMP_TYPE_ERASER_OPTIONS (gimp_eraser_options_get_type ())
+#define GIMP_ERASER_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ERASER_OPTIONS, GimpEraserOptions))
+#define GIMP_ERASER_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ERASER_OPTIONS, GimpEraserOptionsClass))
+#define GIMP_IS_ERASER_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ERASER_OPTIONS))
+#define GIMP_IS_ERASER_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ERASER_OPTIONS))
+#define GIMP_ERASER_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ERASER_OPTIONS, GimpEraserOptionsClass))
+
+
+typedef struct _GimpEraserOptionsClass GimpEraserOptionsClass;
+
+struct _GimpEraserOptions
+{
+ GimpPaintOptions parent_instance;
+
+ gboolean anti_erase;
+};
+
+struct _GimpEraserOptionsClass
+{
+ GimpPaintOptionsClass parent_class;
+};
+
+
+GType gimp_eraser_options_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_ERASER_OPTIONS_H__ */
diff --git a/app/paint/gimpheal.c b/app/paint/gimpheal.c
new file mode 100644
index 0000000..814fd26
--- /dev/null
+++ b/app/paint/gimpheal.c
@@ -0,0 +1,642 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpheal.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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "paint-types.h"
+
+#include "gegl/gimp-gegl-apply-operation.h"
+#include "gegl/gimp-gegl-loops.h"
+
+#include "core/gimpbrush.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdynamics.h"
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimppickable.h"
+#include "core/gimptempbuf.h"
+
+#include "gimpheal.h"
+#include "gimpsourceoptions.h"
+
+#include "gimp-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 gimp_heal_start (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GError **error);
+static GeglBuffer * gimp_heal_get_paint_buffer (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpLayerMode paint_mode,
+ const GimpCoords *coords,
+ gint *paint_buffer_x,
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height);
+
+static void gimp_heal_motion (GimpSourceCore *source_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GeglNode *op,
+ gdouble opacity,
+ GimpPickable *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 (GimpHeal, gimp_heal, GIMP_TYPE_SOURCE_CORE)
+
+#define parent_class gimp_heal_parent_class
+
+
+void
+gimp_heal_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback)
+{
+ (* callback) (gimp,
+ GIMP_TYPE_HEAL,
+ GIMP_TYPE_SOURCE_OPTIONS,
+ "gimp-heal",
+ _("Healing"),
+ "gimp-tool-heal");
+}
+
+static void
+gimp_heal_class_init (GimpHealClass *klass)
+{
+ GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
+ GimpSourceCoreClass *source_core_class = GIMP_SOURCE_CORE_CLASS (klass);
+
+ paint_core_class->start = gimp_heal_start;
+ paint_core_class->get_paint_buffer = gimp_heal_get_paint_buffer;
+
+ source_core_class->motion = gimp_heal_motion;
+}
+
+static void
+gimp_heal_init (GimpHeal *heal)
+{
+}
+
+static gboolean
+gimp_heal_start (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GError **error)
+{
+ GimpSourceCore *source_core = GIMP_SOURCE_CORE (paint_core);
+
+ if (! GIMP_PAINT_CORE_CLASS (parent_class)->start (paint_core, drawable,
+ paint_options, coords,
+ error))
+ {
+ return FALSE;
+ }
+
+ if (! source_core->set_source && gimp_drawable_is_indexed (drawable))
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Healing does not operate on indexed layers."));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static GeglBuffer *
+gimp_heal_get_paint_buffer (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpLayerMode paint_mode,
+ const GimpCoords *coords,
+ gint *paint_buffer_x,
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height)
+{
+ return GIMP_PAINT_CORE_CLASS (parent_class)->get_paint_buffer (core,
+ drawable,
+ paint_options,
+ GIMP_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
+gimp_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
+gimp_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
+gimp_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
+gimp_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 gimp_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
+gimp_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 = gimp_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
+gimp_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 */
+ gimp_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);
+
+ gimp_heal_laplace_loop (diff, height, src_components, width, mask);
+
+ g_free (mask);
+
+ /* add solution to original image and store in dest */
+ gimp_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
+gimp_heal_motion (GimpSourceCore *source_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GeglNode *op,
+ gdouble opacity,
+ GimpPickable *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)
+{
+ GimpPaintCore *paint_core = GIMP_PAINT_CORE (source_core);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpSourceOptions *src_options = GIMP_SOURCE_OPTIONS (paint_options);
+ GimpDynamics *dynamics = GIMP_BRUSH_CORE (paint_core)->dynamics;
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ GeglBuffer *src_copy;
+ GeglBuffer *mask_buffer;
+ GimpPickable *dest_pickable;
+ const GimpTempBuf *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 = gimp_paint_options_get_fade (paint_options, image,
+ paint_core->pixel_dist);
+
+ if (gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_FORCE))
+ force = gimp_dynamics_get_linear_value (dynamics,
+ GIMP_DYNAMICS_OUTPUT_FORCE,
+ coords,
+ paint_options,
+ fade_point);
+ else
+ force = paint_options->brush_force;
+
+ mask_buf = gimp_brush_core_get_brush_mask (GIMP_BRUSH_CORE (source_core),
+ coords,
+ GIMP_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)
+ {
+ gimp_gegl_buffer_copy (src_buffer, src_rect, GEGL_ABYSS_NONE,
+ src_copy, gegl_buffer_get_extent (src_copy));
+ }
+ else
+ {
+ gimp_gegl_apply_operation (src_buffer, NULL, NULL, op,
+ src_copy, gegl_buffer_get_extent (src_copy),
+ FALSE);
+ }
+
+ if (src_options->sample_merged)
+ {
+ dest_pickable = GIMP_PICKABLE (image);
+
+ gimp_item_get_offset (GIMP_ITEM (drawable),
+ &dest_pickable_off_x,
+ &dest_pickable_off_y);
+ }
+ else
+ {
+ dest_pickable = GIMP_PICKABLE (drawable);
+
+ dest_pickable_off_x = 0;
+ dest_pickable_off_y = 0;
+ }
+
+ gimp_gegl_buffer_copy (gimp_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 = gimp_temp_buf_create_buffer ((GimpTempBuf *) 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;
+ }
+
+ gimp_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 */
+ gimp_brush_core_replace_canvas (GIMP_BRUSH_CORE (paint_core), drawable,
+ coords,
+ MIN (opacity, GIMP_OPACITY_OPAQUE),
+ gimp_context_get_opacity (context),
+ gimp_paint_options_get_brush_mode (paint_options),
+ force,
+ GIMP_PAINT_INCREMENTAL);
+}
diff --git a/app/paint/gimpheal.h b/app/paint/gimpheal.h
new file mode 100644
index 0000000..780609d
--- /dev/null
+++ b/app/paint/gimpheal.h
@@ -0,0 +1,52 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_HEAL_H__
+#define __GIMP_HEAL_H__
+
+
+#include "gimpsourcecore.h"
+
+
+#define GIMP_TYPE_HEAL (gimp_heal_get_type ())
+#define GIMP_HEAL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_HEAL, GimpHeal))
+#define GIMP_HEAL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_HEAL, GimpHealClass))
+#define GIMP_IS_HEAL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_HEAL))
+#define GIMP_IS_HEAL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_HEAL))
+#define GIMP_HEAL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_HEAL, GimpHealClass))
+
+
+typedef struct _GimpHealClass GimpHealClass;
+
+struct _GimpHeal
+{
+ GimpSourceCore parent_instance;
+};
+
+struct _GimpHealClass
+{
+ GimpSourceCoreClass parent_class;
+};
+
+
+void gimp_heal_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback);
+
+GType gimp_heal_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_HEAL_H__ */
diff --git a/app/paint/gimpink-blob.c b/app/paint/gimpink-blob.c
new file mode 100644
index 0000000..68a135a
--- /dev/null
+++ b/app/paint/gimpink-blob.c
@@ -0,0 +1,875 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpink-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 "libgimpmath/gimpmath.h"
+
+#include "paint-types.h"
+
+#include "gimpink-blob.h"
+
+
+typedef enum
+{
+ EDGE_NONE = 0,
+ EDGE_LEFT = 1 << 0,
+ EDGE_RIGHT = 1 << 1
+} EdgeType;
+
+
+/* local function prototypes */
+
+static GimpBlob * gimp_blob_new (gint y,
+ gint height);
+static void gimp_blob_fill (GimpBlob *b,
+ EdgeType *present);
+static void gimp_blob_make_convex (GimpBlob *b,
+ EdgeType *present);
+
+#if 0
+static void gimp_blob_line_add_pixel (GimpBlob *b,
+ gint x,
+ gint y);
+static void gimp_blob_line (GimpBlob *b,
+ gint x0,
+ gint y0,
+ gint x1,
+ gint y1);
+#endif
+
+
+/* public functions */
+
+/* Return blob for the given (convex) polygon
+ */
+GimpBlob *
+gimp_blob_polygon (GimpBlobPoint *points,
+ gint n_points)
+{
+ GimpBlob *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 = gimp_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;
+ }
+
+ gimp_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
+ */
+GimpBlob *
+gimp_blob_square (gdouble xc,
+ gdouble yc,
+ gdouble xp,
+ gdouble yp,
+ gdouble xq,
+ gdouble yq)
+{
+ GimpBlobPoint 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 gimp_blob_polygon (points, 4);
+}
+
+/* Scan convert a diamond specified by _offsets_ of major and minor
+ * axes, and by center into a blob
+ */
+GimpBlob *
+gimp_blob_diamond (gdouble xc,
+ gdouble yc,
+ gdouble xp,
+ gdouble yp,
+ gdouble xq,
+ gdouble yq)
+{
+ GimpBlobPoint 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 gimp_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
+ */
+GimpBlob *
+gimp_blob_ellipse (gdouble xc,
+ gdouble yc,
+ gdouble xp,
+ gdouble yp,
+ gdouble xq,
+ gdouble yq)
+{
+ GimpBlob *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 = gimp_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 */
+
+ gimp_blob_fill (result, present);
+ g_free (present);
+
+ return result;
+}
+
+void
+gimp_blob_bounds (GimpBlob *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;
+}
+
+GimpBlob *
+gimp_blob_convex_union (GimpBlob *b1,
+ GimpBlob *b2)
+{
+ GimpBlob *result;
+ gint y;
+ gint i, j;
+ EdgeType *present;
+
+ /* Create the storage for the result */
+
+ y = MIN (b1->y, b2->y);
+ result = gimp_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;
+ }
+ }
+ }
+
+ gimp_blob_make_convex (result, present);
+
+ g_free (present);
+
+ return result;
+}
+
+GimpBlob *
+gimp_blob_duplicate (GimpBlob *b)
+{
+ g_return_val_if_fail (b != NULL, NULL);
+
+ return g_memdup (b, sizeof (GimpBlob) + sizeof (GimpBlobSpan) * (b->height - 1));
+}
+
+#if 0
+void
+gimp_blob_dump (GimpBlob *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 GimpBlob *
+gimp_blob_new (gint y,
+ gint height)
+{
+ GimpBlob *result;
+
+ result = g_malloc (sizeof (GimpBlob) + sizeof (GimpBlobSpan) * (height - 1));
+
+ result->y = y;
+ result->height = height;
+
+ return result;
+}
+
+static void
+gimp_blob_fill (GimpBlob *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
+gimp_blob_make_convex (GimpBlob *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;
+ }
+
+ gimp_blob_fill (b, present);
+}
+
+
+#if 0
+
+static void
+gimp_blob_line_add_pixel (GimpBlob *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
+gimp_blob_line (GimpBlob *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 */
+
+ gimp_blob_line_add_pixel (b, x, y);
+
+ while (x != x1)
+ {
+ if (d <= 0)
+ {
+ d += incrE;
+ x += xstep;
+ }
+ else
+ {
+ d += incrNE;
+ x += xstep;
+ y += ystep;
+ }
+
+ gimp_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 */
+
+ gimp_blob_line_add_pixel (b, x, y);
+
+ while (y != y1)
+ {
+ if (d <= 0)
+ {
+ d += incrE;
+ y += ystep;
+ }
+ else
+ {
+ d += incrNE;
+ x += xstep;
+ y += ystep;
+ }
+
+ gimp_blob_line_add_pixel (b, x, y);
+ }
+ }
+}
+
+#endif
diff --git a/app/paint/gimpink-blob.h b/app/paint/gimpink-blob.h
new file mode 100644
index 0000000..2ddbf76
--- /dev/null
+++ b/app/paint/gimpink-blob.h
@@ -0,0 +1,87 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-1999 Spencer Kimball and Peter Mattis
+ *
+ * gimpink-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 __GIMP_INK_BLOB_H__
+#define __GIMP_INK_BLOB_H__
+
+
+typedef struct _GimpBlobPoint GimpBlobPoint;
+typedef struct _GimpBlobSpan GimpBlobSpan;
+typedef struct _GimpBlob GimpBlob;
+
+typedef GimpBlob * (* GimpBlobFunc) (gdouble xc,
+ gdouble yc,
+ gdouble xp,
+ gdouble yp,
+ gdouble xq,
+ gdouble yq);
+
+struct _GimpBlobPoint
+{
+ gint x;
+ gint y;
+};
+
+struct _GimpBlobSpan
+{
+ gint left;
+ gint right;
+};
+
+struct _GimpBlob
+{
+ gint y;
+ gint height;
+ GimpBlobSpan data[1];
+};
+
+
+GimpBlob * gimp_blob_polygon (GimpBlobPoint *points,
+ gint n_points);
+GimpBlob * gimp_blob_square (gdouble xc,
+ gdouble yc,
+ gdouble xp,
+ gdouble yp,
+ gdouble xq,
+ gdouble yq);
+GimpBlob * gimp_blob_diamond (gdouble xc,
+ gdouble yc,
+ gdouble xp,
+ gdouble yp,
+ gdouble xq,
+ gdouble yq);
+GimpBlob * gimp_blob_ellipse (gdouble xc,
+ gdouble yc,
+ gdouble xp,
+ gdouble yp,
+ gdouble xq,
+ gdouble yq);
+void gimp_blob_bounds (GimpBlob *b,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height);
+GimpBlob * gimp_blob_convex_union (GimpBlob *b1,
+ GimpBlob *b2);
+GimpBlob * gimp_blob_duplicate (GimpBlob *b);
+
+
+#endif /* __GIMP_INK_BLOB_H__ */
diff --git a/app/paint/gimpink.c b/app/paint/gimpink.c
new file mode 100644
index 0000000..50f72b8
--- /dev/null
+++ b/app/paint/gimpink.c
@@ -0,0 +1,785 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "paint-types.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimp-palettes.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-undo.h"
+#include "core/gimppickable.h"
+#include "core/gimpsymmetry.h"
+#include "core/gimptempbuf.h"
+
+#include "gimpinkoptions.h"
+#include "gimpink.h"
+#include "gimpink-blob.h"
+#include "gimpinkundo.h"
+
+#include "gimp-intl.h"
+
+
+#define SUBSAMPLE 8
+
+
+/* local function prototypes */
+
+static void gimp_ink_finalize (GObject *object);
+
+static void gimp_ink_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time);
+static GeglBuffer * gimp_ink_get_paint_buffer (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpLayerMode paint_mode,
+ const GimpCoords *coords,
+ gint *paint_buffer_x,
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height);
+static GimpUndo * gimp_ink_push_undo (GimpPaintCore *core,
+ GimpImage *image,
+ const gchar *undo_desc);
+
+static void gimp_ink_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ guint32 time);
+
+static GimpBlob * ink_pen_ellipse (GimpInkOptions *options,
+ gdouble x_center,
+ gdouble y_center,
+ gdouble pressure,
+ gdouble xtilt,
+ gdouble ytilt,
+ gdouble velocity,
+ const GimpMatrix3 *transform);
+
+static void render_blob (GeglBuffer *buffer,
+ GeglRectangle *rect,
+ GimpBlob *blob);
+
+
+G_DEFINE_TYPE (GimpInk, gimp_ink, GIMP_TYPE_PAINT_CORE)
+
+#define parent_class gimp_ink_parent_class
+
+
+void
+gimp_ink_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback)
+{
+ (* callback) (gimp,
+ GIMP_TYPE_INK,
+ GIMP_TYPE_INK_OPTIONS,
+ "gimp-ink",
+ _("Ink"),
+ "gimp-tool-ink");
+}
+
+static void
+gimp_ink_class_init (GimpInkClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
+
+ object_class->finalize = gimp_ink_finalize;
+
+ paint_core_class->paint = gimp_ink_paint;
+ paint_core_class->get_paint_buffer = gimp_ink_get_paint_buffer;
+ paint_core_class->push_undo = gimp_ink_push_undo;
+}
+
+static void
+gimp_ink_init (GimpInk *ink)
+{
+}
+
+static void
+gimp_ink_finalize (GObject *object)
+{
+ GimpInk *ink = GIMP_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
+gimp_ink_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time)
+{
+ GimpInk *ink = GIMP_INK (paint_core);
+ GimpCoords *cur_coords;
+ GimpCoords last_coords;
+
+ gimp_paint_core_get_last_coords (paint_core, &last_coords);
+ cur_coords = gimp_symmetry_get_origin (sym);
+
+ switch (paint_state)
+ {
+ case GIMP_PAINT_STATE_INIT:
+ {
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpRGB foreground;
+
+ gimp_symmetry_set_stateful (sym, TRUE);
+ gimp_context_get_foreground (context, &foreground);
+ gimp_palettes_add_color_history (context->gimp,
+ &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)
+ {
+ GimpBlob *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,
+ gimp_blob_duplicate (last_blob));
+ }
+ ink->start_blobs = g_list_reverse (ink->start_blobs);
+ }
+ }
+ break;
+
+ case GIMP_PAINT_STATE_MOTION:
+ gimp_ink_motion (paint_core, drawable, paint_options, sym, time);
+ break;
+
+ case GIMP_PAINT_STATE_FINISH:
+ gimp_symmetry_set_stateful (sym, FALSE);
+ break;
+ }
+}
+
+static GeglBuffer *
+gimp_ink_get_paint_buffer (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpLayerMode paint_mode,
+ const GimpCoords *coords,
+ gint *paint_buffer_x,
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height)
+{
+ GimpInk *ink = GIMP_INK (paint_core);
+ gint x, y;
+ gint width, height;
+ gint dwidth, dheight;
+ gint x1, y1, x2, y2;
+
+ gimp_blob_bounds (ink->cur_blob, &x, &y, &width, &height);
+
+ dwidth = gimp_item_get_width (GIMP_ITEM (drawable));
+ dheight = gimp_item_get_height (GIMP_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))
+ {
+ GimpTempBuf *temp_buf;
+ const Babl *format;
+ GimpLayerCompositeMode composite_mode;
+
+ composite_mode = gimp_layer_mode_get_paint_composite_mode (paint_mode);
+
+ format = gimp_layer_mode_get_format (paint_mode,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ composite_mode,
+ gimp_drawable_get_format (drawable));
+
+ temp_buf = gimp_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 = gimp_temp_buf_create_buffer (temp_buf);
+
+ gimp_temp_buf_unref (temp_buf);
+
+ return paint_core->paint_buffer;
+ }
+
+ return NULL;
+}
+
+static GimpUndo *
+gimp_ink_push_undo (GimpPaintCore *core,
+ GimpImage *image,
+ const gchar *undo_desc)
+{
+ return gimp_image_undo_push (image, GIMP_TYPE_INK_UNDO,
+ GIMP_UNDO_INK, undo_desc,
+ 0,
+ "paint-core", core,
+ NULL);
+}
+
+static void
+gimp_ink_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ guint32 time)
+{
+ GimpInk *ink = GIMP_INK (paint_core);
+ GimpInkOptions *options = GIMP_INK_OPTIONS (paint_options);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GList *blob_unions = NULL;
+ GList *blobs_to_render = NULL;
+ GeglBuffer *paint_buffer;
+ gint paint_buffer_x;
+ gint paint_buffer_y;
+ GimpLayerMode paint_mode;
+ GimpRGB foreground;
+ GeglColor *color;
+ GimpBlob *last_blob;
+ GimpCoords *coords;
+ gint n_strokes;
+ gint i;
+
+ n_strokes = gimp_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++)
+ {
+ GimpMatrix3 transform;
+
+ coords = gimp_symmetry_get_coords (sym, i);
+
+ gimp_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,
+ gimp_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++)
+ {
+ GimpBlob *blob;
+ GimpBlob *blob_union = NULL;
+ GimpMatrix3 transform;
+
+ coords = gimp_symmetry_get_coords (sym, i);
+
+ gimp_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 = gimp_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 = gimp_context_get_paint_mode (context);
+
+ gimp_context_get_foreground (context, &foreground);
+ gimp_pickable_srgb_to_image_color (GIMP_PICKABLE (drawable),
+ &foreground, &foreground);
+ color = gimp_gegl_color_new (&foreground);
+
+ for (i = 0; i < n_strokes; i++)
+ {
+ GimpBlob *blob_to_render = g_list_nth_data (blobs_to_render, i);
+
+ coords = gimp_symmetry_get_coords (sym, i);
+
+ ink->cur_blob = blob_to_render;
+ paint_buffer = gimp_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 */
+ gimp_paint_core_paste (paint_core,
+ NULL,
+ paint_core->paint_buffer_x,
+ paint_core->paint_buffer_y,
+ drawable,
+ GIMP_OPACITY_OPAQUE,
+ gimp_context_get_opacity (context),
+ paint_mode,
+ GIMP_PAINT_CONSTANT);
+
+ }
+
+ g_object_unref (color);
+
+ g_list_free_full (blob_unions, g_free);
+}
+
+static GimpBlob *
+ink_pen_ellipse (GimpInkOptions *options,
+ gdouble x_center,
+ gdouble y_center,
+ gdouble pressure,
+ gdouble xtilt,
+ gdouble ytilt,
+ gdouble velocity,
+ const GimpMatrix3 *transform)
+{
+ GimpBlobFunc 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 (gimp_deg_to_rad (options->tilt_angle));
+ tscale_s = tscale * sin (gimp_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);
+ }
+
+ gimp_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 GIMP_INK_BLOB_TYPE_CIRCLE:
+ blob_function = gimp_blob_ellipse;
+ break;
+
+ case GIMP_INK_BLOB_TYPE_SQUARE:
+ blob_function = gimp_blob_square;
+ break;
+
+ case GIMP_INK_BLOB_TYPE_DIAMOND:
+ blob_function = gimp_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 (GimpBlob *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,
+ GimpBlob *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);
+ }
+ }
+}
diff --git a/app/paint/gimpink.h b/app/paint/gimpink.h
new file mode 100644
index 0000000..ac31592
--- /dev/null
+++ b/app/paint/gimpink.h
@@ -0,0 +1,58 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_INK_H__
+#define __GIMP_INK_H__
+
+
+#include "gimppaintcore.h"
+#include "gimpink-blob.h"
+
+
+#define GIMP_TYPE_INK (gimp_ink_get_type ())
+#define GIMP_INK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_INK, GimpInk))
+#define GIMP_INK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_INK, GimpInkClass))
+#define GIMP_IS_INK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_INK))
+#define GIMP_IS_INK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_INK))
+#define GIMP_INK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_INK, GimpInkClass))
+
+
+typedef struct _GimpInkClass GimpInkClass;
+
+struct _GimpInk
+{
+ GimpPaintCore parent_instance;
+
+ GList *start_blobs; /* starting blobs per stroke (for undo) */
+
+ GimpBlob *cur_blob; /* current blob */
+ GList *last_blobs; /* blobs for last stroke positions */
+};
+
+struct _GimpInkClass
+{
+ GimpPaintCoreClass parent_class;
+};
+
+
+void gimp_ink_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback);
+
+GType gimp_ink_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_INK_H__ */
diff --git a/app/paint/gimpinkoptions.c b/app/paint/gimpinkoptions.c
new file mode 100644
index 0000000..d660179
--- /dev/null
+++ b/app/paint/gimpinkoptions.c
@@ -0,0 +1,208 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "paint-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpdrawable.h"
+#include "core/gimppaintinfo.h"
+
+#include "gimpinkoptions.h"
+#include "gimpink-blob.h"
+
+#include "gimp-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 gimp_ink_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_ink_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpInkOptions, gimp_ink_options, GIMP_TYPE_PAINT_OPTIONS)
+
+
+static void
+gimp_ink_options_class_init (GimpInkOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_ink_options_set_property;
+ object_class->get_property = gimp_ink_options_get_property;
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_SIZE,
+ "size",
+ _("Size"),
+ _("Ink Blob Size"),
+ 0.0, 200.0, 16.0,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_TILT_ANGLE,
+ "tilt-angle",
+ _("Angle"),
+ NULL,
+ -90.0, 90.0, 0.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_SIZE_SENSITIVITY,
+ "size-sensitivity",
+ _("Size"),
+ NULL,
+ 0.0, 1.0, 1.0,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_VEL_SENSITIVITY,
+ "vel-sensitivity",
+ _("Speed"),
+ NULL,
+ 0.0, 1.0, 0.8,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_TILT_SENSITIVITY,
+ "tilt-sensitivity",
+ _("Tilt"),
+ NULL,
+ 0.0, 1.0, 0.4,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_BLOB_TYPE,
+ "blob-type",
+ _("Shape"),
+ NULL,
+ GIMP_TYPE_INK_BLOB_TYPE,
+ GIMP_INK_BLOB_TYPE_CIRCLE,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_BLOB_ASPECT,
+ "blob-aspect",
+ _("Aspect ratio"),
+ _("Ink Blob Aspect Ratio"),
+ 1.0, 10.0, 1.0,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_BLOB_ANGLE,
+ "blob-angle",
+ _("Angle"),
+ _("Ink Blob Angle"),
+ -G_PI, G_PI, 0.0,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_ink_options_init (GimpInkOptions *options)
+{
+}
+
+static void
+gimp_ink_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpInkOptions *options = GIMP_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
+gimp_ink_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpInkOptions *options = GIMP_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;
+ }
+}
diff --git a/app/paint/gimpinkoptions.h b/app/paint/gimpinkoptions.h
new file mode 100644
index 0000000..7776b60
--- /dev/null
+++ b/app/paint/gimpinkoptions.h
@@ -0,0 +1,60 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_INK_OPTIONS_H__
+#define __GIMP_INK_OPTIONS_H__
+
+
+#include "gimppaintoptions.h"
+
+
+#define GIMP_TYPE_INK_OPTIONS (gimp_ink_options_get_type ())
+#define GIMP_INK_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_INK_OPTIONS, GimpInkOptions))
+#define GIMP_INK_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_INK_OPTIONS, GimpInkOptionsClass))
+#define GIMP_IS_INK_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_INK_OPTIONS))
+#define GIMP_IS_INK_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_INK_OPTIONS))
+#define GIMP_INK_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_INK_OPTIONS, GimpInkOptionsClass))
+
+
+typedef struct _GimpInkOptionsClass GimpInkOptionsClass;
+
+struct _GimpInkOptions
+{
+ GimpPaintOptions parent_instance;
+
+ gdouble size;
+ gdouble tilt_angle;
+
+ gdouble size_sensitivity;
+ gdouble vel_sensitivity;
+ gdouble tilt_sensitivity;
+
+ GimpInkBlobType blob_type;
+ gdouble blob_aspect;
+ gdouble blob_angle;
+};
+
+struct _GimpInkOptionsClass
+{
+ GimpPaintOptionsClass parent_instance;
+};
+
+
+GType gimp_ink_options_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_INK_OPTIONS_H__ */
diff --git a/app/paint/gimpinkundo.c b/app/paint/gimpinkundo.c
new file mode 100644
index 0000000..a0b604b
--- /dev/null
+++ b/app/paint/gimpinkundo.c
@@ -0,0 +1,125 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "paint-types.h"
+
+#include "gimpink.h"
+#include "gimpink-blob.h"
+#include "gimpinkundo.h"
+
+
+static void gimp_ink_undo_constructed (GObject *object);
+
+static void gimp_ink_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+static void gimp_ink_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_TYPE (GimpInkUndo, gimp_ink_undo, GIMP_TYPE_PAINT_CORE_UNDO)
+
+#define parent_class gimp_ink_undo_parent_class
+
+
+static void
+gimp_ink_undo_class_init (GimpInkUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_ink_undo_constructed;
+
+ undo_class->pop = gimp_ink_undo_pop;
+ undo_class->free = gimp_ink_undo_free;
+}
+
+static void
+gimp_ink_undo_init (GimpInkUndo *undo)
+{
+ undo->last_blobs = NULL;
+}
+
+static void
+gimp_ink_undo_constructed (GObject *object)
+{
+ GimpInkUndo *ink_undo = GIMP_INK_UNDO (object);
+ GimpInk *ink;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_INK (GIMP_PAINT_CORE_UNDO (ink_undo)->paint_core));
+
+ ink = GIMP_INK (GIMP_PAINT_CORE_UNDO (ink_undo)->paint_core);
+
+ if (ink->start_blobs)
+ {
+ gint i;
+ GimpBlob *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,
+ gimp_blob_duplicate (blob));
+ }
+ ink_undo->last_blobs = g_list_reverse (ink_undo->last_blobs);
+ }
+}
+
+static void
+gimp_ink_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpInkUndo *ink_undo = GIMP_INK_UNDO (undo);
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ if (GIMP_PAINT_CORE_UNDO (ink_undo)->paint_core)
+ {
+ GimpInk *ink = GIMP_INK (GIMP_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
+gimp_ink_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpInkUndo *ink_undo = GIMP_INK_UNDO (undo);
+
+ if (ink_undo->last_blobs)
+ {
+ g_list_free_full (ink_undo->last_blobs, g_free);
+ ink_undo->last_blobs = NULL;
+ }
+
+ GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
+}
diff --git a/app/paint/gimpinkundo.h b/app/paint/gimpinkundo.h
new file mode 100644
index 0000000..59abf02
--- /dev/null
+++ b/app/paint/gimpinkundo.h
@@ -0,0 +1,52 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_INK_UNDO_H__
+#define __GIMP_INK_UNDO_H__
+
+
+#include "gimppaintcoreundo.h"
+
+
+#define GIMP_TYPE_INK_UNDO (gimp_ink_undo_get_type ())
+#define GIMP_INK_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_INK_UNDO, GimpInkUndo))
+#define GIMP_INK_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_INK_UNDO, GimpInkUndoClass))
+#define GIMP_IS_INK_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_INK_UNDO))
+#define GIMP_IS_INK_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_INK_UNDO))
+#define GIMP_INK_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_INK_UNDO, GimpInkUndoClass))
+
+
+typedef struct _GimpInkUndo GimpInkUndo;
+typedef struct _GimpInkUndoClass GimpInkUndoClass;
+
+struct _GimpInkUndo
+{
+ GimpPaintCoreUndo parent_instance;
+
+ GList *last_blobs;
+};
+
+struct _GimpInkUndoClass
+{
+ GimpPaintCoreUndoClass parent_class;
+};
+
+
+GType gimp_ink_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_INK_UNDO_H__ */
diff --git a/app/paint/gimpmybrushcore.c b/app/paint/gimpmybrushcore.c
new file mode 100644
index 0000000..9e96821
--- /dev/null
+++ b/app/paint/gimpmybrushcore.c
@@ -0,0 +1,421 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include <mypaint-brush.h>
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "paint-types.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimp.h"
+#include "core/gimp-palettes.h"
+#include "core/gimpdrawable.h"
+#include "core/gimperror.h"
+#include "core/gimpmybrush.h"
+#include "core/gimppickable.h"
+#include "core/gimpsymmetry.h"
+
+#include "gimpmybrushcore.h"
+#include "gimpmybrushsurface.h"
+#include "gimpmybrushoptions.h"
+
+#include "gimp-intl.h"
+
+
+struct _GimpMybrushCorePrivate
+{
+ GimpMybrush *mybrush;
+ GimpMybrushSurface *surface;
+ GList *brushes;
+ gboolean synthetic;
+ gint64 last_time;
+};
+
+
+/* local function prototypes */
+
+static void gimp_mybrush_core_finalize (GObject *object);
+
+static gboolean gimp_mybrush_core_start (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GError **error);
+static void gimp_mybrush_core_interpolate (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ guint32 time);
+static void gimp_mybrush_core_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time);
+static void gimp_mybrush_core_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ guint32 time);
+static void gimp_mybrush_core_create_brushes (GimpMybrushCore *mybrush,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpMybrushCore, gimp_mybrush_core,
+ GIMP_TYPE_PAINT_CORE)
+
+#define parent_class gimp_mybrush_core_parent_class
+
+
+void
+gimp_mybrush_core_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback)
+{
+ (* callback) (gimp,
+ GIMP_TYPE_MYBRUSH_CORE,
+ GIMP_TYPE_MYBRUSH_OPTIONS,
+ "gimp-mybrush",
+ _("Mybrush"),
+ "gimp-tool-mypaint-brush");
+}
+
+static void
+gimp_mybrush_core_class_init (GimpMybrushCoreClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
+
+ object_class->finalize = gimp_mybrush_core_finalize;
+
+ paint_core_class->start = gimp_mybrush_core_start;
+ paint_core_class->paint = gimp_mybrush_core_paint;
+ paint_core_class->interpolate = gimp_mybrush_core_interpolate;
+}
+
+static void
+gimp_mybrush_core_init (GimpMybrushCore *mybrush)
+{
+ mybrush->private = gimp_mybrush_core_get_instance_private (mybrush);
+}
+
+static void
+gimp_mybrush_core_finalize (GObject *object)
+{
+ GimpMybrushCore *core = GIMP_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
+gimp_mybrush_core_start (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GError **error)
+{
+ GimpMybrushCore *core = GIMP_MYBRUSH_CORE (paint_core);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+
+ core->private->mybrush = gimp_context_get_mybrush (context);
+
+ if (! core->private->mybrush)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("No MyPaint brushes available for use with this tool."));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_mybrush_core_interpolate (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ guint32 time)
+{
+ GimpMybrushCore *mybrush = GIMP_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)
+ {
+ GimpCoords saved_coords = paint_core->cur_coords;
+
+ paint_core->cur_coords = paint_core->last_coords;
+
+ mybrush->private->synthetic = TRUE;
+
+ gimp_paint_core_paint (paint_core, drawable, paint_options,
+ GIMP_PAINT_STATE_MOTION, time);
+
+ paint_core->cur_coords = saved_coords;
+ }
+
+ gimp_paint_core_paint (paint_core, drawable, paint_options,
+ GIMP_PAINT_STATE_MOTION, time);
+
+ paint_core->last_coords = paint_core->cur_coords;
+}
+
+static void
+gimp_mybrush_core_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time)
+{
+ GimpMybrushCore *mybrush = GIMP_MYBRUSH_CORE (paint_core);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpRGB fg;
+
+ switch (paint_state)
+ {
+ case GIMP_PAINT_STATE_INIT:
+ gimp_context_get_foreground (context, &fg);
+ gimp_palettes_add_color_history (context->gimp, &fg);
+ gimp_symmetry_set_stateful (sym, TRUE);
+
+ mybrush->private->surface =
+ gimp_mypaint_surface_new (gimp_drawable_get_buffer (drawable),
+ gimp_drawable_get_active_mask (drawable),
+ paint_core->mask_buffer,
+ paint_core->mask_x_offset,
+ paint_core->mask_y_offset,
+ GIMP_MYBRUSH_OPTIONS (paint_options));
+
+ gimp_mybrush_core_create_brushes (mybrush, drawable, paint_options, sym);
+
+ mybrush->private->last_time = -1;
+ mybrush->private->synthetic = FALSE;
+ break;
+
+ case GIMP_PAINT_STATE_MOTION:
+ gimp_mybrush_core_motion (paint_core, drawable, paint_options,
+ sym, time);
+ break;
+
+ case GIMP_PAINT_STATE_FINISH:
+ gimp_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
+gimp_mybrush_core_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ guint32 time)
+{
+ GimpMybrushCore *mybrush = GIMP_MYBRUSH_CORE (paint_core);
+ MyPaintRectangle rect;
+ GList *iter;
+ gdouble dt = 0.0;
+ gint n_strokes;
+ gint i;
+
+ n_strokes = gimp_symmetry_get_size (sym);
+
+ /* 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)
+ {
+ gimp_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;
+ GimpCoords *coords = gimp_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)
+ {
+ GimpVector2 v = { paint_core->cur_coords.x - paint_core->last_coords.x,
+ paint_core->cur_coords.y - paint_core->last_coords.y };
+
+ dt = 0.0005 * gimp_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;
+ GimpCoords *coords = gimp_symmetry_get_coords (sym, i);
+ gdouble pressure = coords->pressure;
+
+ /* libmypaint expects non-extended devices to default to 0.5 pressure */
+ if (! coords->extended)
+ pressure = 0.5f;
+
+ 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);
+
+ gimp_drawable_update (drawable, rect.x, rect.y, rect.width, rect.height);
+ }
+}
+
+static void
+gimp_mybrush_core_create_brushes (GimpMybrushCore *mybrush,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym)
+{
+ GimpMybrushOptions *options = GIMP_MYBRUSH_OPTIONS (paint_options);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpRGB fg;
+ GimpHSV 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)
+ gimp_context_get_background (context, &fg);
+ else
+ gimp_context_get_foreground (context, &fg);
+
+ gimp_pickable_srgb_to_image_color (GIMP_PICKABLE (drawable),
+ &fg, &fg);
+ gimp_rgb_to_hsv (&fg, &hsv);
+
+ n_strokes = gimp_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 = gimp_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 *
+ gimp_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 &&
+ gimp_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);
+}
diff --git a/app/paint/gimpmybrushcore.h b/app/paint/gimpmybrushcore.h
new file mode 100644
index 0000000..9dadfb8
--- /dev/null
+++ b/app/paint/gimpmybrushcore.h
@@ -0,0 +1,55 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_MYBRUSH_CORE_H__
+#define __GIMP_MYBRUSH_CORE_H__
+
+
+#include "gimppaintcore.h"
+
+
+#define GIMP_TYPE_MYBRUSH_CORE (gimp_mybrush_core_get_type ())
+#define GIMP_MYBRUSH_CORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MYBRUSH_CORE, GimpMybrushCore))
+#define GIMP_MYBRUSH_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MYBRUSH_CORE, GimpMybrushCoreClass))
+#define GIMP_IS_MYBRUSH_CORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MYBRUSH_CORE))
+#define GIMP_IS_MYBRUSH_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MYBRUSH_CORE))
+#define GIMP_MYBRUSH_CORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MYBRUSH_CORE, GimpMybrushCoreClass))
+
+
+typedef struct _GimpMybrushCorePrivate GimpMybrushCorePrivate;
+typedef struct _GimpMybrushCoreClass GimpMybrushCoreClass;
+
+struct _GimpMybrushCore
+{
+ GimpPaintCore parent_instance;
+
+ GimpMybrushCorePrivate *private;
+};
+
+struct _GimpMybrushCoreClass
+{
+ GimpPaintCoreClass parent_class;
+};
+
+
+void gimp_mybrush_core_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback);
+
+GType gimp_mybrush_core_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_MYBRUSH_CORE_H__ */
diff --git a/app/paint/gimpmybrushoptions.c b/app/paint/gimpmybrushoptions.c
new file mode 100644
index 0000000..40fa517
--- /dev/null
+++ b/app/paint/gimpmybrushoptions.c
@@ -0,0 +1,219 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "paint-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpmybrush.h"
+#include "core/gimppaintinfo.h"
+
+#include "gimpmybrushoptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_RADIUS,
+ PROP_OPAQUE,
+ PROP_HARDNESS,
+ PROP_ERASER,
+ PROP_NO_ERASING
+};
+
+
+static void gimp_mybrush_options_config_iface_init (GimpConfigInterface *config_iface);
+
+static void gimp_mybrush_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_mybrush_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_mybrush_options_mybrush_changed (GimpContext *context,
+ GimpMybrush *brush);
+
+static void gimp_mybrush_options_reset (GimpConfig *config);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpMybrushOptions, gimp_mybrush_options,
+ GIMP_TYPE_PAINT_OPTIONS,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_mybrush_options_config_iface_init))
+
+static GimpConfigInterface *parent_config_iface = NULL;
+
+
+static void
+gimp_mybrush_options_class_init (GimpMybrushOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpContextClass *context_class = GIMP_CONTEXT_CLASS (klass);
+
+ object_class->set_property = gimp_mybrush_options_set_property;
+ object_class->get_property = gimp_mybrush_options_get_property;
+
+ context_class->mybrush_changed = gimp_mybrush_options_mybrush_changed;
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_RADIUS,
+ "radius",
+ _("Radius"),
+ NULL,
+ -2.0, 6.0, 1.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_OPAQUE,
+ "opaque",
+ _("Base Opacity"),
+ NULL,
+ 0.0, 2.0, 1.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_HARDNESS,
+ "hardness",
+ _("Hardness"),
+ NULL,
+ 0.0, 1.0, 1.0,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_ERASER,
+ "eraser",
+ _("Erase with this brush"),
+ NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_NO_ERASING,
+ "no-erasing",
+ _("No erasing effect"),
+ _("Never decrease alpha of existing pixels"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_mybrush_options_config_iface_init (GimpConfigInterface *config_iface)
+{
+ parent_config_iface = g_type_interface_peek_parent (config_iface);
+
+ config_iface->reset = gimp_mybrush_options_reset;
+}
+
+static void
+gimp_mybrush_options_init (GimpMybrushOptions *options)
+{
+}
+
+static void
+gimp_mybrush_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpMybrushOptions *options = GIMP_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
+gimp_mybrush_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpMybrushOptions *options = GIMP_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
+gimp_mybrush_options_mybrush_changed (GimpContext *context,
+ GimpMybrush *brush)
+{
+ if (brush)
+ g_object_set (context,
+ "radius", gimp_mybrush_get_radius (brush),
+ "opaque", gimp_mybrush_get_opaque (brush),
+ "hardness", gimp_mybrush_get_hardness (brush),
+ "eraser", gimp_mybrush_get_is_eraser (brush),
+ NULL);
+}
+
+static void
+gimp_mybrush_options_reset (GimpConfig *config)
+{
+ GimpContext *context = GIMP_CONTEXT (config);
+ GimpMybrush *brush = gimp_context_get_mybrush (context);
+
+ parent_config_iface->reset (config);
+
+ gimp_mybrush_options_mybrush_changed (context, brush);
+}
diff --git a/app/paint/gimpmybrushoptions.h b/app/paint/gimpmybrushoptions.h
new file mode 100644
index 0000000..b1b5cd5
--- /dev/null
+++ b/app/paint/gimpmybrushoptions.h
@@ -0,0 +1,55 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_MYBRUSH_OPTIONS_H__
+#define __GIMP_MYBRUSH_OPTIONS_H__
+
+
+#include "gimppaintoptions.h"
+
+
+#define GIMP_TYPE_MYBRUSH_OPTIONS (gimp_mybrush_options_get_type ())
+#define GIMP_MYBRUSH_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_MYBRUSH_OPTIONS, GimpMybrushOptions))
+#define GIMP_MYBRUSH_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_MYBRUSH_OPTIONS, GimpMybrushOptionsClass))
+#define GIMP_IS_MYBRUSH_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_MYBRUSH_OPTIONS))
+#define GIMP_IS_MYBRUSH_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_MYBRUSH_OPTIONS))
+#define GIMP_MYBRUSH_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_MYBRUSH_OPTIONS, GimpMybrushOptionsClass))
+
+
+typedef struct _GimpMybrushOptionsClass GimpMybrushOptionsClass;
+
+struct _GimpMybrushOptions
+{
+ GimpPaintOptions parent_instance;
+
+ gdouble radius;
+ gdouble opaque;
+ gdouble hardness;
+ gboolean eraser;
+ gboolean no_erasing;
+};
+
+struct _GimpMybrushOptionsClass
+{
+ GimpPaintOptionsClass parent_instance;
+};
+
+
+GType gimp_mybrush_options_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_MYBRUSH_OPTIONS_H__ */
diff --git a/app/paint/gimpmybrushsurface.c b/app/paint/gimpmybrushsurface.c
new file mode 100644
index 0000000..9b4283c
--- /dev/null
+++ b/app/paint/gimpmybrushsurface.c
@@ -0,0 +1,560 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include <gegl.h>
+
+#include <mypaint-surface.h>
+
+#include "paint-types.h"
+
+#include "libgimpmath/gimpmath.h"
+
+#include <cairo.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include "libgimpcolor/gimpcolor.h"
+
+#include "gimpmybrushoptions.h"
+#include "gimpmybrushsurface.h"
+
+
+struct _GimpMybrushSurface
+{
+ MyPaintSurface surface;
+ GeglBuffer *buffer;
+ GeglBuffer *paint_mask;
+ gint paint_mask_x;
+ gint paint_mask_y;
+ GeglRectangle dirty;
+ GimpComponentMask component_mask;
+ GimpMybrushOptions *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
+gimp_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)
+{
+ GimpMybrushSurface *surface = (GimpMybrushSurface *)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
+gimp_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)
+{
+ GimpMybrushSurface *surface = (GimpMybrushSurface *)base_surface;
+ GeglBufferIterator *iter;
+ GeglRectangle dabRect;
+ GimpComponentMask 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)
+ {
+ GimpHSL pixel_hsl, out_hsl;
+ GimpRGB pixel_rgb = {color_r, color_g, color_b};
+ GimpRGB out_rgb = {r, g, b};
+ float src_term = alpha / a;
+ float dst_term = 1.0f - src_term;
+
+ gimp_rgb_to_hsl (&pixel_rgb, &pixel_hsl);
+ gimp_rgb_to_hsl (&out_rgb, &out_hsl);
+
+ out_hsl.h = pixel_hsl.h;
+ out_hsl.s = pixel_hsl.s;
+ gimp_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 != GIMP_COMPONENT_MASK_ALL)
+ {
+ if (component_mask & GIMP_COMPONENT_MASK_RED)
+ pixel[RED] = r;
+ if (component_mask & GIMP_COMPONENT_MASK_GREEN)
+ pixel[GREEN] = g;
+ if (component_mask & GIMP_COMPONENT_MASK_BLUE)
+ pixel[BLUE] = b;
+ if (component_mask & GIMP_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
+gimp_mypaint_surface_begin_atomic (MyPaintSurface *base_surface)
+{
+
+}
+
+static void
+gimp_mypaint_surface_end_atomic (MyPaintSurface *base_surface,
+ MyPaintRectangle *roi)
+{
+ GimpMybrushSurface *surface = (GimpMybrushSurface *)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
+gimp_mypaint_surface_destroy (MyPaintSurface *base_surface)
+{
+ GimpMybrushSurface *surface = (GimpMybrushSurface *)base_surface;
+
+ g_clear_object (&surface->buffer);
+ g_clear_object (&surface->paint_mask);
+}
+
+GimpMybrushSurface *
+gimp_mypaint_surface_new (GeglBuffer *buffer,
+ GimpComponentMask component_mask,
+ GeglBuffer *paint_mask,
+ gint paint_mask_x,
+ gint paint_mask_y,
+ GimpMybrushOptions *options)
+{
+ GimpMybrushSurface *surface = g_malloc0 (sizeof (GimpMybrushSurface));
+
+ mypaint_surface_init ((MyPaintSurface *)surface);
+
+ surface->surface.get_color = gimp_mypaint_surface_get_color;
+ surface->surface.draw_dab = gimp_mypaint_surface_draw_dab;
+ surface->surface.begin_atomic = gimp_mypaint_surface_begin_atomic;
+ surface->surface.end_atomic = gimp_mypaint_surface_end_atomic;
+ surface->surface.destroy = gimp_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;
+}
diff --git a/app/paint/gimpmybrushsurface.h b/app/paint/gimpmybrushsurface.h
new file mode 100644
index 0000000..71bc0ba
--- /dev/null
+++ b/app/paint/gimpmybrushsurface.h
@@ -0,0 +1,33 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_MYBRUSH_SURFACE_H__
+#define __GIMP_MYBRUSH_SURFACE_H__
+
+
+typedef struct _GimpMybrushSurface GimpMybrushSurface;
+
+GimpMybrushSurface *
+gimp_mypaint_surface_new (GeglBuffer *buffer,
+ GimpComponentMask component_mask,
+ GeglBuffer *paint_mask,
+ gint paint_mask_x,
+ gint paint_mask_y,
+ GimpMybrushOptions *options);
+
+
+#endif /* __GIMP_MYBRUSH_SURFACE_H__ */
diff --git a/app/paint/gimppaintbrush.c b/app/paint/gimppaintbrush.c
new file mode 100644
index 0000000..2afd13c
--- /dev/null
+++ b/app/paint/gimppaintbrush.c
@@ -0,0 +1,395 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpbase/gimpbase.h"
+
+#include "paint-types.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimp.h"
+#include "core/gimp-palettes.h"
+#include "core/gimpbrush.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdynamics.h"
+#include "core/gimpgradient.h"
+#include "core/gimpimage.h"
+#include "core/gimppickable.h"
+#include "core/gimpsymmetry.h"
+#include "core/gimptempbuf.h"
+
+#include "gimppaintbrush.h"
+#include "gimppaintoptions.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_paintbrush_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time);
+
+static gboolean gimp_paintbrush_real_get_color_history_color (GimpPaintbrush *paintbrush,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpRGB *color);
+static void gimp_paintbrush_real_get_paint_params (GimpPaintbrush *paintbrush,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ gdouble grad_point,
+ GimpLayerMode *paint_mode,
+ GimpPaintApplicationMode *paint_appl_mode,
+ const GimpTempBuf **paint_pixmap,
+ GimpRGB *paint_color);
+
+
+G_DEFINE_TYPE (GimpPaintbrush, gimp_paintbrush, GIMP_TYPE_BRUSH_CORE)
+
+
+void
+gimp_paintbrush_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback)
+{
+ (* callback) (gimp,
+ GIMP_TYPE_PAINTBRUSH,
+ GIMP_TYPE_PAINT_OPTIONS,
+ "gimp-paintbrush",
+ _("Paintbrush"),
+ "gimp-tool-paintbrush");
+}
+
+static void
+gimp_paintbrush_class_init (GimpPaintbrushClass *klass)
+{
+ GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
+ GimpBrushCoreClass *brush_core_class = GIMP_BRUSH_CORE_CLASS (klass);
+
+ paint_core_class->paint = gimp_paintbrush_paint;
+
+ brush_core_class->handles_changing_brush = TRUE;
+
+ klass->get_color_history_color = gimp_paintbrush_real_get_color_history_color;
+ klass->get_paint_params = gimp_paintbrush_real_get_paint_params;
+}
+
+static void
+gimp_paintbrush_init (GimpPaintbrush *paintbrush)
+{
+}
+
+static void
+gimp_paintbrush_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time)
+{
+ GimpPaintbrush *paintbrush = GIMP_PAINTBRUSH (paint_core);
+
+ switch (paint_state)
+ {
+ case GIMP_PAINT_STATE_INIT:
+ {
+ GimpRGB color;
+
+ if (GIMP_PAINTBRUSH_GET_CLASS (paintbrush)->get_color_history_color &&
+ GIMP_PAINTBRUSH_GET_CLASS (paintbrush)->get_color_history_color (
+ paintbrush, drawable, paint_options, &color))
+ {
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+
+ gimp_palettes_add_color_history (context->gimp, &color);
+ }
+ }
+ break;
+
+ case GIMP_PAINT_STATE_MOTION:
+ _gimp_paintbrush_motion (paint_core, drawable, paint_options,
+ sym, GIMP_OPACITY_OPAQUE);
+ break;
+
+ case GIMP_PAINT_STATE_FINISH:
+ {
+ if (paintbrush->paint_buffer)
+ {
+ g_object_remove_weak_pointer (
+ G_OBJECT (paintbrush->paint_buffer),
+ (gpointer) &paintbrush->paint_buffer);
+
+ paintbrush->paint_buffer = NULL;
+ }
+
+ g_clear_pointer (&paintbrush->paint_pixmap, gimp_temp_buf_unref);
+ }
+ break;
+ }
+}
+
+static gboolean
+gimp_paintbrush_real_get_color_history_color (GimpPaintbrush *paintbrush,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpRGB *color)
+{
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paintbrush);
+ GimpDynamics *dynamics = gimp_context_get_dynamics (context);
+
+ /* We don't save gradient color history and pixmap brushes
+ * have no color to save.
+ */
+ if (gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_COLOR) ||
+ (brush_core->brush && gimp_brush_get_pixmap (brush_core->brush)))
+ {
+ return FALSE;
+ }
+
+ gimp_context_get_foreground (context, color);
+
+ return TRUE;
+}
+
+static void
+gimp_paintbrush_real_get_paint_params (GimpPaintbrush *paintbrush,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ gdouble grad_point,
+ GimpLayerMode *paint_mode,
+ GimpPaintApplicationMode *paint_appl_mode,
+ const GimpTempBuf **paint_pixmap,
+ GimpRGB *paint_color)
+{
+ GimpPaintCore *paint_core = GIMP_PAINT_CORE (paintbrush);
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paintbrush);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ *paint_mode = gimp_context_get_paint_mode (context);
+
+ if (gimp_paint_options_get_gradient_color (paint_options, image,
+ grad_point,
+ paint_core->pixel_dist,
+ paint_color))
+ {
+ /* optionally take the color from the current gradient */
+ gimp_pickable_srgb_to_image_color (GIMP_PICKABLE (drawable),
+ paint_color, paint_color);
+
+ *paint_appl_mode = GIMP_PAINT_INCREMENTAL;
+ }
+ else if (brush_core->brush && gimp_brush_get_pixmap (brush_core->brush))
+ {
+ /* otherwise check if the brush has a pixmap and use that to
+ * color the area
+ */
+ *paint_pixmap = gimp_brush_core_get_brush_pixmap (brush_core);
+
+ *paint_appl_mode = GIMP_PAINT_INCREMENTAL;
+ }
+ else
+ {
+ /* otherwise fill the area with the foreground color */
+ gimp_context_get_foreground (context, paint_color);
+
+ gimp_pickable_srgb_to_image_color (GIMP_PICKABLE (drawable),
+ paint_color, paint_color);
+ }
+}
+
+void
+_gimp_paintbrush_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ gdouble opacity)
+{
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paint_core);
+ GimpPaintbrush *paintbrush = GIMP_PAINTBRUSH (paint_core);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpDynamics *dynamics = brush_core->dynamics;
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ gdouble fade_point;
+ gdouble grad_point;
+ gdouble force;
+ const GimpCoords *coords;
+ gint n_strokes;
+ gint i;
+
+ fade_point = gimp_paint_options_get_fade (paint_options, image,
+ paint_core->pixel_dist);
+
+ coords = gimp_symmetry_get_origin (sym);
+ /* Some settings are based on the original stroke. */
+ opacity *= gimp_dynamics_get_linear_value (dynamics,
+ GIMP_DYNAMICS_OUTPUT_OPACITY,
+ coords,
+ paint_options,
+ fade_point);
+ if (opacity == 0.0)
+ return;
+
+ if (GIMP_BRUSH_CORE_GET_CLASS (brush_core)->handles_transforming_brush)
+ {
+ gimp_brush_core_eval_transform_dynamics (brush_core,
+ drawable,
+ paint_options,
+ coords);
+ }
+
+ grad_point = gimp_dynamics_get_linear_value (dynamics,
+ GIMP_DYNAMICS_OUTPUT_COLOR,
+ coords,
+ paint_options,
+ fade_point);
+
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; i++)
+ {
+ GimpLayerMode paint_mode;
+ GimpPaintApplicationMode paint_appl_mode;
+ GeglBuffer *paint_buffer;
+ gint paint_buffer_x;
+ gint paint_buffer_y;
+ const GimpTempBuf *paint_pixmap = NULL;
+ GimpRGB paint_color;
+ gint paint_width, paint_height;
+
+ paint_appl_mode = paint_options->application_mode;
+
+ GIMP_PAINTBRUSH_GET_CLASS (paintbrush)->get_paint_params (paintbrush,
+ drawable,
+ paint_options,
+ sym,
+ grad_point,
+ &paint_mode,
+ &paint_appl_mode,
+ &paint_pixmap,
+ &paint_color);
+
+ coords = gimp_symmetry_get_coords (sym, i);
+
+ if (GIMP_BRUSH_CORE_GET_CLASS (brush_core)->handles_transforming_brush)
+ gimp_brush_core_eval_transform_symmetry (brush_core, sym, i);
+
+ paint_buffer = gimp_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;
+ gimp_rgb_set_alpha (&paint_color, GIMP_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->applicator ||
+ paint_buffer != paintbrush->paint_buffer ||
+ paint_pixmap != paintbrush->paint_pixmap ||
+ (! paint_pixmap && (gimp_rgba_distance (&paint_color,
+ &paintbrush->paint_color))))
+ {
+ if (paint_buffer != paintbrush->paint_buffer)
+ {
+ if (paintbrush->paint_buffer)
+ {
+ g_object_remove_weak_pointer (
+ G_OBJECT (paintbrush->paint_buffer),
+ (gpointer) &paintbrush->paint_buffer);
+ }
+
+ paintbrush->paint_buffer = paint_buffer;
+
+ g_object_add_weak_pointer (
+ G_OBJECT (paintbrush->paint_buffer),
+ (gpointer) &paintbrush->paint_buffer);
+ }
+
+ if (paint_pixmap != paintbrush->paint_pixmap)
+ {
+ g_clear_pointer (&paintbrush->paint_pixmap, gimp_temp_buf_unref);
+
+ if (paint_pixmap)
+ paintbrush->paint_pixmap = gimp_temp_buf_ref (paint_pixmap);
+ }
+
+ paintbrush->paint_color = paint_color;
+
+ if (paint_pixmap)
+ {
+ gimp_brush_core_color_area_with_pixmap (brush_core, drawable,
+ coords,
+ paint_buffer,
+ paint_buffer_x,
+ paint_buffer_y,
+ FALSE);
+ }
+ else
+ {
+ GeglColor *color;
+
+ color = gimp_gegl_color_new (&paint_color);
+
+ gegl_buffer_set_color (paint_buffer, NULL, color);
+
+ g_object_unref (color);
+ }
+ }
+
+ if (gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_FORCE))
+ force = gimp_dynamics_get_linear_value (dynamics,
+ GIMP_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 */
+ gimp_brush_core_paste_canvas (brush_core, drawable,
+ coords,
+ MIN (opacity, GIMP_OPACITY_OPAQUE),
+ gimp_context_get_opacity (context),
+ paint_mode,
+ gimp_paint_options_get_brush_mode (paint_options),
+ force,
+ paint_appl_mode);
+ }
+}
diff --git a/app/paint/gimppaintbrush.h b/app/paint/gimppaintbrush.h
new file mode 100644
index 0000000..61f8d85
--- /dev/null
+++ b/app/paint/gimppaintbrush.h
@@ -0,0 +1,80 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PAINTBRUSH_H__
+#define __GIMP_PAINTBRUSH_H__
+
+
+#include "gimpbrushcore.h"
+
+
+#define GIMP_TYPE_PAINTBRUSH (gimp_paintbrush_get_type ())
+#define GIMP_PAINTBRUSH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PAINTBRUSH, GimpPaintbrush))
+#define GIMP_PAINTBRUSH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PAINTBRUSH, GimpPaintbrushClass))
+#define GIMP_IS_PAINTBRUSH(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PAINTBRUSH))
+#define GIMP_IS_PAINTBRUSH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PAINTBRUSH))
+#define GIMP_PAINTBRUSH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PAINTBRUSH, GimpPaintbrushClass))
+
+
+typedef struct _GimpPaintbrushClass GimpPaintbrushClass;
+
+struct _GimpPaintbrush
+{
+ GimpBrushCore parent_instance;
+
+ GeglBuffer *paint_buffer;
+ const GimpTempBuf *paint_pixmap;
+ GimpRGB paint_color;
+};
+
+struct _GimpPaintbrushClass
+{
+ GimpBrushCoreClass parent_class;
+
+ /* virtual functions */
+ gboolean (* get_color_history_color) (GimpPaintbrush *paintbrush,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpRGB *color);
+ void (* get_paint_params) (GimpPaintbrush *paintbrush,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ gdouble grad_point,
+ GimpLayerMode *paint_mode,
+ GimpPaintApplicationMode *paint_appl_mode,
+ const GimpTempBuf **paint_pixmap,
+ GimpRGB *paint_color);
+};
+
+
+void gimp_paintbrush_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback);
+
+GType gimp_paintbrush_get_type (void) G_GNUC_CONST;
+
+
+/* protected */
+
+void _gimp_paintbrush_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ gdouble opacity);
+
+
+#endif /* __GIMP_PAINTBRUSH_H__ */
diff --git a/app/paint/gimppaintcore-loops.cc b/app/paint/gimppaintcore-loops.cc
new file mode 100644
index 0000000..c92370d
--- /dev/null
+++ b/app/paint/gimppaintcore-loops.cc
@@ -0,0 +1,2268 @@
+/* GIMP - The GNU Image Manipulation Program
+ * 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/>.
+ */
+
+#include "config.h"
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+extern "C"
+{
+
+#include "paint-types.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "core/gimptempbuf.h"
+
+#include "operations/gimpoperationmaskcomponents.h"
+
+#include "operations/layer-modes/gimpoperationlayermode.h"
+
+#include "gimppaintcore-loops.h"
+
+} /* extern "C" */
+
+
+#define PIXELS_PER_THREAD \
+ (/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)
+
+
+/* In order to avoid iterating over the same region of the same buffers
+ * multiple times, when calling more than one of the paint-core loop functions
+ * (hereafter referred to as "algorithms") in succession, we provide a single
+ * function, gimp_paint_core_loops_process(), which can be used to perform
+ * multiple algorithms in a row. This function takes a pointer to a
+ * GimpPaintCoreLoopsParams structure, providing the parameters for the
+ * algorithms, and a GimpPaintCoreLoopsAlgorithm bitset, which specifies the
+ * set of algorithms to run; currently, the algorithms are always run in a
+ * fixed order. For convenience, we provide public functions for the
+ * individual algorithms, but they're merely wrappers around
+ * gimp_paint_core_loops_process().
+ *
+ * We use some C++ magic to statically generate specialized versions of
+ * gimp_paint_core_loops_process() for all possible combinations of algorithms,
+ * and, where relevant, formats and input parameters, and to dispatch to the
+ * correct version at runtime.
+ *
+ * To achieve this, each algorithm provides two components:
+ *
+ * - The algorithm class template, which implements the algorithm, following
+ * a common interface. See the AlgorithmBase class for a description of
+ * the interface. Each algorithm class takes its base class as a template
+ * parameter, which allows us to construct a class hierarchy corresponding
+ * to a specific set of algorithms. Some classes in the hierarchy are not
+ * algorithms themselves, but are rather helpers, which provide some
+ * functionality to the algorithms further down the hierarchy, such as
+ * access to specific buffers.
+ *
+ * - A dispatch function, which takes the input parameters, the requested set
+ * of algorithms, the (type of) the current algorithm hierarchy, and a
+ * visitor object. The function calls the visitor with a (potentially)
+ * modified hierarchy, depending on the input. Ihe dispatch function for
+ * an algorithm checks if the requested set of algorithms contains a
+ * certain algorithm, adds the said algorithm to the hierarchy accordingly,
+ * and calls the visitor with the new hierarchy. See the AlgorithmDispatch
+ * class, which provides a dispatch-function implementation which
+ * algorithms can use instead of providing their own dispatch function.
+ *
+ * Helper classes in the hierarchy may also provide dispatch functions,
+ * which likewise modify the hierarchy based on the input parameters. For
+ * example, the dispatch_paint_mask() function adds a certain PaintMask
+ * specialization to the hierarchy, depending on the format of the paint
+ * mask buffer; this can be used to specialize algorithms based on the mask
+ * format; an algorithm that depends on the paint mask may dispatch through
+ * this function, before modifying the hierarchy itself.
+ *
+ * The dispatch() function is used to construct an algorithm hierarchy by
+ * dispatching through a list of functions. gimp_paint_core_loops_process()
+ * calls dispatch() with the full list of algorithm dispatch functions,
+ * receiving in return the algorithm hierarchy matching the input. It then
+ * uses the algorithm interface to perform the actual processing.
+ */
+
+
+enum
+{
+ ALGORITHM_PAINT_BUF = 1u << 31,
+ ALGORITHM_PAINT_MASK = 1u << 30,
+ ALGORITHM_STIPPLE = 1u << 29,
+ ALGORITHM_COMP_MASK = 1u << 28,
+ ALGORITHM_TEMP_COMP_MASK = 1u << 27,
+ ALGORITHM_COMP_BUFFER = 1u << 26,
+ ALGORITHM_TEMP_COMP_BUFFER = 1u << 25,
+ ALGORITHM_CANVAS_BUFFER_ITERATOR = 1u << 24,
+ ALGORITHM_MASK_BUFFER_ITERATOR = 1u << 23
+};
+
+
+template <class T>
+struct identity
+{
+ using type = T;
+};
+
+
+/* dispatch():
+ *
+ * Takes a list of dispatch function objects, and calls each of them, in order,
+ * with the same 'params' and 'algorithms' parameters, passing 'algorithm' as
+ * the input hierarchy to the first dispatch function, and passing the output
+ * hierarchy of the previous dispatch function as the input hierarchy for the
+ * next dispatch function. Calls 'visitor' with the output hierarchy of the
+ * last dispatch function.
+ *
+ * Each algorithm hierarchy should provide a 'filter' static data member, and
+ * each dispatch function object should provide a 'mask' static data member.
+ * If the bitwise-AND of the current hierarchy's 'filter' member and the
+ * current dispatch function's 'mask' member is equal to 'mask', the dispatch
+ * function is skipped. This can be used to make sure that a class appears
+ * only once in the hierarchy, even if its dispatch function is used multiple
+ * times, or to prevent an algorithm from being dispatched, if it cannot be
+ * used together with another algorithm.
+ */
+
+template <class Visitor,
+ class Algorithm>
+static inline void
+dispatch (Visitor visitor,
+ const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms,
+ identity<Algorithm> algorithm)
+{
+ visitor (algorithm);
+}
+
+template <class Algorithm,
+ class Dispatch,
+ gboolean = (Algorithm::filter & Dispatch::mask) == Dispatch::mask>
+struct dispatch_impl
+{
+ template <class Visitor,
+ class... DispatchRest>
+ static void
+ apply (Visitor visitor,
+ const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms,
+ identity<Algorithm> algorithm,
+ Dispatch disp,
+ DispatchRest... disp_rest)
+ {
+ disp (
+ [&] (auto algorithm)
+ {
+ dispatch (visitor, params, algorithms, algorithm, disp_rest...);
+ },
+ params, algorithms, algorithm);
+ }
+};
+
+template <class Algorithm,
+ class Dispatch>
+struct dispatch_impl<Algorithm, Dispatch, TRUE>
+{
+ template <class Visitor,
+ class... DispatchRest>
+ static void
+ apply (Visitor visitor,
+ const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms,
+ identity<Algorithm> algorithm,
+ Dispatch disp,
+ DispatchRest... disp_rest)
+ {
+ dispatch (visitor, params, algorithms, algorithm, disp_rest...);
+ }
+};
+
+template <class Visitor,
+ class Algorithm,
+ class Dispatch,
+ class... DispatchRest>
+static inline void
+dispatch (Visitor visitor,
+ const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms,
+ identity<Algorithm> algorithm,
+ Dispatch disp,
+ DispatchRest... disp_rest)
+{
+ dispatch_impl<Algorithm, Dispatch>::apply (
+ visitor, params, algorithms, algorithm, disp, disp_rest...);
+}
+
+
+/* value_to_float():
+ *
+ * Converts a component value to float.
+ */
+
+static inline gfloat
+value_to_float (guint8 value)
+{
+ return value / 255.0f;
+}
+
+static inline gfloat
+value_to_float (gfloat value)
+{
+ return value;
+}
+
+template <class T>
+static inline gfloat
+value_to_float (T value) = delete;
+
+
+/* AlgorithmBase:
+ *
+ * The base class of the algorithm hierarchy.
+ */
+
+struct AlgorithmBase
+{
+ /* Used to filter-out dispatch functions; see the description of dispatch().
+ * Algorithms that redefine 'filter' should bitwise-OR their filter with that
+ * of their base class.
+ */
+ static constexpr guint filter = 0;
+
+ /* The current maximal number of iterators used by the hierarchy. Algorithms
+ * should redefine 'max_n_iterators' by adding the maximal number of
+ * iterators they use to this value.
+ */
+ static constexpr gint max_n_iterators = 0;
+
+ /* Non-static data members should be initialized in the constructor, and
+ * should not be further modified.
+ */
+ explicit
+ AlgorithmBase (const GimpPaintCoreLoopsParams *params)
+ {
+ }
+
+ /* Algorithms should store their dynamic state in the 'State' member class
+ * template. This template will be instantiated with the most-derived type
+ * of the hierarchy, which allows an algorithm to depend on the properties of
+ * its descendants. Algorithms that provide their own 'State' class should
+ * derive it from the 'State' class of their base class, passing 'Derived' as
+ * the template argument.
+ *
+ * Algorithms can be run in parallel on multiple threads. In this case, each
+ * thread uses its own 'State' object, while the algorithm object itself is
+ * either shared, or is a copy of a shared algorithm object. Either way, the
+ * algorithm object itself is immutable, while the state object is mutable.
+ */
+ template <class Derived>
+ struct State
+ {
+ };
+
+ /* The 'init()' function is called once per state object before processing
+ * starts, and should initialize the state object, and, if necessary, the
+ * iterator.
+ *
+ * 'params' is the same parameter struct passed to the constructor. 'state'
+ * is the state object. 'iter' is the iterator; each distinct state object
+ * uses a distinct iterator; if the algorithm hierarchy doesn't use any
+ * iterator, 'iter' may be NULL. 'roi' is the full region to be processed.
+ * 'area' is the subregion to be processed by the current state object.
+ *
+ * An algorithm that overrides this function should call the 'init()'
+ * function of its base class first, using the same arguments.
+ */
+ template <class Derived>
+ void
+ init (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area) const
+ {
+ }
+
+ /* The 'init_step()' function is called once after each
+ * 'gegl_buffer_iterator_next()' call, and should perform any necessary
+ * initialization required before processing the current chunk.
+ *
+ * The parameters are the same as for 'init()', with the addition of 'rect',
+ * which is the area of the current chunk.
+ *
+ * An algorithm that overrides this function should call the 'init_step()'
+ * function of its base class first, using the same arguments.
+ */
+ template <class Derived>
+ void
+ init_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect) const
+ {
+ }
+
+ /* The 'process_row()' function is called for each row in the current chunk,
+ * and should perform the actual processing.
+ *
+ * The parameters are the same as for 'init_step()', with the addition of
+ * 'y', which is the current row.
+ *
+ * An algorithm that overrides this function should call the 'process_row()'
+ * function of its base class first, using the same arguments.
+ */
+ template <class Derived>
+ void
+ process_row (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect,
+ gint y) const
+ {
+ }
+
+ /* The 'finalize_step()' function is called once per chunk after its
+ * processing is done, and should finalize any chunk-specific resources of
+ * the state object.
+ *
+ * 'params' is the same parameter struct passed to the constructor. 'state'
+ * is the state object.
+ *
+ * An algorithm that overrides this function should call the
+ * 'finalize_step()' function of its base class after performing its own
+ * finalization, using the same arguments.
+ */
+ template <class Derived>
+ void
+ finalize_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state) const
+ {
+ }
+
+ /* The 'finalize()' function is called once per state object after processing
+ * is done, and should finalize the state object.
+ *
+ * 'params' is the same parameter struct passed to the constructor. 'state'
+ * is the state object.
+ *
+ * An algorithm that overrides this function should call the 'finalize()'
+ * function of its base class after performing its own finalization, using
+ * the same arguments.
+ */
+ template <class Derived>
+ void
+ finalize (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state) const
+ {
+ }
+};
+
+
+/* BasicDispatch:
+ *
+ * A class template implementing a simple dispatch function object, which adds
+ * an algorithm to the hierarchy unconditionally. 'AlgorithmTemplate' is the
+ * alogithm class template (usually a helper class, rather than an actual
+ * algorithm), 'Mask' is the dispatch function mask, as described in
+ * 'dispatch()', and 'Dependencies' is a list of (types of) dispatch functions
+ * the algorithm depends on.
+ *
+ * Before adding the algorithm to the hierarchy, the hierarchy is augmented by
+ * dispatching through the list of dependencies, in order.
+ */
+
+template <template <class Base> class AlgorithmTemplate,
+ guint Mask,
+ class... Dependencies>
+struct BasicDispatch
+{
+ static constexpr guint mask = Mask;
+
+ template <class Visitor,
+ class Algorithm>
+ void
+ operator () (Visitor visitor,
+ const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms,
+ identity<Algorithm> algorithm) const
+ {
+ dispatch (
+ [&] (auto algorithm)
+ {
+ using NewAlgorithm = typename decltype (algorithm)::type;
+
+ visitor (identity<AlgorithmTemplate<NewAlgorithm>> ());
+ },
+ params, algorithms, algorithm, Dependencies ()...);
+ }
+};
+
+template <template <class Base> class AlgorithmTemplate,
+ guint Mask>
+struct BasicDispatch<AlgorithmTemplate, Mask>
+{
+ static constexpr guint mask = Mask;
+
+ template <class Visitor,
+ class Algorithm>
+ void
+ operator () (Visitor visitor,
+ const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms,
+ identity<Algorithm> algorithm) const
+ {
+ visitor (identity<AlgorithmTemplate<Algorithm>> ());
+ }
+};
+
+
+/* AlgorithmDispatch:
+ *
+ * A class template implementing a dispatch function suitable for dispatching
+ * algorithms. 'AlgorithmTemplate' is the algorithm class template, 'Mask' is
+ * the dispatch function mask, as described in 'dispatch()', and 'Dependencies'
+ * is a list of (types of) dispatch functions the algorithm depends on, used as
+ * explained below.
+ *
+ * 'AlgorithmDispatch' adds the algorithm to the hierarchy if it's included in
+ * the set of requested algorithms; specifically, if the bitwise-AND of the
+ * requested-algorithms bitset and of 'Mask' is equal to 'Mask'.
+ *
+ * Before adding the algorithm to the hierarchy, the hierarchy is augmented by
+ * dispatching through the list of dependencies, in order.
+ */
+
+template <template <class Base> class AlgorithmTemplate,
+ guint Mask,
+ class... Dependencies>
+struct AlgorithmDispatch
+{
+ static constexpr guint mask = Mask;
+
+ template <class Visitor,
+ class Algorithm>
+ void
+ operator () (Visitor visitor,
+ const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms,
+ identity<Algorithm> algorithm) const
+ {
+ if ((algorithms & mask) == mask)
+ {
+ dispatch (
+ [&] (auto algorithm)
+ {
+ using NewAlgorithm = typename decltype (algorithm)::type;
+
+ visitor (identity<AlgorithmTemplate<NewAlgorithm>> ());
+ },
+ params, algorithms, algorithm, Dependencies ()...);
+ }
+ else
+ {
+ visitor (algorithm);
+ }
+ }
+};
+
+
+/* MandatoryAlgorithmDispatch:
+ *
+ * A class template implementing a dispatch function suitable for dispatching
+ * algorithms that must be included in all hierarchies. 'AlgorithmTemplate' is
+ * the algorithm class template, 'Mask' is the dispatch function mask, as
+ * described in 'dispatch()', and 'Dependencies' is a list of (types of)
+ * dispatch functions the algorithm depends on, used as explained below.
+ *
+ * 'MandatoryAlgorithmDispatch' verifies that the algorithm is included in the
+ * set of requested algorithms (specifically, that the bitwise-AND of the
+ * requested-algorithms bitset and of 'Mask' is equal to 'Mask'), and adds the
+ * it to the hierarchy unconditionally.
+ *
+ * Before adding the algorithm to the hierarchy, the hierarchy is augmented by
+ * dispatching through the list of dependencies, in order.
+ */
+
+template <template <class Base> class AlgorithmTemplate,
+ guint Mask,
+ class... Dependencies>
+struct MandatoryAlgorithmDispatch
+{
+ static constexpr guint mask = Mask;
+
+ template <class Visitor,
+ class Algorithm>
+ void
+ operator () (Visitor visitor,
+ const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms,
+ identity<Algorithm> algorithm) const
+ {
+ g_return_if_fail ((algorithms & Mask) == Mask);
+
+ BasicDispatch<AlgorithmTemplate, Mask, Dependencies...> () (visitor,
+ params,
+ algorithms,
+ algorithm);
+ }
+};
+
+/* SuppressedAlgorithmDispatch:
+ *
+ * A class template implementing a placeholder dispatch function suitable for
+ * dispatching algorithms that are never included in any hierarchy.
+ * 'AlgorithmTemplate' is the algorithm class template, 'Mask' is the dispatch
+ * function mask, as described in 'dispatch()', and 'Dependencies' is a list of
+ * (types of) dispatch functions the algorithm depends on. Note that
+ * 'AlgorithmTemplate' and 'Dependencies' are not actually used, and are merely
+ * included for exposition.
+ *
+ * 'SuppressedAlgorithmDispatch' verifies that the algorithm is not included in
+ * the set of requested algorithms (specifically, that the bitwise-AND of the
+ * requested-algorithms bitset and of 'Mask' is not equal to 'Mask'), and
+ * doesn't modify the hierarchy.
+ */
+
+template <template <class Base> class AlgorithmTemplate,
+ guint Mask,
+ class... Dependencies>
+struct SuppressedAlgorithmDispatch
+{
+ static constexpr guint mask = Mask;
+
+ template <class Visitor,
+ class Algorithm>
+ void
+ operator () (Visitor visitor,
+ const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms,
+ identity<Algorithm> algorithm) const
+ {
+ g_return_if_fail ((algorithms & Mask) != Mask);
+
+ visitor (algorithm);
+ }
+};
+
+
+/* PaintBuf, dispatch_paint_buf():
+ *
+ * An algorithm helper class, providing access to the paint buffer. Algorithms
+ * that use the paint buffer should specify 'dispatch_paint_buf()' as a
+ * dependency, and access 'PaintBuf' members through their base type/subobject.
+ */
+
+template <class Base>
+struct PaintBuf : Base
+{
+ /* Component type of the paint buffer. */
+ using paint_type = gfloat;
+
+ static constexpr guint filter = Base::filter | ALGORITHM_PAINT_BUF;
+
+ /* Paint buffer stride, in 'paint_type' elements. */
+ gint paint_stride;
+ /* Pointer to the start of the paint buffer data. */
+ paint_type *paint_data;
+
+ explicit
+ PaintBuf (const GimpPaintCoreLoopsParams *params) :
+ Base (params)
+ {
+ paint_stride = gimp_temp_buf_get_width (params->paint_buf) * 4;
+ paint_data = (paint_type *) gimp_temp_buf_get_data (params->paint_buf);
+ }
+};
+
+static BasicDispatch<PaintBuf, ALGORITHM_PAINT_BUF> dispatch_paint_buf;
+
+
+/* PaintMask, dispatch_paint_mask():
+ *
+ * An algorithm helper class, providing access to the paint mask. Algorithms
+ * that use the paint mask should specify 'dispatch_paint_mask()' as a
+ * dependency, and access 'PaintMask' members through their base type/
+ * subobject.
+ */
+
+template <class Base,
+ class MaskType>
+struct PaintMask : Base
+{
+ /* Component type of the paint mask. */
+ using mask_type = MaskType;
+
+ static constexpr guint filter = Base::filter | ALGORITHM_PAINT_MASK;
+
+ /* Paint mask stride, in 'mask_type' elements. */
+ gint mask_stride;
+ /* Pointer to the start of the paint mask data, taking the mask offset into
+ * account.
+ */
+ const mask_type *mask_data;
+
+ explicit
+ PaintMask (const GimpPaintCoreLoopsParams *params) :
+ Base (params)
+ {
+ mask_stride = gimp_temp_buf_get_width (params->paint_mask);
+ mask_data =
+ (const mask_type *) gimp_temp_buf_get_data (params->paint_mask) +
+ params->paint_mask_offset_y * mask_stride +
+ params->paint_mask_offset_x;
+ }
+};
+
+struct DispatchPaintMask
+{
+ static constexpr guint mask = ALGORITHM_PAINT_MASK;
+
+ template <class Visitor,
+ class Algorithm>
+ void
+ operator () (Visitor visitor,
+ const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms,
+ identity<Algorithm> algorithm) const
+ {
+ const Babl *mask_format = gimp_temp_buf_get_format (params->paint_mask);
+
+ if (mask_format == babl_format ("Y u8"))
+ visitor (identity<PaintMask<Algorithm, guint8>> ());
+ else if (mask_format == babl_format ("Y float"))
+ visitor (identity<PaintMask<Algorithm, gfloat>> ());
+ else
+ g_warning ("Mask format not supported: %s", babl_get_name (mask_format));
+ }
+} static dispatch_paint_mask;
+
+
+/* Stipple, dispatch_stipple():
+ *
+ * An algorithm helper class, providing access to the 'stipple' parameter.
+ * Algorithms that use the 'stipple' parameter should specify
+ * 'dispatch_stipple()' as a dependency, and access 'Stipple' members through
+ * their base type/subobject.
+ */
+
+template <class Base,
+ gboolean StippleFlag>
+struct Stipple : Base
+{
+ static constexpr guint filter = Base::filter | ALGORITHM_STIPPLE;
+
+ /* The value of the 'stipple' parameter, usable as a constant expression. */
+ static constexpr gboolean stipple = StippleFlag;
+
+ using Base::Base;
+};
+
+struct DispatchStipple
+{
+ static constexpr guint mask = ALGORITHM_STIPPLE;
+
+ template <class Visitor,
+ class Algorithm>
+ void
+ operator () (Visitor visitor,
+ const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms,
+ identity<Algorithm> algorithm) const
+ {
+ if (params->stipple)
+ visitor (identity<Stipple<Algorithm, TRUE>> ());
+ else
+ visitor (identity<Stipple<Algorithm, FALSE>> ());
+ }
+} static dispatch_stipple;
+
+
+/* CompMask, dispatch_comp_mask(), has_comp_mask(), comp_mask_data():
+ *
+ * An algorithm helper class, providing access to the mask used for
+ * compositing. When this class is part of the hierarchy, 'DoLayerBlend' uses
+ * this buffer as the mask, instead of the input parameters' 'mask_buffer'.
+ * Algorithms that use the compositing mask should specify
+ * 'dispatch_comp_mask()' as a dependency, and access 'CompMask' members
+ * through their base type/subobject.
+ *
+ * Note that 'CompMask' only provides *access* to the compositing mask, but
+ * doesn't provide its actual *storage*. This is the responsibility of the
+ * algorithms that use 'CompMask'. Algorithms that need temporary storage for
+ * the compositing mask can use 'TempCompMask'.
+ *
+ * The 'has_comp_mask()' constexpr function determines if a given algorithm
+ * hierarchy uses the compositing mask.
+ *
+ * The 'comp_mask_data()' function returns a pointer to the compositing mask
+ * data for the current row if the hierarchy uses the compositing mask, or NULL
+ * otherwise.
+ */
+
+template <class Base>
+struct CompMask : Base
+{
+ /* Component type of the compositing mask. */
+ using comp_mask_type = gfloat;
+
+ static constexpr guint filter = Base::filter | ALGORITHM_COMP_MASK;
+
+ using Base::Base;
+
+ template <class Derived>
+ struct State : Base::template State<Derived>
+ {
+ /* Pointer to the compositing mask data for the current row. */
+ comp_mask_type *comp_mask_data;
+ };
+};
+
+static BasicDispatch<CompMask, ALGORITHM_COMP_MASK> dispatch_comp_mask;
+
+template <class Base>
+static constexpr gboolean
+has_comp_mask (const CompMask<Base> *algorithm)
+{
+ return TRUE;
+}
+
+static constexpr gboolean
+has_comp_mask (const AlgorithmBase *algorithm)
+{
+ return FALSE;
+}
+
+template <class Base,
+ class State>
+static gfloat *
+comp_mask_data (const CompMask<Base> *algorithm,
+ State *state)
+{
+ return state->comp_mask_data;
+}
+
+template <class State>
+static gfloat *
+comp_mask_data (const AlgorithmBase *algorithm,
+ State *state)
+{
+ return NULL;
+}
+
+
+/* TempCompMask, dispatch_temp_comp_mask():
+ *
+ * An algorithm helper class, providing temporary storage for the compositing
+ * mask. Algorithms that need a temporary compositing mask should specify
+ * 'dispatch_temp_comp_mask()' as a dependency, which itself includes
+ * 'dispatch_comp_mask()' as a dependency.
+ */
+
+template <class Base>
+struct TempCompMask : Base
+{
+ static constexpr guint filter = Base::filter | ALGORITHM_TEMP_COMP_MASK;
+
+ using Base::Base;
+
+ template <class Derived>
+ using State = typename Base::template State<Derived>;
+
+ template <class Derived>
+ void
+ init_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect) const
+ {
+ Base::init_step (params, state, iter, roi, area, rect);
+
+ state->comp_mask_data = gegl_scratch_new (gfloat, rect->width);
+ }
+
+
+ template <class Derived>
+ void
+ finalize_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state) const
+ {
+ gegl_scratch_free (state->comp_mask_data);
+
+ Base::finalize_step (params, state);
+ }
+};
+
+static BasicDispatch<
+ TempCompMask,
+ ALGORITHM_TEMP_COMP_MASK,
+ decltype (dispatch_comp_mask)
+> dispatch_temp_comp_mask;
+
+
+/* CompBuffer, dispatch_comp_buffer(), has_comp_buffer(), comp_buffer_data():
+ *
+ * An algorithm helper class, providing access to the output buffer used for
+ * compositing. When this class is part of the hierarchy, 'DoLayerBlend' uses
+ * this buffer as the output buffer, instead of the input parameters'
+ * 'dest_buffer'. Algorithms that use the compositing buffer should specify
+ * 'dispatch_comp_buffer()' as a dependency, and access 'CompBuffer' members
+ * through their base type/subobject.
+ *
+ * Note that 'CompBuffer' only provides *access* to the compositing buffer, but
+ * doesn't provide its actual *storage*. This is the responsibility of the
+ * algorithms that use 'CompBuffer'. Algorithms that need temporary storage
+ * for the compositing buffer can use 'TempCompBuffer'.
+ *
+ * The 'has_comp_buffer()' constexpr function determines if a given algorithm
+ * hierarchy uses the compositing buffer.
+ *
+ * The 'comp_buffer_data()' function returns a pointer to the compositing
+ * buffer data for the current row if the hierarchy uses the compositing
+ * buffer, or NULL otherwise.
+ */
+
+template <class Base>
+struct CompBuffer : Base
+{
+ /* Component type of the compositing buffer. */
+ using comp_buffer_type = gfloat;
+
+ static constexpr guint filter = Base::filter | ALGORITHM_COMP_BUFFER;
+
+ using Base::Base;
+
+ template <class Derived>
+ struct State : Base::template State<Derived>
+ {
+ /* Pointer to the compositing buffer data for the current row. */
+ comp_buffer_type *comp_buffer_data;
+ };
+};
+
+static BasicDispatch<CompBuffer, ALGORITHM_COMP_BUFFER> dispatch_comp_buffer;
+
+template <class Base>
+static constexpr gboolean
+has_comp_buffer (const CompBuffer<Base> *algorithm)
+{
+ return TRUE;
+}
+
+static constexpr gboolean
+has_comp_buffer (const AlgorithmBase *algorithm)
+{
+ return FALSE;
+}
+
+template <class Base,
+ class State>
+static gfloat *
+comp_buffer_data (const CompBuffer<Base> *algorithm,
+ State *state)
+{
+ return state->comp_buffer_data;
+}
+
+template <class State>
+static gfloat *
+comp_buffer_data (const AlgorithmBase *algorithm,
+ State *state)
+{
+ return NULL;
+}
+
+
+/* TempCompBuffer, dispatch_temp_comp_buffer():
+ *
+ * An algorithm helper class, providing temporary storage for the compositing
+ * buffer. Algorithms that need a temporary compositing buffer should specify
+ * 'dispatch_temp_comp_buffer()' as a dependency, which itself includes
+ * 'dispatch_comp_buffer()' as a dependency.
+ */
+
+template <class Base>
+struct TempCompBuffer : Base
+{
+ static constexpr guint filter = Base::filter | ALGORITHM_TEMP_COMP_BUFFER;
+
+ using Base::Base;
+
+ template <class Derived>
+ using State = typename Base::template State<Derived>;
+
+ template <class Derived>
+ void
+ init_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect) const
+ {
+ Base::init_step (params, state, iter, roi, area, rect);
+
+ state->comp_buffer_data = gegl_scratch_new (gfloat, 4 * rect->width);
+ }
+
+
+ template <class Derived>
+ void
+ finalize_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state) const
+ {
+ gegl_scratch_free (state->comp_buffer_data);
+
+ Base::finalize_step (params, state);
+ }
+};
+
+static BasicDispatch<
+ TempCompBuffer,
+ ALGORITHM_TEMP_COMP_BUFFER,
+ decltype (dispatch_comp_buffer)
+> dispatch_temp_comp_buffer;
+
+
+/* CanvasBufferIterator, DispatchCanvasBufferIterator:
+ *
+ * An algorithm helper class, providing iterator-access to the canvas buffer.
+ * Algorithms that iterate over the canvas buffer should specify
+ * 'DispatchCanvasBufferIterator<Access>' as a dependency, where 'Access' is
+ * the desired access mode to the canvas buffer, and access its members through
+ * their base type/subobject.
+ */
+
+template <class Base,
+ guint Access,
+ gboolean First>
+struct CanvasBufferIterator;
+
+template <class Base,
+ guint Access>
+static constexpr gboolean
+canvas_buffer_iterator_is_first (CanvasBufferIterator<Base, Access, TRUE> *algorithm)
+{
+ return FALSE;
+}
+
+static constexpr gboolean
+canvas_buffer_iterator_is_first (AlgorithmBase *algorithm)
+{
+ return TRUE;
+}
+
+template <class Base,
+ guint Access,
+ gboolean First = canvas_buffer_iterator_is_first ((Base *) NULL)>
+struct CanvasBufferIterator : Base
+{
+ /* The combined canvas-buffer access mode used by the hierarchy, up to, and
+ * including, the current class.
+ */
+ static constexpr GeglAccessMode canvas_buffer_access =
+ (GeglAccessMode) (Base::canvas_buffer_access | Access);
+
+ using Base::Base;
+};
+
+template <class Base,
+ guint Access>
+struct CanvasBufferIterator<Base, Access, TRUE> : Base
+{
+ /* The combined canvas-buffer access mode used by the hierarchy, up to, and
+ * including, the current class.
+ */
+ static constexpr GeglAccessMode canvas_buffer_access =
+ (GeglAccessMode) Access;
+
+ static constexpr gint max_n_iterators =
+ Base::max_n_iterators + 1;
+
+ using Base::Base;
+
+ template <class Derived>
+ struct State : Base::template State<Derived>
+ {
+ gint canvas_buffer_iterator;
+ };
+
+ template <class Derived>
+ void
+ init (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area) const
+ {
+ state->canvas_buffer_iterator = gegl_buffer_iterator_add (
+ iter, params->canvas_buffer, area, 0, babl_format ("Y float"),
+ Derived::canvas_buffer_access, GEGL_ABYSS_NONE);
+
+ /* initialize the base class *after* initializing the iterator, to make
+ * sure that canvas_buffer is the primary buffer of the iterator, if no
+ * subclass added an iterator first.
+ */
+ Base::init (params, state, iter, roi, area);
+ }
+};
+
+template <guint Access>
+struct DispatchCanvasBufferIteratorHelper
+{
+ template <class Base>
+ using algorithm_template = CanvasBufferIterator<Base, Access>;
+};
+
+template <guint Access>
+using DispatchCanvasBufferIterator = BasicDispatch<
+ DispatchCanvasBufferIteratorHelper<Access>::template algorithm_template,
+ ALGORITHM_CANVAS_BUFFER_ITERATOR
+>;
+
+
+/* MaskBufferIterator, mask_buffer_iterator_dispatch(),
+ * has_mask_buffer_iterator():
+ *
+ * An algorithm helper class, providing read-only iterator-access to the mask
+ * buffer. Algorithms that iterate over the mask buffer should specify
+ * 'dispatch_mask_buffer_iterator()' as a dependency, and access
+ * 'MaskBufferIterator' members through their base type/subobject.
+ *
+ * The 'has_mask_buffer_iterator()' constexpr function determines if a given
+ * algorithm hierarchy uses has a mask-buffer iterator.
+ *
+ * The 'mask_buffer_iterator()' function returns the index of the mask-buffer
+ * iterator if the hierarchy has one, or -1 otherwise.
+ */
+
+template <class Base>
+struct MaskBufferIterator : Base
+{
+ static constexpr guint filter = Base::filter | ALGORITHM_MASK_BUFFER_ITERATOR;
+
+ static constexpr gint max_n_iterators = Base::max_n_iterators + 1;
+
+ using Base::Base;
+
+ template <class Derived>
+ struct State : Base::template State<Derived>
+ {
+ gint mask_buffer_iterator;
+ };
+
+ template <class Derived>
+ void
+ init (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area) const
+ {
+ Base::init (params, state, iter, roi, area);
+
+ GeglRectangle mask_area = *area;
+
+ mask_area.x -= params->mask_offset_x;
+ mask_area.y -= params->mask_offset_y;
+
+ state->mask_buffer_iterator = gegl_buffer_iterator_add (
+ iter, params->mask_buffer, &mask_area, 0, babl_format ("Y float"),
+ GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+ }
+};
+
+struct DispatchMaskBufferIterator
+{
+ static constexpr guint mask = ALGORITHM_MASK_BUFFER_ITERATOR;
+
+ template <class Visitor,
+ class Algorithm>
+ void
+ operator () (Visitor visitor,
+ const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms,
+ identity<Algorithm> algorithm) const
+ {
+ if (params->mask_buffer)
+ visitor (identity<MaskBufferIterator<Algorithm>> ());
+ else
+ visitor (algorithm);
+ }
+} static dispatch_mask_buffer_iterator;
+
+template <class Base>
+static constexpr gboolean
+has_mask_buffer_iterator (const MaskBufferIterator<Base> *algorithm)
+{
+ return TRUE;
+}
+
+static constexpr gboolean
+has_mask_buffer_iterator (const AlgorithmBase *algorithm)
+{
+ return FALSE;
+}
+
+template <class Base,
+ class State>
+static gint
+mask_buffer_iterator (const MaskBufferIterator<Base> *algorithm,
+ State *state)
+{
+ return state->mask_buffer_iterator;
+}
+
+template <class State>
+static gint
+mask_buffer_iterator (const AlgorithmBase *algorithm,
+ State *state)
+{
+ return -1;
+}
+
+
+/* CombinePaintMaskToCanvasBufferToPaintBufAlpha,
+ * dispatch_combine_paint_mask_to_canvas_buffer_to_paint_buf_alpha():
+ *
+ * An algorithm class, providing an optimized version combining both the
+ * COMBINE_PAINT_MASK_TO_CANVAS_BUFFER and the CANVAS_BUFFER_TO_PAINT_BUF_ALPHA
+ * algorithms. Used instead of the individual implementations, when both
+ * algorithms are requested.
+ */
+
+template <class Base>
+struct CombinePaintMaskToCanvasBufferToPaintBufAlpha : Base
+{
+ using mask_type = typename Base::mask_type;
+
+ static constexpr guint filter =
+ Base::filter |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_COMBINE_PAINT_MASK_TO_CANVAS_BUFFER |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_PAINT_BUF_ALPHA |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_PAINT_BUF_ALPHA |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_COMP_MASK |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_COMP_MASK;
+
+ using Base::Base;
+
+ template <class Derived>
+ struct State : Base::template State<Derived>
+ {
+ gfloat *canvas_pixel;
+ };
+
+ template <class Derived>
+ void
+ init_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect) const
+ {
+ Base::init_step (params, state, iter, roi, area, rect);
+
+ state->canvas_pixel =
+ (gfloat *) iter->items[state->canvas_buffer_iterator].data;
+ }
+
+ template <class Derived>
+ void
+ process_row (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect,
+ gint y) const
+ {
+ Base::process_row (params, state, iter, roi, area, rect, y);
+
+ gint mask_offset = (y - roi->y) * this->mask_stride +
+ (rect->x - roi->x);
+ const mask_type *mask_pixel = &this->mask_data[mask_offset];
+ gint paint_offset = (y - roi->y) * this->paint_stride +
+ (rect->x - roi->x) * 4;
+ gfloat *paint_pixel = &this->paint_data[paint_offset];
+ gint x;
+
+ for (x = 0; x < rect->width; x++)
+ {
+ if (Base::stipple)
+ {
+ state->canvas_pixel[0] += (1.0 - state->canvas_pixel[0]) *
+ value_to_float (*mask_pixel) *
+ params->paint_opacity;
+ }
+ else
+ {
+ if (params->paint_opacity > state->canvas_pixel[0])
+ {
+ state->canvas_pixel[0] += (params->paint_opacity - state->canvas_pixel[0]) *
+ value_to_float (*mask_pixel) *
+ params->paint_opacity;
+ }
+ }
+
+ paint_pixel[3] *= state->canvas_pixel[0];
+
+ mask_pixel += 1;
+ state->canvas_pixel += 1;
+ paint_pixel += 4;
+ }
+ }
+};
+
+static SuppressedAlgorithmDispatch<
+ CombinePaintMaskToCanvasBufferToPaintBufAlpha,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_COMBINE_PAINT_MASK_TO_CANVAS_BUFFER |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_PAINT_BUF_ALPHA,
+ decltype (dispatch_paint_buf),
+ decltype (dispatch_paint_mask),
+ decltype (dispatch_stipple),
+ DispatchCanvasBufferIterator<GEGL_BUFFER_READWRITE>
+>
+dispatch_combine_paint_mask_to_canvas_buffer_to_paint_buf_alpha;
+
+
+/* CombinePaintMaskToCanvasBuffer,
+ * dispatch_combine_paint_mask_to_canvas_buffer():
+ *
+ * An algorithm class, implementing the COMBINE_PAINT_MASK_TO_CANVAS_BUFFER
+ * algorithm.
+ */
+
+template <class Base>
+struct CombinePaintMaskToCanvasBuffer : Base
+{
+ using mask_type = typename Base::mask_type;
+
+ static constexpr guint filter =
+ Base::filter |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_COMBINE_PAINT_MASK_TO_CANVAS_BUFFER |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_PAINT_BUF_ALPHA |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_PAINT_BUF_ALPHA |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_COMP_MASK;
+
+ using Base::Base;
+
+ template <class Derived>
+ struct State : Base::template State<Derived>
+ {
+ gfloat *canvas_pixel;
+ };
+
+ template <class Derived>
+ void
+ init_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect) const
+ {
+ Base::init_step (params, state, iter, roi, area, rect);
+
+ state->canvas_pixel =
+ (gfloat *) iter->items[state->canvas_buffer_iterator].data;
+ }
+
+ template <class Derived>
+ void
+ process_row (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect,
+ gint y) const
+ {
+ Base::process_row (params, state, iter, roi, area, rect, y);
+
+ gint mask_offset = (y - roi->y) * this->mask_stride +
+ (rect->x - roi->x);
+ const mask_type *mask_pixel = &this->mask_data[mask_offset];
+ gint x;
+
+ for (x = 0; x < rect->width; x++)
+ {
+ if (Base::stipple)
+ {
+ state->canvas_pixel[0] += (1.0 - state->canvas_pixel[0]) *
+ value_to_float (*mask_pixel) *
+ params->paint_opacity;
+ }
+ else
+ {
+ if (params->paint_opacity > state->canvas_pixel[0])
+ {
+ state->canvas_pixel[0] += (params->paint_opacity - state->canvas_pixel[0]) *
+ value_to_float (*mask_pixel) *
+ params->paint_opacity;
+ }
+ }
+
+ mask_pixel += 1;
+ state->canvas_pixel += 1;
+ }
+ }
+};
+
+static AlgorithmDispatch<
+ CombinePaintMaskToCanvasBuffer,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_COMBINE_PAINT_MASK_TO_CANVAS_BUFFER,
+ decltype (dispatch_paint_mask),
+ decltype (dispatch_stipple),
+ DispatchCanvasBufferIterator<GEGL_BUFFER_READWRITE>
+>
+dispatch_combine_paint_mask_to_canvas_buffer;
+
+
+/* CanvasBufferToPaintBufAlpha, dispatch_canvas_buffer_to_paint_buf_alpha():
+ *
+ * An algorithm class, implementing the CANVAS_BUFFER_TO_PAINT_BUF_ALPHA
+ * algorithm.
+ */
+
+template <class Base>
+struct CanvasBufferToPaintBufAlpha : Base
+{
+ static constexpr guint filter =
+ Base::filter |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_PAINT_BUF_ALPHA |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_PAINT_BUF_ALPHA |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_COMP_MASK |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_COMP_MASK;
+
+ using Base::Base;
+
+ template <class Derived>
+ struct State : Base::template State<Derived>
+ {
+ const gfloat *canvas_pixel;
+ };
+
+ template <class Derived>
+ void
+ init_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect) const
+ {
+ Base::init_step (params, state, iter, roi, area, rect);
+
+ state->canvas_pixel =
+ (const gfloat *) iter->items[state->canvas_buffer_iterator].data;
+ }
+
+ template <class Derived>
+ void
+ process_row (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect,
+ gint y) const
+ {
+ Base::process_row (params, state, iter, roi, area, rect, y);
+
+ /* Copy the canvas buffer in rect to the paint buffer's alpha channel */
+
+ gint paint_offset = (y - roi->y) * this->paint_stride +
+ (rect->x - roi->x) * 4;
+ gfloat *paint_pixel = &this->paint_data[paint_offset];
+ gint x;
+
+ for (x = 0; x < rect->width; x++)
+ {
+ paint_pixel[3] *= *state->canvas_pixel;
+
+ state->canvas_pixel += 1;
+ paint_pixel += 4;
+ }
+ }
+};
+
+static SuppressedAlgorithmDispatch<
+ CanvasBufferToPaintBufAlpha,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_PAINT_BUF_ALPHA,
+ decltype (dispatch_paint_buf),
+ DispatchCanvasBufferIterator<GEGL_BUFFER_READ>
+>
+dispatch_canvas_buffer_to_paint_buf_alpha;
+
+
+/* PaintMaskToPaintBufAlpha, dispatch_paint_mask_to_paint_buf_alpha():
+ *
+ * An algorithm class, implementing the PAINT_MASK_TO_PAINT_BUF_ALPHA
+ * algorithm.
+ */
+
+template <class Base>
+struct PaintMaskToPaintBufAlpha : Base
+{
+ using mask_type = typename Base::mask_type;
+
+ static constexpr guint filter =
+ Base::filter |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_PAINT_BUF_ALPHA |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_COMP_MASK |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_COMP_MASK;
+
+ explicit
+ PaintMaskToPaintBufAlpha (const GimpPaintCoreLoopsParams *params) :
+ Base (params)
+ {
+ /* Validate that the paint buffer is within the bounds of the paint mask */
+ g_return_if_fail (gimp_temp_buf_get_width (params->paint_buf) <=
+ gimp_temp_buf_get_width (params->paint_mask) -
+ params->paint_mask_offset_x);
+ g_return_if_fail (gimp_temp_buf_get_height (params->paint_buf) <=
+ gimp_temp_buf_get_height (params->paint_mask) -
+ params->paint_mask_offset_y);
+ }
+
+ template <class Derived>
+ using State = typename Base::template State<Derived>;
+
+ template <class Derived>
+ void
+ process_row (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect,
+ gint y) const
+ {
+ Base::process_row (params, state, iter, roi, area, rect, y);
+
+ gint paint_offset = (y - roi->y) * this->paint_stride +
+ (rect->x - roi->x) * 4;
+ gfloat *paint_pixel = &this->paint_data[paint_offset];
+ gint mask_offset = (y - roi->y) * this->mask_stride +
+ (rect->x - roi->x);
+ const mask_type *mask_pixel = &this->mask_data[mask_offset];
+ gint x;
+
+ for (x = 0; x < rect->width; x++)
+ {
+ paint_pixel[3] *= value_to_float (*mask_pixel) * params->paint_opacity;
+
+ mask_pixel += 1;
+ paint_pixel += 4;
+ }
+ }
+};
+
+static SuppressedAlgorithmDispatch<
+ PaintMaskToPaintBufAlpha,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_PAINT_BUF_ALPHA,
+ decltype (dispatch_paint_buf),
+ decltype (dispatch_paint_mask)
+>
+dispatch_paint_mask_to_paint_buf_alpha;
+
+
+/* CanvasBufferToCompMask, dispatch_canvas_buffer_to_comp_mask():
+ *
+ * An algorithm class, implementing the CANVAS_BUFFER_TO_COMP_MASK algorithm.
+ */
+
+template <class Base,
+ gboolean Direct>
+struct CanvasBufferToCompMask : Base
+{
+ using comp_mask_type = typename Base::comp_mask_type;
+
+ static constexpr guint filter =
+ Base::filter |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_COMP_MASK |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_COMP_MASK;
+
+ using Base::Base;
+
+ template <class Derived>
+ struct State : Base::template State<Derived>
+ {
+ const gfloat *canvas_pixel;
+ const gfloat *mask_pixel;
+ };
+
+ template <class Derived>
+ void
+ init_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect) const
+ {
+ Base::init_step (params, state, iter, roi, area, rect);
+
+ state->canvas_pixel =
+ (const gfloat *) iter->items[state->canvas_buffer_iterator].data;
+ state->mask_pixel =
+ (const gfloat *) iter->items[state->mask_buffer_iterator].data;
+ }
+
+ template <class Derived>
+ void
+ process_row (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect,
+ gint y) const
+ {
+ Base::process_row (params, state, iter, roi, area, rect, y);
+
+ comp_mask_type *comp_mask_pixel = state->comp_mask_data;
+ gint x;
+
+ for (x = 0; x < rect->width; x++)
+ {
+ comp_mask_pixel[0] = state->canvas_pixel[0] * state->mask_pixel[0];
+
+ comp_mask_pixel += 1;
+ state->canvas_pixel += 1;
+ state->mask_pixel += 1;
+ }
+ }
+};
+
+template <class Base>
+struct CanvasBufferToCompMask<Base, TRUE> : Base
+{
+ static constexpr guint filter =
+ Base::filter |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_COMP_MASK |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_COMP_MASK;
+
+ using Base::Base;
+
+ template <class Derived>
+ using State = typename Base::template State<Derived>;
+
+ template <class Derived>
+ void
+ init_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect) const
+ {
+ Base::init_step (params, state, iter, roi, area, rect);
+
+ state->comp_mask_data =
+ (gfloat *) iter->items[state->canvas_buffer_iterator].data - rect->width;
+ }
+
+ template <class Derived>
+ void
+ process_row (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect,
+ gint y) const
+ {
+ Base::process_row (params, state, iter, roi, area, rect, y);
+
+ state->comp_mask_data += rect->width;
+ }
+};
+
+struct DispatchCanvasBufferToCompMask
+{
+ static constexpr guint mask =
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_COMP_MASK;
+
+ template <class Base>
+ using AlgorithmDirect = CanvasBufferToCompMask<Base, TRUE>;
+ template <class Base>
+ using AlgorithmIndirect = CanvasBufferToCompMask<Base, FALSE>;
+
+ using DispatchDirect = BasicDispatch<
+ AlgorithmDirect,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_COMP_MASK,
+ decltype (dispatch_comp_mask)
+ >;
+ using DispatchIndirect = BasicDispatch<
+ AlgorithmIndirect,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_COMP_MASK,
+ decltype (dispatch_temp_comp_mask)
+ >;
+
+ template <class Algorithm,
+ gboolean HasMaskBufferIterator = has_mask_buffer_iterator (
+ (Algorithm *) NULL)>
+ struct Dispatch : DispatchIndirect
+ {
+ };
+
+ template <class Algorithm>
+ struct Dispatch<Algorithm, FALSE> : DispatchDirect
+ {
+ };
+
+ template <class Visitor,
+ class Algorithm>
+ void
+ operator () (Visitor visitor,
+ const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms,
+ identity<Algorithm> algorithm) const
+ {
+ if ((algorithms & mask) == mask)
+ {
+ dispatch (
+ [&] (auto algorithm)
+ {
+ using NewAlgorithm = typename decltype (algorithm)::type;
+
+ Dispatch<NewAlgorithm> () (visitor, params, algorithms, algorithm);
+ },
+ params, algorithms, algorithm,
+ DispatchCanvasBufferIterator<GEGL_BUFFER_READ> (),
+ dispatch_mask_buffer_iterator);
+ }
+ else
+ {
+ visitor (algorithm);
+ }
+ }
+} static dispatch_canvas_buffer_to_comp_mask;
+
+
+/* PaintMaskToCompMask, dispatch_paint_mask_to_comp_mask():
+ *
+ * An algorithm class, implementing the PAINT_MASK_TO_COMP_MASK algorithm.
+ */
+
+template <class Base,
+ gboolean Direct>
+struct PaintMaskToCompMask : Base
+{
+ using mask_type = typename Base::mask_type;
+ using comp_mask_type = typename Base::comp_mask_type;
+
+ static constexpr guint filter =
+ Base::filter |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_COMP_MASK;
+
+ using Base::Base;
+
+ template <class Derived>
+ struct State : Base::template State<Derived>
+ {
+ const gfloat *mask_pixel;
+ };
+
+ template <class Derived>
+ void
+ init_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect) const
+ {
+ Base::init_step (params, state, iter, roi, area, rect);
+
+ if (has_mask_buffer_iterator (this))
+ {
+ state->mask_pixel =
+ (const gfloat *) iter->items[mask_buffer_iterator (this, state)].data;
+ }
+ }
+
+ template <class Derived>
+ void
+ process_row (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect,
+ gint y) const
+ {
+ Base::process_row (params, state, iter, roi, area, rect, y);
+
+ gint mask_offset = (y - roi->y) * this->mask_stride +
+ (rect->x - roi->x);
+ const mask_type *mask_pixel = &this->mask_data[mask_offset];
+ comp_mask_type *comp_mask_pixel = state->comp_mask_data;
+ gint x;
+
+ if (has_mask_buffer_iterator (this))
+ {
+ for (x = 0; x < rect->width; x++)
+ {
+ comp_mask_pixel[0] = value_to_float (mask_pixel[0]) *
+ state->mask_pixel[0] *
+ params->paint_opacity;
+
+ comp_mask_pixel += 1;
+ mask_pixel += 1;
+ state->mask_pixel += 1;
+ }
+ }
+ else
+ {
+ for (x = 0; x < rect->width; x++)
+ {
+ comp_mask_pixel[0] = value_to_float (mask_pixel[0]) *
+ params->paint_opacity;
+
+ comp_mask_pixel += 1;
+ mask_pixel += 1;
+ }
+ }
+ }
+};
+
+template <class Base>
+struct PaintMaskToCompMask<Base, TRUE> : Base
+{
+ using mask_type = typename Base::mask_type;
+
+ static constexpr guint filter =
+ Base::filter |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_COMP_MASK |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_COMP_MASK;
+
+ using Base::Base;
+
+ template <class Derived>
+ using State = typename Base::template State<Derived>;
+
+ template <class Derived>
+ void
+ init_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect) const
+ {
+ Base::init_step (params, state, iter, roi, area, rect);
+
+ gint mask_offset = (rect->y - roi->y) * this->mask_stride +
+ (rect->x - roi->x);
+
+ state->comp_mask_data = (mask_type *) &this->mask_data[mask_offset] -
+ this->mask_stride;
+ }
+
+ template <class Derived>
+ void
+ process_row (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect,
+ gint y) const
+ {
+ Base::process_row (params, state, iter, roi, area, rect, y);
+
+ state->comp_mask_data += this->mask_stride;
+ }
+};
+
+struct DispatchPaintMaskToCompMask
+{
+ static constexpr guint mask =
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_COMP_MASK;
+
+ template <class Base>
+ using AlgorithmDirect = PaintMaskToCompMask<Base, TRUE>;
+ template <class Base>
+ using AlgorithmIndirect = PaintMaskToCompMask<Base, FALSE>;
+
+ using DispatchDirect = BasicDispatch<
+ AlgorithmDirect,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_COMP_MASK,
+ decltype (dispatch_comp_mask)
+ >;
+ using DispatchIndirect = BasicDispatch<
+ AlgorithmIndirect,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_COMP_MASK,
+ decltype (dispatch_temp_comp_mask)
+ >;
+
+ template <class Algorithm,
+ class MaskType = typename Algorithm::mask_type,
+ gboolean HasMaskBufferIterator = has_mask_buffer_iterator (
+ (Algorithm *) NULL)>
+ struct Dispatch : DispatchIndirect
+ {
+ };
+
+ template <class Algorithm>
+ struct Dispatch<Algorithm, gfloat, FALSE> : DispatchDirect
+ {
+ };
+
+ template <class Visitor,
+ class Algorithm>
+ void
+ operator () (Visitor visitor,
+ const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms,
+ identity<Algorithm> algorithm) const
+ {
+ if ((algorithms & mask) == mask)
+ {
+ dispatch (
+ [&] (auto algorithm)
+ {
+ using NewAlgorithm = typename decltype (algorithm)::type;
+
+ if (params->paint_opacity == GIMP_OPACITY_OPAQUE)
+ Dispatch<NewAlgorithm> () (visitor, params, algorithms, algorithm);
+ else
+ DispatchIndirect () (visitor, params, algorithms, algorithm);
+ },
+ params, algorithms, algorithm,
+ dispatch_paint_mask,
+ dispatch_mask_buffer_iterator);
+ }
+ else
+ {
+ visitor (algorithm);
+ }
+ }
+} static dispatch_paint_mask_to_comp_mask;
+
+
+/* DoLayerBlend, dispatch_do_layer_blend():
+ *
+ * An algorithm class, implementing the DO_LAYER_BLEND algorithm.
+ */
+
+template <class Base>
+struct DoLayerBlend : Base
+{
+ static constexpr guint filter =
+ Base::filter |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_DO_LAYER_BLEND;
+
+ static constexpr gint max_n_iterators = Base::max_n_iterators + 2;
+
+ const Babl *iterator_format;
+ GimpOperationLayerMode layer_mode;
+
+ explicit
+ DoLayerBlend (const GimpPaintCoreLoopsParams *params) :
+ Base (params)
+ {
+ layer_mode.layer_mode = params->paint_mode;
+ layer_mode.opacity = params->image_opacity;
+ layer_mode.function = gimp_layer_mode_get_function (params->paint_mode);
+ layer_mode.blend_function = gimp_layer_mode_get_blend_function (params->paint_mode);
+ layer_mode.blend_space = gimp_layer_mode_get_blend_space (params->paint_mode);
+ layer_mode.composite_space = gimp_layer_mode_get_composite_space (params->paint_mode);
+ layer_mode.composite_mode = gimp_layer_mode_get_paint_composite_mode (params->paint_mode);
+
+ iterator_format = gimp_layer_mode_get_format (params->paint_mode,
+ layer_mode.blend_space,
+ layer_mode.composite_space,
+ layer_mode.composite_mode,
+ gimp_temp_buf_get_format (params->paint_buf));
+
+ g_return_if_fail (gimp_temp_buf_get_format (params->paint_buf) == iterator_format);
+ }
+
+ template <class Derived>
+ struct State : Base::template State<Derived>
+ {
+ gint iterator_base;
+
+ GeglRectangle process_roi;
+
+ gfloat *out_pixel;
+ gfloat *in_pixel;
+ gfloat *mask_pixel;
+ gfloat *paint_pixel;
+ };
+
+ template <class Derived>
+ void
+ init (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area) const
+ {
+ state->iterator_base = gegl_buffer_iterator_add (iter, params->src_buffer,
+ area, 0, iterator_format,
+ GEGL_ACCESS_READ,
+ GEGL_ABYSS_NONE);
+
+ if (! has_comp_buffer ((const Derived *) this))
+ {
+ gegl_buffer_iterator_add (iter, params->dest_buffer, area, 0,
+ iterator_format,
+ GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
+ }
+
+ /* initialize the base class *after* initializing the iterator, to make
+ * sure that src_buffer is the primary buffer of the iterator, if no
+ * subclass added an iterator first.
+ */
+ Base::init (params, state, iter, roi, area);
+ }
+
+ template <class Derived>
+ void
+ init_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect) const
+ {
+ Base::init_step (params, state, iter, roi, area, rect);
+
+ state->in_pixel = (gfloat *) iter->items[state->iterator_base + 0].data;
+
+ state->paint_pixel = this->paint_data +
+ (rect->y - roi->y) * this->paint_stride +
+ (rect->x - roi->x) * 4;
+
+ if (! has_comp_mask (this) && has_mask_buffer_iterator (this))
+ {
+ state->mask_pixel =
+ (gfloat *) iter->items[mask_buffer_iterator (this, state)].data;
+ }
+
+ if (! has_comp_buffer ((const Derived *) this))
+ state->out_pixel = (gfloat *) iter->items[state->iterator_base + 1].data;
+
+ state->process_roi.x = rect->x;
+ state->process_roi.width = rect->width;
+ state->process_roi.height = 1;
+ }
+
+ template <class Derived>
+ void
+ process_row (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect,
+ gint y) const
+ {
+ Base::process_row (params, state, iter, roi, area, rect, y);
+
+ gfloat *mask_pixel;
+ gfloat *out_pixel;
+
+ if (has_comp_mask (this))
+ mask_pixel = comp_mask_data (this, state);
+ else if (has_mask_buffer_iterator (this))
+ mask_pixel = state->mask_pixel;
+ else
+ mask_pixel = NULL;
+
+ if (! has_comp_buffer ((const Derived *) this))
+ {
+ out_pixel = state->out_pixel;
+ }
+ else
+ {
+ out_pixel = comp_buffer_data (
+ (const Derived *) this,
+ (typename Derived::template State<Derived> *) state);
+ }
+
+ state->process_roi.y = y;
+
+ layer_mode.function ((GeglOperation*) &layer_mode,
+ state->in_pixel,
+ state->paint_pixel,
+ mask_pixel,
+ out_pixel,
+ rect->width,
+ &state->process_roi,
+ 0);
+
+ state->in_pixel += rect->width * 4;
+ state->paint_pixel += this->paint_stride;
+ if (! has_comp_mask (this) && has_mask_buffer_iterator (this))
+ state->mask_pixel += rect->width;
+ if (! has_comp_buffer ((const Derived *) this))
+ state->out_pixel += rect->width * 4;
+ }
+};
+
+static MandatoryAlgorithmDispatch<
+ DoLayerBlend,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_DO_LAYER_BLEND,
+ decltype (dispatch_paint_buf),
+ decltype (dispatch_mask_buffer_iterator)
+>
+dispatch_do_layer_blend;
+
+
+/* MaskComponents, dispatch_mask_components():
+ *
+ * An algorithm class, implementing the MASK_COMPONENTS algorithm.
+ */
+
+template <class Base>
+struct MaskComponents : Base
+{
+ static constexpr guint filter =
+ Base::filter |
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_MASK_COMPONENTS;
+
+ static constexpr gint max_n_iterators = Base::max_n_iterators + 1;
+
+ const Babl *format;
+ const Babl *comp_fish = NULL;
+
+ explicit
+ MaskComponents (const GimpPaintCoreLoopsParams *params) :
+ Base (params)
+ {
+ format = gimp_operation_mask_components_get_format (
+ gegl_buffer_get_format (params->dest_buffer));
+
+ if (format != this->iterator_format)
+ comp_fish = babl_fish (this->iterator_format, format);
+ }
+
+ template <class Derived>
+ struct State : Base::template State<Derived>
+ {
+ gint dest_buffer_iterator;
+
+ guint8 *dest_pixel;
+ guint8 *comp_pixel;
+ };
+
+ template <class Derived>
+ void
+ init (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area) const
+ {
+ state->dest_buffer_iterator = gegl_buffer_iterator_add (
+ iter, params->dest_buffer, area, 0, format,
+ GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE);
+
+ /* initialize the base class *after* initializing the iterator, to make
+ * sure that dest_buffer is the primary buffer of the iterator, if no
+ * subclass added an iterator first.
+ */
+ Base::init (params, state, iter, roi, area);
+ }
+
+ template <class Derived>
+ void
+ init_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect) const
+ {
+ Base::init_step (params, state, iter, roi, area, rect);
+
+ state->dest_pixel =
+ (guint8 *) iter->items[state->dest_buffer_iterator].data;
+
+ if (comp_fish)
+ {
+ state->comp_pixel = (guint8 *) gegl_scratch_alloc (
+ rect->width * babl_format_get_bytes_per_pixel (format));
+ }
+ }
+
+ template <class Derived>
+ void
+ process_row (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state,
+ GeglBufferIterator *iter,
+ const GeglRectangle *roi,
+ const GeglRectangle *area,
+ const GeglRectangle *rect,
+ gint y) const
+ {
+ Base::process_row (params, state, iter, roi, area, rect, y);
+
+ gpointer comp_pixel;
+
+ if (comp_fish)
+ {
+ babl_process (comp_fish,
+ state->comp_buffer_data, state->comp_pixel,
+ rect->width);
+
+ comp_pixel = state->comp_pixel;
+ }
+ else
+ {
+ comp_pixel = state->comp_buffer_data;
+ }
+
+ gimp_operation_mask_components_process (format,
+ state->dest_pixel, comp_pixel,
+ state->dest_pixel,
+ rect->width, params->affect);
+
+ state->dest_pixel += rect->width * babl_format_get_bytes_per_pixel (format);
+ }
+
+ template <class Derived>
+ void
+ finalize_step (const GimpPaintCoreLoopsParams *params,
+ State<Derived> *state) const
+ {
+ if (comp_fish)
+ gegl_scratch_free (state->comp_pixel);
+
+ Base::finalize_step (params, state);
+ }
+};
+
+static AlgorithmDispatch<
+ MaskComponents,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_MASK_COMPONENTS,
+ decltype (dispatch_temp_comp_buffer)
+>
+dispatch_mask_components;
+
+
+/* gimp_paint_core_loops_process():
+ *
+ * Performs the set of algorithms requested in 'algorithms', specified as a
+ * bitwise-OR of 'GimpPaintCoreLoopsAlgorithm' values, given the set of
+ * parameters 'params'.
+ *
+ * Note that the order in which the algorithms are performed is currently
+ * fixed, and follows their order of appearance in the
+ * 'GimpPaintCoreLoopsAlgorithm' enum.
+ */
+
+void
+gimp_paint_core_loops_process (const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms)
+{
+ GeglRectangle roi;
+
+ if (params->paint_buf)
+ {
+ roi.x = params->paint_buf_offset_x;
+ roi.y = params->paint_buf_offset_y;
+ roi.width = gimp_temp_buf_get_width (params->paint_buf);
+ roi.height = gimp_temp_buf_get_height (params->paint_buf);
+ }
+ else
+ {
+ roi.x = params->paint_buf_offset_x;
+ roi.y = params->paint_buf_offset_y;
+ roi.width = gimp_temp_buf_get_width (params->paint_mask) -
+ params->paint_mask_offset_x;
+ roi.height = gimp_temp_buf_get_height (params->paint_mask) -
+ params->paint_mask_offset_y;
+ }
+
+ dispatch (
+ [&] (auto algorithm_type)
+ {
+ using Algorithm = typename decltype (algorithm_type)::type;
+ using State = typename Algorithm::template State<Algorithm>;
+
+ Algorithm algorithm (params);
+
+ gegl_parallel_distribute_area (
+ &roi, PIXELS_PER_THREAD,
+ [=] (const GeglRectangle *area)
+ {
+ State state;
+ gint y;
+
+ if (Algorithm::max_n_iterators > 0)
+ {
+ GeglBufferIterator *iter;
+
+ iter = gegl_buffer_iterator_empty_new (
+ Algorithm::max_n_iterators);
+
+ algorithm.init (params, &state, iter, &roi, area);
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ const GeglRectangle *rect = &iter->items[0].roi;
+
+ algorithm.init_step (params, &state, iter, &roi, area, rect);
+
+ for (y = 0; y < rect->height; y++)
+ {
+ algorithm.process_row (params, &state,
+ iter, &roi, area, rect,
+ rect->y + y);
+ }
+
+ algorithm.finalize_step (params, &state);
+ }
+
+ algorithm.finalize (params, &state);
+ }
+ else
+ {
+ algorithm.init (params, &state, NULL, &roi, area);
+ algorithm.init_step (params, &state, NULL, &roi, area, area);
+
+ for (y = 0; y < area->height; y++)
+ {
+ algorithm.process_row (params, &state,
+ NULL, &roi, area, area,
+ area->y + y);
+ }
+
+ algorithm.finalize_step (params, &state);
+ algorithm.finalize (params, &state);
+ }
+ });
+ },
+ params, algorithms, identity<AlgorithmBase> (),
+ dispatch_combine_paint_mask_to_canvas_buffer_to_paint_buf_alpha,
+ dispatch_combine_paint_mask_to_canvas_buffer,
+ dispatch_canvas_buffer_to_paint_buf_alpha,
+ dispatch_paint_mask_to_paint_buf_alpha,
+ dispatch_canvas_buffer_to_comp_mask,
+ dispatch_paint_mask_to_comp_mask,
+ dispatch_do_layer_blend,
+ dispatch_mask_components);
+}
diff --git a/app/paint/gimppaintcore-loops.h b/app/paint/gimppaintcore-loops.h
new file mode 100644
index 0000000..1d7e1fd
--- /dev/null
+++ b/app/paint/gimppaintcore-loops.h
@@ -0,0 +1,70 @@
+/* GIMP - The GNU Image Manipulation Program
+ * 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 __GIMP_PAINT_CORE_LOOPS_H__
+#define __GIMP_PAINT_CORE_LOOPS_H__
+
+
+typedef enum
+{
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_NONE = 0,
+
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_COMBINE_PAINT_MASK_TO_CANVAS_BUFFER = 1 << 0,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_PAINT_BUF_ALPHA = 1 << 1,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_PAINT_BUF_ALPHA = 1 << 2,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_COMP_MASK = 1 << 3,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_COMP_MASK = 1 << 4,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_DO_LAYER_BLEND = 1 << 5,
+ GIMP_PAINT_CORE_LOOPS_ALGORITHM_MASK_COMPONENTS = 1 << 6
+} GimpPaintCoreLoopsAlgorithm;
+
+
+typedef struct
+{
+ GeglBuffer *canvas_buffer;
+
+ GimpTempBuf *paint_buf;
+ gint paint_buf_offset_x;
+ gint paint_buf_offset_y;
+
+ const GimpTempBuf *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;
+
+ GimpLayerMode paint_mode;
+
+ GimpComponentMask affect;
+} GimpPaintCoreLoopsParams;
+
+
+void gimp_paint_core_loops_process (const GimpPaintCoreLoopsParams *params,
+ GimpPaintCoreLoopsAlgorithm algorithms);
+
+
+#endif /* __GIMP_PAINT_CORE_LOOPS_H__ */
diff --git a/app/paint/gimppaintcore-stroke.c b/app/paint/gimppaintcore-stroke.c
new file mode 100644
index 0000000..3beac93
--- /dev/null
+++ b/app/paint/gimppaintcore-stroke.c
@@ -0,0 +1,388 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "paint-types.h"
+
+#include "core/gimpboundary.h"
+#include "core/gimpdrawable.h"
+#include "core/gimperror.h"
+#include "core/gimpcoords.h"
+
+#include "vectors/gimpstroke.h"
+#include "vectors/gimpvectors.h"
+
+#include "gimppaintcore.h"
+#include "gimppaintcore-stroke.h"
+#include "gimppaintoptions.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_paint_core_stroke_emulate_dynamics (GimpCoords *coords,
+ gint length);
+
+
+static const GimpCoords default_coords = GIMP_COORDS_DEFAULT_VALUES;
+
+
+gboolean
+gimp_paint_core_stroke (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpCoords *strokes,
+ gint n_strokes,
+ gboolean push_undo,
+ GError **error)
+{
+ g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), FALSE);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), FALSE);
+ g_return_val_if_fail (GIMP_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);
+
+ if (gimp_paint_core_start (core, drawable, paint_options, &strokes[0],
+ error))
+ {
+ gint i;
+
+ core->last_coords = strokes[0];
+
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_PAINT_STATE_INIT, 0);
+
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_PAINT_STATE_MOTION, 0);
+
+ for (i = 1; i < n_strokes; i++)
+ {
+ gimp_paint_core_interpolate (core, drawable, paint_options,
+ &strokes[i], 0);
+ }
+
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_PAINT_STATE_FINISH, 0);
+
+ gimp_paint_core_finish (core, drawable, push_undo);
+
+ gimp_paint_core_cleanup (core);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean
+gimp_paint_core_stroke_boundary (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ gboolean emulate_dynamics,
+ const GimpBoundSeg *bound_segs,
+ gint n_bound_segs,
+ gint offset_x,
+ gint offset_y,
+ gboolean push_undo,
+ GError **error)
+{
+ GimpBoundSeg *stroke_segs;
+ gint n_stroke_segs;
+ gint off_x;
+ gint off_y;
+ GimpCoords *coords;
+ gboolean initialized = FALSE;
+ gint n_coords;
+ gint seg;
+ gint s;
+
+ g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), FALSE);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), FALSE);
+ g_return_val_if_fail (GIMP_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 = gimp_boundary_sort (bound_segs, n_bound_segs,
+ &n_stroke_segs);
+
+ if (n_stroke_segs == 0)
+ return TRUE;
+
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ off_x -= offset_x;
+ off_y -= offset_y;
+
+ coords = g_new0 (GimpCoords, 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 - off_x + 0.5);
+ coords[n_coords].y = (gdouble) (stroke_segs[0].y1 - off_y + 0.5);
+
+ n_coords++;
+
+ 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 - off_x + 0.5);
+ coords[n_coords].y = (gdouble) (stroke_segs[seg].y1 - off_y + 0.5);
+
+ n_coords++;
+ seg++;
+ }
+
+ /* Close the stroke points up */
+ coords[n_coords] = coords[0];
+
+ n_coords++;
+
+ if (emulate_dynamics)
+ gimp_paint_core_stroke_emulate_dynamics (coords, n_coords);
+
+ if (initialized ||
+ gimp_paint_core_start (core, drawable, paint_options, &coords[0],
+ error))
+ {
+ gint i;
+
+ initialized = TRUE;
+
+ core->cur_coords = coords[0];
+ core->last_coords = coords[0];
+
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_PAINT_STATE_INIT, 0);
+
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_PAINT_STATE_MOTION, 0);
+
+ for (i = 1; i < n_coords; i++)
+ {
+ gimp_paint_core_interpolate (core, drawable, paint_options,
+ &coords[i], 0);
+ }
+
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_PAINT_STATE_FINISH, 0);
+ }
+ else
+ {
+ break;
+ }
+
+ n_coords = 0;
+ seg++;
+
+ coords[n_coords] = default_coords;
+ coords[n_coords].x = (gdouble) (stroke_segs[seg].x1 - off_x + 0.5);
+ coords[n_coords].y = (gdouble) (stroke_segs[seg].y1 - off_y + 0.5);
+
+ n_coords++;
+ }
+
+ if (initialized)
+ {
+ gimp_paint_core_finish (core, drawable, push_undo);
+
+ gimp_paint_core_cleanup (core);
+ }
+
+ g_free (coords);
+ g_free (stroke_segs);
+
+ return initialized;
+}
+
+gboolean
+gimp_paint_core_stroke_vectors (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ gboolean emulate_dynamics,
+ GimpVectors *vectors,
+ gboolean push_undo,
+ GError **error)
+{
+ GList *stroke;
+ gboolean initialized = FALSE;
+ gboolean due_to_lack_of_points = FALSE;
+ gint off_x, off_y;
+ gint vectors_off_x, vectors_off_y;
+
+ g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), FALSE);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), FALSE);
+ g_return_val_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options), FALSE);
+ g_return_val_if_fail (GIMP_IS_VECTORS (vectors), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ gimp_item_get_offset (GIMP_ITEM (vectors), &vectors_off_x, &vectors_off_y);
+ gimp_item_get_offset (GIMP_ITEM (drawable), &off_x, &off_y);
+
+ off_x -= vectors_off_x;
+ off_y -= vectors_off_y;
+
+ for (stroke = vectors->strokes->head;
+ stroke;
+ stroke = stroke->next)
+ {
+ GArray *coords;
+ gboolean closed;
+
+ coords = gimp_stroke_interpolate (GIMP_STROKE (stroke->data),
+ 1.0, &closed);
+
+ if (coords && coords->len)
+ {
+ gint i;
+
+ for (i = 0; i < coords->len; i++)
+ {
+ g_array_index (coords, GimpCoords, i).x -= off_x;
+ g_array_index (coords, GimpCoords, i).y -= off_y;
+ }
+
+ if (emulate_dynamics)
+ gimp_paint_core_stroke_emulate_dynamics ((GimpCoords *) coords->data,
+ coords->len);
+
+ if (initialized ||
+ gimp_paint_core_start (core, drawable, paint_options,
+ &g_array_index (coords, GimpCoords, 0),
+ error))
+ {
+ initialized = TRUE;
+
+ core->cur_coords = g_array_index (coords, GimpCoords, 0);
+ core->last_coords = g_array_index (coords, GimpCoords, 0);
+
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_PAINT_STATE_INIT, 0);
+
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_PAINT_STATE_MOTION, 0);
+
+ for (i = 1; i < coords->len; i++)
+ {
+ gimp_paint_core_interpolate (core, drawable, paint_options,
+ &g_array_index (coords, GimpCoords, i),
+ 0);
+ }
+
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_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)
+ {
+ gimp_paint_core_finish (core, drawable, push_undo);
+
+ gimp_paint_core_cleanup (core);
+ }
+
+ if (! initialized && due_to_lack_of_points && *error == NULL)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Not enough points to stroke"));
+ }
+
+ return initialized;
+}
+
+static void
+gimp_paint_core_stroke_emulate_dynamics (GimpCoords *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 = gimp_coords_direction (&coords[i-1], &coords[i]);
+ }
+
+ coords[0].direction = coords[1].direction;
+ }
+}
diff --git a/app/paint/gimppaintcore-stroke.h b/app/paint/gimppaintcore-stroke.h
new file mode 100644
index 0000000..e52b839
--- /dev/null
+++ b/app/paint/gimppaintcore-stroke.h
@@ -0,0 +1,48 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PAINT_CORE_STROKE_H__
+#define __GIMP_PAINT_CORE_STROKE_H__
+
+
+gboolean gimp_paint_core_stroke (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpCoords *strokes,
+ gint n_strokes,
+ gboolean push_undo,
+ GError **error);
+gboolean gimp_paint_core_stroke_boundary (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ gboolean emulate_dynamics,
+ const GimpBoundSeg *bound_segs,
+ gint n_bound_segs,
+ gint offset_x,
+ gint offset_y,
+ gboolean push_undo,
+ GError **error);
+gboolean gimp_paint_core_stroke_vectors (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ gboolean emulate_dynamics,
+ GimpVectors *vectors,
+ gboolean push_undo,
+ GError **error);
+
+
+#endif /* __GIMP_PAINT_CORE_STROKE_H__ */
diff --git a/app/paint/gimppaintcore.c b/app/paint/gimppaintcore.c
new file mode 100644
index 0000000..94f684b
--- /dev/null
+++ b/app/paint/gimppaintcore.c
@@ -0,0 +1,1242 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ * 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/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "paint-types.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "gegl/gimp-gegl-loops.h"
+#include "gegl/gimp-gegl-nodes.h"
+#include "gegl/gimp-gegl-utils.h"
+#include "gegl/gimpapplicator.h"
+
+#include "core/gimp.h"
+#include "core/gimp-utils.h"
+#include "core/gimpchannel.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-guides.h"
+#include "core/gimpimage-symmetry.h"
+#include "core/gimpimage-undo.h"
+#include "core/gimppickable.h"
+#include "core/gimpprojection.h"
+#include "core/gimpsymmetry.h"
+#include "core/gimptempbuf.h"
+
+#include "gimppaintcore.h"
+#include "gimppaintcoreundo.h"
+#include "gimppaintcore-loops.h"
+#include "gimppaintoptions.h"
+
+#include "gimpairbrush.h"
+
+#include "gimp-intl.h"
+
+
+#define STROKE_BUFFER_INIT_SIZE 2000
+
+enum
+{
+ PROP_0,
+ PROP_UNDO_DESC
+};
+
+
+/* local function prototypes */
+
+static void gimp_paint_core_finalize (GObject *object);
+static void gimp_paint_core_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_paint_core_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gboolean gimp_paint_core_real_start (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GError **error);
+static gboolean gimp_paint_core_real_pre_paint (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *options,
+ GimpPaintState paint_state,
+ guint32 time);
+static void gimp_paint_core_real_paint (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time);
+static void gimp_paint_core_real_post_paint (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *options,
+ GimpPaintState paint_state,
+ guint32 time);
+static void gimp_paint_core_real_interpolate (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *options,
+ guint32 time);
+static GeglBuffer *
+ gimp_paint_core_real_get_paint_buffer (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *options,
+ GimpLayerMode paint_mode,
+ const GimpCoords *coords,
+ gint *paint_buffer_x,
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height);
+static GimpUndo* gimp_paint_core_real_push_undo (GimpPaintCore *core,
+ GimpImage *image,
+ const gchar *undo_desc);
+
+
+G_DEFINE_TYPE (GimpPaintCore, gimp_paint_core, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_paint_core_parent_class
+
+static gint global_core_ID = 1;
+
+
+static void
+gimp_paint_core_class_init (GimpPaintCoreClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gimp_paint_core_finalize;
+ object_class->set_property = gimp_paint_core_set_property;
+ object_class->get_property = gimp_paint_core_get_property;
+
+ klass->start = gimp_paint_core_real_start;
+ klass->pre_paint = gimp_paint_core_real_pre_paint;
+ klass->paint = gimp_paint_core_real_paint;
+ klass->post_paint = gimp_paint_core_real_post_paint;
+ klass->interpolate = gimp_paint_core_real_interpolate;
+ klass->get_paint_buffer = gimp_paint_core_real_get_paint_buffer;
+ klass->push_undo = gimp_paint_core_real_push_undo;
+
+ g_object_class_install_property (object_class, PROP_UNDO_DESC,
+ g_param_spec_string ("undo-desc", NULL, NULL,
+ _("Paint"),
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_paint_core_init (GimpPaintCore *core)
+{
+ core->ID = global_core_ID++;
+}
+
+static void
+gimp_paint_core_finalize (GObject *object)
+{
+ GimpPaintCore *core = GIMP_PAINT_CORE (object);
+
+ gimp_paint_core_cleanup (core);
+
+ g_clear_pointer (&core->undo_desc, g_free);
+
+ if (core->stroke_buffer)
+ {
+ g_array_free (core->stroke_buffer, TRUE);
+ core->stroke_buffer = NULL;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_paint_core_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpPaintCore *core = GIMP_PAINT_CORE (object);
+
+ switch (property_id)
+ {
+ case PROP_UNDO_DESC:
+ g_free (core->undo_desc);
+ core->undo_desc = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_paint_core_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpPaintCore *core = GIMP_PAINT_CORE (object);
+
+ switch (property_id)
+ {
+ case PROP_UNDO_DESC:
+ g_value_set_string (value, core->undo_desc);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gimp_paint_core_real_start (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GError **error)
+{
+ return TRUE;
+}
+
+static gboolean
+gimp_paint_core_real_pre_paint (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpPaintState paint_state,
+ guint32 time)
+{
+ return TRUE;
+}
+
+static void
+gimp_paint_core_real_paint (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time)
+{
+}
+
+static void
+gimp_paint_core_real_post_paint (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpPaintState paint_state,
+ guint32 time)
+{
+}
+
+static void
+gimp_paint_core_real_interpolate (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ guint32 time)
+{
+ gimp_paint_core_paint (core, drawable, paint_options,
+ GIMP_PAINT_STATE_MOTION, time);
+
+ core->last_coords = core->cur_coords;
+}
+
+static GeglBuffer *
+gimp_paint_core_real_get_paint_buffer (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpLayerMode paint_mode,
+ const GimpCoords *coords,
+ gint *paint_buffer_x,
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height)
+{
+ return NULL;
+}
+
+static GimpUndo *
+gimp_paint_core_real_push_undo (GimpPaintCore *core,
+ GimpImage *image,
+ const gchar *undo_desc)
+{
+ return gimp_image_undo_push (image, GIMP_TYPE_PAINT_CORE_UNDO,
+ GIMP_UNDO_PAINT, undo_desc,
+ 0,
+ "paint-core", core,
+ NULL);
+}
+
+
+/* public functions */
+
+void
+gimp_paint_core_paint (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpPaintState paint_state,
+ guint32 time)
+{
+ GimpPaintCoreClass *core_class;
+
+ g_return_if_fail (GIMP_IS_PAINT_CORE (core));
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options));
+
+ core_class = GIMP_PAINT_CORE_GET_CLASS (core);
+
+ if (core_class->pre_paint (core, drawable,
+ paint_options,
+ paint_state, time))
+ {
+ GimpSymmetry *sym;
+ GimpImage *image;
+ GimpItem *item;
+
+ item = GIMP_ITEM (drawable);
+ image = gimp_item_get_image (item);
+
+ if (paint_state == GIMP_PAINT_STATE_MOTION)
+ {
+ /* Save coordinates for gimp_paint_core_interpolate() */
+ core->last_paint.x = core->cur_coords.x;
+ core->last_paint.y = core->cur_coords.y;
+ }
+
+ sym = g_object_ref (gimp_image_get_active_symmetry (image));
+ gimp_symmetry_set_origin (sym, drawable, &core->cur_coords);
+
+ core_class->paint (core, drawable,
+ paint_options,
+ sym, paint_state, time);
+
+ gimp_symmetry_clear_origin (sym);
+ g_object_unref (sym);
+
+ core_class->post_paint (core, drawable,
+ paint_options,
+ paint_state, time);
+ }
+}
+
+gboolean
+gimp_paint_core_start (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GError **error)
+{
+ GimpImage *image;
+ GimpItem *item;
+ GimpChannel *mask;
+
+ g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), FALSE);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), FALSE);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), FALSE);
+ g_return_val_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options), FALSE);
+ g_return_val_if_fail (coords != NULL, FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ item = GIMP_ITEM (drawable);
+ image = gimp_item_get_image (item);
+
+ if (core->stroke_buffer)
+ {
+ g_array_free (core->stroke_buffer, TRUE);
+ core->stroke_buffer = NULL;
+ }
+
+ core->stroke_buffer = g_array_sized_new (TRUE, TRUE,
+ sizeof (GimpCoords),
+ STROKE_BUFFER_INIT_SIZE);
+
+ /* remember the last stroke's endpoint for later undo */
+ core->start_coords = core->last_coords;
+
+ core->cur_coords = *coords;
+
+ if (! GIMP_PAINT_CORE_GET_CLASS (core)->start (core, drawable,
+ paint_options,
+ coords, error))
+ {
+ return FALSE;
+ }
+
+ /* Allocate the undo structure */
+ if (core->undo_buffer)
+ g_object_unref (core->undo_buffer);
+
+ core->undo_buffer = gimp_gegl_buffer_dup (gimp_drawable_get_buffer (drawable));
+
+ /* Set the image pickable */
+ if (! core->show_all)
+ core->image_pickable = GIMP_PICKABLE (image);
+ else
+ core->image_pickable = GIMP_PICKABLE (gimp_image_get_projection (image));
+
+ /* Allocate the saved proj structure */
+ g_clear_object (&core->saved_proj_buffer);
+
+ if (core->use_saved_proj)
+ {
+ GeglBuffer *buffer = gimp_pickable_get_buffer (core->image_pickable);
+
+ core->saved_proj_buffer = gimp_gegl_buffer_dup (buffer);
+ }
+
+ /* Allocate the canvas blocks structure */
+ if (core->canvas_buffer)
+ g_object_unref (core->canvas_buffer);
+
+ core->canvas_buffer =
+ gegl_buffer_new (GEGL_RECTANGLE (0, 0,
+ gimp_item_get_width (item),
+ gimp_item_get_height (item)),
+ babl_format ("Y float"));
+
+ /* Get the initial undo extents */
+
+ core->x1 = core->x2 = core->cur_coords.x;
+ core->y1 = core->y2 = core->cur_coords.y;
+
+ core->last_paint.x = -1e6;
+ core->last_paint.y = -1e6;
+
+ mask = gimp_image_get_mask (image);
+
+ /* don't apply the mask to itself and don't apply an empty mask */
+ if (GIMP_DRAWABLE (mask) != drawable && ! gimp_channel_is_empty (mask))
+ {
+ GeglBuffer *mask_buffer;
+ gint offset_x;
+ gint offset_y;
+
+ mask_buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
+ gimp_item_get_offset (item, &offset_x, &offset_y);
+
+ core->mask_buffer = g_object_ref (mask_buffer);
+ core->mask_x_offset = -offset_x;
+ core->mask_y_offset = -offset_y;
+ }
+ else
+ {
+ core->mask_buffer = NULL;
+ }
+
+ if (paint_options->use_applicator)
+ {
+ core->applicator = gimp_applicator_new (NULL);
+
+ if (core->mask_buffer)
+ {
+ gimp_applicator_set_mask_buffer (core->applicator,
+ core->mask_buffer);
+ gimp_applicator_set_mask_offset (core->applicator,
+ core->mask_x_offset,
+ core->mask_y_offset);
+ }
+
+ gimp_applicator_set_affect (core->applicator,
+ gimp_drawable_get_active_mask (drawable));
+ gimp_applicator_set_dest_buffer (core->applicator,
+ gimp_drawable_get_buffer (drawable));
+ }
+
+ /* Freeze the drawable preview so that it isn't constantly updated. */
+ gimp_viewable_preview_freeze (GIMP_VIEWABLE (drawable));
+
+ return TRUE;
+}
+
+void
+gimp_paint_core_finish (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ gboolean push_undo)
+{
+ GimpImage *image;
+
+ g_return_if_fail (GIMP_IS_PAINT_CORE (core));
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+
+ g_clear_object (&core->applicator);
+
+ if (core->stroke_buffer)
+ {
+ g_array_free (core->stroke_buffer, TRUE);
+ core->stroke_buffer = NULL;
+ }
+
+ g_clear_object (&core->mask_buffer);
+
+ image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ /* Determine if any part of the image has been altered--
+ * if nothing has, then just return...
+ */
+ if ((core->x2 == core->x1) || (core->y2 == core->y1))
+ {
+ gimp_viewable_preview_thaw (GIMP_VIEWABLE (drawable));
+ return;
+ }
+
+ if (push_undo)
+ {
+ GeglBuffer *buffer;
+ GeglRectangle rect;
+
+ gimp_rectangle_intersect (core->x1, core->y1,
+ core->x2 - core->x1, core->y2 - core->y1,
+ 0, 0,
+ gimp_item_get_width (GIMP_ITEM (drawable)),
+ gimp_item_get_height (GIMP_ITEM (drawable)),
+ &rect.x, &rect.y, &rect.width, &rect.height);
+
+ gegl_rectangle_align_to_buffer (&rect, &rect, core->undo_buffer,
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+
+ gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_PAINT,
+ core->undo_desc);
+
+ GIMP_PAINT_CORE_GET_CLASS (core)->push_undo (core, image, NULL);
+
+ buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, rect.width, rect.height),
+ gimp_drawable_get_format (drawable));
+
+ gimp_gegl_buffer_copy (core->undo_buffer,
+ &rect,
+ GEGL_ABYSS_NONE,
+ buffer,
+ GEGL_RECTANGLE (0, 0, 0, 0));
+
+ gimp_drawable_push_undo (drawable, NULL,
+ buffer, rect.x, rect.y, rect.width, rect.height);
+
+ g_object_unref (buffer);
+
+ gimp_image_undo_group_end (image);
+ }
+
+ core->image_pickable = NULL;
+
+ g_clear_object (&core->undo_buffer);
+ g_clear_object (&core->saved_proj_buffer);
+
+ gimp_viewable_preview_thaw (GIMP_VIEWABLE (drawable));
+}
+
+void
+gimp_paint_core_cancel (GimpPaintCore *core,
+ GimpDrawable *drawable)
+{
+ gint x, y;
+ gint width, height;
+
+ g_return_if_fail (GIMP_IS_PAINT_CORE (core));
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+
+ /* Determine if any part of the image has been altered--
+ * if nothing has, then just return...
+ */
+ if ((core->x2 == core->x1) || (core->y2 == core->y1))
+ return;
+
+ if (gimp_rectangle_intersect (core->x1, core->y1,
+ core->x2 - core->x1,
+ core->y2 - core->y1,
+ 0, 0,
+ gimp_item_get_width (GIMP_ITEM (drawable)),
+ gimp_item_get_height (GIMP_ITEM (drawable)),
+ &x, &y, &width, &height))
+ {
+ GeglRectangle rect;
+
+ gegl_rectangle_align_to_buffer (&rect,
+ GEGL_RECTANGLE (x, y, width, height),
+ gimp_drawable_get_buffer (drawable),
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+
+ gimp_gegl_buffer_copy (core->undo_buffer,
+ &rect,
+ GEGL_ABYSS_NONE,
+ gimp_drawable_get_buffer (drawable),
+ &rect);
+ }
+
+ g_clear_object (&core->undo_buffer);
+ g_clear_object (&core->saved_proj_buffer);
+
+ gimp_drawable_update (drawable, x, y, width, height);
+
+ gimp_viewable_preview_thaw (GIMP_VIEWABLE (drawable));
+}
+
+void
+gimp_paint_core_cleanup (GimpPaintCore *core)
+{
+ g_return_if_fail (GIMP_IS_PAINT_CORE (core));
+
+ g_clear_object (&core->undo_buffer);
+ g_clear_object (&core->saved_proj_buffer);
+ g_clear_object (&core->canvas_buffer);
+ g_clear_object (&core->paint_buffer);
+}
+
+void
+gimp_paint_core_interpolate (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ guint32 time)
+{
+ g_return_if_fail (GIMP_IS_PAINT_CORE (core));
+ g_return_if_fail (GIMP_IS_DRAWABLE (drawable));
+ g_return_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)));
+ g_return_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options));
+ g_return_if_fail (coords != NULL);
+
+ core->cur_coords = *coords;
+
+ GIMP_PAINT_CORE_GET_CLASS (core)->interpolate (core, drawable,
+ paint_options, time);
+}
+
+void
+gimp_paint_core_set_show_all (GimpPaintCore *core,
+ gboolean show_all)
+{
+ g_return_if_fail (GIMP_IS_PAINT_CORE (core));
+
+ core->show_all = show_all;
+}
+
+gboolean
+gimp_paint_core_get_show_all (GimpPaintCore *core)
+{
+ g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), FALSE);
+
+ return core->show_all;
+}
+
+void
+gimp_paint_core_set_current_coords (GimpPaintCore *core,
+ const GimpCoords *coords)
+{
+ g_return_if_fail (GIMP_IS_PAINT_CORE (core));
+ g_return_if_fail (coords != NULL);
+
+ core->cur_coords = *coords;
+}
+
+void
+gimp_paint_core_get_current_coords (GimpPaintCore *core,
+ GimpCoords *coords)
+{
+ g_return_if_fail (GIMP_IS_PAINT_CORE (core));
+ g_return_if_fail (coords != NULL);
+
+ *coords = core->cur_coords;
+}
+
+void
+gimp_paint_core_set_last_coords (GimpPaintCore *core,
+ const GimpCoords *coords)
+{
+ g_return_if_fail (GIMP_IS_PAINT_CORE (core));
+ g_return_if_fail (coords != NULL);
+
+ core->last_coords = *coords;
+}
+
+void
+gimp_paint_core_get_last_coords (GimpPaintCore *core,
+ GimpCoords *coords)
+{
+ g_return_if_fail (GIMP_IS_PAINT_CORE (core));
+ g_return_if_fail (coords != NULL);
+
+ *coords = core->last_coords;
+}
+
+/**
+ * gimp_paint_core_round_line:
+ * @core: the #GimpPaintCore
+ * @options: the #GimpPaintOptions to use
+ * @constrain_15_degrees: the modifier state
+ * @constrain_offset_angle: the angle by which to offset the lines, in degrees
+ * @constrain_xres: the horizontal resolution
+ * @constrain_yres: the vertical resolution
+ *
+ * Adjusts core->last_coords and core_cur_coords in preparation to
+ * drawing a straight line. If @center_pixels is TRUE the endpoints
+ * get pushed to the center of the pixels. This avoids artifacts
+ * for e.g. the hard mode. The rounding of the slope to 15 degree
+ * steps if ctrl is pressed happens, as does rounding the start and
+ * end coordinates (which may be fractional in high zoom modes) to
+ * the center of pixels.
+ **/
+void
+gimp_paint_core_round_line (GimpPaintCore *core,
+ GimpPaintOptions *paint_options,
+ gboolean constrain_15_degrees,
+ gdouble constrain_offset_angle,
+ gdouble constrain_xres,
+ gdouble constrain_yres)
+{
+ g_return_if_fail (GIMP_IS_PAINT_CORE (core));
+ g_return_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options));
+
+ if (gimp_paint_options_get_brush_mode (paint_options) == GIMP_BRUSH_HARD)
+ {
+ core->last_coords.x = floor (core->last_coords.x) + 0.5;
+ core->last_coords.y = floor (core->last_coords.y) + 0.5;
+ core->cur_coords.x = floor (core->cur_coords.x ) + 0.5;
+ core->cur_coords.y = floor (core->cur_coords.y ) + 0.5;
+ }
+
+ if (constrain_15_degrees)
+ gimp_constrain_line (core->last_coords.x, core->last_coords.y,
+ &core->cur_coords.x, &core->cur_coords.y,
+ GIMP_CONSTRAIN_LINE_15_DEGREES,
+ constrain_offset_angle,
+ constrain_xres, constrain_yres);
+}
+
+
+/* protected functions */
+
+GeglBuffer *
+gimp_paint_core_get_paint_buffer (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpLayerMode paint_mode,
+ const GimpCoords *coords,
+ gint *paint_buffer_x,
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height)
+{
+ GeglBuffer *paint_buffer;
+
+ g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), NULL);
+ g_return_val_if_fail (GIMP_IS_DRAWABLE (drawable), NULL);
+ g_return_val_if_fail (gimp_item_is_attached (GIMP_ITEM (drawable)), NULL);
+ g_return_val_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options), NULL);
+ g_return_val_if_fail (coords != NULL, NULL);
+ g_return_val_if_fail (paint_buffer_x != NULL, NULL);
+ g_return_val_if_fail (paint_buffer_y != NULL, NULL);
+
+ paint_buffer =
+ GIMP_PAINT_CORE_GET_CLASS (core)->get_paint_buffer (core, drawable,
+ paint_options,
+ paint_mode,
+ coords,
+ paint_buffer_x,
+ paint_buffer_y,
+ paint_width,
+ paint_height);
+
+ core->paint_buffer_x = *paint_buffer_x;
+ core->paint_buffer_y = *paint_buffer_y;
+
+ return paint_buffer;
+}
+
+GimpPickable *
+gimp_paint_core_get_image_pickable (GimpPaintCore *core)
+{
+ g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), NULL);
+ g_return_val_if_fail (core->image_pickable != NULL, NULL);
+
+ return core->image_pickable;
+}
+
+GeglBuffer *
+gimp_paint_core_get_orig_image (GimpPaintCore *core)
+{
+ g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), NULL);
+ g_return_val_if_fail (core->undo_buffer != NULL, NULL);
+
+ return core->undo_buffer;
+}
+
+GeglBuffer *
+gimp_paint_core_get_orig_proj (GimpPaintCore *core)
+{
+ g_return_val_if_fail (GIMP_IS_PAINT_CORE (core), NULL);
+ g_return_val_if_fail (core->saved_proj_buffer != NULL, NULL);
+
+ return core->saved_proj_buffer;
+}
+
+void
+gimp_paint_core_paste (GimpPaintCore *core,
+ const GimpTempBuf *paint_mask,
+ gint paint_mask_offset_x,
+ gint paint_mask_offset_y,
+ GimpDrawable *drawable,
+ gdouble paint_opacity,
+ gdouble image_opacity,
+ GimpLayerMode paint_mode,
+ GimpPaintApplicationMode mode)
+{
+ gint width = gegl_buffer_get_width (core->paint_buffer);
+ gint height = gegl_buffer_get_height (core->paint_buffer);
+ GimpComponentMask affect = gimp_drawable_get_active_mask (drawable);
+
+ if (! affect)
+ return;
+
+ if (core->applicator)
+ {
+ /* If the mode is CONSTANT:
+ * combine the canvas buffer and the paint mask to the paint buffer
+ */
+ if (mode == GIMP_PAINT_CONSTANT)
+ {
+ /* Some tools (ink) paint the mask to paint_core->canvas_buffer
+ * directly. Don't need to copy it in this case.
+ */
+ if (paint_mask != NULL)
+ {
+ GeglBuffer *paint_mask_buffer =
+ gimp_temp_buf_create_buffer ((GimpTempBuf *) paint_mask);
+
+ gimp_gegl_combine_mask_weird (paint_mask_buffer,
+ GEGL_RECTANGLE (paint_mask_offset_x,
+ paint_mask_offset_y,
+ width, height),
+ core->canvas_buffer,
+ GEGL_RECTANGLE (core->paint_buffer_x,
+ core->paint_buffer_y,
+ width, height),
+ paint_opacity,
+ GIMP_IS_AIRBRUSH (core));
+
+ g_object_unref (paint_mask_buffer);
+ }
+
+ gimp_gegl_apply_mask (core->canvas_buffer,
+ GEGL_RECTANGLE (core->paint_buffer_x,
+ core->paint_buffer_y,
+ width, height),
+ core->paint_buffer,
+ GEGL_RECTANGLE (0, 0, width, height),
+ 1.0);
+
+ gimp_applicator_set_src_buffer (core->applicator,
+ core->undo_buffer);
+ }
+ /* Otherwise:
+ * combine the paint mask to the paint buffer directly
+ */
+ else
+ {
+ GeglBuffer *paint_mask_buffer =
+ gimp_temp_buf_create_buffer ((GimpTempBuf *) paint_mask);
+
+ gimp_gegl_apply_mask (paint_mask_buffer,
+ GEGL_RECTANGLE (paint_mask_offset_x,
+ paint_mask_offset_y,
+ width, height),
+ core->paint_buffer,
+ GEGL_RECTANGLE (0, 0, width, height),
+ paint_opacity);
+
+ g_object_unref (paint_mask_buffer);
+
+ gimp_applicator_set_src_buffer (core->applicator,
+ gimp_drawable_get_buffer (drawable));
+ }
+
+ gimp_applicator_set_apply_buffer (core->applicator,
+ core->paint_buffer);
+ gimp_applicator_set_apply_offset (core->applicator,
+ core->paint_buffer_x,
+ core->paint_buffer_y);
+
+ gimp_applicator_set_opacity (core->applicator, image_opacity);
+ gimp_applicator_set_mode (core->applicator, paint_mode,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ gimp_layer_mode_get_paint_composite_mode (paint_mode));
+
+ /* apply the paint area to the image */
+ gimp_applicator_blit (core->applicator,
+ GEGL_RECTANGLE (core->paint_buffer_x,
+ core->paint_buffer_y,
+ width, height));
+ }
+ else
+ {
+ GimpPaintCoreLoopsParams params = {};
+ GimpPaintCoreLoopsAlgorithm algorithms = GIMP_PAINT_CORE_LOOPS_ALGORITHM_NONE;
+
+ params.paint_buf = gimp_gegl_buffer_get_temp_buf (core->paint_buffer);
+ params.paint_buf_offset_x = core->paint_buffer_x;
+ params.paint_buf_offset_y = core->paint_buffer_y;
+
+ if (! params.paint_buf)
+ return;
+
+ params.dest_buffer = gimp_drawable_get_buffer (drawable);
+
+ if (mode == GIMP_PAINT_CONSTANT)
+ {
+ params.canvas_buffer = core->canvas_buffer;
+
+ /* This step is skipped by the ink tool, which writes
+ * directly to canvas_buffer
+ */
+ if (paint_mask != NULL)
+ {
+ /* Mix paint mask and canvas_buffer */
+ params.paint_mask = paint_mask;
+ params.paint_mask_offset_x = paint_mask_offset_x;
+ params.paint_mask_offset_y = paint_mask_offset_y;
+ params.stipple = GIMP_IS_AIRBRUSH (core);
+ params.paint_opacity = paint_opacity;
+
+ algorithms |= GIMP_PAINT_CORE_LOOPS_ALGORITHM_COMBINE_PAINT_MASK_TO_CANVAS_BUFFER;
+ }
+
+ /* Write canvas_buffer to paint_buf */
+ algorithms |= GIMP_PAINT_CORE_LOOPS_ALGORITHM_CANVAS_BUFFER_TO_COMP_MASK;
+
+ /* undo buf -> paint_buf -> dest_buffer */
+ params.src_buffer = core->undo_buffer;
+ }
+ else
+ {
+ g_return_if_fail (paint_mask);
+
+ /* Write paint_mask to paint_buf, does not modify canvas_buffer */
+ params.paint_mask = paint_mask;
+ params.paint_mask_offset_x = paint_mask_offset_x;
+ params.paint_mask_offset_y = paint_mask_offset_y;
+ params.paint_opacity = paint_opacity;
+
+ algorithms |= GIMP_PAINT_CORE_LOOPS_ALGORITHM_PAINT_MASK_TO_COMP_MASK;
+
+ /* dest_buffer -> paint_buf -> dest_buffer */
+ params.src_buffer = params.dest_buffer;
+ }
+
+ params.mask_buffer = core->mask_buffer;
+ params.mask_offset_x = core->mask_x_offset;
+ params.mask_offset_y = core->mask_y_offset;
+ params.image_opacity = image_opacity;
+ params.paint_mode = paint_mode;
+
+ algorithms |= GIMP_PAINT_CORE_LOOPS_ALGORITHM_DO_LAYER_BLEND;
+
+ if (affect != GIMP_COMPONENT_MASK_ALL)
+ {
+ params.affect = affect;
+
+ algorithms |= GIMP_PAINT_CORE_LOOPS_ALGORITHM_MASK_COMPONENTS;
+ }
+
+ gimp_paint_core_loops_process (&params, algorithms);
+ }
+
+ /* Update the undo extents */
+ core->x1 = MIN (core->x1, core->paint_buffer_x);
+ core->y1 = MIN (core->y1, core->paint_buffer_y);
+ core->x2 = MAX (core->x2, core->paint_buffer_x + width);
+ core->y2 = MAX (core->y2, core->paint_buffer_y + height);
+
+ /* Update the drawable */
+ gimp_drawable_update (drawable,
+ core->paint_buffer_x,
+ core->paint_buffer_y,
+ width, height);
+}
+
+/* This works similarly to gimp_paint_core_paste. However, instead of
+ * combining the canvas to the paint core drawable using one of the
+ * combination modes, it uses a "replace" mode (i.e. transparent
+ * pixels in the canvas erase the paint core drawable).
+
+ * When not drawing on alpha-enabled images, it just paints using
+ * NORMAL mode.
+ */
+void
+gimp_paint_core_replace (GimpPaintCore *core,
+ const GimpTempBuf *paint_mask,
+ gint paint_mask_offset_x,
+ gint paint_mask_offset_y,
+ GimpDrawable *drawable,
+ gdouble paint_opacity,
+ gdouble image_opacity,
+ GimpPaintApplicationMode mode)
+{
+ gint width, height;
+ GimpComponentMask affect;
+
+ if (! gimp_drawable_has_alpha (drawable))
+ {
+ gimp_paint_core_paste (core, paint_mask,
+ paint_mask_offset_x,
+ paint_mask_offset_y,
+ drawable,
+ paint_opacity,
+ image_opacity,
+ GIMP_LAYER_MODE_NORMAL,
+ mode);
+ return;
+ }
+
+ width = gegl_buffer_get_width (core->paint_buffer);
+ height = gegl_buffer_get_height (core->paint_buffer);
+
+ affect = gimp_drawable_get_active_mask (drawable);
+
+ if (! affect)
+ return;
+
+ if (core->applicator)
+ {
+ GeglRectangle mask_rect;
+ GeglBuffer *mask_buffer;
+
+ /* If the mode is CONSTANT:
+ * combine the paint mask to the canvas buffer, and use it as the mask
+ * buffer
+ */
+ if (mode == GIMP_PAINT_CONSTANT)
+ {
+ /* Some tools (ink) paint the mask to paint_core->canvas_buffer
+ * directly. Don't need to copy it in this case.
+ */
+ if (paint_mask != NULL)
+ {
+ GeglBuffer *paint_mask_buffer =
+ gimp_temp_buf_create_buffer ((GimpTempBuf *) paint_mask);
+
+ gimp_gegl_combine_mask_weird (paint_mask_buffer,
+ GEGL_RECTANGLE (paint_mask_offset_x,
+ paint_mask_offset_y,
+ width, height),
+ core->canvas_buffer,
+ GEGL_RECTANGLE (core->paint_buffer_x,
+ core->paint_buffer_y,
+ width, height),
+ paint_opacity,
+ GIMP_IS_AIRBRUSH (core));
+
+ g_object_unref (paint_mask_buffer);
+ }
+
+ mask_buffer = g_object_ref (core->canvas_buffer);
+ mask_rect = *GEGL_RECTANGLE (core->paint_buffer_x,
+ core->paint_buffer_y,
+ width, height);
+
+ gimp_applicator_set_src_buffer (core->applicator,
+ core->undo_buffer);
+ }
+ /* Otherwise:
+ * use the paint mask as the mask buffer directly
+ */
+ else
+ {
+ mask_buffer =
+ gimp_temp_buf_create_buffer ((GimpTempBuf *) paint_mask);
+ mask_rect = *GEGL_RECTANGLE (paint_mask_offset_x,
+ paint_mask_offset_y,
+ width, height);
+
+ gimp_applicator_set_src_buffer (core->applicator,
+ gimp_drawable_get_buffer (drawable));
+ }
+
+ if (core->mask_buffer)
+ {
+ GeglBuffer *combined_mask_buffer;
+ GeglRectangle combined_mask_rect;
+ GeglRectangle aligned_combined_mask_rect;
+
+ combined_mask_rect = *GEGL_RECTANGLE (core->paint_buffer_x,
+ core->paint_buffer_y,
+ width, height);
+
+ gegl_rectangle_align_to_buffer (
+ &aligned_combined_mask_rect, &combined_mask_rect,
+ gimp_drawable_get_buffer (drawable),
+ GEGL_RECTANGLE_ALIGNMENT_SUPERSET);
+
+ combined_mask_buffer = gegl_buffer_new (&aligned_combined_mask_rect,
+ babl_format ("Y float"));
+
+ gimp_gegl_buffer_copy (
+ core->mask_buffer,
+ GEGL_RECTANGLE (aligned_combined_mask_rect.x -
+ core->mask_x_offset,
+ aligned_combined_mask_rect.y -
+ core->mask_y_offset,
+ aligned_combined_mask_rect.width,
+ aligned_combined_mask_rect.height),
+ GEGL_ABYSS_NONE,
+ combined_mask_buffer,
+ &aligned_combined_mask_rect);
+
+ gimp_gegl_combine_mask (mask_buffer, &mask_rect,
+ combined_mask_buffer, &combined_mask_rect,
+ 1.0);
+
+ g_object_unref (mask_buffer);
+
+ mask_buffer = combined_mask_buffer;
+ mask_rect = combined_mask_rect;
+ }
+
+ gimp_applicator_set_mask_buffer (core->applicator, mask_buffer);
+ gimp_applicator_set_mask_offset (core->applicator,
+ core->paint_buffer_x - mask_rect.x,
+ core->paint_buffer_y - mask_rect.y);
+
+ gimp_applicator_set_apply_buffer (core->applicator,
+ core->paint_buffer);
+ gimp_applicator_set_apply_offset (core->applicator,
+ core->paint_buffer_x,
+ core->paint_buffer_y);
+
+ gimp_applicator_set_opacity (core->applicator, image_opacity);
+ gimp_applicator_set_mode (core->applicator, GIMP_LAYER_MODE_REPLACE,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ GIMP_LAYER_COLOR_SPACE_AUTO,
+ gimp_layer_mode_get_paint_composite_mode (
+ GIMP_LAYER_MODE_REPLACE));
+
+ /* apply the paint area to the image */
+ gimp_applicator_blit (core->applicator,
+ GEGL_RECTANGLE (core->paint_buffer_x,
+ core->paint_buffer_y,
+ width, height));
+
+ gimp_applicator_set_mask_buffer (core->applicator, core->mask_buffer);
+ gimp_applicator_set_mask_offset (core->applicator,
+ core->mask_x_offset,
+ core->mask_y_offset);
+
+ g_object_unref (mask_buffer);
+ }
+ else
+ {
+ gimp_paint_core_paste (core, paint_mask,
+ paint_mask_offset_x,
+ paint_mask_offset_y,
+ drawable,
+ paint_opacity,
+ image_opacity,
+ GIMP_LAYER_MODE_REPLACE,
+ mode);
+ return;
+ }
+
+ /* Update the undo extents */
+ core->x1 = MIN (core->x1, core->paint_buffer_x);
+ core->y1 = MIN (core->y1, core->paint_buffer_y);
+ core->x2 = MAX (core->x2, core->paint_buffer_x + width);
+ core->y2 = MAX (core->y2, core->paint_buffer_y + height);
+
+ /* Update the drawable */
+ gimp_drawable_update (drawable,
+ core->paint_buffer_x,
+ core->paint_buffer_y,
+ width, height);
+}
+
+/**
+ * Smooth and store coords in the stroke buffer
+ */
+
+void
+gimp_paint_core_smooth_coords (GimpPaintCore *core,
+ GimpPaintOptions *paint_options,
+ GimpCoords *coords)
+{
+ GimpSmoothingOptions *smoothing_options = paint_options->smoothing_options;
+ GArray *history = core->stroke_buffer;
+
+ if (core->stroke_buffer == NULL)
+ return; /* Paint core has not initialized yet */
+
+ if (smoothing_options->use_smoothing &&
+ smoothing_options->smoothing_quality > 0)
+ {
+ gint i;
+ guint length;
+ gint min_index;
+ gdouble gaussian_weight = 0.0;
+ gdouble gaussian_weight2 = SQR (smoothing_options->smoothing_factor);
+ gdouble velocity_sum = 0.0;
+ gdouble scale_sum = 0.0;
+
+ g_array_append_val (history, *coords);
+
+ if (history->len < 2)
+ return; /* Just don't bother, nothing to do */
+
+ coords->x = coords->y = 0.0;
+
+ length = MIN (smoothing_options->smoothing_quality, history->len);
+
+ min_index = history->len - length;
+
+ if (gaussian_weight2 != 0.0)
+ gaussian_weight = 1 / (sqrt (2 * G_PI) * smoothing_options->smoothing_factor);
+
+ for (i = history->len - 1; i >= min_index; i--)
+ {
+ gdouble rate = 0.0;
+ GimpCoords *next_coords = &g_array_index (history,
+ GimpCoords, i);
+
+ if (gaussian_weight2 != 0.0)
+ {
+ /* We use gaussian function with velocity as a window function */
+ velocity_sum += next_coords->velocity * 100;
+ rate = gaussian_weight * exp (-velocity_sum * velocity_sum /
+ (2 * gaussian_weight2));
+ }
+
+ scale_sum += rate;
+ coords->x += rate * next_coords->x;
+ coords->y += rate * next_coords->y;
+ }
+
+ if (scale_sum != 0.0)
+ {
+ coords->x /= scale_sum;
+ coords->y /= scale_sum;
+ }
+ }
+}
diff --git a/app/paint/gimppaintcore.h b/app/paint/gimppaintcore.h
new file mode 100644
index 0000000..6e7f4d4
--- /dev/null
+++ b/app/paint/gimppaintcore.h
@@ -0,0 +1,216 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PAINT_CORE_H__
+#define __GIMP_PAINT_CORE_H__
+
+
+#include "core/gimpobject.h"
+
+
+#define GIMP_TYPE_PAINT_CORE (gimp_paint_core_get_type ())
+#define GIMP_PAINT_CORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PAINT_CORE, GimpPaintCore))
+#define GIMP_PAINT_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PAINT_CORE, GimpPaintCoreClass))
+#define GIMP_IS_PAINT_CORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PAINT_CORE))
+#define GIMP_IS_PAINT_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PAINT_CORE))
+#define GIMP_PAINT_CORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PAINT_CORE, GimpPaintCoreClass))
+
+
+typedef struct _GimpPaintCoreClass GimpPaintCoreClass;
+
+struct _GimpPaintCore
+{
+ GimpObject parent_instance;
+
+ gint ID; /* unique instance ID */
+
+ gchar *undo_desc; /* undo description */
+
+ gboolean show_all; /* whether working in show-all mode */
+
+ GimpCoords start_coords; /* the last stroke's endpoint for undo */
+
+ GimpCoords cur_coords; /* current coords */
+ GimpCoords last_coords; /* last coords */
+
+ GimpVector2 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 */
+
+ GimpPickable *image_pickable; /* the image pickable */
+
+ GeglBuffer *undo_buffer; /* 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 */
+ gint mask_x_offset;
+ gint mask_y_offset;
+
+ GimpApplicator *applicator;
+
+ GArray *stroke_buffer;
+};
+
+struct _GimpPaintCoreClass
+{
+ GimpObjectClass parent_class;
+
+ /* virtual functions */
+ gboolean (* start) (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GError **error);
+
+ gboolean (* pre_paint) (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpPaintState paint_state,
+ guint32 time);
+ void (* paint) (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time);
+ void (* post_paint) (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpPaintState paint_state,
+ guint32 time);
+
+ void (* interpolate) (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ guint32 time);
+
+ GeglBuffer * (* get_paint_buffer) (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpLayerMode paint_mode,
+ const GimpCoords *coords,
+ gint *paint_buffer_x,
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height);
+
+ GimpUndo * (* push_undo) (GimpPaintCore *core,
+ GimpImage *image,
+ const gchar *undo_desc);
+};
+
+
+GType gimp_paint_core_get_type (void) G_GNUC_CONST;
+
+void gimp_paint_core_paint (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpPaintState state,
+ guint32 time);
+
+gboolean gimp_paint_core_start (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GError **error);
+void gimp_paint_core_finish (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ gboolean push_undo);
+void gimp_paint_core_cancel (GimpPaintCore *core,
+ GimpDrawable *drawable);
+void gimp_paint_core_cleanup (GimpPaintCore *core);
+
+void gimp_paint_core_interpolate (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ guint32 time);
+
+void gimp_paint_core_set_show_all (GimpPaintCore *core,
+ gboolean show_all);
+gboolean gimp_paint_core_get_show_all (GimpPaintCore *core);
+
+void gimp_paint_core_set_current_coords (GimpPaintCore *core,
+ const GimpCoords *coords);
+void gimp_paint_core_get_current_coords (GimpPaintCore *core,
+ GimpCoords *coords);
+
+void gimp_paint_core_set_last_coords (GimpPaintCore *core,
+ const GimpCoords *coords);
+void gimp_paint_core_get_last_coords (GimpPaintCore *core,
+ GimpCoords *coords);
+
+void gimp_paint_core_round_line (GimpPaintCore *core,
+ GimpPaintOptions *options,
+ gboolean constrain_15_degrees,
+ gdouble constrain_offset_angle,
+ gdouble constrain_xres,
+ gdouble constrain_yres);
+
+
+/* protected functions */
+
+GeglBuffer * gimp_paint_core_get_paint_buffer (GimpPaintCore *core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *options,
+ GimpLayerMode paint_mode,
+ const GimpCoords *coords,
+ gint *paint_buffer_x,
+ gint *paint_buffer_y,
+ gint *paint_width,
+ gint *paint_height);
+
+GimpPickable * gimp_paint_core_get_image_pickable (GimpPaintCore *core);
+
+GeglBuffer * gimp_paint_core_get_orig_image (GimpPaintCore *core);
+GeglBuffer * gimp_paint_core_get_orig_proj (GimpPaintCore *core);
+
+void gimp_paint_core_paste (GimpPaintCore *core,
+ const GimpTempBuf *paint_mask,
+ gint paint_mask_offset_x,
+ gint paint_mask_offset_y,
+ GimpDrawable *drawable,
+ gdouble paint_opacity,
+ gdouble image_opacity,
+ GimpLayerMode paint_mode,
+ GimpPaintApplicationMode mode);
+
+void gimp_paint_core_replace (GimpPaintCore *core,
+ const GimpTempBuf *paint_mask,
+ gint paint_mask_offset_x,
+ gint paint_mask_offset_y,
+ GimpDrawable *drawable,
+ gdouble paint_opacity,
+ gdouble image_opacity,
+ GimpPaintApplicationMode mode);
+
+void gimp_paint_core_smooth_coords (GimpPaintCore *core,
+ GimpPaintOptions *paint_options,
+ GimpCoords *coords);
+
+
+#endif /* __GIMP_PAINT_CORE_H__ */
diff --git a/app/paint/gimppaintcoreundo.c b/app/paint/gimppaintcoreundo.c
new file mode 100644
index 0000000..861642f
--- /dev/null
+++ b/app/paint/gimppaintcoreundo.c
@@ -0,0 +1,172 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "paint-types.h"
+
+#include "gimppaintcore.h"
+#include "gimppaintcoreundo.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_PAINT_CORE
+};
+
+
+static void gimp_paint_core_undo_constructed (GObject *object);
+static void gimp_paint_core_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_paint_core_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_paint_core_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum);
+static void gimp_paint_core_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode);
+
+
+G_DEFINE_TYPE (GimpPaintCoreUndo, gimp_paint_core_undo, GIMP_TYPE_UNDO)
+
+#define parent_class gimp_paint_core_undo_parent_class
+
+
+static void
+gimp_paint_core_undo_class_init (GimpPaintCoreUndoClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass);
+
+ object_class->constructed = gimp_paint_core_undo_constructed;
+ object_class->set_property = gimp_paint_core_undo_set_property;
+ object_class->get_property = gimp_paint_core_undo_get_property;
+
+ undo_class->pop = gimp_paint_core_undo_pop;
+ undo_class->free = gimp_paint_core_undo_free;
+
+ g_object_class_install_property (object_class, PROP_PAINT_CORE,
+ g_param_spec_object ("paint-core", NULL, NULL,
+ GIMP_TYPE_PAINT_CORE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_paint_core_undo_init (GimpPaintCoreUndo *undo)
+{
+}
+
+static void
+gimp_paint_core_undo_constructed (GObject *object)
+{
+ GimpPaintCoreUndo *paint_core_undo = GIMP_PAINT_CORE_UNDO (object);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gimp_assert (GIMP_IS_PAINT_CORE (paint_core_undo->paint_core));
+
+ paint_core_undo->last_coords = paint_core_undo->paint_core->start_coords;
+
+ g_object_add_weak_pointer (G_OBJECT (paint_core_undo->paint_core),
+ (gpointer) &paint_core_undo->paint_core);
+}
+
+static void
+gimp_paint_core_undo_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpPaintCoreUndo *paint_core_undo = GIMP_PAINT_CORE_UNDO (object);
+
+ switch (property_id)
+ {
+ case PROP_PAINT_CORE:
+ 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
+gimp_paint_core_undo_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpPaintCoreUndo *paint_core_undo = GIMP_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
+gimp_paint_core_undo_pop (GimpUndo *undo,
+ GimpUndoMode undo_mode,
+ GimpUndoAccumulator *accum)
+{
+ GimpPaintCoreUndo *paint_core_undo = GIMP_PAINT_CORE_UNDO (undo);
+
+ GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum);
+
+ /* only pop if the core still exists */
+ if (paint_core_undo->paint_core)
+ {
+ GimpCoords 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
+gimp_paint_core_undo_free (GimpUndo *undo,
+ GimpUndoMode undo_mode)
+{
+ GimpPaintCoreUndo *paint_core_undo = GIMP_PAINT_CORE_UNDO (undo);
+
+ if (paint_core_undo->paint_core)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (paint_core_undo->paint_core),
+ (gpointer) &paint_core_undo->paint_core);
+ paint_core_undo->paint_core = NULL;
+ }
+
+ GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode);
+}
diff --git a/app/paint/gimppaintcoreundo.h b/app/paint/gimppaintcoreundo.h
new file mode 100644
index 0000000..dca5c34
--- /dev/null
+++ b/app/paint/gimppaintcoreundo.h
@@ -0,0 +1,53 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PAINT_CORE_UNDO_H__
+#define __GIMP_PAINT_CORE_UNDO_H__
+
+
+#include "core/gimpundo.h"
+
+
+#define GIMP_TYPE_PAINT_CORE_UNDO (gimp_paint_core_undo_get_type ())
+#define GIMP_PAINT_CORE_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PAINT_CORE_UNDO, GimpPaintCoreUndo))
+#define GIMP_PAINT_CORE_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PAINT_CORE_UNDO, GimpPaintCoreUndoClass))
+#define GIMP_IS_PAINT_CORE_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PAINT_CORE_UNDO))
+#define GIMP_IS_PAINT_CORE_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PAINT_CORE_UNDO))
+#define GIMP_PAINT_CORE_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PAINT_CORE_UNDO, GimpPaintCoreUndoClass))
+
+
+typedef struct _GimpPaintCoreUndo GimpPaintCoreUndo;
+typedef struct _GimpPaintCoreUndoClass GimpPaintCoreUndoClass;
+
+struct _GimpPaintCoreUndo
+{
+ GimpUndo parent_instance;
+
+ GimpPaintCore *paint_core;
+ GimpCoords last_coords;
+};
+
+struct _GimpPaintCoreUndoClass
+{
+ GimpUndoClass parent_class;
+};
+
+
+GType gimp_paint_core_undo_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_PAINT_CORE_UNDO_H__ */
diff --git a/app/paint/gimppaintoptions.c b/app/paint/gimppaintoptions.c
new file mode 100644
index 0000000..60c5959
--- /dev/null
+++ b/app/paint/gimppaintoptions.c
@@ -0,0 +1,1272 @@
+/* GIMP - The GNU Image Manipulation Program
+ * 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/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpconfig/gimpconfig.h"
+
+#include "paint-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpbrush-header.h"
+#include "core/gimpbrushgenerated.h"
+#include "core/gimpimage.h"
+#include "core/gimpdynamics.h"
+#include "core/gimpdynamicsoutput.h"
+#include "core/gimpgradient.h"
+#include "core/gimppaintinfo.h"
+
+#include "gimpbrushcore.h"
+#include "gimppaintoptions.h"
+
+#include "gimp-intl.h"
+
+
+#define DEFAULT_BRUSH_SIZE 20.0
+#define DEFAULT_BRUSH_ASPECT_RATIO 0.0
+#define DEFAULT_BRUSH_ANGLE 0.0
+#define DEFAULT_BRUSH_SPACING 0.1
+#define DEFAULT_BRUSH_HARDNESS 1.0 /* Generated brushes have their own */
+#define DEFAULT_BRUSH_FORCE 0.5
+
+#define DEFAULT_BRUSH_LINK_SIZE TRUE
+#define DEFAULT_BRUSH_LINK_ASPECT_RATIO TRUE
+#define DEFAULT_BRUSH_LINK_ANGLE TRUE
+#define DEFAULT_BRUSH_LINK_SPACING TRUE
+#define DEFAULT_BRUSH_LINK_HARDNESS TRUE
+
+#define DEFAULT_BRUSH_LOCK_TO_VIEW FALSE
+
+#define DEFAULT_APPLICATION_MODE GIMP_PAINT_CONSTANT
+#define DEFAULT_HARD FALSE
+
+#define DEFAULT_USE_JITTER FALSE
+#define DEFAULT_JITTER_AMOUNT 0.2
+
+#define DEFAULT_DYNAMICS_EXPANDED FALSE
+
+#define DEFAULT_FADE_LENGTH 100.0
+#define DEFAULT_FADE_REVERSE FALSE
+#define DEFAULT_FADE_REPEAT GIMP_REPEAT_NONE
+#define DEFAULT_FADE_UNIT GIMP_UNIT_PIXEL
+
+#define DEFAULT_GRADIENT_REVERSE FALSE
+#define DEFAULT_GRADIENT_BLEND_SPACE GIMP_GRADIENT_BLEND_RGB_PERCEPTUAL
+#define DEFAULT_GRADIENT_REPEAT GIMP_REPEAT_NONE
+
+#define DYNAMIC_MAX_VALUE 1.0
+#define DYNAMIC_MIN_VALUE 0.0
+
+#define DEFAULT_SMOOTHING_QUALITY 20
+#define DEFAULT_SMOOTHING_FACTOR 50
+
+enum
+{
+ PROP_0,
+
+ PROP_PAINT_INFO,
+
+ PROP_USE_APPLICATOR, /* temp debug */
+
+ PROP_BRUSH_SIZE,
+ PROP_BRUSH_ASPECT_RATIO,
+ PROP_BRUSH_ANGLE,
+ PROP_BRUSH_SPACING,
+ PROP_BRUSH_HARDNESS,
+ PROP_BRUSH_FORCE,
+
+ PROP_BRUSH_LINK_SIZE,
+ PROP_BRUSH_LINK_ASPECT_RATIO,
+ PROP_BRUSH_LINK_ANGLE,
+ PROP_BRUSH_LINK_SPACING,
+ PROP_BRUSH_LINK_HARDNESS,
+
+ PROP_BRUSH_LOCK_TO_VIEW,
+
+ PROP_APPLICATION_MODE,
+ PROP_HARD,
+
+ PROP_USE_JITTER,
+ PROP_JITTER_AMOUNT,
+
+ PROP_DYNAMICS_EXPANDED,
+
+ PROP_FADE_LENGTH,
+ PROP_FADE_REVERSE,
+ PROP_FADE_REPEAT,
+ PROP_FADE_UNIT,
+
+ PROP_GRADIENT_REVERSE,
+ PROP_GRADIENT_BLEND_COLOR_SPACE,
+ PROP_GRADIENT_REPEAT,
+
+ PROP_BRUSH_VIEW_TYPE,
+ PROP_BRUSH_VIEW_SIZE,
+ PROP_DYNAMICS_VIEW_TYPE,
+ PROP_DYNAMICS_VIEW_SIZE,
+ PROP_PATTERN_VIEW_TYPE,
+ PROP_PATTERN_VIEW_SIZE,
+ PROP_GRADIENT_VIEW_TYPE,
+ PROP_GRADIENT_VIEW_SIZE,
+
+ PROP_USE_SMOOTHING,
+ PROP_SMOOTHING_QUALITY,
+ PROP_SMOOTHING_FACTOR
+};
+
+
+static void gimp_paint_options_config_iface_init (GimpConfigInterface *config_iface);
+
+static void gimp_paint_options_dispose (GObject *object);
+static void gimp_paint_options_finalize (GObject *object);
+static void gimp_paint_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_paint_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_paint_options_brush_changed (GimpContext *context,
+ GimpBrush *brush);
+static void gimp_paint_options_brush_notify (GimpBrush *brush,
+ const GParamSpec *pspec,
+ GimpPaintOptions *options);
+
+static GimpConfig * gimp_paint_options_duplicate (GimpConfig *config);
+static gboolean gimp_paint_options_copy (GimpConfig *src,
+ GimpConfig *dest,
+ GParamFlags flags);
+static void gimp_paint_options_reset (GimpConfig *config);
+
+
+G_DEFINE_TYPE_WITH_CODE (GimpPaintOptions, gimp_paint_options,
+ GIMP_TYPE_TOOL_OPTIONS,
+ G_IMPLEMENT_INTERFACE (GIMP_TYPE_CONFIG,
+ gimp_paint_options_config_iface_init))
+
+#define parent_class gimp_paint_options_parent_class
+
+static GimpConfigInterface *parent_config_iface = NULL;
+
+
+static void
+gimp_paint_options_class_init (GimpPaintOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpContextClass *context_class = GIMP_CONTEXT_CLASS (klass);
+
+ object_class->dispose = gimp_paint_options_dispose;
+ object_class->finalize = gimp_paint_options_finalize;
+ object_class->set_property = gimp_paint_options_set_property;
+ object_class->get_property = gimp_paint_options_get_property;
+
+ context_class->brush_changed = gimp_paint_options_brush_changed;
+
+ g_object_class_install_property (object_class, PROP_PAINT_INFO,
+ g_param_spec_object ("paint-info",
+ NULL, NULL,
+ GIMP_TYPE_PAINT_INFO,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_USE_APPLICATOR,
+ g_param_spec_boolean ("use-applicator",
+ "Use GimpApplicator",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_BRUSH_SIZE,
+ "brush-size",
+ _("Size"),
+ _("Brush Size"),
+ 1.0, GIMP_BRUSH_MAX_SIZE, DEFAULT_BRUSH_SIZE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_BRUSH_ASPECT_RATIO,
+ "brush-aspect-ratio",
+ _("Aspect Ratio"),
+ _("Brush Aspect Ratio"),
+ -20.0, 20.0, DEFAULT_BRUSH_ASPECT_RATIO,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_BRUSH_ANGLE,
+ "brush-angle",
+ _("Angle"),
+ _("Brush Angle"),
+ -180.0, 180.0, DEFAULT_BRUSH_ANGLE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_BRUSH_SPACING,
+ "brush-spacing",
+ _("Spacing"),
+ _("Brush Spacing"),
+ 0.01, 50.0, DEFAULT_BRUSH_SPACING,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_BRUSH_HARDNESS,
+ "brush-hardness",
+ _("Hardness"),
+ _("Brush Hardness"),
+ 0.0, 1.0, DEFAULT_BRUSH_HARDNESS,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_BRUSH_FORCE,
+ "brush-force",
+ _("Force"),
+ _("Brush Force"),
+ 0.0, 1.0, DEFAULT_BRUSH_FORCE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_BRUSH_LINK_SIZE,
+ "brush-link-size",
+ _("Link Size"),
+ _("Link brush size to brush native"),
+ DEFAULT_BRUSH_LINK_SIZE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_BRUSH_LINK_ASPECT_RATIO,
+ "brush-link-aspect-ratio",
+ _("Link Aspect Ratio"),
+ _("Link brush aspect ratio to brush native"),
+ DEFAULT_BRUSH_LINK_ASPECT_RATIO,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_BRUSH_LINK_ANGLE,
+ "brush-link-angle",
+ _("Link Angle"),
+ _("Link brush angle to brush native"),
+ DEFAULT_BRUSH_LINK_ANGLE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_BRUSH_LINK_SPACING,
+ "brush-link-spacing",
+ _("Link Spacing"),
+ _("Link brush spacing to brush native"),
+ DEFAULT_BRUSH_LINK_SPACING,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_BRUSH_LINK_HARDNESS,
+ "brush-link-hardness",
+ _("Link Hardness"),
+ _("Link brush hardness to brush native"),
+ DEFAULT_BRUSH_LINK_HARDNESS,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_BRUSH_LOCK_TO_VIEW,
+ "brush-lock-to-view",
+ _("Lock brush to view"),
+ _("Keep brush appearance fixed relative to the view"),
+ DEFAULT_BRUSH_LOCK_TO_VIEW,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_APPLICATION_MODE,
+ "application-mode",
+ _("Incremental"),
+ _("Every stamp has its own opacity"),
+ GIMP_TYPE_PAINT_APPLICATION_MODE,
+ DEFAULT_APPLICATION_MODE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_HARD,
+ "hard",
+ _("Hard edge"),
+ _("Ignore fuzziness of the current brush"),
+ DEFAULT_HARD,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_JITTER,
+ "use-jitter",
+ _("Apply Jitter"),
+ _("Scatter brush as you paint"),
+ DEFAULT_USE_JITTER,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_JITTER_AMOUNT,
+ "jitter-amount",
+ _("Amount"),
+ _("Distance of scattering"),
+ 0.0, 50.0, DEFAULT_JITTER_AMOUNT,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_DYNAMICS_EXPANDED,
+ "dynamics-expanded",
+ _("Dynamics Options"),
+ NULL,
+ DEFAULT_DYNAMICS_EXPANDED,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_FADE_LENGTH,
+ "fade-length",
+ _("Fade length"),
+ _("Distance over which strokes fade out"),
+ 0.0, 32767.0, DEFAULT_FADE_LENGTH,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_UNIT (object_class, PROP_FADE_UNIT,
+ "fade-unit",
+ NULL, NULL,
+ TRUE, TRUE, DEFAULT_FADE_UNIT,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_FADE_REVERSE,
+ "fade-reverse",
+ _("Reverse"),
+ _("Reverse direction of fading"),
+ DEFAULT_FADE_REVERSE,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_FADE_REPEAT,
+ "fade-repeat",
+ _("Repeat"),
+ _("How fade is repeated as you paint"),
+ GIMP_TYPE_REPEAT_MODE,
+ DEFAULT_FADE_REPEAT,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_GRADIENT_REVERSE,
+ "gradient-reverse",
+ NULL, NULL,
+ DEFAULT_GRADIENT_REVERSE,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_GRADIENT_BLEND_COLOR_SPACE,
+ "gradient-blend-color-space",
+ _("Blend Color Space"),
+ _("Which color space to use when blending RGB gradient segments"),
+ GIMP_TYPE_GRADIENT_BLEND_COLOR_SPACE,
+ DEFAULT_GRADIENT_BLEND_SPACE,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_GRADIENT_REPEAT,
+ "gradient-repeat",
+ _("Repeat"),
+ NULL,
+ GIMP_TYPE_REPEAT_MODE,
+ DEFAULT_GRADIENT_REPEAT,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_BRUSH_VIEW_TYPE,
+ "brush-view-type",
+ NULL, NULL,
+ GIMP_TYPE_VIEW_TYPE,
+ GIMP_VIEW_TYPE_GRID,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_INT (object_class, PROP_BRUSH_VIEW_SIZE,
+ "brush-view-size",
+ NULL, NULL,
+ GIMP_VIEW_SIZE_TINY,
+ GIMP_VIEWABLE_MAX_BUTTON_SIZE,
+ GIMP_VIEW_SIZE_SMALL,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_DYNAMICS_VIEW_TYPE,
+ "dynamics-view-type",
+ NULL, NULL,
+ GIMP_TYPE_VIEW_TYPE,
+ GIMP_VIEW_TYPE_LIST,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_INT (object_class, PROP_DYNAMICS_VIEW_SIZE,
+ "dynamics-view-size",
+ NULL, NULL,
+ GIMP_VIEW_SIZE_TINY,
+ GIMP_VIEWABLE_MAX_BUTTON_SIZE,
+ GIMP_VIEW_SIZE_SMALL,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_PATTERN_VIEW_TYPE,
+ "pattern-view-type",
+ NULL, NULL,
+ GIMP_TYPE_VIEW_TYPE,
+ GIMP_VIEW_TYPE_GRID,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_INT (object_class, PROP_PATTERN_VIEW_SIZE,
+ "pattern-view-size",
+ NULL, NULL,
+ GIMP_VIEW_SIZE_TINY,
+ GIMP_VIEWABLE_MAX_BUTTON_SIZE,
+ GIMP_VIEW_SIZE_SMALL,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_GRADIENT_VIEW_TYPE,
+ "gradient-view-type",
+ NULL, NULL,
+ GIMP_TYPE_VIEW_TYPE,
+ GIMP_VIEW_TYPE_LIST,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_INT (object_class, PROP_GRADIENT_VIEW_SIZE,
+ "gradient-view-size",
+ NULL, NULL,
+ GIMP_VIEW_SIZE_TINY,
+ GIMP_VIEWABLE_MAX_BUTTON_SIZE,
+ GIMP_VIEW_SIZE_LARGE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_USE_SMOOTHING,
+ "use-smoothing",
+ _("Smooth stroke"),
+ _("Paint smoother strokes"),
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_INT (object_class, PROP_SMOOTHING_QUALITY,
+ "smoothing-quality",
+ _("Quality"),
+ _("Depth of smoothing"),
+ 1, 100, DEFAULT_SMOOTHING_QUALITY,
+ GIMP_PARAM_STATIC_STRINGS);
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_SMOOTHING_FACTOR,
+ "smoothing-factor",
+ _("Weight"),
+ _("Gravity of the pen"),
+ /* Max velocity is set to 3; allowing for
+ * smoothing factor to be less than velcoty
+ * results in numeric instablility
+ */
+ 3.0, 1000.0, DEFAULT_SMOOTHING_FACTOR,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_paint_options_config_iface_init (GimpConfigInterface *config_iface)
+{
+ parent_config_iface = g_type_interface_peek_parent (config_iface);
+
+ if (! parent_config_iface)
+ parent_config_iface = g_type_default_interface_peek (GIMP_TYPE_CONFIG);
+
+ config_iface->duplicate = gimp_paint_options_duplicate;
+ config_iface->copy = gimp_paint_options_copy;
+ config_iface->reset = gimp_paint_options_reset;
+}
+
+static void
+gimp_paint_options_init (GimpPaintOptions *options)
+{
+ options->application_mode_save = DEFAULT_APPLICATION_MODE;
+
+ options->jitter_options = g_slice_new0 (GimpJitterOptions);
+ options->fade_options = g_slice_new0 (GimpFadeOptions);
+ options->gradient_options = g_slice_new0 (GimpGradientPaintOptions);
+ options->smoothing_options = g_slice_new0 (GimpSmoothingOptions);
+}
+
+static void
+gimp_paint_options_dispose (GObject *object)
+{
+ GimpPaintOptions *options = GIMP_PAINT_OPTIONS (object);
+
+ g_clear_object (&options->paint_info);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_paint_options_finalize (GObject *object)
+{
+ GimpPaintOptions *options = GIMP_PAINT_OPTIONS (object);
+
+ g_slice_free (GimpJitterOptions, options->jitter_options);
+ g_slice_free (GimpFadeOptions, options->fade_options);
+ g_slice_free (GimpGradientPaintOptions, options->gradient_options);
+ g_slice_free (GimpSmoothingOptions, options->smoothing_options);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_paint_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpPaintOptions *options = GIMP_PAINT_OPTIONS (object);
+ GimpFadeOptions *fade_options = options->fade_options;
+ GimpJitterOptions *jitter_options = options->jitter_options;
+ GimpGradientPaintOptions *gradient_options = options->gradient_options;
+ GimpSmoothingOptions *smoothing_options = options->smoothing_options;
+
+ switch (property_id)
+ {
+ case PROP_PAINT_INFO:
+ options->paint_info = g_value_dup_object (value);
+ break;
+
+ case PROP_USE_APPLICATOR:
+ options->use_applicator = g_value_get_boolean (value);
+ break;
+
+ case PROP_BRUSH_SIZE:
+ options->brush_size = g_value_get_double (value);
+ break;
+ case PROP_BRUSH_ASPECT_RATIO:
+ options->brush_aspect_ratio = g_value_get_double (value);
+ break;
+ case PROP_BRUSH_ANGLE:
+ options->brush_angle = - 1.0 * g_value_get_double (value) / 360.0; /* let's make the angle mathematically correct */
+ break;
+ case PROP_BRUSH_SPACING:
+ options->brush_spacing = g_value_get_double (value);
+ break;
+ case PROP_BRUSH_HARDNESS:
+ options->brush_hardness = g_value_get_double (value);
+ break;
+ case PROP_BRUSH_FORCE:
+ options->brush_force = g_value_get_double (value);
+ break;
+
+ case PROP_BRUSH_LINK_SIZE:
+ options->brush_link_size = g_value_get_boolean (value);
+ break;
+ case PROP_BRUSH_LINK_ASPECT_RATIO:
+ options->brush_link_aspect_ratio = g_value_get_boolean (value);
+ break;
+ case PROP_BRUSH_LINK_ANGLE:
+ options->brush_link_angle = g_value_get_boolean (value);
+ break;
+ case PROP_BRUSH_LINK_SPACING:
+ options->brush_link_spacing = g_value_get_boolean (value);
+ break;
+ case PROP_BRUSH_LINK_HARDNESS:
+ options->brush_link_hardness = g_value_get_boolean (value);
+ break;
+
+ case PROP_BRUSH_LOCK_TO_VIEW:
+ options->brush_lock_to_view = g_value_get_boolean (value);
+ break;
+
+ case PROP_APPLICATION_MODE:
+ options->application_mode = g_value_get_enum (value);
+ break;
+ case PROP_HARD:
+ options->hard = g_value_get_boolean (value);
+ break;
+
+ case PROP_USE_JITTER:
+ jitter_options->use_jitter = g_value_get_boolean (value);
+ break;
+ case PROP_JITTER_AMOUNT:
+ jitter_options->jitter_amount = g_value_get_double (value);
+ break;
+
+ case PROP_DYNAMICS_EXPANDED:
+ options->dynamics_expanded = g_value_get_boolean (value);
+ break;
+
+ case PROP_FADE_LENGTH:
+ fade_options->fade_length = g_value_get_double (value);
+ break;
+ case PROP_FADE_REVERSE:
+ fade_options->fade_reverse = g_value_get_boolean (value);
+ break;
+ case PROP_FADE_REPEAT:
+ fade_options->fade_repeat = g_value_get_enum (value);
+ break;
+ case PROP_FADE_UNIT:
+ fade_options->fade_unit = g_value_get_int (value);
+ break;
+
+ case PROP_GRADIENT_REVERSE:
+ gradient_options->gradient_reverse = g_value_get_boolean (value);
+ break;
+ case PROP_GRADIENT_BLEND_COLOR_SPACE:
+ gradient_options->gradient_blend_color_space = g_value_get_enum (value);
+ break;
+ case PROP_GRADIENT_REPEAT:
+ gradient_options->gradient_repeat = g_value_get_enum (value);
+ break;
+
+ case PROP_BRUSH_VIEW_TYPE:
+ options->brush_view_type = g_value_get_enum (value);
+ break;
+ case PROP_BRUSH_VIEW_SIZE:
+ options->brush_view_size = g_value_get_int (value);
+ break;
+
+ case PROP_DYNAMICS_VIEW_TYPE:
+ options->dynamics_view_type = g_value_get_enum (value);
+ break;
+ case PROP_DYNAMICS_VIEW_SIZE:
+ options->dynamics_view_size = g_value_get_int (value);
+ break;
+
+ case PROP_PATTERN_VIEW_TYPE:
+ options->pattern_view_type = g_value_get_enum (value);
+ break;
+ case PROP_PATTERN_VIEW_SIZE:
+ options->pattern_view_size = g_value_get_int (value);
+ break;
+
+ case PROP_GRADIENT_VIEW_TYPE:
+ options->gradient_view_type = g_value_get_enum (value);
+ break;
+ case PROP_GRADIENT_VIEW_SIZE:
+ options->gradient_view_size = g_value_get_int (value);
+ break;
+
+ case PROP_USE_SMOOTHING:
+ smoothing_options->use_smoothing = g_value_get_boolean (value);
+ break;
+ case PROP_SMOOTHING_QUALITY:
+ smoothing_options->smoothing_quality = g_value_get_int (value);
+ break;
+ case PROP_SMOOTHING_FACTOR:
+ smoothing_options->smoothing_factor = g_value_get_double (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_paint_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpPaintOptions *options = GIMP_PAINT_OPTIONS (object);
+ GimpFadeOptions *fade_options = options->fade_options;
+ GimpJitterOptions *jitter_options = options->jitter_options;
+ GimpGradientPaintOptions *gradient_options = options->gradient_options;
+ GimpSmoothingOptions *smoothing_options = options->smoothing_options;
+
+ switch (property_id)
+ {
+ case PROP_PAINT_INFO:
+ g_value_set_object (value, options->paint_info);
+ break;
+
+ case PROP_USE_APPLICATOR:
+ g_value_set_boolean (value, options->use_applicator);
+ break;
+
+ case PROP_BRUSH_SIZE:
+ g_value_set_double (value, options->brush_size);
+ break;
+ case PROP_BRUSH_ASPECT_RATIO:
+ g_value_set_double (value, options->brush_aspect_ratio);
+ break;
+ case PROP_BRUSH_ANGLE:
+ g_value_set_double (value, - 1.0 * options->brush_angle * 360.0); /* mathematically correct -> intuitively correct */
+ break;
+ case PROP_BRUSH_SPACING:
+ g_value_set_double (value, options->brush_spacing);
+ break;
+ case PROP_BRUSH_HARDNESS:
+ g_value_set_double (value, options->brush_hardness);
+ break;
+ case PROP_BRUSH_FORCE:
+ g_value_set_double (value, options->brush_force);
+ break;
+
+ case PROP_BRUSH_LINK_SIZE:
+ g_value_set_boolean (value, options->brush_link_size);
+ break;
+ case PROP_BRUSH_LINK_ASPECT_RATIO:
+ g_value_set_boolean (value, options->brush_link_aspect_ratio);
+ break;
+ case PROP_BRUSH_LINK_ANGLE:
+ g_value_set_boolean (value, options->brush_link_angle);
+ break;
+ case PROP_BRUSH_LINK_SPACING:
+ g_value_set_boolean (value, options->brush_link_spacing);
+ break;
+ case PROP_BRUSH_LINK_HARDNESS:
+ g_value_set_boolean (value, options->brush_link_hardness);
+ break;
+
+ case PROP_BRUSH_LOCK_TO_VIEW:
+ g_value_set_boolean (value, options->brush_lock_to_view);
+ break;
+
+ case PROP_APPLICATION_MODE:
+ g_value_set_enum (value, options->application_mode);
+ break;
+ case PROP_HARD:
+ g_value_set_boolean (value, options->hard);
+ break;
+
+ case PROP_USE_JITTER:
+ g_value_set_boolean (value, jitter_options->use_jitter);
+ break;
+ case PROP_JITTER_AMOUNT:
+ g_value_set_double (value, jitter_options->jitter_amount);
+ break;
+
+ case PROP_DYNAMICS_EXPANDED:
+ g_value_set_boolean (value, options->dynamics_expanded);
+ break;
+
+ case PROP_FADE_LENGTH:
+ g_value_set_double (value, fade_options->fade_length);
+ break;
+ case PROP_FADE_REVERSE:
+ g_value_set_boolean (value, fade_options->fade_reverse);
+ break;
+ case PROP_FADE_REPEAT:
+ g_value_set_enum (value, fade_options->fade_repeat);
+ break;
+ case PROP_FADE_UNIT:
+ g_value_set_int (value, fade_options->fade_unit);
+ break;
+
+ case PROP_GRADIENT_REVERSE:
+ g_value_set_boolean (value, gradient_options->gradient_reverse);
+ break;
+ case PROP_GRADIENT_BLEND_COLOR_SPACE:
+ g_value_set_enum (value, gradient_options->gradient_blend_color_space);
+ break;
+ case PROP_GRADIENT_REPEAT:
+ g_value_set_enum (value, gradient_options->gradient_repeat);
+ break;
+
+ case PROP_BRUSH_VIEW_TYPE:
+ g_value_set_enum (value, options->brush_view_type);
+ break;
+ case PROP_BRUSH_VIEW_SIZE:
+ g_value_set_int (value, options->brush_view_size);
+ break;
+
+ case PROP_DYNAMICS_VIEW_TYPE:
+ g_value_set_enum (value, options->dynamics_view_type);
+ break;
+ case PROP_DYNAMICS_VIEW_SIZE:
+ g_value_set_int (value, options->dynamics_view_size);
+ break;
+
+ case PROP_PATTERN_VIEW_TYPE:
+ g_value_set_enum (value, options->pattern_view_type);
+ break;
+ case PROP_PATTERN_VIEW_SIZE:
+ g_value_set_int (value, options->pattern_view_size);
+ break;
+
+ case PROP_GRADIENT_VIEW_TYPE:
+ g_value_set_enum (value, options->gradient_view_type);
+ break;
+ case PROP_GRADIENT_VIEW_SIZE:
+ g_value_set_int (value, options->gradient_view_size);
+ break;
+
+ case PROP_USE_SMOOTHING:
+ g_value_set_boolean (value, smoothing_options->use_smoothing);
+ break;
+ case PROP_SMOOTHING_QUALITY:
+ g_value_set_int (value, smoothing_options->smoothing_quality);
+ break;
+ case PROP_SMOOTHING_FACTOR:
+ g_value_set_double (value, smoothing_options->smoothing_factor);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_paint_options_brush_changed (GimpContext *context,
+ GimpBrush *brush)
+{
+ GimpPaintOptions *options = GIMP_PAINT_OPTIONS (context);
+
+ if (options->paint_info &&
+ g_type_is_a (options->paint_info->paint_type,
+ GIMP_TYPE_BRUSH_CORE))
+ {
+ if (options->brush)
+ {
+ g_signal_handlers_disconnect_by_func (options->brush,
+ gimp_paint_options_brush_notify,
+ options);
+ g_object_remove_weak_pointer (G_OBJECT (options->brush),
+ (gpointer) &options->brush);
+ }
+
+ options->brush = brush;
+
+ if (options->brush)
+ {
+ g_object_add_weak_pointer (G_OBJECT (options->brush),
+ (gpointer) &options->brush);
+ g_signal_connect_object (options->brush, "notify",
+ G_CALLBACK (gimp_paint_options_brush_notify),
+ options, 0);
+
+ gimp_paint_options_brush_notify (options->brush, NULL, options);
+ }
+ }
+}
+
+static void
+gimp_paint_options_brush_notify (GimpBrush *brush,
+ const GParamSpec *pspec,
+ GimpPaintOptions *options)
+{
+ if (gimp_tool_options_get_gui_mode (GIMP_TOOL_OPTIONS (options)))
+ {
+#define IS_PSPEC(p,n) (p == NULL || ! strcmp (n, p->name))
+
+ if (options->brush_link_size && IS_PSPEC (pspec, "radius"))
+ gimp_paint_options_set_default_brush_size (options, brush);
+
+ if (options->brush_link_aspect_ratio && IS_PSPEC (pspec, "aspect-ratio"))
+ gimp_paint_options_set_default_brush_aspect_ratio (options, brush);
+
+ if (options->brush_link_angle && IS_PSPEC (pspec, "angle"))
+ gimp_paint_options_set_default_brush_angle (options, brush);
+
+ if (options->brush_link_spacing && IS_PSPEC (pspec, "spacing"))
+ gimp_paint_options_set_default_brush_spacing (options, brush);
+
+ if (options->brush_link_hardness && IS_PSPEC (pspec, "hardness"))
+ gimp_paint_options_set_default_brush_hardness (options, brush);
+
+#undef IS_SPEC
+ }
+}
+
+static GimpConfig *
+gimp_paint_options_duplicate (GimpConfig *config)
+{
+ return parent_config_iface->duplicate (config);
+}
+
+static gboolean
+gimp_paint_options_copy (GimpConfig *src,
+ GimpConfig *dest,
+ GParamFlags flags)
+{
+ return parent_config_iface->copy (src, dest, flags);
+}
+
+static void
+gimp_paint_options_reset (GimpConfig *config)
+{
+ GimpBrush *brush = gimp_context_get_brush (GIMP_CONTEXT (config));
+
+ parent_config_iface->reset (config);
+
+ if (brush)
+ {
+ gimp_paint_options_set_default_brush_size (GIMP_PAINT_OPTIONS (config),
+ brush);
+ gimp_paint_options_set_default_brush_hardness (GIMP_PAINT_OPTIONS (config),
+ brush);
+ gimp_paint_options_set_default_brush_aspect_ratio (GIMP_PAINT_OPTIONS (config),
+ brush);
+ gimp_paint_options_set_default_brush_angle (GIMP_PAINT_OPTIONS (config),
+ brush);
+ gimp_paint_options_set_default_brush_spacing (GIMP_PAINT_OPTIONS (config),
+ brush);
+ }
+}
+
+GimpPaintOptions *
+gimp_paint_options_new (GimpPaintInfo *paint_info)
+{
+ GimpPaintOptions *options;
+
+ g_return_val_if_fail (GIMP_IS_PAINT_INFO (paint_info), NULL);
+
+ options = g_object_new (paint_info->paint_options_type,
+ "gimp", paint_info->gimp,
+ "name", gimp_object_get_name (paint_info),
+ "paint-info", paint_info,
+ NULL);
+
+ return options;
+}
+
+gdouble
+gimp_paint_options_get_fade (GimpPaintOptions *paint_options,
+ GimpImage *image,
+ gdouble pixel_dist)
+{
+ GimpFadeOptions *fade_options;
+ gdouble z = -1.0;
+ gdouble fade_out = 0.0;
+ gdouble unit_factor;
+ gdouble pos;
+
+ g_return_val_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options),
+ DYNAMIC_MAX_VALUE);
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), DYNAMIC_MAX_VALUE);
+
+ fade_options = paint_options->fade_options;
+
+ switch (fade_options->fade_unit)
+ {
+ case GIMP_UNIT_PIXEL:
+ fade_out = fade_options->fade_length;
+ break;
+
+ case GIMP_UNIT_PERCENT:
+ fade_out = (MAX (gimp_image_get_width (image),
+ gimp_image_get_height (image)) *
+ fade_options->fade_length / 100);
+ break;
+
+ default:
+ {
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ unit_factor = gimp_unit_get_factor (fade_options->fade_unit);
+ fade_out = (fade_options->fade_length *
+ MAX (xres, yres) / unit_factor);
+ }
+ break;
+ }
+
+ /* factor in the fade out value */
+ if (fade_out > 0.0)
+ {
+ pos = pixel_dist / fade_out;
+ }
+ else
+ pos = DYNAMIC_MAX_VALUE;
+
+ /* for no repeat, set pos close to 1.0 after the first chunk */
+ if (fade_options->fade_repeat == GIMP_REPEAT_NONE && pos >= DYNAMIC_MAX_VALUE)
+ pos = DYNAMIC_MAX_VALUE - 0.0000001;
+
+ if (((gint) pos & 1) &&
+ fade_options->fade_repeat != GIMP_REPEAT_SAWTOOTH)
+ pos = DYNAMIC_MAX_VALUE - (pos - (gint) pos);
+ else
+ pos = pos - (gint) pos;
+
+ z = pos;
+
+ if (fade_options->fade_reverse)
+ z = 1.0 - z;
+
+ return z; /* ln (1/255) */
+}
+
+gdouble
+gimp_paint_options_get_jitter (GimpPaintOptions *paint_options,
+ GimpImage *image)
+{
+ GimpJitterOptions *jitter_options;
+
+ g_return_val_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options), 0.0);
+
+ jitter_options = paint_options->jitter_options;
+
+ if (jitter_options->use_jitter)
+ {
+ return jitter_options->jitter_amount;
+ }
+
+ return 0.0;
+}
+
+gboolean
+gimp_paint_options_get_gradient_color (GimpPaintOptions *paint_options,
+ GimpImage *image,
+ gdouble grad_point,
+ gdouble pixel_dist,
+ GimpRGB *color)
+{
+ GimpDynamics *dynamics;
+
+ g_return_val_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options), FALSE);
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (color != NULL, FALSE);
+
+ dynamics = gimp_context_get_dynamics (GIMP_CONTEXT (paint_options));
+
+ if (gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_COLOR))
+ {
+ GimpGradientPaintOptions *gradient_options = paint_options->gradient_options;
+ GimpGradient *gradient;
+
+ gradient = gimp_context_get_gradient (GIMP_CONTEXT (paint_options));
+
+ gimp_gradient_get_color_at (gradient, GIMP_CONTEXT (paint_options),
+ NULL, grad_point,
+ gradient_options->gradient_reverse,
+ gradient_options->gradient_blend_color_space,
+ color);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+GimpBrushApplicationMode
+gimp_paint_options_get_brush_mode (GimpPaintOptions *paint_options)
+{
+ GimpDynamics *dynamics;
+ gboolean dynamic_force = FALSE;
+
+ g_return_val_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options), GIMP_BRUSH_SOFT);
+
+ if (paint_options->hard)
+ return GIMP_BRUSH_HARD;
+
+ dynamics = gimp_context_get_dynamics (GIMP_CONTEXT (paint_options));
+
+ dynamic_force = gimp_dynamics_is_output_enabled (dynamics,
+ GIMP_DYNAMICS_OUTPUT_FORCE);
+
+ if (dynamic_force || (paint_options->brush_force != 0.5))
+ return GIMP_BRUSH_PRESSURE;
+
+ return GIMP_BRUSH_SOFT;
+}
+
+void
+gimp_paint_options_set_default_brush_size (GimpPaintOptions *paint_options,
+ GimpBrush *brush)
+{
+ g_return_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options));
+ g_return_if_fail (brush == NULL || GIMP_IS_BRUSH (brush));
+
+ if (! brush)
+ brush = gimp_context_get_brush (GIMP_CONTEXT (paint_options));
+
+ if (brush)
+ {
+ gint height;
+ gint width;
+
+ gimp_brush_transform_size (brush, 1.0, 0.0, 0.0, FALSE, &width, &height);
+
+ g_object_set (paint_options,
+ "brush-size", (gdouble) MAX (height, width),
+ NULL);
+ }
+}
+
+void
+gimp_paint_options_set_default_brush_angle (GimpPaintOptions *paint_options,
+ GimpBrush *brush)
+{
+ g_return_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options));
+ g_return_if_fail (brush == NULL || GIMP_IS_BRUSH (brush));
+
+ if (! brush)
+ brush = gimp_context_get_brush (GIMP_CONTEXT (paint_options));
+
+ if (GIMP_IS_BRUSH_GENERATED (brush))
+ {
+ GimpBrushGenerated *generated_brush = GIMP_BRUSH_GENERATED (brush);
+
+ g_object_set (paint_options,
+ "brush-angle", (gdouble) gimp_brush_generated_get_angle (generated_brush),
+ NULL);
+ }
+ else
+ {
+ g_object_set (paint_options,
+ "brush-angle", DEFAULT_BRUSH_ANGLE,
+ NULL);
+ }
+}
+
+void
+gimp_paint_options_set_default_brush_aspect_ratio (GimpPaintOptions *paint_options,
+ GimpBrush *brush)
+{
+ g_return_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options));
+ g_return_if_fail (brush == NULL || GIMP_IS_BRUSH (brush));
+
+ if (! brush)
+ brush = gimp_context_get_brush (GIMP_CONTEXT (paint_options));
+
+ if (GIMP_IS_BRUSH_GENERATED (brush))
+ {
+ GimpBrushGenerated *generated_brush = GIMP_BRUSH_GENERATED (brush);
+ gdouble ratio;
+
+ ratio = gimp_brush_generated_get_aspect_ratio (generated_brush);
+
+ ratio = (ratio - 1.0) * 20.0 / 19.0;
+
+ g_object_set (paint_options,
+ "brush-aspect-ratio", ratio,
+ NULL);
+ }
+ else
+ {
+ g_object_set (paint_options,
+ "brush-aspect-ratio", DEFAULT_BRUSH_ASPECT_RATIO,
+ NULL);
+ }
+}
+
+void
+gimp_paint_options_set_default_brush_spacing (GimpPaintOptions *paint_options,
+ GimpBrush *brush)
+{
+ g_return_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options));
+ g_return_if_fail (brush == NULL || GIMP_IS_BRUSH (brush));
+
+ if (! brush)
+ brush = gimp_context_get_brush (GIMP_CONTEXT (paint_options));
+
+ if (brush)
+ {
+ g_object_set (paint_options,
+ "brush-spacing", (gdouble) gimp_brush_get_spacing (brush) / 100.0,
+ NULL);
+ }
+}
+
+void
+gimp_paint_options_set_default_brush_hardness (GimpPaintOptions *paint_options,
+ GimpBrush *brush)
+{
+ g_return_if_fail (GIMP_IS_PAINT_OPTIONS (paint_options));
+ g_return_if_fail (brush == NULL || GIMP_IS_BRUSH (brush));
+
+ if (! brush)
+ brush = gimp_context_get_brush (GIMP_CONTEXT (paint_options));
+
+ if (GIMP_IS_BRUSH_GENERATED (brush))
+ {
+ GimpBrushGenerated *generated_brush = GIMP_BRUSH_GENERATED (brush);
+
+ g_object_set (paint_options,
+ "brush-hardness", (gdouble) gimp_brush_generated_get_hardness (generated_brush),
+ NULL);
+ }
+ else
+ {
+ g_object_set (paint_options,
+ "brush-hardness", DEFAULT_BRUSH_HARDNESS,
+ NULL);
+ }
+}
+
+static const gchar *brush_props[] =
+{
+ "brush-size",
+ "brush-angle",
+ "brush-aspect-ratio",
+ "brush-spacing",
+ "brush-hardness",
+ "brush-force",
+ "brush-link-size",
+ "brush-link-angle",
+ "brush-link-aspect-ratio",
+ "brush-link-spacing",
+ "brush-link-hardness",
+ "brush-lock-to-view"
+};
+
+static const gchar *dynamics_props[] =
+{
+ "dynamics-expanded",
+ "fade-reverse",
+ "fade-length",
+ "fade-unit",
+ "fade-repeat"
+};
+
+static const gchar *gradient_props[] =
+{
+ "gradient-reverse",
+ "gradient-blend-color-space",
+ "gradient-repeat"
+};
+
+static const gint max_n_props = (G_N_ELEMENTS (brush_props) +
+ G_N_ELEMENTS (dynamics_props) +
+ G_N_ELEMENTS (gradient_props));
+
+gboolean
+gimp_paint_options_is_prop (const gchar *prop_name,
+ GimpContextPropMask prop_mask)
+{
+ gint i;
+
+ g_return_val_if_fail (prop_name != NULL, FALSE);
+
+ if (prop_mask & GIMP_CONTEXT_PROP_MASK_BRUSH)
+ {
+ for (i = 0; i < G_N_ELEMENTS (brush_props); i++)
+ if (! strcmp (prop_name, brush_props[i]))
+ return TRUE;
+ }
+
+ if (prop_mask & GIMP_CONTEXT_PROP_MASK_DYNAMICS)
+ {
+ for (i = 0; i < G_N_ELEMENTS (dynamics_props); i++)
+ if (! strcmp (prop_name, dynamics_props[i]))
+ return TRUE;
+ }
+
+ if (prop_mask & GIMP_CONTEXT_PROP_MASK_GRADIENT)
+ {
+ for (i = 0; i < G_N_ELEMENTS (gradient_props); i++)
+ if (! strcmp (prop_name, gradient_props[i]))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void
+gimp_paint_options_copy_props (GimpPaintOptions *src,
+ GimpPaintOptions *dest,
+ GimpContextPropMask prop_mask)
+{
+ const gchar *names[max_n_props];
+ GValue values[max_n_props];
+ gint n_props = 0;
+ gint i;
+
+ g_return_if_fail (GIMP_IS_PAINT_OPTIONS (src));
+ g_return_if_fail (GIMP_IS_PAINT_OPTIONS (dest));
+
+ if (prop_mask & GIMP_CONTEXT_PROP_MASK_BRUSH)
+ {
+ for (i = 0; i < G_N_ELEMENTS (brush_props); i++)
+ names[n_props++] = brush_props[i];
+ }
+
+ if (prop_mask & GIMP_CONTEXT_PROP_MASK_DYNAMICS)
+ {
+ for (i = 0; i < G_N_ELEMENTS (dynamics_props); i++)
+ names[n_props++] = dynamics_props[i];
+ }
+
+ if (prop_mask & GIMP_CONTEXT_PROP_MASK_GRADIENT)
+ {
+ for (i = 0; i < G_N_ELEMENTS (gradient_props); i++)
+ names[n_props++] = gradient_props[i];
+ }
+
+ if (n_props > 0)
+ {
+ g_object_getv (G_OBJECT (src), n_props, names, values);
+ g_object_setv (G_OBJECT (dest), n_props, names, values);
+
+ while (n_props--)
+ g_value_unset (&values[n_props]);
+ }
+}
diff --git a/app/paint/gimppaintoptions.h b/app/paint/gimppaintoptions.h
new file mode 100644
index 0000000..10218ee
--- /dev/null
+++ b/app/paint/gimppaintoptions.h
@@ -0,0 +1,176 @@
+/* GIMP - The GNU Image Manipulation Program
+ * 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 __GIMP_PAINT_OPTIONS_H__
+#define __GIMP_PAINT_OPTIONS_H__
+
+
+#include "core/gimptooloptions.h"
+
+
+#define GIMP_PAINT_OPTIONS_CONTEXT_MASK GIMP_CONTEXT_PROP_MASK_FOREGROUND | \
+ GIMP_CONTEXT_PROP_MASK_BACKGROUND | \
+ GIMP_CONTEXT_PROP_MASK_OPACITY | \
+ GIMP_CONTEXT_PROP_MASK_PAINT_MODE | \
+ GIMP_CONTEXT_PROP_MASK_BRUSH | \
+ GIMP_CONTEXT_PROP_MASK_DYNAMICS | \
+ GIMP_CONTEXT_PROP_MASK_PALETTE
+
+
+typedef struct _GimpJitterOptions GimpJitterOptions;
+typedef struct _GimpFadeOptions GimpFadeOptions;
+typedef struct _GimpGradientPaintOptions GimpGradientPaintOptions;
+typedef struct _GimpSmoothingOptions GimpSmoothingOptions;
+
+struct _GimpJitterOptions
+{
+ gboolean use_jitter;
+ gdouble jitter_amount;
+};
+
+struct _GimpFadeOptions
+{
+ gboolean fade_reverse;
+ gdouble fade_length;
+ GimpUnit fade_unit;
+ GimpRepeatMode fade_repeat;
+};
+
+struct _GimpGradientPaintOptions
+{
+ gboolean gradient_reverse;
+ GimpGradientBlendColorSpace gradient_blend_color_space;
+ GimpRepeatMode gradient_repeat; /* only used by gradient tool */
+};
+
+struct _GimpSmoothingOptions
+{
+ gboolean use_smoothing;
+ gint smoothing_quality;
+ gdouble smoothing_factor;
+};
+
+
+#define GIMP_TYPE_PAINT_OPTIONS (gimp_paint_options_get_type ())
+#define GIMP_PAINT_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PAINT_OPTIONS, GimpPaintOptions))
+#define GIMP_PAINT_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PAINT_OPTIONS, GimpPaintOptionsClass))
+#define GIMP_IS_PAINT_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PAINT_OPTIONS))
+#define GIMP_IS_PAINT_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PAINT_OPTIONS))
+#define GIMP_PAINT_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PAINT_OPTIONS, GimpPaintOptionsClass))
+
+
+typedef struct _GimpPaintOptionsClass GimpPaintOptionsClass;
+
+struct _GimpPaintOptions
+{
+ GimpToolOptions parent_instance;
+
+ GimpPaintInfo *paint_info;
+
+ gboolean use_applicator;
+
+ GimpBrush *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;
+
+ GimpPaintApplicationMode application_mode;
+ GimpPaintApplicationMode application_mode_save;
+
+ gboolean hard;
+
+ GimpJitterOptions *jitter_options;
+
+ gboolean dynamics_expanded;
+ GimpFadeOptions *fade_options;
+ GimpGradientPaintOptions *gradient_options;
+ GimpSmoothingOptions *smoothing_options;
+
+ GimpViewType brush_view_type;
+ GimpViewSize brush_view_size;
+ GimpViewType dynamics_view_type;
+ GimpViewSize dynamics_view_size;
+ GimpViewType pattern_view_type;
+ GimpViewSize pattern_view_size;
+ GimpViewType gradient_view_type;
+ GimpViewSize gradient_view_size;
+};
+
+struct _GimpPaintOptionsClass
+{
+ GimpToolOptionsClass parent_instance;
+};
+
+
+GType gimp_paint_options_get_type (void) G_GNUC_CONST;
+
+GimpPaintOptions *
+ gimp_paint_options_new (GimpPaintInfo *paint_info);
+
+gdouble gimp_paint_options_get_fade (GimpPaintOptions *options,
+ GimpImage *image,
+ gdouble pixel_dist);
+
+gdouble gimp_paint_options_get_jitter (GimpPaintOptions *options,
+ GimpImage *image);
+
+gboolean gimp_paint_options_get_gradient_color (GimpPaintOptions *options,
+ GimpImage *image,
+ gdouble grad_point,
+ gdouble pixel_dist,
+ GimpRGB *color);
+
+GimpBrushApplicationMode
+ gimp_paint_options_get_brush_mode (GimpPaintOptions *options);
+
+void gimp_paint_options_set_default_brush_size
+ (GimpPaintOptions *options,
+ GimpBrush *brush);
+void gimp_paint_options_set_default_brush_angle
+ (GimpPaintOptions *options,
+ GimpBrush *brush);
+void gimp_paint_options_set_default_brush_aspect_ratio
+ (GimpPaintOptions *options,
+ GimpBrush *brush);
+void gimp_paint_options_set_default_brush_spacing
+ (GimpPaintOptions *options,
+ GimpBrush *brush);
+
+void gimp_paint_options_set_default_brush_hardness
+ (GimpPaintOptions *options,
+ GimpBrush *brush);
+
+gboolean gimp_paint_options_is_prop (const gchar *prop_name,
+ GimpContextPropMask prop_mask);
+void gimp_paint_options_copy_props (GimpPaintOptions *src,
+ GimpPaintOptions *dest,
+ GimpContextPropMask prop_mask);
+
+
+#endif /* __GIMP_PAINT_OPTIONS_H__ */
diff --git a/app/paint/gimppencil.c b/app/paint/gimppencil.c
new file mode 100644
index 0000000..14c5cfe
--- /dev/null
+++ b/app/paint/gimppencil.c
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "paint-types.h"
+
+#include "gimppencil.h"
+#include "gimppenciloptions.h"
+
+#include "gimp-intl.h"
+
+
+G_DEFINE_TYPE (GimpPencil, gimp_pencil, GIMP_TYPE_PAINTBRUSH)
+
+
+void
+gimp_pencil_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback)
+{
+ (* callback) (gimp,
+ GIMP_TYPE_PENCIL,
+ GIMP_TYPE_PENCIL_OPTIONS,
+ "gimp-pencil",
+ _("Pencil"),
+ "gimp-tool-pencil");
+}
+
+static void
+gimp_pencil_class_init (GimpPencilClass *klass)
+{
+}
+
+static void
+gimp_pencil_init (GimpPencil *pencil)
+{
+}
diff --git a/app/paint/gimppencil.h b/app/paint/gimppencil.h
new file mode 100644
index 0000000..21fd8d2
--- /dev/null
+++ b/app/paint/gimppencil.h
@@ -0,0 +1,52 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PENCIL_H__
+#define __GIMP_PENCIL_H__
+
+
+#include "gimppaintbrush.h"
+
+
+#define GIMP_TYPE_PENCIL (gimp_pencil_get_type ())
+#define GIMP_PENCIL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PENCIL, GimpPencil))
+#define GIMP_PENCIL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PENCIL, GimpPencilClass))
+#define GIMP_IS_PENCIL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PENCIL))
+#define GIMP_IS_PENCIL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PENCIL))
+#define GIMP_PENCIL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PENCIL, GimpPencilClass))
+
+
+typedef struct _GimpPencilClass GimpPencilClass;
+
+struct _GimpPencil
+{
+ GimpPaintbrush parent_instance;
+};
+
+struct _GimpPencilClass
+{
+ GimpPaintbrushClass parent_class;
+};
+
+
+void gimp_pencil_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback);
+
+GType gimp_pencil_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_PENCIL_H__ */
diff --git a/app/paint/gimppenciloptions.c b/app/paint/gimppenciloptions.c
new file mode 100644
index 0000000..804e99f
--- /dev/null
+++ b/app/paint/gimppenciloptions.c
@@ -0,0 +1,110 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "paint-types.h"
+
+#include "gimppenciloptions.h"
+
+
+#define PENCIL_DEFAULT_HARD TRUE
+
+
+enum
+{
+ PROP_0,
+ PROP_HARD
+};
+
+
+static void gimp_pencil_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_pencil_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpPencilOptions, gimp_pencil_options,
+ GIMP_TYPE_PAINT_OPTIONS)
+
+
+static void
+gimp_pencil_options_class_init (GimpPencilOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_pencil_options_set_property;
+ object_class->get_property = gimp_pencil_options_get_property;
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_HARD,
+ "hard",
+ NULL, NULL,
+ PENCIL_DEFAULT_HARD,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_pencil_options_init (GimpPencilOptions *options)
+{
+}
+
+static void
+gimp_pencil_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpPaintOptions *options = GIMP_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
+gimp_pencil_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpPaintOptions *options = GIMP_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;
+ }
+}
diff --git a/app/paint/gimppenciloptions.h b/app/paint/gimppenciloptions.h
new file mode 100644
index 0000000..f73850e
--- /dev/null
+++ b/app/paint/gimppenciloptions.h
@@ -0,0 +1,49 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PENCIL_OPTIONS_H__
+#define __GIMP_PENCIL_OPTIONS_H__
+
+
+#include "gimppaintoptions.h"
+
+
+#define GIMP_TYPE_PENCIL_OPTIONS (gimp_pencil_options_get_type ())
+#define GIMP_PENCIL_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PENCIL_OPTIONS, GimpPencilOptions))
+#define GIMP_PENCIL_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PENCIL_OPTIONS, GimpPencilOptionsClass))
+#define GIMP_IS_PENCIL_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PENCIL_OPTIONS))
+#define GIMP_IS_PENCIL_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PENCIL_OPTIONS))
+#define GIMP_PENCIL_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PENCIL_OPTIONS, GimpPencilOptionsClass))
+
+
+typedef struct _GimpPencilOptionsClass GimpPencilOptionsClass;
+
+struct _GimpPencilOptions
+{
+ GimpPaintOptions parent_instance;
+};
+
+struct _GimpPencilOptionsClass
+{
+ GimpPaintOptionsClass parent_class;
+};
+
+
+GType gimp_pencil_options_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_PENCIL_OPTIONS_H__ */
diff --git a/app/paint/gimpperspectiveclone.c b/app/paint/gimpperspectiveclone.c
new file mode 100644
index 0000000..abb9fab
--- /dev/null
+++ b/app/paint/gimpperspectiveclone.c
@@ -0,0 +1,537 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "paint-types.h"
+
+#include "gegl/gimp-gegl-nodes.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimp.h"
+#include "core/gimp-utils.h"
+#include "core/gimpdrawable.h"
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimppattern.h"
+#include "core/gimppickable.h"
+#include "core/gimpsymmetry.h"
+
+#include "gimpperspectiveclone.h"
+#include "gimpperspectivecloneoptions.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_perspective_clone_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time);
+
+static gboolean gimp_perspective_clone_use_source (GimpSourceCore *source_core,
+ GimpSourceOptions *options);
+static GeglBuffer * gimp_perspective_clone_get_source (GimpSourceCore *source_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpPickable *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 gimp_perspective_clone_get_matrix (GimpPerspectiveClone *clone,
+ GimpMatrix3 *matrix);
+
+
+G_DEFINE_TYPE (GimpPerspectiveClone, gimp_perspective_clone,
+ GIMP_TYPE_CLONE)
+
+#define parent_class gimp_perspective_clone_parent_class
+
+
+void
+gimp_perspective_clone_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback)
+{
+ (* callback) (gimp,
+ GIMP_TYPE_PERSPECTIVE_CLONE,
+ GIMP_TYPE_PERSPECTIVE_CLONE_OPTIONS,
+ "gimp-perspective-clone",
+ _("Perspective Clone"),
+ "gimp-tool-perspective-clone");
+}
+
+static void
+gimp_perspective_clone_class_init (GimpPerspectiveCloneClass *klass)
+{
+ GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
+ GimpSourceCoreClass *source_core_class = GIMP_SOURCE_CORE_CLASS (klass);
+
+ paint_core_class->paint = gimp_perspective_clone_paint;
+
+ source_core_class->use_source = gimp_perspective_clone_use_source;
+ source_core_class->get_source = gimp_perspective_clone_get_source;
+}
+
+static void
+gimp_perspective_clone_init (GimpPerspectiveClone *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;
+
+ gimp_matrix3_identity (&clone->transform);
+ gimp_matrix3_identity (&clone->transform_inv);
+}
+
+static void
+gimp_perspective_clone_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time)
+{
+ GimpSourceCore *source_core = GIMP_SOURCE_CORE (paint_core);
+ GimpPerspectiveClone *clone = GIMP_PERSPECTIVE_CLONE (paint_core);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpCloneOptions *clone_options = GIMP_CLONE_OPTIONS (paint_options);
+ GimpSourceOptions *options = GIMP_SOURCE_OPTIONS (paint_options);
+ const GimpCoords *coords;
+
+ /* The source is based on the original stroke */
+ coords = gimp_symmetry_get_origin (sym);
+
+ switch (paint_state)
+ {
+ case GIMP_PAINT_STATE_INIT:
+ if (source_core->set_source)
+ {
+ g_object_set (source_core, "src-drawable", drawable, NULL);
+
+ source_core->src_x = floor (coords->x);
+ source_core->src_y = floor (coords->y);
+
+ /* get source coordinates in front view perspective */
+ gimp_matrix3_transform_point (&clone->transform_inv,
+ source_core->src_x,
+ source_core->src_y,
+ &clone->src_x_fv,
+ &clone->src_y_fv);
+
+ source_core->first_stroke = TRUE;
+ }
+ else
+ {
+ GeglBuffer *orig_buffer = NULL;
+ GeglNode *tile = NULL;
+ GeglNode *src_node;
+
+ if (options->align_mode == GIMP_SOURCE_ALIGN_NO)
+ {
+ source_core->orig_src_x = source_core->src_x;
+ source_core->orig_src_y = source_core->src_y;
+
+ 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 GIMP_CLONE_IMAGE:
+ {
+ GimpPickable *src_pickable;
+ GimpImage *src_image;
+ GimpImage *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_pickable = GIMP_PICKABLE (source_core->src_drawable);
+ src_image = gimp_pickable_get_image (src_pickable);
+
+ if (options->sample_merged)
+ src_pickable = GIMP_PICKABLE (src_image);
+
+ dest_image = gimp_item_get_image (GIMP_ITEM (drawable));
+
+ if ((options->sample_merged &&
+ (src_image != dest_image)) ||
+ (! options->sample_merged &&
+ (source_core->src_drawable != drawable)))
+ {
+ orig_buffer = gimp_pickable_get_buffer (src_pickable);
+ }
+ else
+ {
+ if (options->sample_merged)
+ orig_buffer = gimp_paint_core_get_orig_proj (paint_core);
+ else
+ orig_buffer = gimp_paint_core_get_orig_image (paint_core);
+ }
+ }
+ break;
+
+ case GIMP_CLONE_PATTERN:
+ {
+ GimpPattern *pattern = gimp_context_get_pattern (context);
+
+ orig_buffer = gimp_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;
+ }
+
+ 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", GIMP_INTERPOLATION_LINEAR,
+ NULL);
+
+ clone->dest_node =
+ gegl_node_new_child (clone->node,
+ "operation", "gegl:write-buffer",
+ NULL);
+
+ if (tile)
+ {
+ gegl_node_link_many (src_node,
+ tile,
+ clone->crop,
+ clone->transform_node,
+ clone->dest_node,
+ NULL);
+
+ g_object_unref (orig_buffer);
+ }
+ else
+ {
+ gegl_node_link_many (src_node,
+ clone->transform_node,
+ clone->dest_node,
+ NULL);
+ }
+ }
+ break;
+
+ case GIMP_PAINT_STATE_MOTION:
+ if (source_core->set_source)
+ {
+ /* If the control key is down, move the src target and return */
+
+ source_core->src_x = floor (coords->x);
+ source_core->src_y = floor (coords->y);
+
+ /* get source coordinates in front view perspective */
+ gimp_matrix3_transform_point (&clone->transform_inv,
+ source_core->src_x,
+ source_core->src_y,
+ &clone->src_x_fv,
+ &clone->src_y_fv);
+
+ source_core->first_stroke = TRUE;
+ }
+ else
+ {
+ /* otherwise, update the target */
+
+ gint dest_x;
+ gint dest_y;
+ gint n_strokes;
+ gint i;
+
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; i++)
+ {
+ coords = gimp_symmetry_get_coords (sym, i);
+
+ dest_x = floor (coords->x);
+ dest_y = floor (coords->y);
+
+ if (options->align_mode == GIMP_SOURCE_ALIGN_REGISTERED)
+ {
+ source_core->offset_x = 0;
+ source_core->offset_y = 0;
+ }
+ else if (options->align_mode == GIMP_SOURCE_ALIGN_FIXED)
+ {
+ source_core->offset_x = source_core->src_x - dest_x;
+ source_core->offset_y = source_core->src_y - dest_y;
+ }
+ else if (source_core->first_stroke)
+ {
+ source_core->offset_x = source_core->src_x - dest_x;
+ source_core->offset_y = source_core->src_y - dest_y;
+
+ /* get destination coordinates in front view perspective */
+ gimp_matrix3_transform_point (&clone->transform_inv,
+ dest_x,
+ dest_y,
+ &clone->dest_x_fv,
+ &clone->dest_y_fv);
+
+ source_core->first_stroke = FALSE;
+ }
+ }
+
+ gimp_source_core_motion (source_core, drawable, paint_options, sym);
+ }
+ break;
+
+ case GIMP_PAINT_STATE_FINISH:
+ g_clear_object (&clone->node);
+ clone->crop = NULL;
+ clone->transform_node = NULL;
+ clone->dest_node = NULL;
+ break;
+
+ default:
+ break;
+ }
+
+ g_object_notify (G_OBJECT (clone), "src-x");
+ g_object_notify (G_OBJECT (clone), "src-y");
+}
+
+static gboolean
+gimp_perspective_clone_use_source (GimpSourceCore *source_core,
+ GimpSourceOptions *options)
+{
+ return TRUE;
+}
+
+static GeglBuffer *
+gimp_perspective_clone_get_source (GimpSourceCore *source_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpPickable *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)
+{
+ GimpPerspectiveClone *clone = GIMP_PERSPECTIVE_CLONE (source_core);
+ GimpCloneOptions *clone_options = GIMP_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;
+ GimpMatrix3 matrix;
+ GimpMatrix3 gegl_matrix;
+
+ src_buffer = gimp_pickable_get_buffer (src_pickable);
+ src_format_alpha = gimp_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
+ */
+ gimp_perspective_clone_get_source_point (clone, x1d, y1d, &x1s, &y1s);
+ gimp_perspective_clone_get_source_point (clone, x1d, y2d, &x2s, &y2s);
+ gimp_perspective_clone_get_source_point (clone, x2d, y1d, &x3s, &y3s);
+ gimp_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 GIMP_CLONE_IMAGE:
+ if (! gimp_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;
+ }
+ break;
+
+ case GIMP_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);
+
+ gimp_perspective_clone_get_matrix (clone, &matrix);
+
+ gimp_matrix3_identity (&gegl_matrix);
+ gimp_matrix3_mult (&matrix, &gegl_matrix);
+ gimp_matrix3_translate (&gegl_matrix, -x1d, -y1d);
+
+ gimp_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
+gimp_perspective_clone_set_transform (GimpPerspectiveClone *clone,
+ GimpMatrix3 *transform)
+{
+ g_return_if_fail (GIMP_IS_PERSPECTIVE_CLONE (clone));
+ g_return_if_fail (transform != NULL);
+
+ clone->transform = *transform;
+
+ clone->transform_inv = clone->transform;
+ gimp_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
+gimp_perspective_clone_get_source_point (GimpPerspectiveClone *clone,
+ gdouble x,
+ gdouble y,
+ gdouble *newx,
+ gdouble *newy)
+{
+ gdouble temp_x, temp_y;
+
+ g_return_if_fail (GIMP_IS_PERSPECTIVE_CLONE (clone));
+ g_return_if_fail (newx != NULL);
+ g_return_if_fail (newy != NULL);
+
+ gimp_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 */
+ gimp_matrix3_transform_point (&clone->transform,
+ temp_x, temp_y, newx, newy);
+}
+
+
+/* private functions */
+
+static void
+gimp_perspective_clone_get_matrix (GimpPerspectiveClone *clone,
+ GimpMatrix3 *matrix)
+{
+ GimpMatrix3 temp;
+
+ gimp_matrix3_identity (&temp);
+ gimp_matrix3_translate (&temp,
+ clone->dest_x_fv - clone->src_x_fv,
+ clone->dest_y_fv - clone->src_y_fv);
+
+ *matrix = clone->transform_inv;
+ gimp_matrix3_mult (&temp, matrix);
+ gimp_matrix3_mult (&clone->transform, matrix);
+}
diff --git a/app/paint/gimpperspectiveclone.h b/app/paint/gimpperspectiveclone.h
new file mode 100644
index 0000000..1d24957
--- /dev/null
+++ b/app/paint/gimpperspectiveclone.h
@@ -0,0 +1,74 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PERSPECTIVE_CLONE_H__
+#define __GIMP_PERSPECTIVE_CLONE_H__
+
+
+#include "gimpclone.h"
+
+
+#define GIMP_TYPE_PERSPECTIVE_CLONE (gimp_perspective_clone_get_type ())
+#define GIMP_PERSPECTIVE_CLONE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PERSPECTIVE_CLONE, GimpPerspectiveClone))
+#define GIMP_PERSPECTIVE_CLONE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PERSPECTIVE_CLONE, GimpPerspectiveCloneClass))
+#define GIMP_IS_PERSPECTIVE_CLONE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PERSPECTIVE_CLONE))
+#define GIMP_IS_PERSPECTIVE_CLONE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PERSPECTIVE_CLONE))
+#define GIMP_PERSPECTIVE_CLONE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PERSPECTIVE_CLONE, GimpPerspectiveCloneClass))
+
+
+typedef struct _GimpPerspectiveCloneClass GimpPerspectiveCloneClass;
+
+struct _GimpPerspectiveClone
+{
+ GimpClone 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;
+
+ GimpMatrix3 transform;
+ GimpMatrix3 transform_inv;
+
+ GeglNode *node;
+ GeglNode *crop;
+ GeglNode *transform_node;
+ GeglNode *dest_node;
+};
+
+struct _GimpPerspectiveCloneClass
+{
+ GimpCloneClass parent_class;
+};
+
+
+void gimp_perspective_clone_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback);
+
+GType gimp_perspective_clone_get_type (void) G_GNUC_CONST;
+
+void gimp_perspective_clone_set_transform (GimpPerspectiveClone *clone,
+ GimpMatrix3 *transform);
+void gimp_perspective_clone_get_source_point (GimpPerspectiveClone *clone,
+ gdouble x,
+ gdouble y,
+ gdouble *newx,
+ gdouble *newy);
+
+
+#endif /* __GIMP_PERSPECTIVE_CLONE_H__ */
diff --git a/app/paint/gimpperspectivecloneoptions.c b/app/paint/gimpperspectivecloneoptions.c
new file mode 100644
index 0000000..cdd0f32
--- /dev/null
+++ b/app/paint/gimpperspectivecloneoptions.c
@@ -0,0 +1,112 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "paint-types.h"
+
+#include "gimpperspectivecloneoptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_CLONE_MODE
+};
+
+
+static void gimp_perspective_clone_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_perspective_clone_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpPerspectiveCloneOptions, gimp_perspective_clone_options,
+ GIMP_TYPE_CLONE_OPTIONS)
+
+
+static void
+gimp_perspective_clone_options_class_init (GimpPerspectiveCloneOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_perspective_clone_options_set_property;
+ object_class->get_property = gimp_perspective_clone_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_CLONE_MODE,
+ "clone-mode",
+ NULL, NULL,
+ GIMP_TYPE_PERSPECTIVE_CLONE_MODE,
+ GIMP_PERSPECTIVE_CLONE_MODE_ADJUST,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_perspective_clone_options_init (GimpPerspectiveCloneOptions *options)
+{
+}
+
+static void
+gimp_perspective_clone_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpPerspectiveCloneOptions *options = GIMP_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
+gimp_perspective_clone_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpPerspectiveCloneOptions *options = GIMP_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;
+ }
+}
diff --git a/app/paint/gimpperspectivecloneoptions.h b/app/paint/gimpperspectivecloneoptions.h
new file mode 100644
index 0000000..026c24b
--- /dev/null
+++ b/app/paint/gimpperspectivecloneoptions.h
@@ -0,0 +1,51 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_PERSPECTIVE_CLONE_OPTIONS_H__
+#define __GIMP_PERSPECTIVE_CLONE_OPTIONS_H__
+
+
+#include "gimpcloneoptions.h"
+
+
+#define GIMP_TYPE_PERSPECTIVE_CLONE_OPTIONS (gimp_perspective_clone_options_get_type ())
+#define GIMP_PERSPECTIVE_CLONE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_PERSPECTIVE_CLONE_OPTIONS, GimpPerspectiveCloneOptions))
+#define GIMP_PERSPECTIVE_CLONE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_PERSPECTIVE_CLONE_OPTIONS, GimpPerspectiveCloneOptionsClass))
+#define GIMP_IS_PERSPECTIVE_CLONE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_PERSPECTIVE_CLONE_OPTIONS))
+#define GIMP_IS_PERSPECTIVE_CLONE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_PERSPECTIVE_CLONE_OPTIONS))
+#define GIMP_PERSPECTIVE_CLONE_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_PERSPECTIVE_CLONE_OPTIONS, GimpPerspectiveCloneOptionsClass))
+
+
+typedef struct _GimpPerspectiveCloneOptionsClass GimpPerspectiveCloneOptionsClass;
+
+struct _GimpPerspectiveCloneOptions
+{
+ GimpCloneOptions paint_instance;
+
+ GimpPerspectiveCloneMode clone_mode;
+};
+
+struct _GimpPerspectiveCloneOptionsClass
+{
+ GimpCloneOptionsClass parent_class;
+};
+
+
+GType gimp_perspective_clone_options_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_PERSPECTIVE_CLONE_OPTIONS_H__ */
diff --git a/app/paint/gimpsmudge.c b/app/paint/gimpsmudge.c
new file mode 100644
index 0000000..86f61bf
--- /dev/null
+++ b/app/paint/gimpsmudge.c
@@ -0,0 +1,575 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "paint-types.h"
+
+#include "gegl/gimp-gegl-loops.h"
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimp-palettes.h"
+#include "core/gimpbrush.h"
+#include "core/gimpbrush-header.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdynamics.h"
+#include "core/gimpimage.h"
+#include "core/gimppickable.h"
+#include "core/gimpsymmetry.h"
+#include "core/gimptempbuf.h"
+
+#include "gimpsmudge.h"
+#include "gimpsmudgeoptions.h"
+
+#include "gimp-intl.h"
+
+
+static void gimp_smudge_finalize (GObject *object);
+
+static void gimp_smudge_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time);
+static gboolean gimp_smudge_start (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym);
+static void gimp_smudge_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym);
+
+static void gimp_smudge_accumulator_coords (GimpPaintCore *paint_core,
+ const GimpCoords *coords,
+ gint stroke,
+ gint *x,
+ gint *y);
+
+static void gimp_smudge_accumulator_size (GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ gint *accumulator_size);
+
+
+G_DEFINE_TYPE (GimpSmudge, gimp_smudge, GIMP_TYPE_BRUSH_CORE)
+
+#define parent_class gimp_smudge_parent_class
+
+
+void
+gimp_smudge_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback)
+{
+ (* callback) (gimp,
+ GIMP_TYPE_SMUDGE,
+ GIMP_TYPE_SMUDGE_OPTIONS,
+ "gimp-smudge",
+ _("Smudge"),
+ "gimp-tool-smudge");
+}
+
+static void
+gimp_smudge_class_init (GimpSmudgeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
+ GimpBrushCoreClass *brush_core_class = GIMP_BRUSH_CORE_CLASS (klass);
+
+ object_class->finalize = gimp_smudge_finalize;
+
+ paint_core_class->paint = gimp_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
+gimp_smudge_init (GimpSmudge *smudge)
+{
+}
+
+static void
+gimp_smudge_finalize (GObject *object)
+{
+ GimpSmudge *smudge = GIMP_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
+gimp_smudge_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time)
+{
+ GimpSmudge *smudge = GIMP_SMUDGE (paint_core);
+
+ switch (paint_state)
+ {
+ case GIMP_PAINT_STATE_INIT:
+ {
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpSmudgeOptions *options = GIMP_SMUDGE_OPTIONS (paint_options);
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paint_core);
+ GimpDynamics *dynamics = gimp_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 &&
+ ! gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_COLOR) &&
+ ! (brush_core->brush && gimp_brush_get_pixmap (brush_core->brush)))
+ {
+ GimpRGB foreground;
+
+ gimp_context_get_foreground (context, &foreground);
+ gimp_palettes_add_color_history (context->gimp, &foreground);
+ }
+ }
+ break;
+
+ case GIMP_PAINT_STATE_MOTION:
+ /* initialization fails if the user starts outside the drawable */
+ if (! smudge->initialized)
+ smudge->initialized = gimp_smudge_start (paint_core, drawable,
+ paint_options, sym);
+
+ if (smudge->initialized)
+ gimp_smudge_motion (paint_core, drawable, paint_options, sym);
+ break;
+
+ case GIMP_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
+gimp_smudge_start (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym)
+{
+ GimpSmudge *smudge = GIMP_SMUDGE (paint_core);
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paint_core);
+ GimpSmudgeOptions *options = GIMP_SMUDGE_OPTIONS (paint_options);
+ GimpPickable *dest_pickable;
+ GeglBuffer *pickable_buffer;
+ GeglBuffer *paint_buffer;
+ GimpCoords *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;
+
+ coords = gimp_symmetry_get_origin (sym);
+ gimp_brush_core_eval_transform_dynamics (brush_core,
+ drawable,
+ paint_options,
+ coords);
+
+ if (options->sample_merged)
+ {
+ dest_pickable = gimp_paint_core_get_image_pickable (paint_core);
+
+ gimp_item_get_offset (GIMP_ITEM (drawable),
+ &dest_pickable_off_x,
+ &dest_pickable_off_y);
+ }
+ else
+ {
+ dest_pickable = GIMP_PICKABLE (drawable);
+
+ dest_pickable_off_x = 0;
+ dest_pickable_off_y = 0;
+ }
+
+ pickable_buffer = gimp_pickable_get_buffer (dest_pickable);
+
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; i++)
+ {
+ GeglBuffer *accum_buffer;
+
+ coords = gimp_symmetry_get_coords (sym, i);
+
+ gimp_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 = gimp_paint_core_get_paint_buffer (paint_core, drawable,
+ paint_options,
+ GIMP_LAYER_MODE_NORMAL,
+ coords,
+ &paint_buffer_x,
+ &paint_buffer_y,
+ NULL, NULL);
+ if (! paint_buffer)
+ continue;
+
+ gimp_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);
+
+ gimp_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. */
+ gimp_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
+gimp_smudge_motion (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym)
+{
+ GimpSmudge *smudge = GIMP_SMUDGE (paint_core);
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (paint_core);
+ GimpSmudgeOptions *options = GIMP_SMUDGE_OPTIONS (paint_options);
+ GimpContext *context = GIMP_CONTEXT (paint_options);
+ GimpDynamics *dynamics = GIMP_BRUSH_CORE (paint_core)->dynamics;
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ GimpPickable *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 */
+ GimpRGB brush_color;
+ GimpRGB *brush_color_ptr; /* whether use single color or pixmap */
+ /* accum buffer */
+ gint x, y;
+ GeglBuffer *accum_buffer;
+ /* other variables */
+ gdouble force;
+ GimpCoords *coords;
+ gint paint_width, paint_height;
+ gint n_strokes;
+ gint i;
+
+ if (options->sample_merged)
+ {
+ dest_pickable = gimp_paint_core_get_image_pickable (paint_core);
+
+ gimp_item_get_offset (GIMP_ITEM (drawable),
+ &dest_pickable_off_x,
+ &dest_pickable_off_y);
+ }
+ else
+ {
+ dest_pickable = GIMP_PICKABLE (drawable);
+
+ dest_pickable_off_x = 0;
+ dest_pickable_off_y = 0;
+ }
+
+ fade_point = gimp_paint_options_get_fade (paint_options, image,
+ paint_core->pixel_dist);
+
+ coords = gimp_symmetry_get_origin (sym);
+ opacity = gimp_dynamics_get_linear_value (dynamics,
+ GIMP_DYNAMICS_OUTPUT_OPACITY,
+ coords,
+ paint_options,
+ fade_point);
+ if (opacity == 0.0)
+ return;
+
+ gimp_brush_core_eval_transform_dynamics (brush_core,
+ drawable,
+ paint_options,
+ coords);
+
+ /* Get brush dynamic values other than opacity */
+ rate = ((options->rate / 100.0) *
+ gimp_dynamics_get_linear_value (dynamics,
+ GIMP_DYNAMICS_OUTPUT_RATE,
+ coords,
+ paint_options,
+ fade_point));
+
+ flow = ((options->flow / 100.0) *
+ gimp_dynamics_get_linear_value (dynamics,
+ GIMP_DYNAMICS_OUTPUT_FLOW,
+ coords,
+ paint_options,
+ fade_point));
+
+ grad_point = gimp_dynamics_get_linear_value (dynamics,
+ GIMP_DYNAMICS_OUTPUT_COLOR,
+ coords,
+ paint_options,
+ fade_point);
+
+ /* Get current gradient color, brush pixmap, or foreground color */
+ brush_color_ptr = &brush_color;
+ if (gimp_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 && gimp_brush_get_pixmap (brush_core->brush))
+ {
+ brush_color_ptr = NULL;
+ }
+ else
+ {
+ gimp_context_get_foreground (context, &brush_color);
+ }
+
+ /* Convert to linear RGBA */
+ if (brush_color_ptr)
+ gimp_pickable_srgb_to_pixel (dest_pickable,
+ &brush_color,
+ babl_format ("RGBA double"),
+ &brush_color);
+
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; i++)
+ {
+ coords = gimp_symmetry_get_coords (sym, i);
+
+ gimp_brush_core_eval_transform_symmetry (brush_core, sym, i);
+
+ paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable,
+ paint_options,
+ GIMP_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 acumulator coordinates */
+ gimp_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
+ * gimp_gegl_smudge_with_paint() instead of calling
+ * gegl_buffer_set_color() to reduce gegl's internal processing.
+ */
+ if (! brush_color_ptr && flow > 0.0)
+ {
+ gimp_brush_core_color_area_with_pixmap (brush_core, drawable,
+ coords,
+ paint_buffer,
+ paint_buffer_x,
+ paint_buffer_y,
+ TRUE);
+ }
+
+ gimp_gegl_smudge_with_paint (accum_buffer,
+ GEGL_RECTANGLE (paint_buffer_x - x,
+ paint_buffer_y - y,
+ paint_buffer_width,
+ paint_buffer_height),
+ gimp_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 (gimp_dynamics_is_output_enabled (dynamics, GIMP_DYNAMICS_OUTPUT_FORCE))
+ force = gimp_dynamics_get_linear_value (dynamics,
+ GIMP_DYNAMICS_OUTPUT_FORCE,
+ coords,
+ paint_options,
+ fade_point);
+ else
+ force = paint_options->brush_force;
+
+ gimp_brush_core_replace_canvas (GIMP_BRUSH_CORE (paint_core), drawable,
+ coords,
+ MIN (opacity, GIMP_OPACITY_OPAQUE),
+ gimp_context_get_opacity (context),
+ gimp_paint_options_get_brush_mode (paint_options),
+ force,
+ GIMP_PAINT_INCREMENTAL);
+ }
+}
+
+static void
+gimp_smudge_accumulator_coords (GimpPaintCore *paint_core,
+ const GimpCoords *coords,
+ gint stroke,
+ gint *x,
+ gint *y)
+{
+ GimpSmudge *smudge = GIMP_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
+gimp_smudge_accumulator_size (GimpPaintOptions *paint_options,
+ const GimpCoords *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,
+ GIMP_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);
+}
diff --git a/app/paint/gimpsmudge.h b/app/paint/gimpsmudge.h
new file mode 100644
index 0000000..28be505
--- /dev/null
+++ b/app/paint/gimpsmudge.h
@@ -0,0 +1,55 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_SMUDGE_H__
+#define __GIMP_SMUDGE_H__
+
+
+#include "gimpbrushcore.h"
+
+
+#define GIMP_TYPE_SMUDGE (gimp_smudge_get_type ())
+#define GIMP_SMUDGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SMUDGE, GimpSmudge))
+#define GIMP_SMUDGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SMUDGE, GimpSmudgeClass))
+#define GIMP_IS_SMUDGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SMUDGE))
+#define GIMP_IS_SMUDGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SMUDGE))
+#define GIMP_SMUDGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SMUDGE, GimpSmudgeClass))
+
+
+typedef struct _GimpSmudgeClass GimpSmudgeClass;
+
+struct _GimpSmudge
+{
+ GimpBrushCore parent_instance;
+
+ gboolean initialized;
+ GList *accum_buffers;
+};
+
+struct _GimpSmudgeClass
+{
+ GimpBrushCoreClass parent_class;
+};
+
+
+void gimp_smudge_register (Gimp *gimp,
+ GimpPaintRegisterCallback callback);
+
+GType gimp_smudge_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_SMUDGE_H__ */
diff --git a/app/paint/gimpsmudgeoptions.c b/app/paint/gimpsmudgeoptions.c
new file mode 100644
index 0000000..cdf6f0b
--- /dev/null
+++ b/app/paint/gimpsmudgeoptions.c
@@ -0,0 +1,159 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "paint-types.h"
+
+#include "gimpsmudgeoptions.h"
+
+#include "gimp-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 gimp_smudge_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_smudge_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpSmudgeOptions, gimp_smudge_options,
+ GIMP_TYPE_PAINT_OPTIONS)
+
+
+static void
+gimp_smudge_options_class_init (GimpSmudgeOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_smudge_options_set_property;
+ object_class->get_property = gimp_smudge_options_get_property;
+
+ GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_RATE,
+ "rate",
+ C_("smudge-tool", "Rate"),
+ _("The strength of smudging"),
+ 0.0, 100.0, SMUDGE_DEFAULT_RATE,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_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,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_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,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_SAMPLE_MERGED,
+ "sample-merged",
+ _("Sample merged"),
+ NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_smudge_options_init (GimpSmudgeOptions *options)
+{
+}
+
+static void
+gimp_smudge_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSmudgeOptions *options = GIMP_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
+gimp_smudge_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSmudgeOptions *options = GIMP_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;
+ }
+}
diff --git a/app/paint/gimpsmudgeoptions.h b/app/paint/gimpsmudgeoptions.h
new file mode 100644
index 0000000..910ff52
--- /dev/null
+++ b/app/paint/gimpsmudgeoptions.h
@@ -0,0 +1,54 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_SMUDGE_OPTIONS_H__
+#define __GIMP_SMUDGE_OPTIONS_H__
+
+
+#include "gimppaintoptions.h"
+
+
+#define GIMP_TYPE_SMUDGE_OPTIONS (gimp_smudge_options_get_type ())
+#define GIMP_SMUDGE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SMUDGE_OPTIONS, GimpSmudgeOptions))
+#define GIMP_SMUDGE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SMUDGE_OPTIONS, GimpSmudgeOptionsClass))
+#define GIMP_IS_SMUDGE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SMUDGE_OPTIONS))
+#define GIMP_IS_SMUDGE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SMUDGE_OPTIONS))
+#define GIMP_SMUDGE_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SMUDGE_OPTIONS, GimpSmudgeOptionsClass))
+
+
+typedef struct _GimpSmudgeOptionsClass GimpSmudgeOptionsClass;
+
+struct _GimpSmudgeOptions
+{
+ GimpPaintOptions parent_instance;
+
+ gdouble rate;
+ gdouble flow;
+ gboolean no_erasing;
+ gboolean sample_merged;
+};
+
+struct _GimpSmudgeOptionsClass
+{
+ GimpPaintOptionsClass parent_class;
+};
+
+
+GType gimp_smudge_options_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_SMUDGE_OPTIONS_H__ */
diff --git a/app/paint/gimpsourcecore.c b/app/paint/gimpsourcecore.c
new file mode 100644
index 0000000..94238ad
--- /dev/null
+++ b/app/paint/gimpsourcecore.c
@@ -0,0 +1,679 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "paint-types.h"
+
+#include "gegl/gimp-gegl-utils.h"
+
+#include "core/gimp.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpdynamics.h"
+#include "core/gimperror.h"
+#include "core/gimpimage.h"
+#include "core/gimppickable.h"
+#include "core/gimpsymmetry.h"
+
+#include "gimpsourcecore.h"
+#include "gimpsourceoptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_SRC_DRAWABLE,
+ PROP_SRC_X,
+ PROP_SRC_Y
+};
+
+
+static void gimp_source_core_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_source_core_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static gboolean gimp_source_core_start (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GError **error);
+static void gimp_source_core_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time);
+
+#if 0
+static void gimp_source_core_motion (GimpSourceCore *source_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym);
+#endif
+
+static gboolean gimp_source_core_real_use_source (GimpSourceCore *source_core,
+ GimpSourceOptions *options);
+static GeglBuffer *
+ gimp_source_core_real_get_source (GimpSourceCore *source_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpPickable *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 gimp_source_core_set_src_drawable (GimpSourceCore *source_core,
+ GimpDrawable *drawable);
+
+
+G_DEFINE_TYPE (GimpSourceCore, gimp_source_core, GIMP_TYPE_BRUSH_CORE)
+
+#define parent_class gimp_source_core_parent_class
+
+
+static void
+gimp_source_core_class_init (GimpSourceCoreClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass);
+ GimpBrushCoreClass *brush_core_class = GIMP_BRUSH_CORE_CLASS (klass);
+
+ object_class->set_property = gimp_source_core_set_property;
+ object_class->get_property = gimp_source_core_get_property;
+
+ paint_core_class->start = gimp_source_core_start;
+ paint_core_class->paint = gimp_source_core_paint;
+
+ brush_core_class->handles_changing_brush = TRUE;
+
+ klass->use_source = gimp_source_core_real_use_source;
+ klass->get_source = gimp_source_core_real_get_source;
+ klass->motion = NULL;
+
+ g_object_class_install_property (object_class, PROP_SRC_DRAWABLE,
+ g_param_spec_object ("src-drawable",
+ NULL, NULL,
+ GIMP_TYPE_DRAWABLE,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SRC_X,
+ g_param_spec_int ("src-x", NULL, NULL,
+ 0, GIMP_MAX_IMAGE_SIZE,
+ 0,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SRC_Y,
+ g_param_spec_int ("src-y", NULL, NULL,
+ 0, GIMP_MAX_IMAGE_SIZE,
+ 0,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_source_core_init (GimpSourceCore *source_core)
+{
+ source_core->set_source = FALSE;
+
+ source_core->src_drawable = NULL;
+ source_core->src_x = 0;
+ source_core->src_y = 0;
+
+ 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 void
+gimp_source_core_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSourceCore *source_core = GIMP_SOURCE_CORE (object);
+
+ switch (property_id)
+ {
+ case PROP_SRC_DRAWABLE:
+ gimp_source_core_set_src_drawable (source_core,
+ g_value_get_object (value));
+ break;
+ case PROP_SRC_X:
+ source_core->src_x = g_value_get_int (value);
+ break;
+ case PROP_SRC_Y:
+ source_core->src_y = g_value_get_int (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_source_core_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSourceCore *source_core = GIMP_SOURCE_CORE (object);
+
+ switch (property_id)
+ {
+ case PROP_SRC_DRAWABLE:
+ g_value_set_object (value, source_core->src_drawable);
+ break;
+ case PROP_SRC_X:
+ g_value_set_int (value, source_core->src_x);
+ break;
+ case PROP_SRC_Y:
+ g_value_set_int (value, source_core->src_y);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gimp_source_core_start (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GError **error)
+{
+ GimpSourceCore *source_core = GIMP_SOURCE_CORE (paint_core);
+ GimpSourceOptions *options = GIMP_SOURCE_OPTIONS (paint_options);
+
+ if (! GIMP_PAINT_CORE_CLASS (parent_class)->start (paint_core, drawable,
+ paint_options, coords,
+ error))
+ {
+ return FALSE;
+ }
+
+ paint_core->use_saved_proj = FALSE;
+
+ if (! source_core->set_source &&
+ gimp_source_core_use_source (source_core, options))
+ {
+ if (! source_core->src_drawable)
+ {
+ g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED,
+ _("Set a source image first."));
+ return FALSE;
+ }
+
+ if (options->sample_merged &&
+ gimp_item_get_image (GIMP_ITEM (source_core->src_drawable)) ==
+ gimp_item_get_image (GIMP_ITEM (drawable)))
+ {
+ paint_core->use_saved_proj = TRUE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void
+gimp_source_core_paint (GimpPaintCore *paint_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym,
+ GimpPaintState paint_state,
+ guint32 time)
+{
+ GimpSourceCore *source_core = GIMP_SOURCE_CORE (paint_core);
+ GimpSourceOptions *options = GIMP_SOURCE_OPTIONS (paint_options);
+ const GimpCoords *coords;
+
+ /* The source is based on the original stroke */
+ coords = gimp_symmetry_get_origin (sym);
+
+ switch (paint_state)
+ {
+ case GIMP_PAINT_STATE_INIT:
+ if (source_core->set_source)
+ {
+ gimp_source_core_set_src_drawable (source_core, drawable);
+
+ /* FIXME(?): subpixel source sampling */
+ source_core->src_x = floor (coords->x);
+ source_core->src_y = floor (coords->y);
+
+ source_core->first_stroke = TRUE;
+ }
+ else if (options->align_mode == GIMP_SOURCE_ALIGN_NO)
+ {
+ source_core->orig_src_x = source_core->src_x;
+ source_core->orig_src_y = source_core->src_y;
+
+ source_core->first_stroke = TRUE;
+ }
+ break;
+
+ case GIMP_PAINT_STATE_MOTION:
+ if (source_core->set_source)
+ {
+ /* If the control key is down, move the src target and return */
+
+ source_core->src_x = floor (coords->x);
+ source_core->src_y = floor (coords->y);
+
+ source_core->first_stroke = TRUE;
+ }
+ else
+ {
+ /* otherwise, update the target */
+
+ gint dest_x;
+ gint dest_y;
+
+ dest_x = floor (coords->x);
+ dest_y = floor (coords->y);
+
+ if (options->align_mode == GIMP_SOURCE_ALIGN_REGISTERED)
+ {
+ source_core->offset_x = 0;
+ source_core->offset_y = 0;
+ }
+ else if (options->align_mode == GIMP_SOURCE_ALIGN_FIXED)
+ {
+ source_core->offset_x = source_core->src_x - dest_x;
+ source_core->offset_y = source_core->src_y - dest_y;
+ }
+ else if (source_core->first_stroke)
+ {
+ source_core->offset_x = source_core->src_x - dest_x;
+ source_core->offset_y = source_core->src_y - dest_y;
+
+ source_core->first_stroke = FALSE;
+ }
+
+ source_core->src_x = dest_x + source_core->offset_x;
+ source_core->src_y = dest_y + source_core->offset_y;
+
+ gimp_source_core_motion (source_core, drawable, paint_options,
+ sym);
+ }
+ break;
+
+ case GIMP_PAINT_STATE_FINISH:
+ if (options->align_mode == GIMP_SOURCE_ALIGN_NO &&
+ ! source_core->first_stroke)
+ {
+ source_core->src_x = source_core->orig_src_x;
+ source_core->src_y = source_core->orig_src_y;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ g_object_notify (G_OBJECT (source_core), "src-x");
+ g_object_notify (G_OBJECT (source_core), "src-y");
+}
+
+void
+gimp_source_core_motion (GimpSourceCore *source_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym)
+
+{
+ GimpPaintCore *paint_core = GIMP_PAINT_CORE (source_core);
+ GimpBrushCore *brush_core = GIMP_BRUSH_CORE (source_core);
+ GimpSourceOptions *options = GIMP_SOURCE_OPTIONS (paint_options);
+ GimpDynamics *dynamics = GIMP_BRUSH_CORE (paint_core)->dynamics;
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ GimpPickable *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;
+ GimpLayerMode paint_mode;
+ GeglNode *op;
+ GimpCoords *origin;
+ GimpCoords *coords;
+ gint n_strokes;
+ gint i;
+
+ fade_point = gimp_paint_options_get_fade (paint_options, image,
+ paint_core->pixel_dist);
+
+ origin = gimp_symmetry_get_origin (sym);
+ /* Some settings are based on the original stroke. */
+ opacity = gimp_dynamics_get_linear_value (dynamics,
+ GIMP_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 (gimp_source_core_use_source (source_core, options))
+ {
+ src_pickable = GIMP_PICKABLE (source_core->src_drawable);
+
+ if (options->sample_merged)
+ {
+ GimpImage *src_image = gimp_pickable_get_image (src_pickable);
+ gint off_x, off_y;
+
+ if (! gimp_paint_core_get_show_all (paint_core))
+ {
+ src_pickable = GIMP_PICKABLE (src_image);
+ }
+ else
+ {
+ src_pickable = GIMP_PICKABLE (
+ gimp_image_get_projection (src_image));
+ }
+
+ gimp_item_get_offset (GIMP_ITEM (source_core->src_drawable),
+ &off_x, &off_y);
+
+ base_src_offset_x += off_x;
+ base_src_offset_y += off_y;
+ }
+ }
+
+ gimp_brush_core_eval_transform_dynamics (brush_core,
+ drawable,
+ paint_options,
+ origin);
+
+ paint_mode = gimp_context_get_paint_mode (GIMP_CONTEXT (paint_options));
+
+ n_strokes = gimp_symmetry_get_size (sym);
+ for (i = 0; i < n_strokes; i++)
+ {
+ coords = gimp_symmetry_get_coords (sym, i);
+
+ gimp_brush_core_eval_transform_symmetry (brush_core, sym, i);
+
+ paint_buffer = gimp_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 (gimp_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_offset_y += floor (origin->y) - floor (coords->y);
+ src_buffer =
+ GIMP_SOURCE_CORE_GET_CLASS (source_core)->get_source (source_core,
+ drawable,
+ paint_options,
+ 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 = gimp_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", -(source_core->src_x + 0.5),
+ "y", -(source_core->src_y + 0.5),
+ NULL);
+
+ gegl_node_add_child (node, op);
+
+ translate_after = gegl_node_new_child (
+ node,
+ "operation", "gegl:translate",
+ "x", (source_core->src_x + 0.5) +
+ (paint_area_offset_x - src_rect.x),
+ "y", (source_core->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;
+ }
+
+ GIMP_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
+gimp_source_core_use_source (GimpSourceCore *source_core,
+ GimpSourceOptions *options)
+{
+ return GIMP_SOURCE_CORE_GET_CLASS (source_core)->use_source (source_core,
+ options);
+}
+
+static gboolean
+gimp_source_core_real_use_source (GimpSourceCore *source_core,
+ GimpSourceOptions *options)
+{
+ return TRUE;
+}
+
+static GeglBuffer *
+gimp_source_core_real_get_source (GimpSourceCore *source_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpPickable *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)
+{
+ GimpSourceOptions *options = GIMP_SOURCE_OPTIONS (paint_options);
+ GimpImage *image = gimp_item_get_image (GIMP_ITEM (drawable));
+ GimpImage *src_image = gimp_pickable_get_image (src_pickable);
+ GeglBuffer *src_buffer = gimp_pickable_get_buffer (src_pickable);
+ GeglBuffer *dest_buffer;
+ gint x, y;
+ gint width, height;
+
+ if (! gimp_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 (( options->sample_merged && (src_image != image)) ||
+ (! options->sample_merged && (source_core->src_drawable != drawable)))
+ {
+ dest_buffer = src_buffer;
+ }
+ else
+ {
+ /* get the original image */
+ if (options->sample_merged)
+ dest_buffer = gimp_paint_core_get_orig_proj (GIMP_PAINT_CORE (source_core));
+ else
+ dest_buffer = gimp_paint_core_get_orig_image (GIMP_PAINT_CORE (source_core));
+ }
+
+ *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);
+}
+
+static void
+gimp_source_core_src_drawable_removed (GimpDrawable *drawable,
+ GimpSourceCore *source_core)
+{
+ if (drawable == source_core->src_drawable)
+ {
+ source_core->src_drawable = NULL;
+ }
+
+ g_signal_handlers_disconnect_by_func (drawable,
+ gimp_source_core_src_drawable_removed,
+ source_core);
+}
+
+static void
+gimp_source_core_set_src_drawable (GimpSourceCore *source_core,
+ GimpDrawable *drawable)
+{
+ if (source_core->src_drawable == drawable)
+ return;
+
+ if (source_core->src_drawable)
+ g_signal_handlers_disconnect_by_func (source_core->src_drawable,
+ gimp_source_core_src_drawable_removed,
+ source_core);
+
+ source_core->src_drawable = drawable;
+
+ if (source_core->src_drawable)
+ g_signal_connect (source_core->src_drawable, "removed",
+ G_CALLBACK (gimp_source_core_src_drawable_removed),
+ source_core);
+
+ g_object_notify (G_OBJECT (source_core), "src-drawable");
+}
diff --git a/app/paint/gimpsourcecore.h b/app/paint/gimpsourcecore.h
new file mode 100644
index 0000000..158920b
--- /dev/null
+++ b/app/paint/gimpsourcecore.h
@@ -0,0 +1,110 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_SOURCE_CORE_H__
+#define __GIMP_SOURCE_CORE_H__
+
+
+#include "gimpbrushcore.h"
+
+
+#define GIMP_TYPE_SOURCE_CORE (gimp_source_core_get_type ())
+#define GIMP_SOURCE_CORE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SOURCE_CORE, GimpSourceCore))
+#define GIMP_SOURCE_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SOURCE_CORE, GimpSourceCoreClass))
+#define GIMP_IS_SOURCE_CORE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SOURCE_CORE))
+#define GIMP_IS_SOURCE_CORE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SOURCE_CORE))
+#define GIMP_SOURCE_CORE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SOURCE_CORE, GimpSourceCoreClass))
+
+
+typedef struct _GimpSourceCoreClass GimpSourceCoreClass;
+
+struct _GimpSourceCore
+{
+ GimpBrushCore parent_instance;
+
+ gboolean set_source;
+
+ GimpDrawable *src_drawable;
+ gint src_x;
+ gint src_y;
+
+ gint orig_src_x;
+ gint orig_src_y;
+
+ gint offset_x;
+ gint offset_y;
+ gboolean first_stroke;
+};
+
+struct _GimpSourceCoreClass
+{
+ GimpBrushCoreClass parent_class;
+
+ gboolean (* use_source) (GimpSourceCore *source_core,
+ GimpSourceOptions *options);
+
+ GeglBuffer * (* get_source) (GimpSourceCore *source_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpPickable *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) (GimpSourceCore *source_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ const GimpCoords *coords,
+ GeglNode *op,
+ gdouble opacity,
+ GimpPickable *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 gimp_source_core_get_type (void) G_GNUC_CONST;
+
+gboolean gimp_source_core_use_source (GimpSourceCore *source_core,
+ GimpSourceOptions *options);
+
+/* TEMP HACK */
+void gimp_source_core_motion (GimpSourceCore *source_core,
+ GimpDrawable *drawable,
+ GimpPaintOptions *paint_options,
+ GimpSymmetry *sym);
+
+
+#endif /* __GIMP_SOURCE_CORE_H__ */
diff --git a/app/paint/gimpsourceoptions.c b/app/paint/gimpsourceoptions.c
new file mode 100644
index 0000000..7190d1c
--- /dev/null
+++ b/app/paint/gimpsourceoptions.c
@@ -0,0 +1,124 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gegl.h>
+
+#include "libgimpconfig/gimpconfig.h"
+
+#include "paint-types.h"
+
+#include "gimpsourceoptions.h"
+
+#include "gimp-intl.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_ALIGN_MODE,
+ PROP_SAMPLE_MERGED
+};
+
+
+static void gimp_source_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_source_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+
+G_DEFINE_TYPE (GimpSourceOptions, gimp_source_options, GIMP_TYPE_PAINT_OPTIONS)
+
+
+static void
+gimp_source_options_class_init (GimpSourceOptionsClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->set_property = gimp_source_options_set_property;
+ object_class->get_property = gimp_source_options_get_property;
+
+ GIMP_CONFIG_PROP_ENUM (object_class, PROP_ALIGN_MODE,
+ "align-mode",
+ _("Alignment"),
+ NULL,
+ GIMP_TYPE_SOURCE_ALIGN_MODE,
+ GIMP_SOURCE_ALIGN_NO,
+ GIMP_PARAM_STATIC_STRINGS);
+
+ GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_SAMPLE_MERGED,
+ "sample-merged",
+ _("Sample merged"),
+ NULL,
+ FALSE,
+ GIMP_PARAM_STATIC_STRINGS);
+}
+
+static void
+gimp_source_options_init (GimpSourceOptions *options)
+{
+}
+
+static void
+gimp_source_options_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSourceOptions *options = GIMP_SOURCE_OPTIONS (object);
+
+ switch (property_id)
+ {
+ 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
+gimp_source_options_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpSourceOptions *options = GIMP_SOURCE_OPTIONS (object);
+
+ switch (property_id)
+ {
+ 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;
+ }
+}
diff --git a/app/paint/gimpsourceoptions.h b/app/paint/gimpsourceoptions.h
new file mode 100644
index 0000000..e4e2e2e
--- /dev/null
+++ b/app/paint/gimpsourceoptions.h
@@ -0,0 +1,52 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_SOURCE_OPTIONS_H__
+#define __GIMP_SOURCE_OPTIONS_H__
+
+
+#include "gimppaintoptions.h"
+
+
+#define GIMP_TYPE_SOURCE_OPTIONS (gimp_source_options_get_type ())
+#define GIMP_SOURCE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_SOURCE_OPTIONS, GimpSourceOptions))
+#define GIMP_SOURCE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_SOURCE_OPTIONS, GimpSourceOptionsClass))
+#define GIMP_IS_SOURCE_OPTIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_SOURCE_OPTIONS))
+#define GIMP_IS_SOURCE_OPTIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_SOURCE_OPTIONS))
+#define GIMP_SOURCE_OPTIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_SOURCE_OPTIONS, GimpSourceOptionsClass))
+
+
+typedef struct _GimpSourceOptionsClass GimpSourceOptionsClass;
+
+struct _GimpSourceOptions
+{
+ GimpPaintOptions parent_instance;
+
+ GimpSourceAlignMode align_mode;
+ gboolean sample_merged;
+};
+
+struct _GimpSourceOptionsClass
+{
+ GimpPaintOptionsClass parent_class;
+};
+
+
+GType gimp_source_options_get_type (void) G_GNUC_CONST;
+
+
+#endif /* __GIMP_SOURCE_OPTIONS_H__ */
diff --git a/app/paint/paint-enums.c b/app/paint/paint-enums.c
new file mode 100644
index 0000000..8f6de34
--- /dev/null
+++ b/app/paint/paint-enums.c
@@ -0,0 +1,104 @@
+
+/* Generated data (by gimp-mkenums) */
+
+#include "config.h"
+#include <gio/gio.h>
+#include "libgimpbase/gimpbase.h"
+#include "paint-enums.h"
+#include "gimp-intl.h"
+
+/* enumerations from "paint-enums.h" */
+GType
+gimp_brush_application_mode_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_BRUSH_HARD, "GIMP_BRUSH_HARD", "hard" },
+ { GIMP_BRUSH_SOFT, "GIMP_BRUSH_SOFT", "soft" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_BRUSH_HARD, "GIMP_BRUSH_HARD", NULL },
+ { GIMP_BRUSH_SOFT, "GIMP_BRUSH_SOFT", NULL },
+ { 0, NULL, NULL }
+ };
+
+ static GType type = 0;
+
+ if (G_UNLIKELY (! type))
+ {
+ type = g_enum_register_static ("GimpBrushApplicationMode", values);
+ gimp_type_set_translation_context (type, "brush-application-mode");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_perspective_clone_mode_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_PERSPECTIVE_CLONE_MODE_ADJUST, "GIMP_PERSPECTIVE_CLONE_MODE_ADJUST", "adjust" },
+ { GIMP_PERSPECTIVE_CLONE_MODE_PAINT, "GIMP_PERSPECTIVE_CLONE_MODE_PAINT", "paint" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_PERSPECTIVE_CLONE_MODE_ADJUST, NC_("perspective-clone-mode", "Modify Perspective"), NULL },
+ { GIMP_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 ("GimpPerspectiveCloneMode", values);
+ gimp_type_set_translation_context (type, "perspective-clone-mode");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+GType
+gimp_source_align_mode_get_type (void)
+{
+ static const GEnumValue values[] =
+ {
+ { GIMP_SOURCE_ALIGN_NO, "GIMP_SOURCE_ALIGN_NO", "no" },
+ { GIMP_SOURCE_ALIGN_YES, "GIMP_SOURCE_ALIGN_YES", "yes" },
+ { GIMP_SOURCE_ALIGN_REGISTERED, "GIMP_SOURCE_ALIGN_REGISTERED", "registered" },
+ { GIMP_SOURCE_ALIGN_FIXED, "GIMP_SOURCE_ALIGN_FIXED", "fixed" },
+ { 0, NULL, NULL }
+ };
+
+ static const GimpEnumDesc descs[] =
+ {
+ { GIMP_SOURCE_ALIGN_NO, NC_("source-align-mode", "None"), NULL },
+ { GIMP_SOURCE_ALIGN_YES, NC_("source-align-mode", "Aligned"), NULL },
+ { GIMP_SOURCE_ALIGN_REGISTERED, NC_("source-align-mode", "Registered"), NULL },
+ { GIMP_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 ("GimpSourceAlignMode", values);
+ gimp_type_set_translation_context (type, "source-align-mode");
+ gimp_enum_set_value_descriptions (type, descs);
+ }
+
+ return type;
+}
+
+
+/* Generated data ends here */
+
diff --git a/app/paint/paint-enums.h b/app/paint/paint-enums.h
new file mode 100644
index 0000000..b764b6d
--- /dev/null
+++ b/app/paint/paint-enums.h
@@ -0,0 +1,85 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PAINT_ENUMS_H__
+#define __PAINT_ENUMS_H__
+
+#if 0
+ This file is parsed by two scripts, enumgen.pl in pdb,
+ and gimp-mkenums. All enums that are not marked with
+ /*< pdb-skip >*/ are exported to libgimp 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 GIMP_TYPE_BRUSH_APPLICATION_MODE (gimp_brush_application_mode_get_type ())
+
+GType gimp_brush_application_mode_get_type (void) G_GNUC_CONST;
+
+typedef enum
+{
+ GIMP_BRUSH_HARD,
+ GIMP_BRUSH_SOFT,
+ GIMP_BRUSH_PRESSURE /*< pdb-skip, skip >*/
+} GimpBrushApplicationMode;
+
+
+#define GIMP_TYPE_PERSPECTIVE_CLONE_MODE (gimp_perspective_clone_mode_get_type ())
+
+GType gimp_perspective_clone_mode_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_PERSPECTIVE_CLONE_MODE_ADJUST, /*< desc="Modify Perspective" >*/
+ GIMP_PERSPECTIVE_CLONE_MODE_PAINT /*< desc="Perspective Clone" >*/
+} GimpPerspectiveCloneMode;
+
+
+#define GIMP_TYPE_SOURCE_ALIGN_MODE (gimp_source_align_mode_get_type ())
+
+GType gimp_source_align_mode_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< pdb-skip >*/
+{
+ GIMP_SOURCE_ALIGN_NO, /*< desc="None" >*/
+ GIMP_SOURCE_ALIGN_YES, /*< desc="Aligned" >*/
+ GIMP_SOURCE_ALIGN_REGISTERED, /*< desc="Registered" >*/
+ GIMP_SOURCE_ALIGN_FIXED /*< desc="Fixed" >*/
+} GimpSourceAlignMode;
+
+
+/*
+ * non-registered enums; register them if needed
+ */
+
+typedef enum /*< skip, pdb-skip >*/
+{
+ GIMP_PAINT_STATE_INIT, /* Setup PaintFunc internals */
+ GIMP_PAINT_STATE_MOTION, /* PaintFunc performs motion-related rendering */
+ GIMP_PAINT_STATE_FINISH /* Cleanup and/or reset PaintFunc operation */
+} GimpPaintState;
+
+
+#endif /* __PAINT_ENUMS_H__ */
diff --git a/app/paint/paint-types.h b/app/paint/paint-types.h
new file mode 100644
index 0000000..1c9d8eb
--- /dev/null
+++ b/app/paint/paint-types.h
@@ -0,0 +1,76 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __PAINT_TYPES_H__
+#define __PAINT_TYPES_H__
+
+
+#include "core/core-types.h"
+#include "paint/paint-enums.h"
+
+
+/* paint cores */
+
+typedef struct _GimpPaintCore GimpPaintCore;
+typedef struct _GimpBrushCore GimpBrushCore;
+typedef struct _GimpSourceCore GimpSourceCore;
+
+typedef struct _GimpAirbrush GimpAirbrush;
+typedef struct _GimpClone GimpClone;
+typedef struct _GimpConvolve GimpConvolve;
+typedef struct _GimpDodgeBurn GimpDodgeBurn;
+typedef struct _GimpEraser GimpEraser;
+typedef struct _GimpHeal GimpHeal;
+typedef struct _GimpInk GimpInk;
+typedef struct _GimpMybrushCore GimpMybrushCore;
+typedef struct _GimpPaintbrush GimpPaintbrush;
+typedef struct _GimpPencil GimpPencil;
+typedef struct _GimpPerspectiveClone GimpPerspectiveClone;
+typedef struct _GimpSmudge GimpSmudge;
+
+
+/* paint options */
+
+typedef struct _GimpPaintOptions GimpPaintOptions;
+typedef struct _GimpSourceOptions GimpSourceOptions;
+
+typedef struct _GimpAirbrushOptions GimpAirbrushOptions;
+typedef struct _GimpCloneOptions GimpCloneOptions;
+typedef struct _GimpConvolveOptions GimpConvolveOptions;
+typedef struct _GimpDodgeBurnOptions GimpDodgeBurnOptions;
+typedef struct _GimpEraserOptions GimpEraserOptions;
+typedef struct _GimpInkOptions GimpInkOptions;
+typedef struct _GimpMybrushOptions GimpMybrushOptions;
+typedef struct _GimpPencilOptions GimpPencilOptions;
+typedef struct _GimpPerspectiveCloneOptions GimpPerspectiveCloneOptions;
+typedef struct _GimpSmudgeOptions GimpSmudgeOptions;
+
+
+/* functions */
+
+typedef void (* GimpPaintRegisterCallback) (Gimp *gimp,
+ GType paint_type,
+ GType paint_options_type,
+ const gchar *identifier,
+ const gchar *blurb,
+ const gchar *icon_name);
+
+typedef void (* GimpPaintRegisterFunc) (Gimp *gimp,
+ GimpPaintRegisterCallback callback);
+
+
+#endif /* __PAINT_TYPES_H__ */