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