diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 03:13:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 03:13:10 +0000 |
commit | 3c57dd931145d43f2b0aef96c4d178135956bf91 (patch) | |
tree | 3de698981e9f0cc2c4f9569b19a5f3595e741f6b /app/vectors | |
parent | Initial commit. (diff) | |
download | gimp-3c57dd931145d43f2b0aef96c4d178135956bf91.tar.xz gimp-3c57dd931145d43f2b0aef96c4d178135956bf91.zip |
Adding upstream version 2.10.36.upstream/2.10.36
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'app/vectors')
30 files changed, 10421 insertions, 0 deletions
diff --git a/app/vectors/Makefile.am b/app/vectors/Makefile.am new file mode 100644 index 0000000..aa6e10d --- /dev/null +++ b/app/vectors/Makefile.am @@ -0,0 +1,44 @@ +## Process this file with automake to produce Makefile.in + +AM_CPPFLAGS = \ + -DG_LOG_DOMAIN=\"Gimp-Vectors\" \ + -I$(top_builddir) \ + -I$(top_srcdir) \ + -I$(top_builddir)/app \ + -I$(top_srcdir)/app \ + $(CAIRO_CFLAGS) \ + $(GEGL_CFLAGS) \ + $(GDK_PIXBUF_CFLAGS) \ + -I$(includedir) + +noinst_LIBRARIES = libappvectors.a + +libappvectors_a_SOURCES = \ + vectors-enums.h \ + vectors-types.h \ + gimpanchor.c \ + gimpanchor.h \ + gimpbezierstroke.h \ + gimpbezierstroke.c \ + gimpstroke.h \ + gimpstroke.c \ + gimpstroke-new.h \ + gimpstroke-new.c \ + gimpvectors.c \ + gimpvectors.h \ + gimpvectors-compat.c \ + gimpvectors-compat.h \ + gimpvectors-export.c \ + gimpvectors-export.h \ + gimpvectors-import.c \ + gimpvectors-import.h \ + gimpvectors-preview.c \ + gimpvectors-preview.h \ + gimpvectors-warp.c \ + gimpvectors-warp.h \ + gimpvectorsmodundo.c \ + gimpvectorsmodundo.h \ + gimpvectorspropundo.c \ + gimpvectorspropundo.h \ + gimpvectorsundo.c \ + gimpvectorsundo.h diff --git a/app/vectors/Makefile.in b/app/vectors/Makefile.in new file mode 100644 index 0000000..5a08c69 --- /dev/null +++ b/app/vectors/Makefile.in @@ -0,0 +1,992 @@ +# 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/vectors +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 = +libappvectors_a_AR = $(AR) $(ARFLAGS) +libappvectors_a_LIBADD = +am_libappvectors_a_OBJECTS = gimpanchor.$(OBJEXT) \ + gimpbezierstroke.$(OBJEXT) gimpstroke.$(OBJEXT) \ + gimpstroke-new.$(OBJEXT) gimpvectors.$(OBJEXT) \ + gimpvectors-compat.$(OBJEXT) gimpvectors-export.$(OBJEXT) \ + gimpvectors-import.$(OBJEXT) gimpvectors-preview.$(OBJEXT) \ + gimpvectors-warp.$(OBJEXT) gimpvectorsmodundo.$(OBJEXT) \ + gimpvectorspropundo.$(OBJEXT) gimpvectorsundo.$(OBJEXT) +libappvectors_a_OBJECTS = $(am_libappvectors_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)/gimpanchor.Po \ + ./$(DEPDIR)/gimpbezierstroke.Po ./$(DEPDIR)/gimpstroke-new.Po \ + ./$(DEPDIR)/gimpstroke.Po ./$(DEPDIR)/gimpvectors-compat.Po \ + ./$(DEPDIR)/gimpvectors-export.Po \ + ./$(DEPDIR)/gimpvectors-import.Po \ + ./$(DEPDIR)/gimpvectors-preview.Po \ + ./$(DEPDIR)/gimpvectors-warp.Po ./$(DEPDIR)/gimpvectors.Po \ + ./$(DEPDIR)/gimpvectorsmodundo.Po \ + ./$(DEPDIR)/gimpvectorspropundo.Po \ + ./$(DEPDIR)/gimpvectorsundo.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 = +SOURCES = $(libappvectors_a_SOURCES) +DIST_SOURCES = $(libappvectors_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-Vectors\" \ + -I$(top_builddir) \ + -I$(top_srcdir) \ + -I$(top_builddir)/app \ + -I$(top_srcdir)/app \ + $(CAIRO_CFLAGS) \ + $(GEGL_CFLAGS) \ + $(GDK_PIXBUF_CFLAGS) \ + -I$(includedir) + +noinst_LIBRARIES = libappvectors.a +libappvectors_a_SOURCES = \ + vectors-enums.h \ + vectors-types.h \ + gimpanchor.c \ + gimpanchor.h \ + gimpbezierstroke.h \ + gimpbezierstroke.c \ + gimpstroke.h \ + gimpstroke.c \ + gimpstroke-new.h \ + gimpstroke-new.c \ + gimpvectors.c \ + gimpvectors.h \ + gimpvectors-compat.c \ + gimpvectors-compat.h \ + gimpvectors-export.c \ + gimpvectors-export.h \ + gimpvectors-import.c \ + gimpvectors-import.h \ + gimpvectors-preview.c \ + gimpvectors-preview.h \ + gimpvectors-warp.c \ + gimpvectors-warp.h \ + gimpvectorsmodundo.c \ + gimpvectorsmodundo.h \ + gimpvectorspropundo.c \ + gimpvectorspropundo.h \ + gimpvectorsundo.c \ + gimpvectorsundo.h + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .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/vectors/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu app/vectors/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) + +libappvectors.a: $(libappvectors_a_OBJECTS) $(libappvectors_a_DEPENDENCIES) $(EXTRA_libappvectors_a_DEPENDENCIES) + $(AM_V_at)-rm -f libappvectors.a + $(AM_V_AR)$(libappvectors_a_AR) libappvectors.a $(libappvectors_a_OBJECTS) $(libappvectors_a_LIBADD) + $(AM_V_at)$(RANLIB) libappvectors.a + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpanchor.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpbezierstroke.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpstroke-new.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpstroke.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectors-compat.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectors-export.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectors-import.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectors-preview.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectors-warp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectors.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectorsmodundo.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectorspropundo.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gimpvectorsundo.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 $@ $< + +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: + +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)/gimpanchor.Po + -rm -f ./$(DEPDIR)/gimpbezierstroke.Po + -rm -f ./$(DEPDIR)/gimpstroke-new.Po + -rm -f ./$(DEPDIR)/gimpstroke.Po + -rm -f ./$(DEPDIR)/gimpvectors-compat.Po + -rm -f ./$(DEPDIR)/gimpvectors-export.Po + -rm -f ./$(DEPDIR)/gimpvectors-import.Po + -rm -f ./$(DEPDIR)/gimpvectors-preview.Po + -rm -f ./$(DEPDIR)/gimpvectors-warp.Po + -rm -f ./$(DEPDIR)/gimpvectors.Po + -rm -f ./$(DEPDIR)/gimpvectorsmodundo.Po + -rm -f ./$(DEPDIR)/gimpvectorspropundo.Po + -rm -f ./$(DEPDIR)/gimpvectorsundo.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)/gimpanchor.Po + -rm -f ./$(DEPDIR)/gimpbezierstroke.Po + -rm -f ./$(DEPDIR)/gimpstroke-new.Po + -rm -f ./$(DEPDIR)/gimpstroke.Po + -rm -f ./$(DEPDIR)/gimpvectors-compat.Po + -rm -f ./$(DEPDIR)/gimpvectors-export.Po + -rm -f ./$(DEPDIR)/gimpvectors-import.Po + -rm -f ./$(DEPDIR)/gimpvectors-preview.Po + -rm -f ./$(DEPDIR)/gimpvectors-warp.Po + -rm -f ./$(DEPDIR)/gimpvectors.Po + -rm -f ./$(DEPDIR)/gimpvectorsmodundo.Po + -rm -f ./$(DEPDIR)/gimpvectorspropundo.Po + -rm -f ./$(DEPDIR)/gimpvectorsundo.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 + + +# 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/vectors/gimpanchor.c b/app/vectors/gimpanchor.c new file mode 100644 index 0000000..f5085cd --- /dev/null +++ b/app/vectors/gimpanchor.c @@ -0,0 +1,71 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpanchor.c + * Copyright (C) 2002 Simon Budig <simon@gimp.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 "vectors-types.h" + +#include "gimpanchor.h" + + +GType +gimp_anchor_get_type (void) +{ + static GType anchor_type = 0; + + if (!anchor_type) + anchor_type = g_boxed_type_register_static ("GimpAnchor", + (GBoxedCopyFunc) gimp_anchor_copy, + (GBoxedFreeFunc) gimp_anchor_free); + + return anchor_type; +} + +GimpAnchor * +gimp_anchor_new (GimpAnchorType type, + const GimpCoords *position) +{ + GimpAnchor *anchor = g_slice_new0 (GimpAnchor); + + anchor->type = type; + + if (position) + anchor->position = *position; + + return anchor; +} + +GimpAnchor * +gimp_anchor_copy (const GimpAnchor *anchor) +{ + g_return_val_if_fail (anchor != NULL, NULL); + + return g_slice_dup (GimpAnchor, anchor); +} + +void +gimp_anchor_free (GimpAnchor *anchor) +{ + g_return_if_fail (anchor != NULL); + + g_slice_free (GimpAnchor, anchor); +} diff --git a/app/vectors/gimpanchor.h b/app/vectors/gimpanchor.h new file mode 100644 index 0000000..75203a4 --- /dev/null +++ b/app/vectors/gimpanchor.h @@ -0,0 +1,48 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpanchor.h + * Copyright (C) 2002 Simon Budig <simon@gimp.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_ANCHOR_H__ +#define __GIMP_ANCHOR_H__ + +#define GIMP_ANCHOR(anchor) ((GimpAnchor *) (anchor)) + +#define GIMP_TYPE_ANCHOR (gimp_anchor_get_type ()) +#define GIMP_VALUE_HOLDS_ANCHOR(value) (G_TYPE_CHECK_VALUE_TYPE ((value), GIMP_TYPE_ANCHOR)) + +GType gimp_anchor_get_type (void) G_GNUC_CONST; + + +struct _GimpAnchor +{ + GimpCoords position; + + GimpAnchorType type; /* Interpretation dependent on GimpStroke type */ + gboolean selected; +}; + + +GimpAnchor * gimp_anchor_new (GimpAnchorType type, + const GimpCoords *position); + +GimpAnchor * gimp_anchor_copy (const GimpAnchor *anchor); +void gimp_anchor_free (GimpAnchor *anchor); + + +#endif /* __GIMP_ANCHOR_H__ */ diff --git a/app/vectors/gimpbezierstroke.c b/app/vectors/gimpbezierstroke.c new file mode 100644 index 0000000..3cfe735 --- /dev/null +++ b/app/vectors/gimpbezierstroke.c @@ -0,0 +1,2309 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpbezierstroke.c + * Copyright (C) 2002 Simon Budig <simon@gimp.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 <cairo.h> + +#include "libgimpmath/gimpmath.h" + +#include "vectors-types.h" + +#include "core/gimp-transform-utils.h" +#include "core/gimpbezierdesc.h" +#include "core/gimpcoords.h" +#include "core/gimpcoords-interpolate.h" + +#include "gimpanchor.h" +#include "gimpbezierstroke.h" + + +/* local prototypes */ + +static gdouble + gimp_bezier_stroke_nearest_point_get (GimpStroke *stroke, + const GimpCoords *coord, + gdouble precision, + GimpCoords *ret_point, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + gdouble *ret_pos); +static gdouble + gimp_bezier_stroke_segment_nearest_point_get + (const GimpCoords *beziercoords, + const GimpCoords *coord, + gdouble precision, + GimpCoords *ret_point, + gdouble *ret_pos, + gint depth); +static gdouble + gimp_bezier_stroke_nearest_tangent_get (GimpStroke *stroke, + const GimpCoords *coord1, + const GimpCoords *coord2, + gdouble precision, + GimpCoords *nearest, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + gdouble *ret_pos); +static gdouble + gimp_bezier_stroke_segment_nearest_tangent_get + (const GimpCoords *beziercoords, + const GimpCoords *coord1, + const GimpCoords *coord2, + gdouble precision, + GimpCoords *ret_point, + gdouble *ret_pos); +static void + gimp_bezier_stroke_anchor_move_relative + (GimpStroke *stroke, + GimpAnchor *anchor, + const GimpCoords *deltacoord, + GimpAnchorFeatureType feature); +static void + gimp_bezier_stroke_anchor_move_absolute + (GimpStroke *stroke, + GimpAnchor *anchor, + const GimpCoords *coord, + GimpAnchorFeatureType feature); +static void + gimp_bezier_stroke_anchor_convert (GimpStroke *stroke, + GimpAnchor *anchor, + GimpAnchorFeatureType feature); +static void + gimp_bezier_stroke_anchor_delete (GimpStroke *stroke, + GimpAnchor *anchor); +static gboolean + gimp_bezier_stroke_point_is_movable (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position); +static void + gimp_bezier_stroke_point_move_relative (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position, + const GimpCoords *deltacoord, + GimpAnchorFeatureType feature); +static void + gimp_bezier_stroke_point_move_absolute (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position, + const GimpCoords *coord, + GimpAnchorFeatureType feature); + +static void gimp_bezier_stroke_close (GimpStroke *stroke); + +static GimpStroke * + gimp_bezier_stroke_open (GimpStroke *stroke, + GimpAnchor *end_anchor); +static gboolean + gimp_bezier_stroke_anchor_is_insertable + (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position); +static GimpAnchor * + gimp_bezier_stroke_anchor_insert (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position); +static gboolean + gimp_bezier_stroke_is_extendable (GimpStroke *stroke, + GimpAnchor *neighbor); +static gboolean + gimp_bezier_stroke_connect_stroke (GimpStroke *stroke, + GimpAnchor *anchor, + GimpStroke *extension, + GimpAnchor *neighbor); +static GArray * + gimp_bezier_stroke_interpolate (GimpStroke *stroke, + gdouble precision, + gboolean *closed); +static GimpBezierDesc * + gimp_bezier_stroke_make_bezier (GimpStroke *stroke); +static void gimp_bezier_stroke_transform (GimpStroke *stroke, + const GimpMatrix3 *matrix, + GQueue *ret_strokes); + +static void gimp_bezier_stroke_finalize (GObject *object); + + +static GList * gimp_bezier_stroke_get_anchor_listitem + (GList *list); + + +G_DEFINE_TYPE (GimpBezierStroke, gimp_bezier_stroke, GIMP_TYPE_STROKE) + +#define parent_class gimp_bezier_stroke_parent_class + + +static void +gimp_bezier_stroke_class_init (GimpBezierStrokeClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpStrokeClass *stroke_class = GIMP_STROKE_CLASS (klass); + + object_class->finalize = gimp_bezier_stroke_finalize; + + stroke_class->nearest_point_get = gimp_bezier_stroke_nearest_point_get; + stroke_class->nearest_tangent_get = gimp_bezier_stroke_nearest_tangent_get; + stroke_class->nearest_intersection_get = NULL; + stroke_class->anchor_move_relative = gimp_bezier_stroke_anchor_move_relative; + stroke_class->anchor_move_absolute = gimp_bezier_stroke_anchor_move_absolute; + stroke_class->anchor_convert = gimp_bezier_stroke_anchor_convert; + stroke_class->anchor_delete = gimp_bezier_stroke_anchor_delete; + stroke_class->point_is_movable = gimp_bezier_stroke_point_is_movable; + stroke_class->point_move_relative = gimp_bezier_stroke_point_move_relative; + stroke_class->point_move_absolute = gimp_bezier_stroke_point_move_absolute; + stroke_class->close = gimp_bezier_stroke_close; + stroke_class->open = gimp_bezier_stroke_open; + stroke_class->anchor_is_insertable = gimp_bezier_stroke_anchor_is_insertable; + stroke_class->anchor_insert = gimp_bezier_stroke_anchor_insert; + stroke_class->is_extendable = gimp_bezier_stroke_is_extendable; + stroke_class->extend = gimp_bezier_stroke_extend; + stroke_class->connect_stroke = gimp_bezier_stroke_connect_stroke; + stroke_class->interpolate = gimp_bezier_stroke_interpolate; + stroke_class->make_bezier = gimp_bezier_stroke_make_bezier; + stroke_class->transform = gimp_bezier_stroke_transform; +} + +static void +gimp_bezier_stroke_init (GimpBezierStroke *stroke) +{ +} + +static void +gimp_bezier_stroke_finalize (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->finalize (object); +} + + +/* Bezier specific functions */ + +GimpStroke * +gimp_bezier_stroke_new (void) +{ + return g_object_new (GIMP_TYPE_BEZIER_STROKE, NULL); +} + + +GimpStroke * +gimp_bezier_stroke_new_from_coords (const GimpCoords *coords, + gint n_coords, + gboolean closed) +{ + GimpStroke *stroke; + GimpAnchor *last_anchor; + gint count; + + g_return_val_if_fail (coords != NULL, NULL); + g_return_val_if_fail (n_coords >= 3, NULL); + g_return_val_if_fail ((n_coords % 3) == 0, NULL); + + stroke = gimp_bezier_stroke_new (); + + last_anchor = NULL; + + for (count = 0; count < n_coords; count++) + last_anchor = gimp_bezier_stroke_extend (stroke, + &coords[count], + last_anchor, + EXTEND_SIMPLE); + + if (closed) + gimp_stroke_close (stroke); + + return stroke; +} + +static void +gimp_bezier_stroke_anchor_delete (GimpStroke *stroke, + GimpAnchor *anchor) +{ + GList *list; + GList *list2; + gint i; + + /* Anchors always are surrounded by two handles that have to + * be deleted too + */ + + list2 = g_queue_find (stroke->anchors, anchor); + list = g_list_previous (list2); + + for (i = 0; i < 3; i++) + { + g_return_if_fail (list != NULL); + + list2 = g_list_next (list); + gimp_anchor_free (list->data); + g_queue_delete_link (stroke->anchors, list); + list = list2; + } +} + +static GimpStroke * +gimp_bezier_stroke_open (GimpStroke *stroke, + GimpAnchor *end_anchor) +{ + GList *list; + GList *list2; + GimpStroke *new_stroke = NULL; + + list = g_queue_find (stroke->anchors, end_anchor); + + g_return_val_if_fail (list != NULL && list->next != NULL, NULL); + + list = g_list_next (list); /* protect the handle... */ + + list2 = list->next; + list->next = NULL; + + if (list2 != NULL) + { + GList *tail = stroke->anchors->tail; + + stroke->anchors->tail = list; + stroke->anchors->length -= g_list_length (list2); + + list2->prev = NULL; + + if (stroke->closed) + { + GList *l; + + for (l = tail; l; l = g_list_previous (l)) + g_queue_push_head (stroke->anchors, l->data); + + g_list_free (list2); + } + else + { + new_stroke = gimp_bezier_stroke_new (); + new_stroke->anchors->head = list2; + new_stroke->anchors->tail = g_list_last (list2); + new_stroke->anchors->length = g_list_length (list2); + } + } + + stroke->closed = FALSE; + g_object_notify (G_OBJECT (stroke), "closed"); + + return new_stroke; +} + +static gboolean +gimp_bezier_stroke_anchor_is_insertable (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position) +{ + return (g_queue_find (stroke->anchors, predec) != NULL); +} + + +static GimpAnchor * +gimp_bezier_stroke_anchor_insert (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position) +{ + GList *segment_start; + GList *list; + GList *list2; + GimpCoords subdivided[8]; + GimpCoords beziercoords[4]; + gint i; + + segment_start = g_queue_find (stroke->anchors, predec); + + if (! segment_start) + return NULL; + + list = segment_start; + + for (i = 0; i <= 3; i++) + { + beziercoords[i] = GIMP_ANCHOR (list->data)->position; + list = g_list_next (list); + if (! list) + list = stroke->anchors->head; + } + + subdivided[0] = beziercoords[0]; + subdivided[6] = beziercoords[3]; + + gimp_coords_mix (1-position, &(beziercoords[0]), + position, &(beziercoords[1]), + &(subdivided[1])); + + gimp_coords_mix (1-position, &(beziercoords[1]), + position, &(beziercoords[2]), + &(subdivided[7])); + + gimp_coords_mix (1-position, &(beziercoords[2]), + position, &(beziercoords[3]), + &(subdivided[5])); + + gimp_coords_mix (1-position, &(subdivided[1]), + position, &(subdivided[7]), + &(subdivided[2])); + + gimp_coords_mix (1-position, &(subdivided[7]), + position, &(subdivided[5]), + &(subdivided[4])); + + gimp_coords_mix (1-position, &(subdivided[2]), + position, &(subdivided[4]), + &(subdivided[3])); + + /* subdivided 0-6 contains the bezier segment subdivided at <position> */ + + list = segment_start; + + for (i = 0; i <= 6; i++) + { + if (i >= 2 && i <= 4) + { + list2 = g_list_append (NULL, + gimp_anchor_new ((i == 3 ? + GIMP_ANCHOR_ANCHOR: + GIMP_ANCHOR_CONTROL), + &(subdivided[i]))); + /* insert it *before* list manually. */ + list2->next = list; + list2->prev = list->prev; + if (list->prev) + list->prev->next = list2; + list->prev = list2; + + list = list2; + + if (i == 3) + segment_start = list; + } + else + { + GIMP_ANCHOR (list->data)->position = subdivided[i]; + } + + list = g_list_next (list); + if (! list) + list = stroke->anchors->head; + } + + stroke->anchors->head = g_list_first (list); + stroke->anchors->tail = g_list_last (list); + stroke->anchors->length += 3; + + return GIMP_ANCHOR (segment_start->data); +} + + +static gboolean +gimp_bezier_stroke_point_is_movable (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position) +{ + return (g_queue_find (stroke->anchors, predec) != NULL); +} + + +static void +gimp_bezier_stroke_point_move_relative (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position, + const GimpCoords *deltacoord, + GimpAnchorFeatureType feature) +{ + GimpCoords offsetcoords[2]; + GList *segment_start; + GList *list; + gint i; + gdouble feel_good; + + segment_start = g_queue_find (stroke->anchors, predec); + + g_return_if_fail (segment_start != NULL); + + /* dragging close to endpoints just moves the handle related to + * the endpoint. Just make sure that feel_good is in the range from + * 0 to 1. The 1.0 / 6.0 and 5.0 / 6.0 are duplicated in + * tools/gimpvectortool.c. + */ + if (position <= 1.0 / 6.0) + feel_good = 0; + else if (position <= 0.5) + feel_good = (pow((6 * position - 1) / 2.0, 3)) / 2; + else if (position <= 5.0 / 6.0) + feel_good = (1 - pow((6 * (1-position) - 1) / 2.0, 3)) / 2 + 0.5; + else + feel_good = 1; + + gimp_coords_scale ((1-feel_good)/(3*position* + (1-position)*(1-position)), + deltacoord, + &(offsetcoords[0])); + gimp_coords_scale (feel_good/(3*position*position*(1-position)), + deltacoord, + &(offsetcoords[1])); + + list = segment_start; + list = g_list_next (list); + if (! list) + list = stroke->anchors->head; + + for (i = 0; i <= 1; i++) + { + gimp_stroke_anchor_move_relative (stroke, GIMP_ANCHOR (list->data), + &(offsetcoords[i]), feature); + list = g_list_next (list); + if (! list) + list = stroke->anchors->head; + } +} + + +static void +gimp_bezier_stroke_point_move_absolute (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position, + const GimpCoords *coord, + GimpAnchorFeatureType feature) +{ + GimpCoords deltacoord; + GimpCoords tmp1, tmp2, abs_pos; + GimpCoords beziercoords[4]; + GList *segment_start; + GList *list; + gint i; + + segment_start = g_queue_find (stroke->anchors, predec); + + g_return_if_fail (segment_start != NULL); + + list = segment_start; + + for (i = 0; i <= 3; i++) + { + beziercoords[i] = GIMP_ANCHOR (list->data)->position; + list = g_list_next (list); + if (! list) + list = stroke->anchors->head; + } + + gimp_coords_mix ((1-position)*(1-position)*(1-position), &(beziercoords[0]), + 3*(1-position)*(1-position)*position, &(beziercoords[1]), + &tmp1); + gimp_coords_mix (3*(1-position)*position*position, &(beziercoords[2]), + position*position*position, &(beziercoords[3]), + &tmp2); + gimp_coords_add (&tmp1, &tmp2, &abs_pos); + + gimp_coords_difference (coord, &abs_pos, &deltacoord); + + gimp_bezier_stroke_point_move_relative (stroke, predec, position, + &deltacoord, feature); +} + +static void +gimp_bezier_stroke_close (GimpStroke *stroke) +{ + GList *start; + GList *end; + GimpAnchor *anchor; + + start = stroke->anchors->head; + end = stroke->anchors->tail; + + g_return_if_fail (start->next != NULL && end->prev != NULL); + + if (start->next != end->prev) + { + if (gimp_coords_equal (&(GIMP_ANCHOR (start->next->data)->position), + &(GIMP_ANCHOR (start->data)->position)) && + gimp_coords_equal (&(GIMP_ANCHOR (start->data)->position), + &(GIMP_ANCHOR (end->data)->position)) && + gimp_coords_equal (&(GIMP_ANCHOR (end->data)->position), + &(GIMP_ANCHOR (end->prev->data)->position))) + { + /* redundant segment */ + + gimp_anchor_free (stroke->anchors->tail->data); + g_queue_delete_link (stroke->anchors, stroke->anchors->tail); + + gimp_anchor_free (stroke->anchors->tail->data); + g_queue_delete_link (stroke->anchors, stroke->anchors->tail); + + anchor = stroke->anchors->tail->data; + g_queue_delete_link (stroke->anchors, stroke->anchors->tail); + + gimp_anchor_free (stroke->anchors->head->data); + stroke->anchors->head->data = anchor; + } + } + + GIMP_STROKE_CLASS (parent_class)->close (stroke); +} + +static gdouble +gimp_bezier_stroke_nearest_point_get (GimpStroke *stroke, + const GimpCoords *coord, + gdouble precision, + GimpCoords *ret_point, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + gdouble *ret_pos) +{ + gdouble min_dist, dist, pos; + GimpCoords point = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; + GimpCoords segmentcoords[4]; + GList *anchorlist; + GimpAnchor *segment_start; + GimpAnchor *segment_end = NULL; + GimpAnchor *anchor; + gint count; + + if (g_queue_is_empty (stroke->anchors)) + return -1.0; + + count = 0; + min_dist = -1; + pos = 0; + + for (anchorlist = stroke->anchors->head; + GIMP_ANCHOR (anchorlist->data)->type != GIMP_ANCHOR_ANCHOR; + anchorlist = g_list_next (anchorlist)); + + segment_start = anchorlist->data; + + for ( ; anchorlist; anchorlist = g_list_next (anchorlist)) + { + anchor = anchorlist->data; + + segmentcoords[count] = anchor->position; + count++; + + if (count == 4) + { + segment_end = anchorlist->data; + dist = gimp_bezier_stroke_segment_nearest_point_get (segmentcoords, + coord, precision, + &point, &pos, + 10); + + if (dist < min_dist || min_dist < 0) + { + min_dist = dist; + + if (ret_pos) + *ret_pos = pos; + if (ret_point) + *ret_point = point; + if (ret_segment_start) + *ret_segment_start = segment_start; + if (ret_segment_end) + *ret_segment_end = segment_end; + } + + segment_start = anchorlist->data; + segmentcoords[0] = segmentcoords[3]; + count = 1; + } + } + + if (stroke->closed && stroke->anchors->head) + { + anchorlist = stroke->anchors->head; + + while (count < 3) + { + segmentcoords[count] = GIMP_ANCHOR (anchorlist->data)->position; + count++; + } + + anchorlist = g_list_next (anchorlist); + + if (anchorlist) + { + segment_end = GIMP_ANCHOR (anchorlist->data); + segmentcoords[3] = segment_end->position; + } + + dist = gimp_bezier_stroke_segment_nearest_point_get (segmentcoords, + coord, precision, + &point, &pos, + 10); + + if (dist < min_dist || min_dist < 0) + { + min_dist = dist; + + if (ret_pos) + *ret_pos = pos; + if (ret_point) + *ret_point = point; + if (ret_segment_start) + *ret_segment_start = segment_start; + if (ret_segment_end) + *ret_segment_end = segment_end; + } + } + + return min_dist; +} + + +static gdouble +gimp_bezier_stroke_segment_nearest_point_get (const GimpCoords *beziercoords, + const GimpCoords *coord, + gdouble precision, + GimpCoords *ret_point, + gdouble *ret_pos, + gint depth) +{ + /* + * beziercoords has to contain four GimpCoords with the four control points + * of the bezier segment. We subdivide it at the parameter 0.5. + */ + + GimpCoords subdivided[8]; + gdouble dist1, dist2; + GimpCoords point1, point2; + gdouble pos1, pos2; + + gimp_coords_difference (&beziercoords[1], &beziercoords[0], &point1); + gimp_coords_difference (&beziercoords[3], &beziercoords[2], &point2); + + if (! depth || (gimp_coords_bezier_is_straight (beziercoords, precision) && + gimp_coords_length_squared (&point1) < precision && + gimp_coords_length_squared (&point2) < precision)) + { + GimpCoords line, dcoord; + gdouble length2, scalar; + gint i; + + gimp_coords_difference (&(beziercoords[3]), + &(beziercoords[0]), + &line); + + gimp_coords_difference (coord, + &(beziercoords[0]), + &dcoord); + + length2 = gimp_coords_scalarprod (&line, &line); + scalar = gimp_coords_scalarprod (&line, &dcoord) / length2; + + scalar = CLAMP (scalar, 0.0, 1.0); + + /* lines look the same as bezier curves where the handles + * sit on the anchors, however, they are parametrized + * differently. Hence we have to do some weird approximation. */ + + pos1 = pos2 = 0.5; + + for (i = 0; i <= 15; i++) + { + pos2 *= 0.5; + + if (3 * pos1 * pos1 * (1-pos1) + pos1 * pos1 * pos1 < scalar) + pos1 += pos2; + else + pos1 -= pos2; + } + + *ret_pos = pos1; + + gimp_coords_mix (1.0, &(beziercoords[0]), + scalar, &line, + ret_point); + + gimp_coords_difference (coord, ret_point, &dcoord); + + return gimp_coords_length (&dcoord); + } + + /* ok, we have to subdivide */ + + subdivided[0] = beziercoords[0]; + subdivided[6] = beziercoords[3]; + + /* if (!depth) g_printerr ("Hit recursion depth limit!\n"); */ + + gimp_coords_average (&(beziercoords[0]), &(beziercoords[1]), + &(subdivided[1])); + + gimp_coords_average (&(beziercoords[1]), &(beziercoords[2]), + &(subdivided[7])); + + gimp_coords_average (&(beziercoords[2]), &(beziercoords[3]), + &(subdivided[5])); + + gimp_coords_average (&(subdivided[1]), &(subdivided[7]), + &(subdivided[2])); + + gimp_coords_average (&(subdivided[7]), &(subdivided[5]), + &(subdivided[4])); + + gimp_coords_average (&(subdivided[2]), &(subdivided[4]), + &(subdivided[3])); + + /* + * We now have the coordinates of the two bezier segments in + * subdivided [0-3] and subdivided [3-6] + */ + + dist1 = gimp_bezier_stroke_segment_nearest_point_get (&(subdivided[0]), + coord, precision, + &point1, &pos1, + depth - 1); + + dist2 = gimp_bezier_stroke_segment_nearest_point_get (&(subdivided[3]), + coord, precision, + &point2, &pos2, + depth - 1); + + if (dist1 <= dist2) + { + *ret_point = point1; + *ret_pos = 0.5 * pos1; + return dist1; + } + else + { + *ret_point = point2; + *ret_pos = 0.5 + 0.5 * pos2; + return dist2; + } +} + + +static gdouble +gimp_bezier_stroke_nearest_tangent_get (GimpStroke *stroke, + const GimpCoords *coord1, + const GimpCoords *coord2, + gdouble precision, + GimpCoords *nearest, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + gdouble *ret_pos) +{ + gdouble min_dist, dist, pos; + GimpCoords point; + GimpCoords segmentcoords[4]; + GList *anchorlist; + GimpAnchor *segment_start; + GimpAnchor *segment_end = NULL; + GimpAnchor *anchor; + gint count; + + if (g_queue_is_empty (stroke->anchors)) + return -1.0; + + count = 0; + min_dist = -1; + + for (anchorlist = stroke->anchors->head; + GIMP_ANCHOR (anchorlist->data)->type != GIMP_ANCHOR_ANCHOR; + anchorlist = g_list_next (anchorlist)); + + segment_start = anchorlist->data; + + for ( ; anchorlist; anchorlist = g_list_next (anchorlist)) + { + anchor = anchorlist->data; + + segmentcoords[count] = anchor->position; + count++; + + if (count == 4) + { + segment_end = anchorlist->data; + dist = gimp_bezier_stroke_segment_nearest_tangent_get (segmentcoords, + coord1, coord2, + precision, + &point, &pos); + + if (dist >= 0 && (dist < min_dist || min_dist < 0)) + { + min_dist = dist; + + if (ret_pos) + *ret_pos = pos; + if (nearest) + *nearest = point; + if (ret_segment_start) + *ret_segment_start = segment_start; + if (ret_segment_end) + *ret_segment_end = segment_end; + } + + segment_start = anchorlist->data; + segmentcoords[0] = segmentcoords[3]; + count = 1; + } + } + + if (stroke->closed && ! g_queue_is_empty (stroke->anchors)) + { + anchorlist = stroke->anchors->head; + + while (count < 3) + { + segmentcoords[count] = GIMP_ANCHOR (anchorlist->data)->position; + count++; + } + + anchorlist = g_list_next (anchorlist); + + if (anchorlist) + { + segment_end = GIMP_ANCHOR (anchorlist->data); + segmentcoords[3] = segment_end->position; + } + + dist = gimp_bezier_stroke_segment_nearest_tangent_get (segmentcoords, + coord1, coord2, + precision, + &point, &pos); + + if (dist >= 0 && (dist < min_dist || min_dist < 0)) + { + min_dist = dist; + + if (ret_pos) + *ret_pos = pos; + if (nearest) + *nearest = point; + if (ret_segment_start) + *ret_segment_start = segment_start; + if (ret_segment_end) + *ret_segment_end = segment_end; + } + } + + return min_dist; +} + +static gdouble +gimp_bezier_stroke_segment_nearest_tangent_get (const GimpCoords *beziercoords, + const GimpCoords *coord1, + const GimpCoords *coord2, + gdouble precision, + GimpCoords *ret_point, + gdouble *ret_pos) +{ + GArray *ret_coords; + GArray *ret_params; + GimpCoords dir, line, dcoord, min_point; + gdouble min_dist = -1; + gdouble dist, length2, scalar, ori, ori2; + gint i; + + gimp_coords_difference (coord2, coord1, &line); + + ret_coords = g_array_new (FALSE, FALSE, sizeof (GimpCoords)); + ret_params = g_array_new (FALSE, FALSE, sizeof (gdouble)); + + g_printerr ("(%.2f, %.2f)-(%.2f,%.2f): ", coord1->x, coord1->y, + coord2->x, coord2->y); + + gimp_coords_interpolate_bezier (beziercoords, precision, + ret_coords, ret_params); + + g_return_val_if_fail (ret_coords->len == ret_params->len, -1.0); + + if (ret_coords->len < 2) + return -1; + + gimp_coords_difference (&g_array_index (ret_coords, GimpCoords, 1), + &g_array_index (ret_coords, GimpCoords, 0), + &dir); + ori = dir.x * line.y - dir.y * line.x; + + for (i = 2; i < ret_coords->len; i++) + { + gimp_coords_difference (&g_array_index (ret_coords, GimpCoords, i), + &g_array_index (ret_coords, GimpCoords, i-1), + &dir); + ori2 = dir.x * line.y - dir.y * line.x; + + if (ori * ori2 <= 0) + { + gimp_coords_difference (&g_array_index (ret_coords, GimpCoords, i), + coord1, + &dcoord); + + length2 = gimp_coords_scalarprod (&line, &line); + scalar = gimp_coords_scalarprod (&line, &dcoord) / length2; + + if (scalar >= 0 && scalar <= 1) + { + gimp_coords_mix (1.0, coord1, + scalar, &line, + &min_point); + gimp_coords_difference (&min_point, + &g_array_index (ret_coords, GimpCoords, i), + &dcoord); + dist = gimp_coords_length (&dcoord); + + if (dist < min_dist || min_dist < 0) + { + min_dist = dist; + *ret_point = g_array_index (ret_coords, GimpCoords, i); + *ret_pos = g_array_index (ret_params, gdouble, i); + } + } + } + ori = ori2; + } + + if (min_dist < 0) + g_printerr ("-\n"); + else + g_printerr ("%f: (%.2f, %.2f) /%.3f/\n", min_dist, + (*ret_point).x, (*ret_point).y, *ret_pos); + + g_array_free (ret_coords, TRUE); + g_array_free (ret_params, TRUE); + + return min_dist; +} + +static gboolean +gimp_bezier_stroke_is_extendable (GimpStroke *stroke, + GimpAnchor *neighbor) +{ + GList *listneighbor; + gint loose_end; + + if (stroke->closed) + return FALSE; + + if (g_queue_is_empty (stroke->anchors)) + return TRUE; + + /* assure that there is a neighbor specified */ + g_return_val_if_fail (neighbor != NULL, FALSE); + + loose_end = 0; + listneighbor = stroke->anchors->tail; + + /* Check if the neighbor is at an end of the control points */ + if (listneighbor->data == neighbor) + { + loose_end = 1; + } + else + { + listneighbor = g_list_first (stroke->anchors->head); + + if (listneighbor->data == neighbor) + { + loose_end = -1; + } + else + { + /* + * It isn't. If we are on a handle go to the nearest + * anchor and see if we can find an end from it. + * Yes, this is tedious. + */ + + listneighbor = g_queue_find (stroke->anchors, neighbor); + + if (listneighbor && neighbor->type == GIMP_ANCHOR_CONTROL) + { + if (listneighbor->prev && + GIMP_ANCHOR (listneighbor->prev->data)->type == GIMP_ANCHOR_ANCHOR) + { + listneighbor = listneighbor->prev; + } + else if (listneighbor->next && + GIMP_ANCHOR (listneighbor->next->data)->type == GIMP_ANCHOR_ANCHOR) + { + listneighbor = listneighbor->next; + } + else + { + loose_end = 0; + listneighbor = NULL; + } + } + + if (listneighbor) + /* we found a suitable ANCHOR_ANCHOR now, lets + * search for its loose end. + */ + { + if (listneighbor->prev && + listneighbor->prev->prev == NULL) + { + loose_end = -1; + } + else if (listneighbor->next && + listneighbor->next->next == NULL) + { + loose_end = 1; + } + } + } + } + + return (loose_end != 0); +} + +GimpAnchor * +gimp_bezier_stroke_extend (GimpStroke *stroke, + const GimpCoords *coords, + GimpAnchor *neighbor, + GimpVectorExtendMode extend_mode) +{ + GimpAnchor *anchor = NULL; + GList *listneighbor; + gint loose_end, control_count; + + if (g_queue_is_empty (stroke->anchors)) + { + /* assure that there is no neighbor specified */ + g_return_val_if_fail (neighbor == NULL, NULL); + + anchor = gimp_anchor_new (GIMP_ANCHOR_CONTROL, coords); + + g_queue_push_tail (stroke->anchors, anchor); + + switch (extend_mode) + { + case EXTEND_SIMPLE: + break; + + case EXTEND_EDITABLE: + anchor = gimp_bezier_stroke_extend (stroke, + coords, anchor, + EXTEND_SIMPLE); + + /* we return the GIMP_ANCHOR_ANCHOR */ + gimp_bezier_stroke_extend (stroke, + coords, anchor, + EXTEND_SIMPLE); + + break; + + default: + anchor = NULL; + } + + return anchor; + } + else + { + /* assure that there is a neighbor specified */ + g_return_val_if_fail (neighbor != NULL, NULL); + + loose_end = 0; + listneighbor = stroke->anchors->tail; + + /* Check if the neighbor is at an end of the control points */ + if (listneighbor->data == neighbor) + { + loose_end = 1; + } + else + { + listneighbor = stroke->anchors->head; + + if (listneighbor->data == neighbor) + { + loose_end = -1; + } + else + { + /* + * It isn't. If we are on a handle go to the nearest + * anchor and see if we can find an end from it. + * Yes, this is tedious. + */ + + listneighbor = g_queue_find (stroke->anchors, neighbor); + + if (listneighbor && neighbor->type == GIMP_ANCHOR_CONTROL) + { + if (listneighbor->prev && + GIMP_ANCHOR (listneighbor->prev->data)->type == GIMP_ANCHOR_ANCHOR) + { + listneighbor = listneighbor->prev; + } + else if (listneighbor->next && + GIMP_ANCHOR (listneighbor->next->data)->type == GIMP_ANCHOR_ANCHOR) + { + listneighbor = listneighbor->next; + } + else + { + loose_end = 0; + listneighbor = NULL; + } + } + + if (listneighbor) + /* we found a suitable ANCHOR_ANCHOR now, lets + * search for its loose end. + */ + { + if (listneighbor->next && + listneighbor->next->next == NULL) + { + loose_end = 1; + listneighbor = listneighbor->next; + } + else if (listneighbor->prev && + listneighbor->prev->prev == NULL) + { + loose_end = -1; + listneighbor = listneighbor->prev; + } + } + } + } + + if (loose_end) + { + GimpAnchorType type; + + /* We have to detect the type of the point to add... */ + + control_count = 0; + + if (loose_end == 1) + { + while (listneighbor && + GIMP_ANCHOR (listneighbor->data)->type == GIMP_ANCHOR_CONTROL) + { + control_count++; + listneighbor = listneighbor->prev; + } + } + else + { + while (listneighbor && + GIMP_ANCHOR (listneighbor->data)->type == GIMP_ANCHOR_CONTROL) + { + control_count++; + listneighbor = listneighbor->next; + } + } + + switch (extend_mode) + { + case EXTEND_SIMPLE: + switch (control_count) + { + case 0: + type = GIMP_ANCHOR_CONTROL; + break; + case 1: + if (listneighbor) /* only one handle in the path? */ + type = GIMP_ANCHOR_CONTROL; + else + type = GIMP_ANCHOR_ANCHOR; + break; + case 2: + type = GIMP_ANCHOR_ANCHOR; + break; + default: + g_warning ("inconsistent bezier curve: " + "%d successive control handles", control_count); + type = GIMP_ANCHOR_ANCHOR; + } + + anchor = gimp_anchor_new (type, coords); + + if (loose_end == 1) + g_queue_push_tail (stroke->anchors, anchor); + + if (loose_end == -1) + g_queue_push_head (stroke->anchors, anchor); + break; + + case EXTEND_EDITABLE: + switch (control_count) + { + case 0: + neighbor = gimp_bezier_stroke_extend (stroke, + &(neighbor->position), + neighbor, + EXTEND_SIMPLE); + case 1: + neighbor = gimp_bezier_stroke_extend (stroke, + coords, + neighbor, + EXTEND_SIMPLE); + case 2: + anchor = gimp_bezier_stroke_extend (stroke, + coords, + neighbor, + EXTEND_SIMPLE); + + neighbor = gimp_bezier_stroke_extend (stroke, + coords, + anchor, + EXTEND_SIMPLE); + break; + default: + g_warning ("inconsistent bezier curve: " + "%d successive control handles", control_count); + } + } + + return anchor; + } + + return NULL; + } +} + +static gboolean +gimp_bezier_stroke_connect_stroke (GimpStroke *stroke, + GimpAnchor *anchor, + GimpStroke *extension, + GimpAnchor *neighbor) +{ + GList *list1; + GList *list2; + + list1 = g_queue_find (stroke->anchors, anchor); + list1 = gimp_bezier_stroke_get_anchor_listitem (list1); + list2 = g_queue_find (extension->anchors, neighbor); + list2 = gimp_bezier_stroke_get_anchor_listitem (list2); + + g_return_val_if_fail (list1 != NULL && list2 != NULL, FALSE); + + if (stroke == extension) + { + g_return_val_if_fail ((list1->prev && list1->prev->prev == NULL && + list2->next && list2->next->next == NULL) || + (list1->next && list1->next->next == NULL && + list2->prev && list2->prev->prev == NULL), FALSE); + gimp_stroke_close (stroke); + return TRUE; + } + + if (list1->prev && list1->prev->prev == NULL) + { + g_queue_reverse (stroke->anchors); + } + + g_return_val_if_fail (list1->next && list1->next->next == NULL, FALSE); + + if (list2->next && list2->next->next == NULL) + { + g_queue_reverse (extension->anchors); + } + + g_return_val_if_fail (list2->prev && list2->prev->prev == NULL, FALSE); + + for (list1 = extension->anchors->head; list1; list1 = g_list_next (list1)) + g_queue_push_tail (stroke->anchors, list1->data); + + g_queue_clear (extension->anchors); + + return TRUE; +} + + +static void +gimp_bezier_stroke_anchor_move_relative (GimpStroke *stroke, + GimpAnchor *anchor, + const GimpCoords *deltacoord, + GimpAnchorFeatureType feature) +{ + GimpCoords delta, coord1, coord2; + GList *anchor_list; + + delta = *deltacoord; + delta.pressure = 0; + delta.xtilt = 0; + delta.ytilt = 0; + delta.wheel = 0; + + gimp_coords_add (&(anchor->position), &delta, &coord1); + anchor->position = coord1; + + anchor_list = g_queue_find (stroke->anchors, anchor); + g_return_if_fail (anchor_list != NULL); + + if (anchor->type == GIMP_ANCHOR_ANCHOR) + { + if (g_list_previous (anchor_list)) + { + coord2 = GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position; + gimp_coords_add (&coord2, &delta, &coord1); + GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position = coord1; + } + + if (g_list_next (anchor_list)) + { + coord2 = GIMP_ANCHOR (g_list_next (anchor_list)->data)->position; + gimp_coords_add (&coord2, &delta, &coord1); + GIMP_ANCHOR (g_list_next (anchor_list)->data)->position = coord1; + } + } + else + { + if (feature == GIMP_ANCHOR_FEATURE_SYMMETRIC) + { + GList *neighbour = NULL, *opposite = NULL; + + /* search for opposite control point. Sigh. */ + neighbour = g_list_previous (anchor_list); + if (neighbour && + GIMP_ANCHOR (neighbour->data)->type == GIMP_ANCHOR_ANCHOR) + { + opposite = g_list_previous (neighbour); + } + else + { + neighbour = g_list_next (anchor_list); + if (neighbour && + GIMP_ANCHOR (neighbour->data)->type == GIMP_ANCHOR_ANCHOR) + { + opposite = g_list_next (neighbour); + } + } + if (opposite && + GIMP_ANCHOR (opposite->data)->type == GIMP_ANCHOR_CONTROL) + { + gimp_coords_difference (&(GIMP_ANCHOR (neighbour->data)->position), + &(anchor->position), &delta); + gimp_coords_add (&(GIMP_ANCHOR (neighbour->data)->position), + &delta, &coord1); + GIMP_ANCHOR (opposite->data)->position = coord1; + } + } + } +} + + +static void +gimp_bezier_stroke_anchor_move_absolute (GimpStroke *stroke, + GimpAnchor *anchor, + const GimpCoords *coord, + GimpAnchorFeatureType feature) +{ + GimpCoords deltacoord; + + gimp_coords_difference (coord, &anchor->position, &deltacoord); + gimp_bezier_stroke_anchor_move_relative (stroke, anchor, + &deltacoord, feature); +} + +static void +gimp_bezier_stroke_anchor_convert (GimpStroke *stroke, + GimpAnchor *anchor, + GimpAnchorFeatureType feature) +{ + GList *anchor_list; + + anchor_list = g_queue_find (stroke->anchors, anchor); + + g_return_if_fail (anchor_list != NULL); + + switch (feature) + { + case GIMP_ANCHOR_FEATURE_EDGE: + if (anchor->type == GIMP_ANCHOR_ANCHOR) + { + if (g_list_previous (anchor_list)) + GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position = + anchor->position; + + if (g_list_next (anchor_list)) + GIMP_ANCHOR (g_list_next (anchor_list)->data)->position = + anchor->position; + } + else + { + if (g_list_previous (anchor_list) && + GIMP_ANCHOR (g_list_previous (anchor_list)->data)->type == GIMP_ANCHOR_ANCHOR) + anchor->position = GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position; + if (g_list_next (anchor_list) && + GIMP_ANCHOR (g_list_next (anchor_list)->data)->type == GIMP_ANCHOR_ANCHOR) + anchor->position = GIMP_ANCHOR (g_list_next (anchor_list)->data)->position; + } + + break; + + default: + g_warning ("gimp_bezier_stroke_anchor_convert: " + "unimplemented anchor conversion %d\n", feature); + } +} + + +static GimpBezierDesc * +gimp_bezier_stroke_make_bezier (GimpStroke *stroke) +{ + GArray *points; + GArray *cmd_array; + GimpBezierDesc *bezdesc; + cairo_path_data_t pathdata; + gint num_cmds, i; + + points = gimp_stroke_control_points_get (stroke, NULL); + + g_return_val_if_fail (points && points->len % 3 == 0, NULL); + if (points->len < 3) + return NULL; + + /* Moveto + (n-1) * curveto + (if closed) curveto + closepath */ + num_cmds = 2 + (points->len / 3 - 1) * 4; + if (stroke->closed) + num_cmds += 1 + 4; + + cmd_array = g_array_sized_new (FALSE, FALSE, + sizeof (cairo_path_data_t), + num_cmds); + + pathdata.header.type = CAIRO_PATH_MOVE_TO; + pathdata.header.length = 2; + g_array_append_val (cmd_array, pathdata); + pathdata.point.x = g_array_index (points, GimpAnchor, 1).position.x; + pathdata.point.y = g_array_index (points, GimpAnchor, 1).position.y; + g_array_append_val (cmd_array, pathdata); + + for (i = 2; i+2 < points->len; i += 3) + { + pathdata.header.type = CAIRO_PATH_CURVE_TO; + pathdata.header.length = 4; + g_array_append_val (cmd_array, pathdata); + + pathdata.point.x = g_array_index (points, GimpAnchor, i).position.x; + pathdata.point.y = g_array_index (points, GimpAnchor, i).position.y; + g_array_append_val (cmd_array, pathdata); + + pathdata.point.x = g_array_index (points, GimpAnchor, i+1).position.x; + pathdata.point.y = g_array_index (points, GimpAnchor, i+1).position.y; + g_array_append_val (cmd_array, pathdata); + + pathdata.point.x = g_array_index (points, GimpAnchor, i+2).position.x; + pathdata.point.y = g_array_index (points, GimpAnchor, i+2).position.y; + g_array_append_val (cmd_array, pathdata); + } + + if (stroke->closed) + { + pathdata.header.type = CAIRO_PATH_CURVE_TO; + pathdata.header.length = 4; + g_array_append_val (cmd_array, pathdata); + + pathdata.point.x = g_array_index (points, GimpAnchor, i).position.x; + pathdata.point.y = g_array_index (points, GimpAnchor, i).position.y; + g_array_append_val (cmd_array, pathdata); + + pathdata.point.x = g_array_index (points, GimpAnchor, 0).position.x; + pathdata.point.y = g_array_index (points, GimpAnchor, 0).position.y; + g_array_append_val (cmd_array, pathdata); + + pathdata.point.x = g_array_index (points, GimpAnchor, 1).position.x; + pathdata.point.y = g_array_index (points, GimpAnchor, 1).position.y; + g_array_append_val (cmd_array, pathdata); + + pathdata.header.type = CAIRO_PATH_CLOSE_PATH; + pathdata.header.length = 1; + g_array_append_val (cmd_array, pathdata); + } + + if (cmd_array->len != num_cmds) + g_printerr ("miscalculated path cmd length! (%d vs. %d)\n", + cmd_array->len, num_cmds); + + bezdesc = gimp_bezier_desc_new ((cairo_path_data_t *) cmd_array->data, + cmd_array->len); + g_array_free (points, TRUE); + g_array_free (cmd_array, FALSE); + + return bezdesc; +} + + +static GArray * +gimp_bezier_stroke_interpolate (GimpStroke *stroke, + gdouble precision, + gboolean *ret_closed) +{ + GArray *ret_coords; + GimpAnchor *anchor; + GList *anchorlist; + GimpCoords segmentcoords[4]; + gint count; + gboolean need_endpoint = FALSE; + + if (g_queue_is_empty (stroke->anchors)) + { + if (ret_closed) + *ret_closed = FALSE; + return NULL; + } + + ret_coords = g_array_new (FALSE, FALSE, sizeof (GimpCoords)); + + count = 0; + + for (anchorlist = stroke->anchors->head; + anchorlist && GIMP_ANCHOR (anchorlist->data)->type != GIMP_ANCHOR_ANCHOR; + anchorlist = g_list_next (anchorlist)); + + for ( ; anchorlist; anchorlist = g_list_next (anchorlist)) + { + anchor = anchorlist->data; + + segmentcoords[count] = anchor->position; + count++; + + if (count == 4) + { + gimp_coords_interpolate_bezier (segmentcoords, precision, + ret_coords, NULL); + segmentcoords[0] = segmentcoords[3]; + count = 1; + need_endpoint = TRUE; + } + } + + if (stroke->closed && ! g_queue_is_empty (stroke->anchors)) + { + anchorlist = stroke->anchors->head; + + while (count < 3) + { + segmentcoords[count] = GIMP_ANCHOR (anchorlist->data)->position; + count++; + } + anchorlist = g_list_next (anchorlist); + if (anchorlist) + segmentcoords[3] = GIMP_ANCHOR (anchorlist->data)->position; + + gimp_coords_interpolate_bezier (segmentcoords, precision, + ret_coords, NULL); + need_endpoint = TRUE; + + } + + if (need_endpoint) + ret_coords = g_array_append_val (ret_coords, segmentcoords[3]); + + if (ret_closed) + *ret_closed = stroke->closed; + + if (ret_coords->len == 0) + { + g_array_free (ret_coords, TRUE); + ret_coords = NULL; + } + + return ret_coords; +} + + +static void +gimp_bezier_stroke_transform (GimpStroke *stroke, + const GimpMatrix3 *matrix, + GQueue *ret_strokes) +{ + GimpStroke *first_stroke = NULL; + GimpStroke *last_stroke = NULL; + GList *anchorlist; + GimpAnchor *anchor; + GimpCoords segmentcoords[4]; + GQueue *transformed[2]; + gint n_transformed; + gint count; + gboolean first; + gboolean last; + + /* if there's no need for clipping, use the default implementation */ + if (! ret_strokes || + gimp_matrix3_is_affine (matrix) || + g_queue_is_empty (stroke->anchors)) + { + GIMP_STROKE_CLASS (parent_class)->transform (stroke, matrix, ret_strokes); + + return; + } + + /* transform the individual segments */ + count = 0; + first = TRUE; + last = FALSE; + + /* find the first non-control anchor */ + for (anchorlist = stroke->anchors->head; + anchorlist && GIMP_ANCHOR (anchorlist->data)->type != GIMP_ANCHOR_ANCHOR; + anchorlist = g_list_next (anchorlist)); + + for ( ; anchorlist || stroke->closed; anchorlist = g_list_next (anchorlist)) + { + /* wrap around if 'stroke' is closed, so that we transform the final + * segment + */ + if (! anchorlist) + { + anchorlist = stroke->anchors->head; + last = TRUE; + } + + anchor = anchorlist->data; + + segmentcoords[count] = anchor->position; + count++; + + if (count == 4) + { + gboolean start_in; + gboolean end_in; + gint i; + + gimp_transform_bezier_coords (matrix, segmentcoords, + transformed, &n_transformed, + &start_in, &end_in); + + for (i = 0; i < n_transformed; i++) + { + GimpStroke *s = NULL; + GList *list; + gint j; + + if (i == 0 && start_in) + { + /* current stroke is connected to last stroke */ + s = last_stroke; + } + else if (last_stroke) + { + /* current stroke is not connected to last stroke. finalize + * last stroke. + */ + anchor = g_queue_peek_tail (last_stroke->anchors); + + g_queue_push_tail (last_stroke->anchors, + gimp_anchor_new (GIMP_ANCHOR_CONTROL, + &anchor->position)); + } + + for (list = transformed[i]->head; list; list = g_list_next (list)) + { + GimpCoords *transformedcoords = list->data; + + if (! s) + { + /* start a new stroke */ + s = gimp_bezier_stroke_new (); + + g_queue_push_tail (s->anchors, + gimp_anchor_new (GIMP_ANCHOR_CONTROL, + &transformedcoords[0])); + + g_queue_push_tail (ret_strokes, s); + + j = 0; + } + else + { + /* continue an existing stroke, skipping the first anchor, + * which is the same as the last anchor of the last stroke + */ + j = 1; + } + + for (; j < 4; j++) + { + GimpAnchorType type; + + if (j == 0 || j == 3) + type = GIMP_ANCHOR_ANCHOR; + else + type = GIMP_ANCHOR_CONTROL; + + g_queue_push_tail (s->anchors, + gimp_anchor_new (type, + &transformedcoords[j])); + } + + g_free (transformedcoords); + } + + g_queue_free (transformed[i]); + + /* if the current stroke is an initial segment of 'stroke', + * remember it, so that we can possibly connect it to the last + * stroke later. + */ + if (i == 0 && start_in && first) + first_stroke = s; + + last_stroke = s; + first = FALSE; + } + + if (! end_in && last_stroke) + { + /* the next stroke is not connected to the last stroke. finalize + * the last stroke. + */ + anchor = g_queue_peek_tail (last_stroke->anchors); + + g_queue_push_tail (last_stroke->anchors, + gimp_anchor_new (GIMP_ANCHOR_CONTROL, + &anchor->position)); + + last_stroke = NULL; + } + + if (last) + break; + + segmentcoords[0] = segmentcoords[3]; + count = 1; + } + } + + /* if the last stroke is a final segment of 'stroke'... */ + if (last_stroke) + { + /* ... and the first stroke is an initial segment of 'stroke', and + * 'stroke' is closed ... + */ + if (first_stroke && stroke->closed) + { + /* connect the first and last strokes */ + + /* remove the first anchor, which is a synthetic control point */ + gimp_anchor_free (g_queue_pop_head (first_stroke->anchors)); + /* remove the last anchor, which is the same anchor point as the + * first anchor + */ + gimp_anchor_free (g_queue_pop_tail (last_stroke->anchors)); + + if (first_stroke == last_stroke) + { + /* the result is a single stroke. move the last anchor, which is + * an orphan control point, to the front, to fill in the removed + * control point of the first anchor, and close the stroke. + */ + g_queue_push_head (first_stroke->anchors, + g_queue_pop_tail (first_stroke->anchors)); + + first_stroke->closed = TRUE; + } + else + { + /* the result is multiple strokes. prepend the last stroke to + * the first stroke, and discard it. + */ + while ((anchor = g_queue_pop_tail (last_stroke->anchors))) + g_queue_push_head (first_stroke->anchors, anchor); + + g_object_unref (g_queue_pop_tail (ret_strokes)); + } + } + else + { + /* otherwise, the first and last strokes are not connected. finalize + * the last stroke. + */ + anchor = g_queue_peek_tail (last_stroke->anchors); + + g_queue_push_tail (last_stroke->anchors, + gimp_anchor_new (GIMP_ANCHOR_CONTROL, + &anchor->position)); + } + } +} + + +GimpStroke * +gimp_bezier_stroke_new_moveto (const GimpCoords *start) +{ + GimpStroke *stroke = gimp_bezier_stroke_new (); + + g_queue_push_tail (stroke->anchors, + gimp_anchor_new (GIMP_ANCHOR_CONTROL, + start)); + g_queue_push_tail (stroke->anchors, + gimp_anchor_new (GIMP_ANCHOR_ANCHOR, + start)); + g_queue_push_tail (stroke->anchors, + gimp_anchor_new (GIMP_ANCHOR_CONTROL, + start)); + return stroke; +} + +void +gimp_bezier_stroke_lineto (GimpStroke *stroke, + const GimpCoords *end) +{ + g_return_if_fail (GIMP_IS_BEZIER_STROKE (stroke)); + g_return_if_fail (stroke->closed == FALSE); + g_return_if_fail (g_queue_is_empty (stroke->anchors) == FALSE); + + g_queue_push_tail (stroke->anchors, + gimp_anchor_new (GIMP_ANCHOR_CONTROL, + end)); + g_queue_push_tail (stroke->anchors, + gimp_anchor_new (GIMP_ANCHOR_ANCHOR, + end)); + g_queue_push_tail (stroke->anchors, + gimp_anchor_new (GIMP_ANCHOR_CONTROL, + end)); +} + +void +gimp_bezier_stroke_conicto (GimpStroke *stroke, + const GimpCoords *control, + const GimpCoords *end) +{ + GimpCoords start, coords; + + g_return_if_fail (GIMP_IS_BEZIER_STROKE (stroke)); + g_return_if_fail (stroke->closed == FALSE); + g_return_if_fail (g_queue_get_length (stroke->anchors) > 1); + + start = GIMP_ANCHOR (stroke->anchors->tail->prev->data)->position; + + gimp_coords_mix (2.0 / 3.0, control, 1.0 / 3.0, &start, &coords); + + GIMP_ANCHOR (stroke->anchors->tail->data)->position = coords; + + gimp_coords_mix (2.0 / 3.0, control, 1.0 / 3.0, end, &coords); + + g_queue_push_tail (stroke->anchors, + gimp_anchor_new (GIMP_ANCHOR_CONTROL, + &coords)); + g_queue_push_tail (stroke->anchors, + gimp_anchor_new (GIMP_ANCHOR_ANCHOR, + end)); + g_queue_push_tail (stroke->anchors, + gimp_anchor_new (GIMP_ANCHOR_CONTROL, + end)); +} + +void +gimp_bezier_stroke_cubicto (GimpStroke *stroke, + const GimpCoords *control1, + const GimpCoords *control2, + const GimpCoords *end) +{ + g_return_if_fail (GIMP_IS_BEZIER_STROKE (stroke)); + g_return_if_fail (stroke->closed == FALSE); + g_return_if_fail (g_queue_is_empty (stroke->anchors) == FALSE); + + GIMP_ANCHOR (stroke->anchors->tail->data)->position = *control1; + + g_queue_push_tail (stroke->anchors, + gimp_anchor_new (GIMP_ANCHOR_CONTROL, + control2)); + g_queue_push_tail (stroke->anchors, + gimp_anchor_new (GIMP_ANCHOR_ANCHOR, + end)); + g_queue_push_tail (stroke->anchors, + gimp_anchor_new (GIMP_ANCHOR_CONTROL, + end)); +} + +static gdouble +arcto_circleparam (gdouble h, + gdouble *y) +{ + gdouble t0 = 0.5; + gdouble dt = 0.25; + gdouble pt0; + gdouble y01, y12, y23, y012, y123, y0123; /* subdividing y[] */ + + while (dt >= 0.00001) + { + pt0 = ( y[0] * (1-t0) * (1-t0) * (1-t0) + + 3 * y[1] * (1-t0) * (1-t0) * t0 + + 3 * y[2] * (1-t0) * t0 * t0 + + y[3] * t0 * t0 * t0 ); + + if (pt0 > h) + t0 = t0 - dt; + else if (pt0 < h) + t0 = t0 + dt; + else + break; + dt = dt/2; + } + + y01 = y[0] * (1-t0) + y[1] * t0; + y12 = y[1] * (1-t0) + y[2] * t0; + y23 = y[2] * (1-t0) + y[3] * t0; + y012 = y01 * (1-t0) + y12 * t0; + y123 = y12 * (1-t0) + y23 * t0; + y0123 = y012 * (1-t0) + y123 * t0; + + y[0] = y0123; y[1] = y123; y[2] = y23; /* y[3] unchanged */ + + return t0; +} + +static void +arcto_subdivide (gdouble t, + gint part, + GimpCoords *p) +{ + GimpCoords p01, p12, p23, p012, p123, p0123; + + gimp_coords_mix (1-t, &(p[0]), t, &(p[1]), &p01 ); + gimp_coords_mix (1-t, &(p[1]), t, &(p[2]), &p12 ); + gimp_coords_mix (1-t, &(p[2]), t, &(p[3]), &p23 ); + gimp_coords_mix (1-t, &p01 , t, &p12 , &p012 ); + gimp_coords_mix (1-t, &p12 , t, &p23 , &p123 ); + gimp_coords_mix (1-t, &p012 , t, &p123 , &p0123); + + if (part == 0) + { + /* p[0] unchanged */ + p[1] = p01; + p[2] = p012; + p[3] = p0123; + } + else + { + p[0] = p0123; + p[1] = p123; + p[2] = p23; + /* p[3] unchanged */ + } +} + +static void +arcto_ellipsesegment (gdouble radius_x, + gdouble radius_y, + gdouble phi0, + gdouble phi1, + GimpCoords *ellips) +{ + const GimpCoords template = GIMP_COORDS_DEFAULT_VALUES; + const gdouble circlemagic = 4.0 * (G_SQRT2 - 1.0) / 3.0; + + gdouble phi_s, phi_e; + gdouble y[4]; + gdouble h0, h1; + gdouble t0, t1; + + g_return_if_fail (ellips != NULL); + + y[0] = 0.0; + y[1] = circlemagic; + y[2] = 1.0; + y[3] = 1.0; + + ellips[0] = template; + ellips[1] = template; + ellips[2] = template; + ellips[3] = template; + + if (phi0 < phi1) + { + phi_s = floor (phi0 / G_PI_2) * G_PI_2; + while (phi_s < 0) phi_s += 2 * G_PI; + phi_e = phi_s + G_PI_2; + } + else + { + phi_e = floor (phi1 / G_PI_2) * G_PI_2; + while (phi_e < 0) phi_e += 2 * G_PI; + phi_s = phi_e + G_PI_2; + } + + h0 = sin (fabs (phi0-phi_s)); + h1 = sin (fabs (phi1-phi_s)); + + ellips[0].x = cos (phi_s); ellips[0].y = sin (phi_s); + ellips[3].x = cos (phi_e); ellips[3].y = sin (phi_e); + + gimp_coords_mix (1, &(ellips[0]), circlemagic, &(ellips[3]), &(ellips[1])); + gimp_coords_mix (circlemagic, &(ellips[0]), 1, &(ellips[3]), &(ellips[2])); + + if (h0 > y[0]) + { + t0 = arcto_circleparam (h0, y); /* also subdivides y[] at t0 */ + arcto_subdivide (t0, 1, ellips); + } + + if (h1 < y[3]) + { + t1 = arcto_circleparam (h1, y); + arcto_subdivide (t1, 0, ellips); + } + + ellips[0].x *= radius_x ; ellips[0].y *= radius_y; + ellips[1].x *= radius_x ; ellips[1].y *= radius_y; + ellips[2].x *= radius_x ; ellips[2].y *= radius_y; + ellips[3].x *= radius_x ; ellips[3].y *= radius_y; +} + +void +gimp_bezier_stroke_arcto (GimpStroke *bez_stroke, + gdouble radius_x, + gdouble radius_y, + gdouble angle_rad, + gboolean large_arc, + gboolean sweep, + const GimpCoords *end) +{ + GimpCoords start; + GimpCoords middle; /* between start and end */ + GimpCoords trans_delta; + GimpCoords trans_center; + GimpCoords tmp_center; + GimpCoords center; + GimpCoords ellips[4]; /* control points of untransformed ellipse segment */ + GimpCoords ctrl[4]; /* control points of next bezier segment */ + + GimpMatrix3 anglerot; + + gdouble lambda; + gdouble phi0, phi1, phi2; + gdouble tmpx, tmpy; + + g_return_if_fail (GIMP_IS_BEZIER_STROKE (bez_stroke)); + g_return_if_fail (bez_stroke->closed == FALSE); + g_return_if_fail (g_queue_get_length (bez_stroke->anchors) > 1); + + if (radius_x == 0 || radius_y == 0) + { + gimp_bezier_stroke_lineto (bez_stroke, end); + return; + } + + start = GIMP_ANCHOR (bez_stroke->anchors->tail->prev->data)->position; + + gimp_matrix3_identity (&anglerot); + gimp_matrix3_rotate (&anglerot, -angle_rad); + + gimp_coords_mix (0.5, &start, -0.5, end, &trans_delta); + gimp_matrix3_transform_point (&anglerot, + trans_delta.x, trans_delta.y, + &tmpx, &tmpy); + trans_delta.x = tmpx; + trans_delta.y = tmpy; + + lambda = (SQR (trans_delta.x) / SQR (radius_x) + + SQR (trans_delta.y) / SQR (radius_y)); + + if (lambda < 0.00001) + { + /* don't bother with it - endpoint is too close to startpoint */ + return; + } + + trans_center = trans_delta; + + if (lambda > 1.0) + { + /* The radii are too small for a matching ellipse. We expand them + * so that they fit exactly (center of the ellipse between the + * start- and endpoint + */ + radius_x *= sqrt (lambda); + radius_y *= sqrt (lambda); + trans_center.x = 0.0; + trans_center.y = 0.0; + } + else + { + gdouble factor = sqrt ((1.0 - lambda) / lambda); + + trans_center.x = trans_delta.y * radius_x / radius_y * factor; + trans_center.y = - trans_delta.x * radius_y / radius_x * factor; + } + + if ((large_arc && sweep) || (!large_arc && !sweep)) + { + trans_center.x *= -1; + trans_center.y *= -1; + } + + gimp_matrix3_identity (&anglerot); + gimp_matrix3_rotate (&anglerot, angle_rad); + + tmp_center = trans_center; + gimp_matrix3_transform_point (&anglerot, + tmp_center.x, tmp_center.y, + &tmpx, &tmpy); + tmp_center.x = tmpx; + tmp_center.y = tmpy; + + gimp_coords_mix (0.5, &start, 0.5, end, &middle); + gimp_coords_add (&tmp_center, &middle, ¢er); + + phi1 = atan2 ((trans_delta.y - trans_center.y) / radius_y, + (trans_delta.x - trans_center.x) / radius_x); + + phi2 = atan2 ((- trans_delta.y - trans_center.y) / radius_y, + (- trans_delta.x - trans_center.x) / radius_x); + + if (phi1 < 0) + phi1 += 2 * G_PI; + + if (phi2 < 0) + phi2 += 2 * G_PI; + + if (sweep) + { + while (phi2 < phi1) + phi2 += 2 * G_PI; + + phi0 = floor (phi1 / G_PI_2) * G_PI_2; + + while (phi0 < phi2) + { + arcto_ellipsesegment (radius_x, radius_y, + MAX (phi0, phi1), MIN (phi0 + G_PI_2, phi2), + ellips); + + gimp_matrix3_transform_point (&anglerot, ellips[0].x, ellips[0].y, + &tmpx, &tmpy); + ellips[0].x = tmpx; ellips[0].y = tmpy; + gimp_matrix3_transform_point (&anglerot, ellips[1].x, ellips[1].y, + &tmpx, &tmpy); + ellips[1].x = tmpx; ellips[1].y = tmpy; + gimp_matrix3_transform_point (&anglerot, ellips[2].x, ellips[2].y, + &tmpx, &tmpy); + ellips[2].x = tmpx; ellips[2].y = tmpy; + gimp_matrix3_transform_point (&anglerot, ellips[3].x, ellips[3].y, + &tmpx, &tmpy); + ellips[3].x = tmpx; ellips[3].y = tmpy; + + gimp_coords_add (¢er, &(ellips[1]), &(ctrl[1])); + gimp_coords_add (¢er, &(ellips[2]), &(ctrl[2])); + gimp_coords_add (¢er, &(ellips[3]), &(ctrl[3])); + + gimp_bezier_stroke_cubicto (bez_stroke, + &(ctrl[1]), &(ctrl[2]), &(ctrl[3])); + phi0 += G_PI_2; + } + } + else + { + while (phi1 < phi2) + phi1 += 2 * G_PI; + + phi0 = ceil (phi1 / G_PI_2) * G_PI_2; + + while (phi0 > phi2) + { + arcto_ellipsesegment (radius_x, radius_y, + MIN (phi0, phi1), MAX (phi0 - G_PI_2, phi2), + ellips); + + gimp_matrix3_transform_point (&anglerot, ellips[0].x, ellips[0].y, + &tmpx, &tmpy); + ellips[0].x = tmpx; ellips[0].y = tmpy; + gimp_matrix3_transform_point (&anglerot, ellips[1].x, ellips[1].y, + &tmpx, &tmpy); + ellips[1].x = tmpx; ellips[1].y = tmpy; + gimp_matrix3_transform_point (&anglerot, ellips[2].x, ellips[2].y, + &tmpx, &tmpy); + ellips[2].x = tmpx; ellips[2].y = tmpy; + gimp_matrix3_transform_point (&anglerot, ellips[3].x, ellips[3].y, + &tmpx, &tmpy); + ellips[3].x = tmpx; ellips[3].y = tmpy; + + gimp_coords_add (¢er, &(ellips[1]), &(ctrl[1])); + gimp_coords_add (¢er, &(ellips[2]), &(ctrl[2])); + gimp_coords_add (¢er, &(ellips[3]), &(ctrl[3])); + + gimp_bezier_stroke_cubicto (bez_stroke, + &(ctrl[1]), &(ctrl[2]), &(ctrl[3])); + phi0 -= G_PI_2; + } + } +} + +GimpStroke * +gimp_bezier_stroke_new_ellipse (const GimpCoords *center, + gdouble radius_x, + gdouble radius_y, + gdouble angle) +{ + GimpStroke *stroke; + GimpCoords p1 = *center; + GimpCoords p2 = *center; + GimpCoords p3 = *center; + GimpCoords dx = { 0, }; + GimpCoords dy = { 0, }; + const gdouble circlemagic = 4.0 * (G_SQRT2 - 1.0) / 3.0; + GimpAnchor *handle; + + dx.x = radius_x * cos (angle); + dx.y = - radius_x * sin (angle); + dy.x = radius_y * sin (angle); + dy.y = radius_y * cos (angle); + + gimp_coords_mix (1.0, center, 1.0, &dx, &p1); + stroke = gimp_bezier_stroke_new_moveto (&p1); + + handle = g_queue_peek_head (stroke->anchors); + gimp_coords_mix (1.0, &p1, -circlemagic, &dy, &handle->position); + + gimp_coords_mix (1.0, &p1, circlemagic, &dy, &p1); + gimp_coords_mix (1.0, center, 1.0, &dy, &p3); + gimp_coords_mix (1.0, &p3, circlemagic, &dx, &p2); + gimp_bezier_stroke_cubicto (stroke, &p1, &p2, &p3); + + gimp_coords_mix (1.0, &p3, -circlemagic, &dx, &p1); + gimp_coords_mix (1.0, center, -1.0, &dx, &p3); + gimp_coords_mix (1.0, &p3, circlemagic, &dy, &p2); + gimp_bezier_stroke_cubicto (stroke, &p1, &p2, &p3); + + gimp_coords_mix (1.0, &p3, -circlemagic, &dy, &p1); + gimp_coords_mix (1.0, center, -1.0, &dy, &p3); + gimp_coords_mix (1.0, &p3, -circlemagic, &dx, &p2); + gimp_bezier_stroke_cubicto (stroke, &p1, &p2, &p3); + + handle = g_queue_peek_tail (stroke->anchors); + gimp_coords_mix (1.0, &p3, circlemagic, &dx, &handle->position); + + gimp_stroke_close (stroke); + + return stroke; +} + + +/* helper function to get the associated anchor of a listitem */ + +static GList * +gimp_bezier_stroke_get_anchor_listitem (GList *list) +{ + if (!list) + return NULL; + + if (GIMP_ANCHOR (list->data)->type == GIMP_ANCHOR_ANCHOR) + return list; + + if (list->prev && GIMP_ANCHOR (list->prev->data)->type == GIMP_ANCHOR_ANCHOR) + return list->prev; + + if (list->next && GIMP_ANCHOR (list->next->data)->type == GIMP_ANCHOR_ANCHOR) + return list->next; + + g_return_val_if_fail (/* bezier stroke inconsistent! */ FALSE, NULL); + + return NULL; +} diff --git a/app/vectors/gimpbezierstroke.h b/app/vectors/gimpbezierstroke.h new file mode 100644 index 0000000..2fe6b98 --- /dev/null +++ b/app/vectors/gimpbezierstroke.h @@ -0,0 +1,84 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpbezierstroke.h + * Copyright (C) 2002 Simon Budig <simon@gimp.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_BEZIER_STROKE_H__ +#define __GIMP_BEZIER_STROKE_H__ + +#include "gimpstroke.h" + + +#define GIMP_TYPE_BEZIER_STROKE (gimp_bezier_stroke_get_type ()) +#define GIMP_BEZIER_STROKE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_BEZIER_STROKE, GimpBezierStroke)) +#define GIMP_BEZIER_STROKE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_BEZIER_STROKE, GimpBezierStrokeClass)) +#define GIMP_IS_BEZIER_STROKE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_BEZIER_STROKE)) +#define GIMP_IS_BEZIER_STROKE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_BEZIER_STROKE)) +#define GIMP_BEZIER_STROKE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_BEZIER_STROKE, GimpBezierStrokeClass)) + + +typedef struct _GimpBezierStrokeClass GimpBezierStrokeClass; + +struct _GimpBezierStroke +{ + GimpStroke parent_instance; +}; + +struct _GimpBezierStrokeClass +{ + GimpStrokeClass parent_class; +}; + + +GType gimp_bezier_stroke_get_type (void) G_GNUC_CONST; + +GimpStroke * gimp_bezier_stroke_new (void); +GimpStroke * gimp_bezier_stroke_new_from_coords (const GimpCoords *coords, + gint n_coords, + gboolean closed); + +GimpStroke * gimp_bezier_stroke_new_moveto (const GimpCoords *start); +void gimp_bezier_stroke_lineto (GimpStroke *bez_stroke, + const GimpCoords *end); +void gimp_bezier_stroke_conicto (GimpStroke *bez_stroke, + const GimpCoords *control, + const GimpCoords *end); +void gimp_bezier_stroke_cubicto (GimpStroke *bez_stroke, + const GimpCoords *control1, + const GimpCoords *control2, + const GimpCoords *end); +void gimp_bezier_stroke_arcto (GimpStroke *bez_stroke, + gdouble radius_x, + gdouble radius_y, + gdouble angle_rad, + gboolean large_arc, + gboolean sweep, + const GimpCoords *end); +GimpStroke * gimp_bezier_stroke_new_ellipse (const GimpCoords *center, + gdouble radius_x, + gdouble radius_y, + gdouble angle); + + +GimpAnchor * gimp_bezier_stroke_extend (GimpStroke *stroke, + const GimpCoords *coords, + GimpAnchor *neighbor, + GimpVectorExtendMode extend_mode); + + +#endif /* __GIMP_BEZIER_STROKE_H__ */ diff --git a/app/vectors/gimpstroke-new.c b/app/vectors/gimpstroke-new.c new file mode 100644 index 0000000..daba683 --- /dev/null +++ b/app/vectors/gimpstroke-new.c @@ -0,0 +1,47 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpstroke-new.c + * Copyright (C) 2006 Simon Budig <simon@gimp.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 "vectors-types.h" + +#include "gimpstroke-new.h" +#include "gimpbezierstroke.h" + + +GimpStroke * +gimp_stroke_new_from_coords (GimpVectorsStrokeType type, + const GimpCoords *coords, + gint n_coords, + gboolean closed) +{ + switch (type) + { + case GIMP_VECTORS_STROKE_TYPE_BEZIER: + return gimp_bezier_stroke_new_from_coords (coords, n_coords, closed); + break; + default: + g_warning ("unknown type in gimp_stroke_new_from_coords(): %d", type); + return NULL; + } +} + diff --git a/app/vectors/gimpstroke-new.h b/app/vectors/gimpstroke-new.h new file mode 100644 index 0000000..5365cc5 --- /dev/null +++ b/app/vectors/gimpstroke-new.h @@ -0,0 +1,31 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpstroke-new.c + * Copyright (C) 2006 Simon Budig <simon@gimp.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_VECTORS_NEW_H__ +#define __GIMP_VECTORS_NEW_H__ + + +GimpStroke * gimp_stroke_new_from_coords (GimpVectorsStrokeType type, + const GimpCoords *coords, + gint n_coords, + gboolean closed); + + +#endif /* __GIMP_VECTORS_NEW_H__ */ diff --git a/app/vectors/gimpstroke.c b/app/vectors/gimpstroke.c new file mode 100644 index 0000000..3a3ab90 --- /dev/null +++ b/app/vectors/gimpstroke.c @@ -0,0 +1,1431 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpstroke.c + * Copyright (C) 2002 Simon Budig <simon@gimp.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 <cairo.h> +#include <gegl.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "vectors-types.h" + +#include "core/gimp-memsize.h" +#include "core/gimpcoords.h" +#include "core/gimpparamspecs.h" +#include "core/gimp-transform-utils.h" + +#include "gimpanchor.h" +#include "gimpstroke.h" + +enum +{ + PROP_0, + PROP_CONTROL_POINTS, + PROP_CLOSED +}; + +/* Prototypes */ + +static void gimp_stroke_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_stroke_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gimp_stroke_finalize (GObject *object); + +static gint64 gimp_stroke_get_memsize (GimpObject *object, + gint64 *gui_size); + +static GimpAnchor * gimp_stroke_real_anchor_get (GimpStroke *stroke, + const GimpCoords *coord); +static GimpAnchor * gimp_stroke_real_anchor_get_next (GimpStroke *stroke, + const GimpAnchor *prev); +static void gimp_stroke_real_anchor_select (GimpStroke *stroke, + GimpAnchor *anchor, + gboolean selected, + gboolean exclusive); +static void gimp_stroke_real_anchor_move_relative (GimpStroke *stroke, + GimpAnchor *anchor, + const GimpCoords *delta, + GimpAnchorFeatureType feature); +static void gimp_stroke_real_anchor_move_absolute (GimpStroke *stroke, + GimpAnchor *anchor, + const GimpCoords *delta, + GimpAnchorFeatureType feature); +static void gimp_stroke_real_anchor_convert (GimpStroke *stroke, + GimpAnchor *anchor, + GimpAnchorFeatureType feature); +static void gimp_stroke_real_anchor_delete (GimpStroke *stroke, + GimpAnchor *anchor); +static gboolean gimp_stroke_real_point_is_movable + (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position); +static void gimp_stroke_real_point_move_relative + (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position, + const GimpCoords *deltacoord, + GimpAnchorFeatureType feature); +static void gimp_stroke_real_point_move_absolute + (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position, + const GimpCoords *coord, + GimpAnchorFeatureType feature); + +static void gimp_stroke_real_close (GimpStroke *stroke); +static GimpStroke * gimp_stroke_real_open (GimpStroke *stroke, + GimpAnchor *end_anchor); +static gboolean gimp_stroke_real_anchor_is_insertable + (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position); +static GimpAnchor * gimp_stroke_real_anchor_insert (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position); + +static gboolean gimp_stroke_real_is_extendable (GimpStroke *stroke, + GimpAnchor *neighbor); + +static GimpAnchor * gimp_stroke_real_extend (GimpStroke *stroke, + const GimpCoords *coords, + GimpAnchor *neighbor, + GimpVectorExtendMode extend_mode); + +gboolean gimp_stroke_real_connect_stroke (GimpStroke *stroke, + GimpAnchor *anchor, + GimpStroke *extension, + GimpAnchor *neighbor); + + +static gboolean gimp_stroke_real_is_empty (GimpStroke *stroke); + +static gdouble gimp_stroke_real_get_length (GimpStroke *stroke, + gdouble precision); +static gdouble gimp_stroke_real_get_distance (GimpStroke *stroke, + const GimpCoords *coord); +static GArray * gimp_stroke_real_interpolate (GimpStroke *stroke, + gdouble precision, + gboolean *closed); +static GimpStroke * gimp_stroke_real_duplicate (GimpStroke *stroke); +static GimpBezierDesc * gimp_stroke_real_make_bezier (GimpStroke *stroke); + +static void gimp_stroke_real_translate (GimpStroke *stroke, + gdouble offset_x, + gdouble offset_y); +static void gimp_stroke_real_scale (GimpStroke *stroke, + gdouble scale_x, + gdouble scale_y); +static void gimp_stroke_real_rotate (GimpStroke *stroke, + gdouble center_x, + gdouble center_y, + gdouble angle); +static void gimp_stroke_real_flip (GimpStroke *stroke, + GimpOrientationType flip_type, + gdouble axis); +static void gimp_stroke_real_flip_free (GimpStroke *stroke, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2); +static void gimp_stroke_real_transform (GimpStroke *stroke, + const GimpMatrix3 *matrix, + GQueue *ret_strokes); + +static GList * gimp_stroke_real_get_draw_anchors (GimpStroke *stroke); +static GList * gimp_stroke_real_get_draw_controls (GimpStroke *stroke); +static GArray * gimp_stroke_real_get_draw_lines (GimpStroke *stroke); +static GArray * gimp_stroke_real_control_points_get (GimpStroke *stroke, + gboolean *ret_closed); +static gboolean gimp_stroke_real_get_point_at_dist (GimpStroke *stroke, + gdouble dist, + gdouble precision, + GimpCoords *position, + gdouble *slope); + + +G_DEFINE_TYPE (GimpStroke, gimp_stroke, GIMP_TYPE_OBJECT) + +#define parent_class gimp_stroke_parent_class + + +static void +gimp_stroke_class_init (GimpStrokeClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass); + GParamSpec *param_spec; + + object_class->finalize = gimp_stroke_finalize; + object_class->get_property = gimp_stroke_get_property; + object_class->set_property = gimp_stroke_set_property; + + gimp_object_class->get_memsize = gimp_stroke_get_memsize; + + klass->changed = NULL; + klass->removed = NULL; + + klass->anchor_get = gimp_stroke_real_anchor_get; + klass->anchor_get_next = gimp_stroke_real_anchor_get_next; + klass->anchor_select = gimp_stroke_real_anchor_select; + klass->anchor_move_relative = gimp_stroke_real_anchor_move_relative; + klass->anchor_move_absolute = gimp_stroke_real_anchor_move_absolute; + klass->anchor_convert = gimp_stroke_real_anchor_convert; + klass->anchor_delete = gimp_stroke_real_anchor_delete; + + klass->point_is_movable = gimp_stroke_real_point_is_movable; + klass->point_move_relative = gimp_stroke_real_point_move_relative; + klass->point_move_absolute = gimp_stroke_real_point_move_absolute; + + klass->nearest_point_get = NULL; + klass->nearest_tangent_get = NULL; + klass->nearest_intersection_get = NULL; + klass->close = gimp_stroke_real_close; + klass->open = gimp_stroke_real_open; + klass->anchor_is_insertable = gimp_stroke_real_anchor_is_insertable; + klass->anchor_insert = gimp_stroke_real_anchor_insert; + klass->is_extendable = gimp_stroke_real_is_extendable; + klass->extend = gimp_stroke_real_extend; + klass->connect_stroke = gimp_stroke_real_connect_stroke; + + klass->is_empty = gimp_stroke_real_is_empty; + klass->get_length = gimp_stroke_real_get_length; + klass->get_distance = gimp_stroke_real_get_distance; + klass->get_point_at_dist = gimp_stroke_real_get_point_at_dist; + klass->interpolate = gimp_stroke_real_interpolate; + + klass->duplicate = gimp_stroke_real_duplicate; + klass->make_bezier = gimp_stroke_real_make_bezier; + + klass->translate = gimp_stroke_real_translate; + klass->scale = gimp_stroke_real_scale; + klass->rotate = gimp_stroke_real_rotate; + klass->flip = gimp_stroke_real_flip; + klass->flip_free = gimp_stroke_real_flip_free; + klass->transform = gimp_stroke_real_transform; + + + klass->get_draw_anchors = gimp_stroke_real_get_draw_anchors; + klass->get_draw_controls = gimp_stroke_real_get_draw_controls; + klass->get_draw_lines = gimp_stroke_real_get_draw_lines; + klass->control_points_get = gimp_stroke_real_control_points_get; + + param_spec = g_param_spec_boxed ("gimp-anchor", + "Gimp Anchor", + "The control points of a Stroke", + GIMP_TYPE_ANCHOR, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_property (object_class, PROP_CONTROL_POINTS, + gimp_param_spec_value_array ("control-points", + "Control Points", + "This is an ValueArray " + "with the initial " + "control points of " + "the new Stroke", + param_spec, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_CLOSED, + g_param_spec_boolean ("closed", + "Close Flag", + "this flag indicates " + "whether the stroke " + "is closed or not", + FALSE, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_stroke_init (GimpStroke *stroke) +{ + stroke->anchors = g_queue_new (); +} + +static void +gimp_stroke_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpStroke *stroke = GIMP_STROKE (object); + GimpValueArray *val_array; + gint length; + gint i; + + switch (property_id) + { + case PROP_CLOSED: + stroke->closed = g_value_get_boolean (value); + break; + + case PROP_CONTROL_POINTS: + g_return_if_fail (g_queue_is_empty (stroke->anchors)); + g_return_if_fail (value != NULL); + + val_array = g_value_get_boxed (value); + + if (val_array == NULL) + return; + + length = gimp_value_array_length (val_array); + + for (i = 0; i < length; i++) + { + GValue *item = gimp_value_array_index (val_array, i); + + g_return_if_fail (G_VALUE_HOLDS (item, GIMP_TYPE_ANCHOR)); + g_queue_push_tail (stroke->anchors, g_value_dup_boxed (item)); + } + + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_stroke_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpStroke *stroke = GIMP_STROKE (object); + + switch (property_id) + { + case PROP_CLOSED: + g_value_set_boolean (value, stroke->closed); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_stroke_finalize (GObject *object) +{ + GimpStroke *stroke = GIMP_STROKE (object); + + g_queue_free_full (stroke->anchors, (GDestroyNotify) gimp_anchor_free); + stroke->anchors = NULL; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gint64 +gimp_stroke_get_memsize (GimpObject *object, + gint64 *gui_size) +{ + GimpStroke *stroke = GIMP_STROKE (object); + gint64 memsize = 0; + + memsize += gimp_g_queue_get_memsize (stroke->anchors, sizeof (GimpAnchor)); + + return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, + gui_size); +} + +void +gimp_stroke_set_ID (GimpStroke *stroke, + gint id) +{ + g_return_if_fail (GIMP_IS_STROKE (stroke)); + g_return_if_fail (stroke->ID == 0 /* we don't want changing IDs... */); + + stroke->ID = id; +} + +gint +gimp_stroke_get_ID (GimpStroke *stroke) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), -1); + + return stroke->ID; +} + + +GimpAnchor * +gimp_stroke_anchor_get (GimpStroke *stroke, + const GimpCoords *coord) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL); + + return GIMP_STROKE_GET_CLASS (stroke)->anchor_get (stroke, coord); +} + + +gdouble +gimp_stroke_nearest_point_get (GimpStroke *stroke, + const GimpCoords *coord, + const gdouble precision, + GimpCoords *ret_point, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + gdouble *ret_pos) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE); + g_return_val_if_fail (coord != NULL, FALSE); + + if (GIMP_STROKE_GET_CLASS (stroke)->nearest_point_get) + return GIMP_STROKE_GET_CLASS (stroke)->nearest_point_get (stroke, + coord, + precision, + ret_point, + ret_segment_start, + ret_segment_end, + ret_pos); + return -1; +} + +gdouble +gimp_stroke_nearest_tangent_get (GimpStroke *stroke, + const GimpCoords *coords1, + const GimpCoords *coords2, + gdouble precision, + GimpCoords *nearest, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + gdouble *ret_pos) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE); + g_return_val_if_fail (coords1 != NULL, FALSE); + g_return_val_if_fail (coords2 != NULL, FALSE); + + if (GIMP_STROKE_GET_CLASS (stroke)->nearest_tangent_get) + return GIMP_STROKE_GET_CLASS (stroke)->nearest_tangent_get (stroke, + coords1, + coords2, + precision, + nearest, + ret_segment_start, + ret_segment_end, + ret_pos); + return -1; +} + +gdouble +gimp_stroke_nearest_intersection_get (GimpStroke *stroke, + const GimpCoords *coords1, + const GimpCoords *direction, + gdouble precision, + GimpCoords *nearest, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + gdouble *ret_pos) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE); + g_return_val_if_fail (coords1 != NULL, FALSE); + g_return_val_if_fail (direction != NULL, FALSE); + + if (GIMP_STROKE_GET_CLASS (stroke)->nearest_intersection_get) + return GIMP_STROKE_GET_CLASS (stroke)->nearest_intersection_get (stroke, + coords1, + direction, + precision, + nearest, + ret_segment_start, + ret_segment_end, + ret_pos); + return -1; +} + +static GimpAnchor * +gimp_stroke_real_anchor_get (GimpStroke *stroke, + const GimpCoords *coord) +{ + gdouble dx, dy; + gdouble mindist = -1; + GList *anchors; + GList *list; + GimpAnchor *anchor = NULL; + + anchors = gimp_stroke_get_draw_controls (stroke); + + for (list = anchors; list; list = g_list_next (list)) + { + dx = coord->x - GIMP_ANCHOR (list->data)->position.x; + dy = coord->y - GIMP_ANCHOR (list->data)->position.y; + + if (mindist < 0 || mindist > dx * dx + dy * dy) + { + mindist = dx * dx + dy * dy; + anchor = GIMP_ANCHOR (list->data); + } + } + + g_list_free (anchors); + + anchors = gimp_stroke_get_draw_anchors (stroke); + + for (list = anchors; list; list = g_list_next (list)) + { + dx = coord->x - GIMP_ANCHOR (list->data)->position.x; + dy = coord->y - GIMP_ANCHOR (list->data)->position.y; + + if (mindist < 0 || mindist > dx * dx + dy * dy) + { + mindist = dx * dx + dy * dy; + anchor = GIMP_ANCHOR (list->data); + } + } + + g_list_free (anchors); + + return anchor; +} + + +GimpAnchor * +gimp_stroke_anchor_get_next (GimpStroke *stroke, + const GimpAnchor *prev) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL); + + return GIMP_STROKE_GET_CLASS (stroke)->anchor_get_next (stroke, prev); +} + +static GimpAnchor * +gimp_stroke_real_anchor_get_next (GimpStroke *stroke, + const GimpAnchor *prev) +{ + GList *list; + + if (prev) + { + list = g_queue_find (stroke->anchors, prev); + if (list) + list = g_list_next (list); + } + else + { + list = stroke->anchors->head; + } + + if (list) + return GIMP_ANCHOR (list->data); + + return NULL; +} + + +void +gimp_stroke_anchor_select (GimpStroke *stroke, + GimpAnchor *anchor, + gboolean selected, + gboolean exclusive) +{ + g_return_if_fail (GIMP_IS_STROKE (stroke)); + + GIMP_STROKE_GET_CLASS (stroke)->anchor_select (stroke, anchor, + selected, exclusive); +} + +static void +gimp_stroke_real_anchor_select (GimpStroke *stroke, + GimpAnchor *anchor, + gboolean selected, + gboolean exclusive) +{ + GList *list = stroke->anchors->head; + + if (exclusive) + { + while (list) + { + GIMP_ANCHOR (list->data)->selected = FALSE; + list = g_list_next (list); + } + } + + list = g_queue_find (stroke->anchors, anchor); + + if (list) + GIMP_ANCHOR (list->data)->selected = selected; +} + + +void +gimp_stroke_anchor_move_relative (GimpStroke *stroke, + GimpAnchor *anchor, + const GimpCoords *delta, + GimpAnchorFeatureType feature) +{ + g_return_if_fail (GIMP_IS_STROKE (stroke)); + g_return_if_fail (anchor != NULL); + g_return_if_fail (g_queue_find (stroke->anchors, anchor)); + + GIMP_STROKE_GET_CLASS (stroke)->anchor_move_relative (stroke, anchor, + delta, feature); +} + +static void +gimp_stroke_real_anchor_move_relative (GimpStroke *stroke, + GimpAnchor *anchor, + const GimpCoords *delta, + GimpAnchorFeatureType feature) +{ + anchor->position.x += delta->x; + anchor->position.y += delta->y; +} + + +void +gimp_stroke_anchor_move_absolute (GimpStroke *stroke, + GimpAnchor *anchor, + const GimpCoords *coord, + GimpAnchorFeatureType feature) +{ + g_return_if_fail (GIMP_IS_STROKE (stroke)); + g_return_if_fail (anchor != NULL); + g_return_if_fail (g_queue_find (stroke->anchors, anchor)); + + GIMP_STROKE_GET_CLASS (stroke)->anchor_move_absolute (stroke, anchor, + coord, feature); +} + +static void +gimp_stroke_real_anchor_move_absolute (GimpStroke *stroke, + GimpAnchor *anchor, + const GimpCoords *coord, + GimpAnchorFeatureType feature) +{ + anchor->position.x = coord->x; + anchor->position.y = coord->y; +} + +gboolean +gimp_stroke_point_is_movable (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE); + + return GIMP_STROKE_GET_CLASS (stroke)->point_is_movable (stroke, predec, + position); +} + + +static gboolean +gimp_stroke_real_point_is_movable (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position) +{ + return FALSE; +} + + +void +gimp_stroke_point_move_relative (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position, + const GimpCoords *deltacoord, + GimpAnchorFeatureType feature) +{ + g_return_if_fail (GIMP_IS_STROKE (stroke)); + + GIMP_STROKE_GET_CLASS (stroke)->point_move_relative (stroke, predec, + position, deltacoord, + feature); +} + + +static void +gimp_stroke_real_point_move_relative (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position, + const GimpCoords *deltacoord, + GimpAnchorFeatureType feature) +{ + g_printerr ("gimp_stroke_point_move_relative: default implementation\n"); +} + + +void +gimp_stroke_point_move_absolute (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position, + const GimpCoords *coord, + GimpAnchorFeatureType feature) +{ + g_return_if_fail (GIMP_IS_STROKE (stroke)); + + GIMP_STROKE_GET_CLASS (stroke)->point_move_absolute (stroke, predec, + position, coord, + feature); +} + +static void +gimp_stroke_real_point_move_absolute (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position, + const GimpCoords *coord, + GimpAnchorFeatureType feature) +{ + g_printerr ("gimp_stroke_point_move_absolute: default implementation\n"); +} + + +void +gimp_stroke_close (GimpStroke *stroke) +{ + g_return_if_fail (GIMP_IS_STROKE (stroke)); + g_return_if_fail (g_queue_is_empty (stroke->anchors) == FALSE); + + GIMP_STROKE_GET_CLASS (stroke)->close (stroke); +} + +static void +gimp_stroke_real_close (GimpStroke *stroke) +{ + stroke->closed = TRUE; + g_object_notify (G_OBJECT (stroke), "closed"); +} + + +void +gimp_stroke_anchor_convert (GimpStroke *stroke, + GimpAnchor *anchor, + GimpAnchorFeatureType feature) +{ + g_return_if_fail (GIMP_IS_STROKE (stroke)); + + GIMP_STROKE_GET_CLASS (stroke)->anchor_convert (stroke, anchor, feature); +} + +static void +gimp_stroke_real_anchor_convert (GimpStroke *stroke, + GimpAnchor *anchor, + GimpAnchorFeatureType feature) +{ + g_printerr ("gimp_stroke_anchor_convert: default implementation\n"); +} + + +void +gimp_stroke_anchor_delete (GimpStroke *stroke, + GimpAnchor *anchor) +{ + g_return_if_fail (GIMP_IS_STROKE (stroke)); + g_return_if_fail (anchor && anchor->type == GIMP_ANCHOR_ANCHOR); + + GIMP_STROKE_GET_CLASS (stroke)->anchor_delete (stroke, anchor); +} + +static void +gimp_stroke_real_anchor_delete (GimpStroke *stroke, + GimpAnchor *anchor) +{ + g_printerr ("gimp_stroke_anchor_delete: default implementation\n"); +} + +GimpStroke * +gimp_stroke_open (GimpStroke *stroke, + GimpAnchor *end_anchor) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL); + g_return_val_if_fail (end_anchor && + end_anchor->type == GIMP_ANCHOR_ANCHOR, NULL); + + return GIMP_STROKE_GET_CLASS (stroke)->open (stroke, end_anchor); +} + +static GimpStroke * +gimp_stroke_real_open (GimpStroke *stroke, + GimpAnchor *end_anchor) +{ + g_printerr ("gimp_stroke_open: default implementation\n"); + return NULL; +} + +gboolean +gimp_stroke_anchor_is_insertable (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE); + + return GIMP_STROKE_GET_CLASS (stroke)->anchor_is_insertable (stroke, + predec, + position); +} + +static gboolean +gimp_stroke_real_anchor_is_insertable (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE); + + return FALSE; +} + +GimpAnchor * +gimp_stroke_anchor_insert (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL); + g_return_val_if_fail (predec->type == GIMP_ANCHOR_ANCHOR, NULL); + + return GIMP_STROKE_GET_CLASS (stroke)->anchor_insert (stroke, + predec, position); +} + +static GimpAnchor * +gimp_stroke_real_anchor_insert (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL); + + return NULL; +} + + +gboolean +gimp_stroke_is_extendable (GimpStroke *stroke, + GimpAnchor *neighbor) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE); + + return GIMP_STROKE_GET_CLASS (stroke)->is_extendable (stroke, neighbor); +} + +static gboolean +gimp_stroke_real_is_extendable (GimpStroke *stroke, + GimpAnchor *neighbor) +{ + return FALSE; +} + + +GimpAnchor * +gimp_stroke_extend (GimpStroke *stroke, + const GimpCoords *coords, + GimpAnchor *neighbor, + GimpVectorExtendMode extend_mode) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL); + g_return_val_if_fail (!stroke->closed, NULL); + + return GIMP_STROKE_GET_CLASS (stroke)->extend (stroke, coords, + neighbor, extend_mode); +} + +static GimpAnchor * +gimp_stroke_real_extend (GimpStroke *stroke, + const GimpCoords *coords, + GimpAnchor *neighbor, + GimpVectorExtendMode extend_mode) +{ + g_printerr ("gimp_stroke_extend: default implementation\n"); + return NULL; +} + +gboolean +gimp_stroke_connect_stroke (GimpStroke *stroke, + GimpAnchor *anchor, + GimpStroke *extension, + GimpAnchor *neighbor) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE); + g_return_val_if_fail (GIMP_IS_STROKE (extension), FALSE); + g_return_val_if_fail (stroke->closed == FALSE && + extension->closed == FALSE, FALSE); + + return GIMP_STROKE_GET_CLASS (stroke)->connect_stroke (stroke, anchor, + extension, neighbor); +} + +gboolean +gimp_stroke_real_connect_stroke (GimpStroke *stroke, + GimpAnchor *anchor, + GimpStroke *extension, + GimpAnchor *neighbor) +{ + g_printerr ("gimp_stroke_connect_stroke: default implementation\n"); + return FALSE; +} + +gboolean +gimp_stroke_is_empty (GimpStroke *stroke) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE); + + return GIMP_STROKE_GET_CLASS (stroke)->is_empty (stroke); +} + +static gboolean +gimp_stroke_real_is_empty (GimpStroke *stroke) +{ + return g_queue_is_empty (stroke->anchors); +} + + +gdouble +gimp_stroke_get_length (GimpStroke *stroke, + gdouble precision) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), 0.0); + + return GIMP_STROKE_GET_CLASS (stroke)->get_length (stroke, precision); +} + +static gdouble +gimp_stroke_real_get_length (GimpStroke *stroke, + gdouble precision) +{ + GArray *points; + gint i; + gdouble length; + GimpCoords difference; + + if (g_queue_is_empty (stroke->anchors)) + return -1; + + points = gimp_stroke_interpolate (stroke, precision, NULL); + if (points == NULL) + return -1; + + length = 0; + + for (i = 0; i < points->len - 1; i++ ) + { + gimp_coords_difference (&(g_array_index (points, GimpCoords, i)), + &(g_array_index (points, GimpCoords, i+1)), + &difference); + length += gimp_coords_length (&difference); + } + + g_array_free(points, TRUE); + + return length; +} + + +gdouble +gimp_stroke_get_distance (GimpStroke *stroke, + const GimpCoords *coord) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), 0.0); + + return GIMP_STROKE_GET_CLASS (stroke)->get_distance (stroke, coord); +} + +static gdouble +gimp_stroke_real_get_distance (GimpStroke *stroke, + const GimpCoords *coord) +{ + g_printerr ("gimp_stroke_get_distance: default implementation\n"); + + return 0.0; +} + + +GArray * +gimp_stroke_interpolate (GimpStroke *stroke, + gdouble precision, + gboolean *ret_closed) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL); + + return GIMP_STROKE_GET_CLASS (stroke)->interpolate (stroke, precision, + ret_closed); +} + +static GArray * +gimp_stroke_real_interpolate (GimpStroke *stroke, + gdouble precision, + gboolean *ret_closed) +{ + g_printerr ("gimp_stroke_interpolate: default implementation\n"); + + return NULL; +} + +GimpStroke * +gimp_stroke_duplicate (GimpStroke *stroke) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL); + + return GIMP_STROKE_GET_CLASS (stroke)->duplicate (stroke); +} + +static GimpStroke * +gimp_stroke_real_duplicate (GimpStroke *stroke) +{ + GimpStroke *new_stroke; + GList *list; + + new_stroke = g_object_new (G_TYPE_FROM_INSTANCE (stroke), + "name", gimp_object_get_name (stroke), + NULL); + + g_queue_free_full (new_stroke->anchors, (GDestroyNotify) gimp_anchor_free); + new_stroke->anchors = g_queue_copy (stroke->anchors); + + for (list = new_stroke->anchors->head; list; list = g_list_next (list)) + { + list->data = gimp_anchor_copy (GIMP_ANCHOR (list->data)); + } + + new_stroke->closed = stroke->closed; + /* we do *not* copy the ID! */ + + return new_stroke; +} + + +GimpBezierDesc * +gimp_stroke_make_bezier (GimpStroke *stroke) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL); + + return GIMP_STROKE_GET_CLASS (stroke)->make_bezier (stroke); +} + +static GimpBezierDesc * +gimp_stroke_real_make_bezier (GimpStroke *stroke) +{ + g_printerr ("gimp_stroke_make_bezier: default implementation\n"); + + return NULL; +} + + +void +gimp_stroke_translate (GimpStroke *stroke, + gdouble offset_x, + gdouble offset_y) +{ + g_return_if_fail (GIMP_IS_STROKE (stroke)); + + GIMP_STROKE_GET_CLASS (stroke)->translate (stroke, offset_x, offset_y); +} + +static void +gimp_stroke_real_translate (GimpStroke *stroke, + gdouble offset_x, + gdouble offset_y) +{ + GList *list; + + for (list = stroke->anchors->head; list; list = g_list_next (list)) + { + GimpAnchor *anchor = list->data; + + anchor->position.x += offset_x; + anchor->position.y += offset_y; + } +} + + +void +gimp_stroke_scale (GimpStroke *stroke, + gdouble scale_x, + gdouble scale_y) +{ + g_return_if_fail (GIMP_IS_STROKE (stroke)); + + GIMP_STROKE_GET_CLASS (stroke)->scale (stroke, scale_x, scale_y); +} + +static void +gimp_stroke_real_scale (GimpStroke *stroke, + gdouble scale_x, + gdouble scale_y) +{ + GList *list; + + for (list = stroke->anchors->head; list; list = g_list_next (list)) + { + GimpAnchor *anchor = list->data; + + anchor->position.x *= scale_x; + anchor->position.y *= scale_y; + } +} + +void +gimp_stroke_rotate (GimpStroke *stroke, + gdouble center_x, + gdouble center_y, + gdouble angle) +{ + g_return_if_fail (GIMP_IS_STROKE (stroke)); + + GIMP_STROKE_GET_CLASS (stroke)->rotate (stroke, center_x, center_y, angle); +} + +static void +gimp_stroke_real_rotate (GimpStroke *stroke, + gdouble center_x, + gdouble center_y, + gdouble angle) +{ + GimpMatrix3 matrix; + + angle = angle / 180.0 * G_PI; + gimp_matrix3_identity (&matrix); + gimp_transform_matrix_rotate_center (&matrix, center_x, center_y, angle); + + gimp_stroke_transform (stroke, &matrix, NULL); +} + +void +gimp_stroke_flip (GimpStroke *stroke, + GimpOrientationType flip_type, + gdouble axis) +{ + g_return_if_fail (GIMP_IS_STROKE (stroke)); + + GIMP_STROKE_GET_CLASS (stroke)->flip (stroke, flip_type, axis); +} + +static void +gimp_stroke_real_flip (GimpStroke *stroke, + GimpOrientationType flip_type, + gdouble axis) +{ + GimpMatrix3 matrix; + + gimp_matrix3_identity (&matrix); + gimp_transform_matrix_flip (&matrix, flip_type, axis); + gimp_stroke_transform (stroke, &matrix, NULL); +} + +void +gimp_stroke_flip_free (GimpStroke *stroke, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2) +{ + g_return_if_fail (GIMP_IS_STROKE (stroke)); + + GIMP_STROKE_GET_CLASS (stroke)->flip_free (stroke, x1, y1, x2, y2); +} + +static void +gimp_stroke_real_flip_free (GimpStroke *stroke, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2) +{ + /* x, y, width and height parameter in gimp_transform_matrix_flip_free are unused */ + GimpMatrix3 matrix; + + gimp_matrix3_identity (&matrix); + gimp_transform_matrix_flip_free (&matrix, x1, y1, x2, y2); + + gimp_stroke_transform (stroke, &matrix, NULL); +} + +/* transforms 'stroke' by 'matrix'. due to clipping, the transformation may + * result in multiple strokes. + * + * if 'ret_strokes' is not NULL, the transformed strokes are appended to the + * queue, and 'stroke' is left in an unspecified state. one of the resulting + * strokes may alias 'stroke'. + * + * if 'ret_strokes' is NULL, the transformation is performed in-place. if the + * transformation results in multiple strokes (which, atm, can only happen for + * non-affine transformation), the result is undefined. + */ +void +gimp_stroke_transform (GimpStroke *stroke, + const GimpMatrix3 *matrix, + GQueue *ret_strokes) +{ + g_return_if_fail (GIMP_IS_STROKE (stroke)); + + GIMP_STROKE_GET_CLASS (stroke)->transform (stroke, matrix, ret_strokes); +} + +static void +gimp_stroke_real_transform (GimpStroke *stroke, + const GimpMatrix3 *matrix, + GQueue *ret_strokes) +{ + GList *list; + + for (list = stroke->anchors->head; list; list = g_list_next (list)) + { + GimpAnchor *anchor = list->data; + + gimp_matrix3_transform_point (matrix, + anchor->position.x, + anchor->position.y, + &anchor->position.x, + &anchor->position.y); + } + + if (ret_strokes) + { + stroke->ID = 0; + + g_queue_push_tail (ret_strokes, g_object_ref (stroke)); + } +} + + +GList * +gimp_stroke_get_draw_anchors (GimpStroke *stroke) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL); + + return GIMP_STROKE_GET_CLASS (stroke)->get_draw_anchors (stroke); +} + +static GList * +gimp_stroke_real_get_draw_anchors (GimpStroke *stroke) +{ + GList *list; + GList *ret_list = NULL; + + for (list = stroke->anchors->head; list; list = g_list_next (list)) + { + if (GIMP_ANCHOR (list->data)->type == GIMP_ANCHOR_ANCHOR) + ret_list = g_list_prepend (ret_list, list->data); + } + + return g_list_reverse (ret_list); +} + + +GList * +gimp_stroke_get_draw_controls (GimpStroke *stroke) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL); + + return GIMP_STROKE_GET_CLASS (stroke)->get_draw_controls (stroke); +} + +static GList * +gimp_stroke_real_get_draw_controls (GimpStroke *stroke) +{ + GList *list; + GList *ret_list = NULL; + + for (list = stroke->anchors->head; list; list = g_list_next (list)) + { + GimpAnchor *anchor = list->data; + + if (anchor->type == GIMP_ANCHOR_CONTROL) + { + GimpAnchor *next = list->next ? list->next->data : NULL; + GimpAnchor *prev = list->prev ? list->prev->data : NULL; + + if (next && next->type == GIMP_ANCHOR_ANCHOR && next->selected) + { + /* Ok, this is a hack. + * The idea is to give control points at the end of a + * stroke a higher priority for the interactive tool. + */ + if (prev) + ret_list = g_list_prepend (ret_list, anchor); + else + ret_list = g_list_append (ret_list, anchor); + } + else if (prev && prev->type == GIMP_ANCHOR_ANCHOR && prev->selected) + { + /* same here... */ + if (next) + ret_list = g_list_prepend (ret_list, anchor); + else + ret_list = g_list_append (ret_list, anchor); + } + } + } + + return g_list_reverse (ret_list); +} + + +GArray * +gimp_stroke_get_draw_lines (GimpStroke *stroke) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL); + + return GIMP_STROKE_GET_CLASS (stroke)->get_draw_lines (stroke); +} + +static GArray * +gimp_stroke_real_get_draw_lines (GimpStroke *stroke) +{ + GList *list; + GArray *ret_lines = NULL; + gint count = 0; + + for (list = stroke->anchors->head; list; list = g_list_next (list)) + { + GimpAnchor *anchor = list->data; + + if (anchor->type == GIMP_ANCHOR_ANCHOR && anchor->selected) + { + if (list->next) + { + GimpAnchor *next = list->next->data; + + if (count == 0) + ret_lines = g_array_new (FALSE, FALSE, sizeof (GimpCoords)); + + ret_lines = g_array_append_val (ret_lines, anchor->position); + ret_lines = g_array_append_val (ret_lines, next->position); + count += 1; + } + + if (list->prev) + { + GimpAnchor *prev = list->prev->data; + + if (count == 0) + ret_lines = g_array_new (FALSE, FALSE, sizeof (GimpCoords)); + + ret_lines = g_array_append_val (ret_lines, anchor->position); + ret_lines = g_array_append_val (ret_lines, prev->position); + count += 1; + } + } + } + + return ret_lines; +} + +GArray * +gimp_stroke_control_points_get (GimpStroke *stroke, + gboolean *ret_closed) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL); + + return GIMP_STROKE_GET_CLASS (stroke)->control_points_get (stroke, + ret_closed); +} + +static GArray * +gimp_stroke_real_control_points_get (GimpStroke *stroke, + gboolean *ret_closed) +{ + guint num_anchors; + GArray *ret_array; + GList *list; + + num_anchors = g_queue_get_length (stroke->anchors); + ret_array = g_array_sized_new (FALSE, FALSE, + sizeof (GimpAnchor), num_anchors); + + for (list = stroke->anchors->head; list; list = g_list_next (list)) + { + g_array_append_vals (ret_array, list->data, 1); + } + + if (ret_closed) + *ret_closed = stroke->closed; + + return ret_array; +} + +gboolean +gimp_stroke_get_point_at_dist (GimpStroke *stroke, + gdouble dist, + gdouble precision, + GimpCoords *position, + gdouble *slope) +{ + g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE); + + return GIMP_STROKE_GET_CLASS (stroke)->get_point_at_dist (stroke, + dist, + precision, + position, + slope); +} + + +static gboolean +gimp_stroke_real_get_point_at_dist (GimpStroke *stroke, + gdouble dist, + gdouble precision, + GimpCoords *position, + gdouble *slope) +{ + GArray *points; + gint i; + gdouble length; + gdouble segment_length; + gboolean ret = FALSE; + GimpCoords difference; + + points = gimp_stroke_interpolate (stroke, precision, NULL); + if (points == NULL) + return ret; + + length = 0; + for (i=0; i < points->len - 1; i++) + { + gimp_coords_difference (&(g_array_index (points, GimpCoords , i)), + &(g_array_index (points, GimpCoords , i+1)), + &difference); + segment_length = gimp_coords_length (&difference); + + if (segment_length == 0 || length + segment_length < dist ) + { + length += segment_length; + } + else + { + /* x = x1 + (x2 - x1 ) u */ + /* x = x1 (1-u) + u x2 */ + + gdouble u = (dist - length) / segment_length; + + gimp_coords_mix (1 - u, &(g_array_index (points, GimpCoords , i)), + u, &(g_array_index (points, GimpCoords , i+1)), + position); + + if (difference.x == 0) + *slope = G_MAXDOUBLE; + else + *slope = difference.y / difference.x; + + ret = TRUE; + break; + } + } + + g_array_free (points, TRUE); + + return ret; +} diff --git a/app/vectors/gimpstroke.h b/app/vectors/gimpstroke.h new file mode 100644 index 0000000..f44b9ab --- /dev/null +++ b/app/vectors/gimpstroke.h @@ -0,0 +1,343 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpstroke.h + * Copyright (C) 2002 Simon Budig <simon@gimp.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_STROKE_H__ +#define __GIMP_STROKE_H__ + +#include "core/gimpobject.h" + + +#define GIMP_TYPE_STROKE (gimp_stroke_get_type ()) +#define GIMP_STROKE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_STROKE, GimpStroke)) +#define GIMP_STROKE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_STROKE, GimpStrokeClass)) +#define GIMP_IS_STROKE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_STROKE)) +#define GIMP_IS_STROKE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_STROKE)) +#define GIMP_STROKE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_STROKE, GimpStrokeClass)) + + +typedef struct _GimpStrokeClass GimpStrokeClass; + +struct _GimpStroke +{ + GimpObject parent_instance; + gint ID; + + GQueue *anchors; + + gboolean closed; +}; + +struct _GimpStrokeClass +{ + GimpObjectClass parent_class; + + void (* changed) (GimpStroke *stroke); + void (* removed) (GimpStroke *stroke); + + GimpAnchor * (* anchor_get) (GimpStroke *stroke, + const GimpCoords *coord); + gdouble (* nearest_point_get) (GimpStroke *stroke, + const GimpCoords *coord, + gdouble precision, + GimpCoords *ret_point, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + gdouble *ret_pos); + gdouble (* nearest_tangent_get) (GimpStroke *stroke, + const GimpCoords *coord1, + const GimpCoords *coord2, + gdouble precision, + GimpCoords *nearest, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + gdouble *ret_pos); + gdouble (* nearest_intersection_get) + (GimpStroke *stroke, + const GimpCoords *coord1, + const GimpCoords *direction, + gdouble precision, + GimpCoords *nearest, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + gdouble *ret_pos); + GimpAnchor * (* anchor_get_next) (GimpStroke *stroke, + const GimpAnchor *prev); + void (* anchor_select) (GimpStroke *stroke, + GimpAnchor *anchor, + gboolean selected, + gboolean exclusive); + void (* anchor_move_relative) (GimpStroke *stroke, + GimpAnchor *anchor, + const GimpCoords *deltacoord, + GimpAnchorFeatureType feature); + void (* anchor_move_absolute) (GimpStroke *stroke, + GimpAnchor *anchor, + const GimpCoords *coord, + GimpAnchorFeatureType feature); + void (* anchor_convert) (GimpStroke *stroke, + GimpAnchor *anchor, + GimpAnchorFeatureType feature); + void (* anchor_delete) (GimpStroke *stroke, + GimpAnchor *anchor); + + gboolean (* point_is_movable) (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position); + void (* point_move_relative) (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position, + const GimpCoords *deltacoord, + GimpAnchorFeatureType feature); + void (* point_move_absolute) (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position, + const GimpCoords *coord, + GimpAnchorFeatureType feature); + + void (* close) (GimpStroke *stroke); + GimpStroke * (* open) (GimpStroke *stroke, + GimpAnchor *end_anchor); + gboolean (* anchor_is_insertable) (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position); + GimpAnchor * (* anchor_insert) (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position); + gboolean (* is_extendable) (GimpStroke *stroke, + GimpAnchor *neighbor); + GimpAnchor * (* extend) (GimpStroke *stroke, + const GimpCoords *coords, + GimpAnchor *neighbor, + GimpVectorExtendMode extend_mode); + gboolean (* connect_stroke) (GimpStroke *stroke, + GimpAnchor *anchor, + GimpStroke *extension, + GimpAnchor *neighbor); + + gboolean (* is_empty) (GimpStroke *stroke); + gdouble (* get_length) (GimpStroke *stroke, + gdouble precision); + gdouble (* get_distance) (GimpStroke *stroke, + const GimpCoords *coord); + gboolean (* get_point_at_dist) (GimpStroke *stroke, + gdouble dist, + gdouble precision, + GimpCoords *position, + gdouble *slope); + + GArray * (* interpolate) (GimpStroke *stroke, + gdouble precision, + gboolean *ret_closed); + + GimpStroke * (* duplicate) (GimpStroke *stroke); + + GimpBezierDesc * (* make_bezier) (GimpStroke *stroke); + + void (* translate) (GimpStroke *stroke, + gdouble offset_x, + gdouble offset_y); + void (* scale) (GimpStroke *stroke, + gdouble scale_x, + gdouble scale_y); + void (* rotate) (GimpStroke *stroke, + gdouble center_x, + gdouble center_y, + gdouble angle); + void (* flip) (GimpStroke *stroke, + GimpOrientationType flip_type, + gdouble axis); + void (* flip_free) (GimpStroke *stroke, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2); + void (* transform) (GimpStroke *stroke, + const GimpMatrix3 *matrix, + GQueue *ret_strokes); + + GList * (* get_draw_anchors) (GimpStroke *stroke); + GList * (* get_draw_controls) (GimpStroke *stroke); + GArray * (* get_draw_lines) (GimpStroke *stroke); + GArray * (* control_points_get) (GimpStroke *stroke, + gboolean *ret_closed); +}; + + +GType gimp_stroke_get_type (void) G_GNUC_CONST; + +void gimp_stroke_set_ID (GimpStroke *stroke, + gint id); +gint gimp_stroke_get_ID (GimpStroke *stroke); + + +/* accessing / modifying the anchors */ + +GArray * gimp_stroke_control_points_get (GimpStroke *stroke, + gboolean *closed); + +GimpAnchor * gimp_stroke_anchor_get (GimpStroke *stroke, + const GimpCoords *coord); + +gdouble gimp_stroke_nearest_point_get (GimpStroke *stroke, + const GimpCoords *coord, + gdouble precision, + GimpCoords *ret_point, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + gdouble *ret_pos); +gdouble gimp_stroke_nearest_tangent_get (GimpStroke *stroke, + const GimpCoords *coords1, + const GimpCoords *coords2, + gdouble precision, + GimpCoords *nearest, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + gdouble *ret_pos); +gdouble gimp_stroke_nearest_intersection_get (GimpStroke *stroke, + const GimpCoords *coords1, + const GimpCoords *direction, + gdouble precision, + GimpCoords *nearest, + GimpAnchor **ret_segment_start, + GimpAnchor **ret_segment_end, + gdouble *ret_pos); + + +/* prev == NULL: "first" anchor */ +GimpAnchor * gimp_stroke_anchor_get_next (GimpStroke *stroke, + const GimpAnchor *prev); + +void gimp_stroke_anchor_select (GimpStroke *stroke, + GimpAnchor *anchor, + gboolean selected, + gboolean exclusive); + +/* type will be an xorable enum: + * VECTORS_NONE, VECTORS_FIX_ANGLE, VECTORS_FIX_RATIO, VECTORS_RESTRICT_ANGLE + * or so. + */ +void gimp_stroke_anchor_move_relative (GimpStroke *stroke, + GimpAnchor *anchor, + const GimpCoords *delta, + GimpAnchorFeatureType feature); +void gimp_stroke_anchor_move_absolute (GimpStroke *stroke, + GimpAnchor *anchor, + const GimpCoords *coord, + GimpAnchorFeatureType feature); + +gboolean gimp_stroke_point_is_movable (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position); +void gimp_stroke_point_move_relative (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position, + const GimpCoords *deltacoord, + GimpAnchorFeatureType feature); +void gimp_stroke_point_move_absolute (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position, + const GimpCoords *coord, + GimpAnchorFeatureType feature); + +void gimp_stroke_close (GimpStroke *stroke); + +void gimp_stroke_anchor_convert (GimpStroke *stroke, + GimpAnchor *anchor, + GimpAnchorFeatureType feature); + +void gimp_stroke_anchor_delete (GimpStroke *stroke, + GimpAnchor *anchor); + +GimpStroke * gimp_stroke_open (GimpStroke *stroke, + GimpAnchor *end_anchor); +gboolean gimp_stroke_anchor_is_insertable (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position); +GimpAnchor * gimp_stroke_anchor_insert (GimpStroke *stroke, + GimpAnchor *predec, + gdouble position); + +gboolean gimp_stroke_is_extendable (GimpStroke *stroke, + GimpAnchor *neighbor); + +GimpAnchor * gimp_stroke_extend (GimpStroke *stroke, + const GimpCoords *coords, + GimpAnchor *neighbor, + GimpVectorExtendMode extend_mode); + +gboolean gimp_stroke_connect_stroke (GimpStroke *stroke, + GimpAnchor *anchor, + GimpStroke *extension, + GimpAnchor *neighbor); + +gboolean gimp_stroke_is_empty (GimpStroke *stroke); + +/* accessing the shape of the curve */ + +gdouble gimp_stroke_get_length (GimpStroke *stroke, + gdouble precision); +gdouble gimp_stroke_get_distance (GimpStroke *stroke, + const GimpCoords *coord); + +gboolean gimp_stroke_get_point_at_dist (GimpStroke *stroke, + gdouble dist, + gdouble precision, + GimpCoords *position, + gdouble *slope); + +/* returns an array of valid coordinates */ +GArray * gimp_stroke_interpolate (GimpStroke *stroke, + const gdouble precision, + gboolean *closed); + +GimpStroke * gimp_stroke_duplicate (GimpStroke *stroke); + +/* creates a bezier approximation. */ +GimpBezierDesc * gimp_stroke_make_bezier (GimpStroke *stroke); + +void gimp_stroke_translate (GimpStroke *stroke, + gdouble offset_x, + gdouble offset_y); +void gimp_stroke_scale (GimpStroke *stroke, + gdouble scale_x, + gdouble scale_y); +void gimp_stroke_rotate (GimpStroke *stroke, + gdouble center_x, + gdouble center_y, + gdouble angle); +void gimp_stroke_flip (GimpStroke *stroke, + GimpOrientationType flip_type, + gdouble axis); +void gimp_stroke_flip_free (GimpStroke *stroke, + gdouble x1, + gdouble y1, + gdouble x2, + gdouble y2); +void gimp_stroke_transform (GimpStroke *stroke, + const GimpMatrix3 *matrix, + GQueue *ret_strokes); + + +GList * gimp_stroke_get_draw_anchors (GimpStroke *stroke); +GList * gimp_stroke_get_draw_controls (GimpStroke *stroke); +GArray * gimp_stroke_get_draw_lines (GimpStroke *stroke); + +#endif /* __GIMP_STROKE_H__ */ + diff --git a/app/vectors/gimpvectors-compat.c b/app/vectors/gimpvectors-compat.c new file mode 100644 index 0000000..5416d00 --- /dev/null +++ b/app/vectors/gimpvectors-compat.c @@ -0,0 +1,285 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpvectors-compat.c + * Copyright (C) 2003 Michael Natterer <mitch@gimp.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 <gdk-pixbuf/gdk-pixbuf.h> +#include <gegl.h> + +#include "vectors-types.h" + +#include "core/gimpimage.h" + +#include "gimpanchor.h" +#include "gimpbezierstroke.h" +#include "gimpvectors.h" +#include "gimpvectors-compat.h" + + +enum +{ + GIMP_VECTORS_COMPAT_ANCHOR = 1, + GIMP_VECTORS_COMPAT_CONTROL = 2, + GIMP_VECTORS_COMPAT_NEW_STROKE = 3 +}; + + +static const GimpCoords default_coords = GIMP_COORDS_DEFAULT_VALUES; + + +GimpVectors * +gimp_vectors_compat_new (GimpImage *image, + const gchar *name, + GimpVectorsCompatPoint *points, + gint n_points, + gboolean closed) +{ + GimpVectors *vectors; + GimpStroke *stroke; + GimpCoords *coords; + GimpCoords *curr_stroke; + GimpCoords *curr_coord; + gint i; + + g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL); + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (points != NULL || n_points == 0, NULL); + g_return_val_if_fail (n_points >= 0, NULL); + + vectors = gimp_vectors_new (image, name); + + coords = g_new0 (GimpCoords, n_points + 1); + + curr_stroke = curr_coord = coords; + + /* skip the first control point, will set it later */ + curr_coord++; + + for (i = 0; i < n_points; i++) + { + *curr_coord = default_coords; + + curr_coord->x = points[i].x; + curr_coord->y = points[i].y; + + /* copy the first anchor to be the first control point */ + if (curr_coord == curr_stroke + 1) + *curr_stroke = *curr_coord; + + /* found new stroke start */ + if (points[i].type == GIMP_VECTORS_COMPAT_NEW_STROKE) + { + /* copy the last control point to the beginning of the stroke */ + *curr_stroke = *(curr_coord - 1); + + stroke = + gimp_bezier_stroke_new_from_coords (curr_stroke, + curr_coord - curr_stroke - 1, + TRUE); + gimp_vectors_stroke_add (vectors, stroke); + g_object_unref (stroke); + + /* start a new stroke */ + curr_stroke = curr_coord - 1; + + /* copy the first anchor to be the first control point */ + *curr_stroke = *curr_coord; + } + + curr_coord++; + } + + if (closed) + { + /* copy the last control point to the beginning of the stroke */ + curr_coord--; + *curr_stroke = *curr_coord; + } + + stroke = gimp_bezier_stroke_new_from_coords (curr_stroke, + curr_coord - curr_stroke, + closed); + gimp_vectors_stroke_add (vectors, stroke); + g_object_unref (stroke); + + g_free (coords); + + return vectors; +} + +gboolean +gimp_vectors_compat_is_compatible (GimpImage *image) +{ + GList *list; + + g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE); + + for (list = gimp_image_get_vectors_iter (image); + list; + list = g_list_next (list)) + { + GimpVectors *vectors = GIMP_VECTORS (list->data); + GList *strokes; + gint open_count = 0; + + if (gimp_item_get_visible (GIMP_ITEM (vectors))) + return FALSE; + + for (strokes = vectors->strokes->head; + strokes; + strokes = g_list_next (strokes)) + { + GimpStroke *stroke = GIMP_STROKE (strokes->data); + + if (! GIMP_IS_BEZIER_STROKE (stroke)) + return FALSE; + + if (!stroke->closed) + open_count++; + } + + if (open_count >= 2) + return FALSE; + } + + return TRUE; +} + +GimpVectorsCompatPoint * +gimp_vectors_compat_get_points (GimpVectors *vectors, + gint32 *n_points, + gint32 *closed) +{ + GimpVectorsCompatPoint *points; + GList *strokes; + gint i; + GList *postponed = NULL; /* for the one open stroke... */ + gint open_count; + gboolean first_stroke = TRUE; + + g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL); + g_return_val_if_fail (n_points != NULL, NULL); + g_return_val_if_fail (closed != NULL, NULL); + + *n_points = 0; + *closed = TRUE; + + open_count = 0; + + for (strokes = vectors->strokes->head; + strokes; + strokes = g_list_next (strokes)) + { + GimpStroke *stroke = strokes->data; + gint n_anchors; + + if (! stroke->closed) + { + open_count++; + postponed = strokes; + *closed = FALSE; + + if (open_count >= 2) + { + g_warning ("gimp_vectors_compat_get_points(): convert failed"); + *n_points = 0; + return NULL; + } + } + + n_anchors = g_queue_get_length (stroke->anchors); + + if (! stroke->closed) + n_anchors--; + + *n_points += n_anchors; + } + + points = g_new0 (GimpVectorsCompatPoint, *n_points); + + i = 0; + + for (strokes = vectors->strokes->head; + strokes || postponed; + strokes = g_list_next (strokes)) + { + GimpStroke *stroke; + GList *anchors; + + if (strokes) + { + if (postponed && strokes == postponed) + /* we need to visit the open stroke last... */ + continue; + else + stroke = GIMP_STROKE (strokes->data); + } + else + { + stroke = GIMP_STROKE (postponed->data); + postponed = NULL; + } + + for (anchors = stroke->anchors->head; + anchors; + anchors = g_list_next (anchors)) + { + GimpAnchor *anchor = anchors->data; + + /* skip the first anchor, will add it at the end if needed */ + if (! anchors->prev) + continue; + + switch (anchor->type) + { + case GIMP_ANCHOR_ANCHOR: + if (anchors->prev == stroke->anchors->head && ! first_stroke) + points[i].type = GIMP_VECTORS_COMPAT_NEW_STROKE; + else + points[i].type = GIMP_VECTORS_COMPAT_ANCHOR; + break; + + case GIMP_ANCHOR_CONTROL: + points[i].type = GIMP_VECTORS_COMPAT_CONTROL; + break; + } + + points[i].x = anchor->position.x; + points[i].y = anchor->position.y; + + i++; + + /* write the skipped control point */ + if (! anchors->next && stroke->closed) + { + anchor = g_queue_peek_head (stroke->anchors); + + points[i].type = GIMP_VECTORS_COMPAT_CONTROL; + points[i].x = anchor->position.x; + points[i].y = anchor->position.y; + + i++; + } + } + first_stroke = FALSE; + } + + return points; +} diff --git a/app/vectors/gimpvectors-compat.h b/app/vectors/gimpvectors-compat.h new file mode 100644 index 0000000..561043a --- /dev/null +++ b/app/vectors/gimpvectors-compat.h @@ -0,0 +1,48 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpvectors-compat.h + * Copyright (C) 2003 Michael Natterer <mitch@gimp.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_VECTORS_COMPAT_H__ +#define __GIMP_VECTORS_COMPAT_H__ + + +typedef struct _GimpVectorsCompatPoint GimpVectorsCompatPoint; + +struct _GimpVectorsCompatPoint +{ + guint32 type; + gdouble x; + gdouble y; +}; + + +GimpVectors * gimp_vectors_compat_new (GimpImage *image, + const gchar *name, + GimpVectorsCompatPoint *points, + gint n_points, + gboolean closed); + +gboolean gimp_vectors_compat_is_compatible (GimpImage *image); + +GimpVectorsCompatPoint * gimp_vectors_compat_get_points (GimpVectors *vectors, + gint32 *n_points, + gint32 *closed); + + +#endif /* __GIMP_VECTORS_COMPAT_H__ */ diff --git a/app/vectors/gimpvectors-export.c b/app/vectors/gimpvectors-export.c new file mode 100644 index 0000000..f8a33e8 --- /dev/null +++ b/app/vectors/gimpvectors-export.c @@ -0,0 +1,333 @@ +/* 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 "vectors-types.h" + +#include "core/gimpimage.h" +#include "core/gimpitem.h" + +#include "gimpanchor.h" +#include "gimpstroke.h" +#include "gimpbezierstroke.h" +#include "gimpvectors.h" +#include "gimpvectors-export.h" + +#include "gimp-intl.h" + + +static GString * gimp_vectors_export (GimpImage *image, + GimpVectors *vectors); +static void gimp_vectors_export_image_size (GimpImage *image, + GString *str); +static void gimp_vectors_export_path (GimpVectors *vectors, + GString *str); +static gchar * gimp_vectors_export_path_data (GimpVectors *vectors); + + +/** + * gimp_vectors_export_file: + * @image: the #GimpImage from which to export vectors + * @vectors: a #GimpVectors object or %NULL to export all vectors in @image + * @file: the file to write + * @error: return location for errors + * + * Exports one or more vectors to a SVG file. + * + * Return value: %TRUE on success, + * %FALSE if there was an error writing the file + **/ +gboolean +gimp_vectors_export_file (GimpImage *image, + GimpVectors *vectors, + GFile *file, + GError **error) +{ + GOutputStream *output; + GString *string; + GError *my_error = NULL; + + g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE); + g_return_val_if_fail (vectors == NULL || GIMP_IS_VECTORS (vectors), FALSE); + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + output = G_OUTPUT_STREAM (g_file_replace (file, + NULL, FALSE, G_FILE_CREATE_NONE, + NULL, error)); + if (! output) + return FALSE; + + string = gimp_vectors_export (image, vectors); + + if (! g_output_stream_write_all (output, string->str, string->len, + NULL, NULL, &my_error)) + { + GCancellable *cancellable = g_cancellable_new (); + + g_set_error (error, my_error->domain, my_error->code, + _("Writing SVG file '%s' failed: %s"), + gimp_file_get_utf8_name (file), my_error->message); + g_clear_error (&my_error); + g_string_free (string, TRUE); + + /* Cancel the overwrite initiated by g_file_replace(). */ + g_cancellable_cancel (cancellable); + g_output_stream_close (output, cancellable, NULL); + g_object_unref (cancellable); + g_object_unref (output); + + return FALSE; + } + + g_string_free (string, TRUE); + g_object_unref (output); + + return TRUE; +} + +/** + * gimp_vectors_export_string: + * @image: the #GimpImage from which to export vectors + * @vectors: a #GimpVectors object or %NULL to export all vectors in @image + * + * Exports one or more vectors to a SVG string. + * + * Return value: a %NUL-terminated string that holds a complete XML document + **/ +gchar * +gimp_vectors_export_string (GimpImage *image, + GimpVectors *vectors) +{ + g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL); + g_return_val_if_fail (vectors == NULL || GIMP_IS_VECTORS (vectors), NULL); + + return g_string_free (gimp_vectors_export (image, vectors), FALSE); +} + +static GString * +gimp_vectors_export (GimpImage *image, + GimpVectors *vectors) +{ + GString *str = g_string_new (NULL); + + g_string_append_printf (str, + "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n" + "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\"\n" + " \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n" + "\n" + "<svg xmlns=\"http://www.w3.org/2000/svg\"\n"); + + g_string_append (str, " "); + gimp_vectors_export_image_size (image, str); + g_string_append_c (str, '\n'); + + g_string_append_printf (str, + " viewBox=\"0 0 %d %d\">\n", + gimp_image_get_width (image), + gimp_image_get_height (image)); + + if (vectors) + { + gimp_vectors_export_path (vectors, str); + } + else + { + GList *list; + + for (list = gimp_image_get_vectors_iter (image); + list; + list = list->next) + { + gimp_vectors_export_path (GIMP_VECTORS (list->data), str); + } + } + + g_string_append (str, "</svg>\n"); + + return str; +} + +static void +gimp_vectors_export_image_size (GimpImage *image, + GString *str) +{ + GimpUnit unit; + const gchar *abbrev; + gchar wbuf[G_ASCII_DTOSTR_BUF_SIZE]; + gchar hbuf[G_ASCII_DTOSTR_BUF_SIZE]; + gdouble xres; + gdouble yres; + gdouble w, h; + + gimp_image_get_resolution (image, &xres, &yres); + + w = (gdouble) gimp_image_get_width (image) / xres; + h = (gdouble) gimp_image_get_height (image) / yres; + + /* FIXME: should probably use the display unit here */ + unit = gimp_image_get_unit (image); + switch (unit) + { + case GIMP_UNIT_INCH: abbrev = "in"; break; + case GIMP_UNIT_MM: abbrev = "mm"; break; + case GIMP_UNIT_POINT: abbrev = "pt"; break; + case GIMP_UNIT_PICA: abbrev = "pc"; break; + default: abbrev = "cm"; + unit = GIMP_UNIT_MM; + w /= 10.0; + h /= 10.0; + break; + } + + g_ascii_formatd (wbuf, sizeof (wbuf), "%g", w * gimp_unit_get_factor (unit)); + g_ascii_formatd (hbuf, sizeof (hbuf), "%g", h * gimp_unit_get_factor (unit)); + + g_string_append_printf (str, + "width=\"%s%s\" height=\"%s%s\"", + wbuf, abbrev, hbuf, abbrev); +} + +static void +gimp_vectors_export_path (GimpVectors *vectors, + GString *str) +{ + const gchar *name = gimp_object_get_name (vectors); + gchar *data = gimp_vectors_export_path_data (vectors); + gchar *esc_name; + + esc_name = g_markup_escape_text (name, strlen (name)); + + g_string_append_printf (str, + " <path id=\"%s\"\n" + " fill=\"none\" stroke=\"black\" stroke-width=\"1\"\n" + " d=\"%s\" />\n", + esc_name, data); + + g_free (esc_name); + g_free (data); +} + + +#define NEWLINE "\n " + +static gchar * +gimp_vectors_export_path_data (GimpVectors *vectors) +{ + GString *str; + GList *strokes; + gchar x_string[G_ASCII_DTOSTR_BUF_SIZE]; + gchar y_string[G_ASCII_DTOSTR_BUF_SIZE]; + gboolean closed = FALSE; + + str = g_string_new (NULL); + + for (strokes = vectors->strokes->head; + strokes; + strokes = strokes->next) + { + GimpStroke *stroke = strokes->data; + GArray *control_points; + GimpAnchor *anchor; + gint i; + + if (closed) + g_string_append_printf (str, NEWLINE); + + control_points = gimp_stroke_control_points_get (stroke, &closed); + + if (GIMP_IS_BEZIER_STROKE (stroke)) + { + if (control_points->len >= 3) + { + anchor = &g_array_index (control_points, GimpAnchor, 1); + g_ascii_formatd (x_string, G_ASCII_DTOSTR_BUF_SIZE, + "%.2f", anchor->position.x); + g_ascii_formatd (y_string, G_ASCII_DTOSTR_BUF_SIZE, + "%.2f", anchor->position.y); + g_string_append_printf (str, "M %s,%s", x_string, y_string); + } + + if (control_points->len > 3) + { + g_string_append_printf (str, NEWLINE "C"); + } + + for (i = 2; i < (control_points->len + (closed ? 2 : - 1)); i++) + { + if (i > 2 && i % 3 == 2) + g_string_append_printf (str, NEWLINE " "); + + anchor = &g_array_index (control_points, GimpAnchor, + i % control_points->len); + g_ascii_formatd (x_string, G_ASCII_DTOSTR_BUF_SIZE, + "%.2f", anchor->position.x); + g_ascii_formatd (y_string, G_ASCII_DTOSTR_BUF_SIZE, + "%.2f", anchor->position.y); + g_string_append_printf (str, " %s,%s", x_string, y_string); + } + + if (closed && control_points->len > 3) + g_string_append_printf (str, " Z"); + } + else + { + g_printerr ("Unknown stroke type\n"); + + if (control_points->len >= 1) + { + anchor = &g_array_index (control_points, GimpAnchor, 0); + g_ascii_formatd (x_string, G_ASCII_DTOSTR_BUF_SIZE, + ".2f", anchor->position.x); + g_ascii_formatd (y_string, G_ASCII_DTOSTR_BUF_SIZE, + ".2f", anchor->position.y); + g_string_append_printf (str, "M %s,%s", x_string, y_string); + } + + if (control_points->len > 1) + { + g_string_append_printf (str, NEWLINE "L"); + } + + for (i = 1; i < control_points->len; i++) + { + if (i > 1 && i % 3 == 1) + g_string_append_printf (str, NEWLINE " "); + + anchor = &g_array_index (control_points, GimpAnchor, i); + g_ascii_formatd (x_string, G_ASCII_DTOSTR_BUF_SIZE, + "%.2f", anchor->position.x); + g_ascii_formatd (y_string, G_ASCII_DTOSTR_BUF_SIZE, + "%.2f", anchor->position.y); + g_string_append_printf (str, " %s,%s", x_string, y_string); + } + + if (closed && control_points->len > 1) + g_string_append_printf (str, " Z"); + } + + g_array_free (control_points, TRUE); + } + + return g_strchomp (g_string_free (str, FALSE)); +} diff --git a/app/vectors/gimpvectors-export.h b/app/vectors/gimpvectors-export.h new file mode 100644 index 0000000..d61eae2 --- /dev/null +++ b/app/vectors/gimpvectors-export.h @@ -0,0 +1,30 @@ +/* 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_VECTORS_EXPORT_H__ +#define __GIMP_VECTORS_EXPORT_H__ + + +gboolean gimp_vectors_export_file (GimpImage *image, + GimpVectors *vectors, + GFile *file, + GError **error); +gchar * gimp_vectors_export_string (GimpImage *image, + GimpVectors *vectors); + + +#endif /* __GIMP_VECTORS_IMPORT_H__ */ diff --git a/app/vectors/gimpvectors-import.c b/app/vectors/gimpvectors-import.c new file mode 100644 index 0000000..fa631ee --- /dev/null +++ b/app/vectors/gimpvectors-import.c @@ -0,0 +1,1784 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpVectors Import + * Copyright (C) 2003-2004 Sven Neumann <sven@gimp.org> + * + * Some code here is based on code from librsvg that was originally + * written by Raph Levien <raph@artofcode.com> for Gill. + * + * This SVG path importer implements a subset of SVG that is + * sufficient to parse path elements and basic shapes and to apply + * transformations as described by the SVG specification: + * http://www.w3.org/TR/SVG/. It must handle the SVG files exported + * by GIMP but it is also supposed to be able to extract paths and + * shapes from foreign SVG documents. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 <errno.h> + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gegl.h> + +#include "libgimpbase/gimpbase.h" +#include "libgimpmath/gimpmath.h" + +#include "vectors-types.h" + +#include "config/gimpxmlparser.h" + +#include "core/gimperror.h" +#include "core/gimpimage.h" +#include "core/gimpimage-undo.h" + +#include "gimpbezierstroke.h" +#include "gimpstroke.h" +#include "gimpvectors.h" +#include "gimpvectors-import.h" + +#include "gimp-intl.h" + + +#define COORDS_INIT \ + { \ + .x = 0.0, \ + .y = 0.0, \ + .pressure = 1.0, \ + .xtilt = 0.0, \ + .ytilt = 0.0, \ + .wheel = 0.5, \ + .velocity = 0.0, \ + .direction = 0.0 \ + } + + +typedef struct +{ + GQueue *stack; + GimpImage *image; + gboolean scale; + gint svg_depth; +} SvgParser; + + +typedef struct _SvgHandler SvgHandler; + +struct _SvgHandler +{ + const gchar *name; + + void (* start) (SvgHandler *handler, + const gchar **names, + const gchar **values, + SvgParser *parser); + void (* end) (SvgHandler *handler, + SvgParser *parser); + + gdouble width; + gdouble height; + gchar *id; + GList *paths; + GimpMatrix3 *transform; +}; + + +typedef struct +{ + gchar *id; + GList *strokes; +} SvgPath; + + +static gboolean gimp_vectors_import (GimpImage *image, + GFile *file, + const gchar *str, + gsize len, + gboolean merge, + gboolean scale, + GimpVectors *parent, + gint position, + GList **ret_vectors, + GError **error); + +static void svg_parser_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error); +static void svg_parser_end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error); + +static const GMarkupParser markup_parser = +{ + svg_parser_start_element, + svg_parser_end_element, + NULL, /* characters */ + NULL, /* passthrough */ + NULL /* error */ +}; + + +static void svg_handler_svg_start (SvgHandler *handler, + const gchar **names, + const gchar **values, + SvgParser *parser); +static void svg_handler_svg_end (SvgHandler *handler, + SvgParser *parser); +static void svg_handler_group_start (SvgHandler *handler, + const gchar **names, + const gchar **values, + SvgParser *parser); +static void svg_handler_path_start (SvgHandler *handler, + const gchar **names, + const gchar **values, + SvgParser *parser); +static void svg_handler_rect_start (SvgHandler *handler, + const gchar **names, + const gchar **values, + SvgParser *parser); +static void svg_handler_ellipse_start (SvgHandler *handler, + const gchar **names, + const gchar **values, + SvgParser *parser); +static void svg_handler_line_start (SvgHandler *handler, + const gchar **names, + const gchar **values, + SvgParser *parser); +static void svg_handler_poly_start (SvgHandler *handler, + const gchar **names, + const gchar **values, + SvgParser *parser); + +static const SvgHandler svg_handlers[] = +{ + { "svg", svg_handler_svg_start, svg_handler_svg_end }, + { "g", svg_handler_group_start, NULL }, + { "path", svg_handler_path_start, NULL }, + { "rect", svg_handler_rect_start, NULL }, + { "circle", svg_handler_ellipse_start, NULL }, + { "ellipse", svg_handler_ellipse_start, NULL }, + { "line", svg_handler_line_start, NULL }, + { "polyline", svg_handler_poly_start, NULL }, + { "polygon", svg_handler_poly_start, NULL } +}; + + +static gboolean parse_svg_length (const gchar *value, + gdouble reference, + gdouble resolution, + gdouble *length); +static gboolean parse_svg_viewbox (const gchar *value, + gdouble *width, + gdouble *height, + GimpMatrix3 *matrix); +static gboolean parse_svg_transform (const gchar *value, + GimpMatrix3 *matrix); +static GList * parse_path_data (const gchar *data); + + +/** + * gimp_vectors_import_file: + * @image: the #GimpImage to add the paths to + * @file: a SVG file + * @merge: should multiple paths be merged into a single #GimpVectors object + * @scale: should the SVG be scaled to fit the image dimensions + * @position: position in the image's vectors stack where to add the vectors + * @error: location to store possible errors + * + * Imports one or more paths and basic shapes from a SVG file. + * + * Return value: %TRUE on success, %FALSE if an error occurred + **/ +gboolean +gimp_vectors_import_file (GimpImage *image, + GFile *file, + gboolean merge, + gboolean scale, + GimpVectors *parent, + gint position, + GList **ret_vectors, + GError **error) +{ + g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE); + g_return_val_if_fail (G_IS_FILE (file), FALSE); + g_return_val_if_fail (parent == NULL || + parent == GIMP_IMAGE_ACTIVE_PARENT || + GIMP_IS_VECTORS (parent), FALSE); + g_return_val_if_fail (parent == NULL || + parent == GIMP_IMAGE_ACTIVE_PARENT || + gimp_item_is_attached (GIMP_ITEM (parent)), FALSE); + g_return_val_if_fail (parent == NULL || + parent == GIMP_IMAGE_ACTIVE_PARENT || + gimp_item_get_image (GIMP_ITEM (parent)) == image, + FALSE); + g_return_val_if_fail (parent == NULL || + parent == GIMP_IMAGE_ACTIVE_PARENT || + gimp_viewable_get_children (GIMP_VIEWABLE (parent)), + FALSE); + g_return_val_if_fail (ret_vectors == NULL || *ret_vectors == NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + return gimp_vectors_import (image, file, NULL, 0, merge, scale, + parent, position, + ret_vectors, error); +} + +/** + * gimp_vectors_import_string: + * @image: the #GimpImage to add the paths to + * @buffer: a character buffer to parse + * @len: number of bytes in @str or -1 if @str is %NUL-terminated + * @merge: should multiple paths be merged into a single #GimpVectors object + * @scale: should the SVG be scaled to fit the image dimensions + * @error: location to store possible errors + * + * Imports one or more paths and basic shapes from a SVG file. + * + * Return value: %TRUE on success, %FALSE if an error occurred + **/ +gboolean +gimp_vectors_import_buffer (GimpImage *image, + const gchar *buffer, + gsize len, + gboolean merge, + gboolean scale, + GimpVectors *parent, + gint position, + GList **ret_vectors, + GError **error) +{ + g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE); + g_return_val_if_fail (buffer != NULL || len == 0, FALSE); + g_return_val_if_fail (parent == NULL || + parent == GIMP_IMAGE_ACTIVE_PARENT || + GIMP_IS_VECTORS (parent), FALSE); + g_return_val_if_fail (parent == NULL || + parent == GIMP_IMAGE_ACTIVE_PARENT || + gimp_item_is_attached (GIMP_ITEM (parent)), FALSE); + g_return_val_if_fail (parent == NULL || + parent == GIMP_IMAGE_ACTIVE_PARENT || + gimp_item_get_image (GIMP_ITEM (parent)) == image, + FALSE); + g_return_val_if_fail (parent == NULL || + parent == GIMP_IMAGE_ACTIVE_PARENT || + gimp_viewable_get_children (GIMP_VIEWABLE (parent)), + FALSE); + g_return_val_if_fail (ret_vectors == NULL || *ret_vectors == NULL, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + return gimp_vectors_import (image, NULL, buffer, len, merge, scale, + parent, position, + ret_vectors, error); +} + +static gboolean +gimp_vectors_import (GimpImage *image, + GFile *file, + const gchar *str, + gsize len, + gboolean merge, + gboolean scale, + GimpVectors *parent, + gint position, + GList **ret_vectors, + GError **error) +{ + GimpXmlParser *xml_parser; + SvgParser parser; + GList *paths; + SvgHandler *base; + gboolean success = TRUE; + + parser.stack = g_queue_new (); + parser.image = image; + parser.scale = scale; + parser.svg_depth = 0; + + /* the base of the stack, defines the size of the view-port */ + base = g_slice_new0 (SvgHandler); + base->name = "image"; + base->width = gimp_image_get_width (image); + base->height = gimp_image_get_height (image); + + g_queue_push_head (parser.stack, base); + + xml_parser = gimp_xml_parser_new (&markup_parser, &parser); + + if (file) + success = gimp_xml_parser_parse_gfile (xml_parser, file, error); + else + success = gimp_xml_parser_parse_buffer (xml_parser, str, len, error); + + gimp_xml_parser_free (xml_parser); + + if (success) + { + if (base->paths) + { + GimpVectors *vectors = NULL; + + base->paths = g_list_reverse (base->paths); + + merge = merge && base->paths->next; + + gimp_image_undo_group_start (image, GIMP_UNDO_GROUP_VECTORS_IMPORT, + _("Import Paths")); + + for (paths = base->paths; paths; paths = paths->next) + { + SvgPath *path = paths->data; + GList *list; + + if (! merge || ! vectors) + { + vectors = gimp_vectors_new (image, + ((merge || ! path->id) ? + _("Imported Path") : path->id)); + gimp_image_add_vectors (image, vectors, + parent, position, TRUE); + gimp_vectors_freeze (vectors); + + if (ret_vectors) + *ret_vectors = g_list_prepend (*ret_vectors, vectors); + + if (position != -1) + position++; + } + + for (list = path->strokes; list; list = list->next) + gimp_vectors_stroke_add (vectors, GIMP_STROKE (list->data)); + + if (! merge) + gimp_vectors_thaw (vectors); + + g_list_free_full (path->strokes, g_object_unref); + path->strokes = NULL; + } + + if (merge) + gimp_vectors_thaw (vectors); + + gimp_image_undo_group_end (image); + } + else + { + if (file) + g_set_error (error, GIMP_ERROR, GIMP_FAILED, + _("No paths found in '%s'"), + gimp_file_get_utf8_name (file)); + else + g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED, + _("No paths found in the buffer")); + + success = FALSE; + } + } + else if (error && *error && file) /* parser reported an error */ + { + gchar *msg = (*error)->message; + + (*error)->message = + g_strdup_printf (_("Failed to import paths from '%s': %s"), + gimp_file_get_utf8_name (file), msg); + + g_free (msg); + } + + while ((base = g_queue_pop_head (parser.stack)) != NULL) + { + for (paths = base->paths; paths; paths = paths->next) + { + SvgPath *path = paths->data; + GList *list; + + g_free (path->id); + + for (list = path->strokes; list; list = list->next) + g_object_unref (list->data); + + g_list_free (path->strokes); + + g_slice_free (SvgPath, path); + } + + g_list_free (base->paths); + + g_slice_free (GimpMatrix3, base->transform); + g_slice_free (SvgHandler, base); + } + + g_queue_free (parser.stack); + + return success; +} + +static void +svg_parser_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + SvgParser *parser = user_data; + SvgHandler *handler; + SvgHandler *base; + gint i = 0; + + handler = g_slice_new0 (SvgHandler); + base = g_queue_peek_head (parser->stack); + + /* if the element is not rendered, always use the generic handler */ + if (base->width <= 0.0 || base->height <= 0.0) + i = G_N_ELEMENTS (svg_handlers); + + for (; i < G_N_ELEMENTS (svg_handlers); i++) + if (strcmp (svg_handlers[i].name, element_name) == 0) + { + handler->name = svg_handlers[i].name; + handler->start = svg_handlers[i].start; + break; + } + + handler->width = base->width; + handler->height = base->height; + + g_queue_push_head (parser->stack, handler); + + if (handler->start) + handler->start (handler, attribute_names, attribute_values, parser); +} + +static void +svg_parser_end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + SvgParser *parser = user_data; + SvgHandler *handler; + SvgHandler *base; + GList *paths; + + handler = g_queue_pop_head (parser->stack); + + g_return_if_fail (handler != NULL && + (handler->name == NULL || + strcmp (handler->name, element_name) == 0)); + + if (handler->end) + handler->end (handler, parser); + + if (handler->paths) + { + if (handler->transform) + { + for (paths = handler->paths; paths; paths = paths->next) + { + SvgPath *path = paths->data; + GList *list; + + for (list = path->strokes; list; list = list->next) + gimp_stroke_transform (GIMP_STROKE (list->data), + handler->transform, NULL); + } + + g_slice_free (GimpMatrix3, handler->transform); + } + + base = g_queue_peek_head (parser->stack); + base->paths = g_list_concat (base->paths, handler->paths); + } + + g_slice_free (SvgHandler, handler); +} + +static void +svg_handler_svg_start (SvgHandler *handler, + const gchar **names, + const gchar **values, + SvgParser *parser) +{ + GimpMatrix3 *matrix; + GimpMatrix3 box; + const gchar *viewbox = NULL; + gdouble x = 0; + gdouble y = 0; + gdouble w = handler->width; + gdouble h = handler->height; + gdouble xres; + gdouble yres; + + matrix = g_slice_new (GimpMatrix3); + gimp_matrix3_identity (matrix); + + gimp_image_get_resolution (parser->image, &xres, &yres); + + while (*names) + { + switch (*names[0]) + { + case 'x': + if (strcmp (*names, "x") == 0) + parse_svg_length (*values, handler->width, xres, &x); + break; + + case 'y': + if (strcmp (*names, "y") == 0) + parse_svg_length (*values, handler->height, yres, &y); + break; + + case 'w': + if (strcmp (*names, "width") == 0) + parse_svg_length (*values, handler->width, xres, &w); + break; + + case 'h': + if (strcmp (*names, "height") == 0) + parse_svg_length (*values, handler->height, yres, &h); + break; + + case 'v': + if (strcmp (*names, "viewBox") == 0) + viewbox = *values; + break; + } + + names++; + values++; + } + + if (x || y) + { + /* according to the spec offsets are meaningless on the outermost svg */ + if (parser->svg_depth > 0) + gimp_matrix3_translate (matrix, x, y); + } + + if (viewbox && parse_svg_viewbox (viewbox, &w, &h, &box)) + { + gimp_matrix3_mult (&box, matrix); + } + + /* optionally scale the outermost svg to image size */ + if (parser->scale && parser->svg_depth == 0) + { + if (w > 0.0 && h > 0.0) + gimp_matrix3_scale (matrix, + gimp_image_get_width (parser->image) / w, + gimp_image_get_height (parser->image) / h); + } + + handler->width = w; + handler->height = h; + + handler->transform = matrix; + + parser->svg_depth++; +} + +static void +svg_handler_svg_end (SvgHandler *handler, + SvgParser *parser) +{ + parser->svg_depth--; +} + +static void +svg_handler_group_start (SvgHandler *handler, + const gchar **names, + const gchar **values, + SvgParser *parser) +{ + while (*names) + { + if (strcmp (*names, "transform") == 0 && ! handler->transform) + { + GimpMatrix3 matrix; + + if (parse_svg_transform (*values, &matrix)) + { + handler->transform = g_slice_dup (GimpMatrix3, &matrix); + +#ifdef DEBUG_VECTORS_IMPORT + g_printerr ("transform %s: %g %g %g %g %g %g %g %g %g\n", + handler->id ? handler->id : "(null)", + handler->transform->coeff[0][0], + handler->transform->coeff[0][1], + handler->transform->coeff[0][2], + handler->transform->coeff[1][0], + handler->transform->coeff[1][1], + handler->transform->coeff[1][2], + handler->transform->coeff[2][0], + handler->transform->coeff[2][1], + handler->transform->coeff[2][2]); +#endif + } + } + + names++; + values++; + } +} + +static void +svg_handler_path_start (SvgHandler *handler, + const gchar **names, + const gchar **values, + SvgParser *parser) +{ + SvgPath *path = g_slice_new0 (SvgPath); + + while (*names) + { + switch (*names[0]) + { + case 'i': + if (strcmp (*names, "id") == 0 && ! path->id) + path->id = g_strdup (*values); + break; + + case 'd': + if (strcmp (*names, "d") == 0 && ! path->strokes) + path->strokes = parse_path_data (*values); + break; + + case 't': + if (strcmp (*names, "transform") == 0 && ! handler->transform) + { + GimpMatrix3 matrix; + + if (parse_svg_transform (*values, &matrix)) + { + handler->transform = g_slice_dup (GimpMatrix3, &matrix); + } + } + break; + } + + names++; + values++; + } + + handler->paths = g_list_prepend (handler->paths, path); +} + +static void +svg_handler_rect_start (SvgHandler *handler, + const gchar **names, + const gchar **values, + SvgParser *parser) +{ + SvgPath *path = g_slice_new0 (SvgPath); + gdouble x = 0.0; + gdouble y = 0.0; + gdouble width = 0.0; + gdouble height = 0.0; + gdouble rx = 0.0; + gdouble ry = 0.0; + gdouble xres; + gdouble yres; + + gimp_image_get_resolution (parser->image, &xres, &yres); + + while (*names) + { + switch (*names[0]) + { + case 'i': + if (strcmp (*names, "id") == 0 && ! path->id) + path->id = g_strdup (*values); + break; + + case 'x': + if (strcmp (*names, "x") == 0) + parse_svg_length (*values, handler->width, xres, &x); + break; + + case 'y': + if (strcmp (*names, "y") == 0) + parse_svg_length (*values, handler->height, yres, &y); + break; + + case 'w': + if (strcmp (*names, "width") == 0) + parse_svg_length (*values, handler->width, xres, &width); + break; + + case 'h': + if (strcmp (*names, "height") == 0) + parse_svg_length (*values, handler->height, yres, &height); + break; + + case 'r': + if (strcmp (*names, "rx") == 0) + parse_svg_length (*values, handler->width, xres, &rx); + else if (strcmp (*names, "ry") == 0) + parse_svg_length (*values, handler->height, yres, &ry); + break; + + case 't': + if (strcmp (*names, "transform") == 0 && ! handler->transform) + { + GimpMatrix3 matrix; + + if (parse_svg_transform (*values, &matrix)) + { + handler->transform = g_slice_dup (GimpMatrix3, &matrix); + } + } + break; + } + + names++; + values++; + } + + if (width > 0.0 && height > 0.0 && rx >= 0.0 && ry >= 0.0) + { + GimpStroke *stroke; + GimpCoords point = COORDS_INIT; + + if (rx == 0.0) + rx = ry; + if (ry == 0.0) + ry = rx; + + rx = MIN (rx, width / 2); + ry = MIN (ry, height / 2); + + point.x = x + width - rx; + point.y = y; + stroke = gimp_bezier_stroke_new_moveto (&point); + + if (rx) + { + GimpCoords end = COORDS_INIT; + + end.x = x + width; + end.y = y + ry; + + gimp_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end); + } + + point.x = x + width; + point.y = y + height - ry; + gimp_bezier_stroke_lineto (stroke, &point); + + if (rx) + { + GimpCoords end = COORDS_INIT; + + end.x = x + width - rx; + end.y = y + height; + + gimp_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end); + } + + point.x = x + rx; + point.y = y + height; + gimp_bezier_stroke_lineto (stroke, &point); + + if (rx) + { + GimpCoords end = COORDS_INIT; + + end.x = x; + end.y = y + height - ry; + + gimp_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end); + } + + point.x = x; + point.y = y + ry; + gimp_bezier_stroke_lineto (stroke, &point); + + if (rx) + { + GimpCoords end = COORDS_INIT; + + end.x = x + rx; + end.y = y; + + gimp_bezier_stroke_arcto (stroke, rx, ry, 0, FALSE, TRUE, &end); + } + + /* the last line is handled by closing the stroke */ + gimp_stroke_close (stroke); + + path->strokes = g_list_prepend (path->strokes, stroke); + } + + handler->paths = g_list_prepend (handler->paths, path); +} + +static void +svg_handler_ellipse_start (SvgHandler *handler, + const gchar **names, + const gchar **values, + SvgParser *parser) +{ + SvgPath *path = g_slice_new0 (SvgPath); + GimpCoords center = COORDS_INIT; + gdouble rx = 0.0; + gdouble ry = 0.0; + gdouble xres; + gdouble yres; + + gimp_image_get_resolution (parser->image, &xres, &yres); + + while (*names) + { + switch (*names[0]) + { + case 'i': + if (strcmp (*names, "id") == 0 && ! path->id) + path->id = g_strdup (*values); + break; + + case 'c': + if (strcmp (*names, "cx") == 0) + parse_svg_length (*values, handler->width, xres, ¢er.x); + else if (strcmp (*names, "cy") == 0) + parse_svg_length (*values, handler->height, yres, ¢er.y); + break; + + case 'r': + if (strcmp (*names, "r") == 0) + { + parse_svg_length (*values, handler->width, xres, &rx); + parse_svg_length (*values, handler->height, yres, &ry); + } + else if (strcmp (*names, "rx") == 0) + { + parse_svg_length (*values, handler->width, xres, &rx); + } + else if (strcmp (*names, "ry") == 0) + { + parse_svg_length (*values, handler->height, yres, &ry); + } + break; + + case 't': + if (strcmp (*names, "transform") == 0 && ! handler->transform) + { + GimpMatrix3 matrix; + + if (parse_svg_transform (*values, &matrix)) + { + handler->transform = g_slice_dup (GimpMatrix3, &matrix); + } + } + break; + } + + names++; + values++; + } + + if (rx >= 0.0 && ry >= 0.0) + path->strokes = g_list_prepend (path->strokes, + gimp_bezier_stroke_new_ellipse (¢er, + rx, ry, + 0.0)); + + handler->paths = g_list_prepend (handler->paths, path); +} + +static void +svg_handler_line_start (SvgHandler *handler, + const gchar **names, + const gchar **values, + SvgParser *parser) +{ + SvgPath *path = g_slice_new0 (SvgPath); + GimpCoords start = COORDS_INIT; + GimpCoords end = COORDS_INIT; + GimpStroke *stroke; + gdouble xres; + gdouble yres; + + gimp_image_get_resolution (parser->image, &xres, &yres); + + while (*names) + { + switch (*names[0]) + { + case 'i': + if (strcmp (*names, "id") == 0 && ! path->id) + path->id = g_strdup (*values); + break; + + case 'x': + if (strcmp (*names, "x1") == 0) + parse_svg_length (*values, handler->width, xres, &start.x); + else if (strcmp (*names, "x2") == 0) + parse_svg_length (*values, handler->width, xres, &end.x); + break; + + case 'y': + if (strcmp (*names, "y1") == 0) + parse_svg_length (*values, handler->height, yres, &start.y); + else if (strcmp (*names, "y2") == 0) + parse_svg_length (*values, handler->height, yres, &end.y); + break; + + case 't': + if (strcmp (*names, "transform") == 0 && ! handler->transform) + { + GimpMatrix3 matrix; + + if (parse_svg_transform (*values, &matrix)) + { + handler->transform = g_slice_dup (GimpMatrix3, &matrix); + } + } + break; + } + + names++; + values++; + } + + stroke = gimp_bezier_stroke_new_moveto (&start); + gimp_bezier_stroke_lineto (stroke, &end); + + path->strokes = g_list_prepend (path->strokes, stroke); + + handler->paths = g_list_prepend (handler->paths, path); +} + +static void +svg_handler_poly_start (SvgHandler *handler, + const gchar **names, + const gchar **values, + SvgParser *parser) +{ + SvgPath *path = g_slice_new0 (SvgPath); + GString *points = NULL; + + while (*names) + { + switch (*names[0]) + { + case 'i': + if (strcmp (*names, "id") == 0 && ! path->id) + path->id = g_strdup (*values); + break; + + case 'p': + if (strcmp (*names, "points") == 0 && ! points) + { + const gchar *p = *values; + const gchar *m = NULL; + const gchar *l = NULL; + gint n = 0; + + while (*p) + { + while (g_ascii_isspace (*p) || *p == ',') + p++; + + switch (n) + { + case 0: + m = p; + break; + case 2: + l = p; + break; + } + + if (*p) + n++; + + while (*p && ! g_ascii_isspace (*p) && *p != ',') + p++; + } + + if ((n > 3) && (n % 2 == 0)) + { + points = g_string_sized_new (p - *values + 8); + + g_string_append_len (points, "M ", 2); + g_string_append_len (points, m, l - m); + + g_string_append_len (points, "L ", 2); + g_string_append_len (points, l, p - l); + + if (strcmp (handler->name, "polygon") == 0) + g_string_append_c (points, 'Z'); + } + } + break; + + case 't': + if (strcmp (*names, "transform") == 0 && ! handler->transform) + { + GimpMatrix3 matrix; + + if (parse_svg_transform (*values, &matrix)) + { + handler->transform = g_slice_dup (GimpMatrix3, &matrix); + } + } + break; + } + + names++; + values++; + } + + if (points) + { + path->strokes = parse_path_data (points->str); + g_string_free (points, TRUE); + } + + handler->paths = g_list_prepend (handler->paths, path); +} + +static gboolean +parse_svg_length (const gchar *value, + gdouble reference, + gdouble resolution, + gdouble *length) +{ + GimpUnit unit = GIMP_UNIT_PIXEL; + gdouble len; + gchar *ptr; + + len = g_ascii_strtod (value, &ptr); + + while (g_ascii_isspace (*ptr)) + ptr++; + + switch (ptr[0]) + { + case '\0': + break; + + case 'p': + switch (ptr[1]) + { + case 'x': break; + case 't': unit = GIMP_UNIT_POINT; break; + case 'c': unit = GIMP_UNIT_PICA; break; + default: + return FALSE; + } + ptr += 2; + break; + + case 'c': + if (ptr[1] == 'm') + len *= 10.0, unit = GIMP_UNIT_MM; + else + return FALSE; + ptr += 2; + break; + + case 'm': + if (ptr[1] == 'm') + unit = GIMP_UNIT_MM; + else + return FALSE; + ptr += 2; + break; + + case 'i': + if (ptr[1] == 'n') + unit = GIMP_UNIT_INCH; + else + return FALSE; + ptr += 2; + break; + + case '%': + unit = GIMP_UNIT_PERCENT; + ptr += 1; + break; + + default: + return FALSE; + } + + while (g_ascii_isspace (*ptr)) + ptr++; + + if (*ptr) + return FALSE; + + switch (unit) + { + case GIMP_UNIT_PERCENT: + *length = len * reference / 100.0; + break; + + case GIMP_UNIT_PIXEL: + *length = len; + break; + + default: + *length = len * resolution / gimp_unit_get_factor (unit); + break; + } + + return TRUE; +} + +static gboolean +parse_svg_viewbox (const gchar *value, + gdouble *width, + gdouble *height, + GimpMatrix3 *matrix) +{ + gdouble x, y, w, h; + gchar *tok; + gchar *str = g_strdup (value); + gboolean success = FALSE; + + x = y = w = h = 0; + + tok = strtok (str, ", \t"); + if (tok) + { + x = g_ascii_strtod (tok, NULL); + tok = strtok (NULL, ", \t"); + if (tok) + { + y = g_ascii_strtod (tok, NULL); + tok = strtok (NULL, ", \t"); + if (tok != NULL) + { + w = g_ascii_strtod (tok, NULL); + tok = strtok (NULL, ", \t"); + if (tok) + { + h = g_ascii_strtod (tok, NULL); + success = TRUE; + } + } + } + } + + g_free (str); + + if (success) + { + gimp_matrix3_identity (matrix); + gimp_matrix3_translate (matrix, -x, -y); + + if (w > 0.0 && h > 0.0) + { + gimp_matrix3_scale (matrix, *width / w, *height / h); + } + else /* disable rendering of the element */ + { +#ifdef DEBUG_VECTORS_IMPORT + g_printerr ("empty viewBox"); +#endif + *width = *height = 0.0; + } + } + else + { + g_printerr ("SVG import: cannot parse viewBox attribute\n"); + } + + return success; +} + +static gboolean +parse_svg_transform (const gchar *value, + GimpMatrix3 *matrix) +{ + gint i; + + gimp_matrix3_identity (matrix); + + for (i = 0; value[i]; i++) + { + GimpMatrix3 trafo; + gchar keyword[32]; + gdouble args[6]; + gint n_args; + gint key_len; + + gimp_matrix3_identity (&trafo); + + /* skip initial whitespace */ + while (g_ascii_isspace (value[i])) + i++; + + /* parse keyword */ + for (key_len = 0; key_len < sizeof (keyword); key_len++) + { + gchar c = value[i]; + + if (g_ascii_isalpha (c) || c == '-') + keyword[key_len] = value[i++]; + else + break; + } + + if (key_len >= sizeof (keyword)) + return FALSE; + + keyword[key_len] = '\0'; + + /* skip whitespace */ + while (g_ascii_isspace (value[i])) + i++; + + if (value[i] != '(') + return FALSE; + i++; + + for (n_args = 0; ; n_args++) + { + gchar c; + gchar *end_ptr; + + /* skip whitespace */ + while (g_ascii_isspace (value[i])) + i++; + + c = value[i]; + if (g_ascii_isdigit (c) || c == '+' || c == '-' || c == '.') + { + if (n_args == G_N_ELEMENTS (args)) + return FALSE; /* too many args */ + + args[n_args] = g_ascii_strtod (value + i, &end_ptr); + i = end_ptr - value; + + while (g_ascii_isspace (value[i])) + i++; + + /* skip optional comma */ + if (value[i] == ',') + i++; + } + else if (c == ')') + break; + else + return FALSE; + } + + /* OK, have parsed keyword and args, now calculate the transform matrix */ + + if (strcmp (keyword, "matrix") == 0) + { + if (n_args != 6) + return FALSE; + + gimp_matrix3_affine (&trafo, + args[0], args[1], + args[2], args[3], + args[4], args[5]); + } + else if (strcmp (keyword, "translate") == 0) + { + if (n_args == 1) + args[1] = 0.0; + else if (n_args != 2) + return FALSE; + + gimp_matrix3_translate (&trafo, args[0], args[1]); + } + else if (strcmp (keyword, "scale") == 0) + { + if (n_args == 1) + args[1] = args[0]; + else if (n_args != 2) + return FALSE; + + gimp_matrix3_scale (&trafo, args[0], args[1]); + } + else if (strcmp (keyword, "rotate") == 0) + { + if (n_args == 1) + { + gimp_matrix3_rotate (&trafo, gimp_deg_to_rad (args[0])); + } + else if (n_args == 3) + { + gimp_matrix3_translate (&trafo, -args[1], -args[2]); + gimp_matrix3_rotate (&trafo, gimp_deg_to_rad (args[0])); + gimp_matrix3_translate (&trafo, args[1], args[2]); + } + else + return FALSE; + } + else if (strcmp (keyword, "skewX") == 0) + { + if (n_args != 1) + return FALSE; + + gimp_matrix3_xshear (&trafo, tan (gimp_deg_to_rad (args[0]))); + } + else if (strcmp (keyword, "skewY") == 0) + { + if (n_args != 1) + return FALSE; + + gimp_matrix3_yshear (&trafo, tan (gimp_deg_to_rad (args[0]))); + } + else + { + return FALSE; /* unknown keyword */ + } + + gimp_matrix3_invert (&trafo); + gimp_matrix3_mult (&trafo, matrix); + } + + gimp_matrix3_invert (matrix); + + return TRUE; +} + + +/**********************************************************/ +/* Below is the code that parses the actual path data. */ +/* */ +/* This code is taken from librsvg and was originally */ +/* written by Raph Levien <raph@artofcode.com> for Gill. */ +/**********************************************************/ + +typedef struct +{ + GList *strokes; + GimpStroke *stroke; + gdouble cpx, cpy; /* current point */ + gdouble rpx, rpy; /* reflection point (for 's' and 't' commands) */ + gchar cmd; /* current command (lowercase) */ + gint param; /* number of parameters */ + gboolean rel; /* true if relative coords */ + gdouble params[7]; /* parameters that have been parsed */ +} ParsePathContext; + + +static void parse_path_default_xy (ParsePathContext *ctx, + gint n_params); +static void parse_path_do_cmd (ParsePathContext *ctx, + gboolean final); + + +static GList * +parse_path_data (const gchar *data) +{ + ParsePathContext ctx; + + gboolean in_num = FALSE; + gboolean in_frac = FALSE; + gboolean in_exp = FALSE; + gboolean exp_wait_sign = FALSE; + gdouble val = 0.0; + gchar c = 0; + gint sign = 0; + gint exp = 0; + gint exp_sign = 0; + gdouble frac = 0.0; + gint i; + + memset (&ctx, 0, sizeof (ParsePathContext)); + + for (i = 0; ; i++) + { + c = data[i]; + + if (c >= '0' && c <= '9') + { + /* digit */ + if (in_num) + { + if (in_exp) + { + exp = (exp * 10) + c - '0'; + exp_wait_sign = FALSE; + } + else if (in_frac) + val += (frac *= 0.1) * (c - '0'); + else + val = (val * 10) + c - '0'; + } + else + { + in_num = TRUE; + in_frac = FALSE; + in_exp = FALSE; + exp = 0; + exp_sign = 1; + exp_wait_sign = FALSE; + val = c - '0'; + sign = 1; + } + } + else if (c == '.') + { + if (! in_num) + { + in_num = TRUE; + val = 0; + } + + in_frac = TRUE; + frac = 1; + } + else if ((c == 'E' || c == 'e') && in_num) + { + in_exp = TRUE; + exp_wait_sign = TRUE; + exp = 0; + exp_sign = 1; + } + else if ((c == '+' || c == '-') && in_exp) + { + exp_sign = c == '+' ? 1 : -1; + } + else if (in_num) + { + /* end of number */ + + val *= sign * pow (10, exp_sign * exp); + + if (ctx.rel) + { + /* Handle relative coordinates. This switch statement attempts + to determine _what_ the coords are relative to. This is + underspecified in the 12 Apr working draft. */ + switch (ctx.cmd) + { + case 'l': + case 'm': + case 'c': + case 's': + case 'q': + case 't': + /* rule: even-numbered params are x-relative, odd-numbered + are y-relative */ + if ((ctx.param & 1) == 0) + val += ctx.cpx; + else if ((ctx.param & 1) == 1) + val += ctx.cpy; + break; + + case 'a': + /* rule: sixth and seventh are x and y, rest are not + relative */ + if (ctx.param == 5) + val += ctx.cpx; + else if (ctx.param == 6) + val += ctx.cpy; + break; + + case 'h': + /* rule: x-relative */ + val += ctx.cpx; + break; + + case 'v': + /* rule: y-relative */ + val += ctx.cpy; + break; + } + } + + ctx.params[ctx.param++] = val; + parse_path_do_cmd (&ctx, FALSE); + in_num = FALSE; + } + + if (c == '\0') + { + break; + } + else if ((c == '+' || c == '-') && ! exp_wait_sign) + { + sign = c == '+' ? 1 : -1; + val = 0; + in_num = TRUE; + in_frac = FALSE; + in_exp = FALSE; + exp = 0; + exp_sign = 1; + exp_wait_sign = FALSE; + } + else if (c == 'z' || c == 'Z') + { + if (ctx.param) + parse_path_do_cmd (&ctx, TRUE); + + if (ctx.stroke) + gimp_stroke_close (ctx.stroke); + } + else if (c >= 'A' && c <= 'Z' && c != 'E') + { + if (ctx.param) + parse_path_do_cmd (&ctx, TRUE); + + ctx.cmd = c + 'a' - 'A'; + ctx.rel = FALSE; + } + else if (c >= 'a' && c <= 'z' && c != 'e') + { + if (ctx.param) + parse_path_do_cmd (&ctx, TRUE); + + ctx.cmd = c; + ctx.rel = TRUE; + } + /* else c _should_ be whitespace or , */ + } + + return g_list_reverse (ctx.strokes); +} + +/* supply defaults for missing parameters, assuming relative coordinates + are to be interpreted as x,y */ +static void +parse_path_default_xy (ParsePathContext *ctx, + gint n_params) +{ + gint i; + + if (ctx->rel) + { + for (i = ctx->param; i < n_params; i++) + { + if (i > 2) + ctx->params[i] = ctx->params[i - 2]; + else if (i == 1) + ctx->params[i] = ctx->cpy; + else if (i == 0) + /* we shouldn't get here (ctx->param > 0 as precondition) */ + ctx->params[i] = ctx->cpx; + } + } + else + { + for (i = ctx->param; i < n_params; i++) + ctx->params[i] = 0.0; + } +} + +static void +parse_path_do_cmd (ParsePathContext *ctx, + gboolean final) +{ + GimpCoords coords = COORDS_INIT; + + switch (ctx->cmd) + { + case 'm': + /* moveto */ + if (ctx->param == 2 || final) + { + parse_path_default_xy (ctx, 2); + + coords.x = ctx->cpx = ctx->rpx = ctx->params[0]; + coords.y = ctx->cpy = ctx->rpy = ctx->params[1]; + + ctx->stroke = gimp_bezier_stroke_new_moveto (&coords); + ctx->strokes = g_list_prepend (ctx->strokes, ctx->stroke); + + ctx->param = 0; + + /* If a moveto is followed by multiple pairs of coordinates, + * the subsequent pairs are treated as implicit lineto commands. + */ + ctx->cmd = 'l'; + } + break; + + case 'l': + /* lineto */ + if (ctx->param == 2 || final) + { + parse_path_default_xy (ctx, 2); + + coords.x = ctx->cpx = ctx->rpx = ctx->params[0]; + coords.y = ctx->cpy = ctx->rpy = ctx->params[1]; + + gimp_bezier_stroke_lineto (ctx->stroke, &coords); + + ctx->param = 0; + } + break; + + case 'c': + /* curveto */ + if (ctx->param == 6 || final) + { + GimpCoords ctrl1 = COORDS_INIT; + GimpCoords ctrl2 = COORDS_INIT; + + parse_path_default_xy (ctx, 6); + + ctrl1.x = ctx->params[0]; + ctrl1.y = ctx->params[1]; + ctrl2.x = ctx->rpx = ctx->params[2]; + ctrl2.y = ctx->rpy = ctx->params[3]; + coords.x = ctx->cpx = ctx->params[4]; + coords.y = ctx->cpy = ctx->params[5]; + + gimp_bezier_stroke_cubicto (ctx->stroke, &ctrl1, &ctrl2, &coords); + + ctx->param = 0; + } + break; + + case 's': + /* smooth curveto */ + if (ctx->param == 4 || final) + { + GimpCoords ctrl1 = COORDS_INIT; + GimpCoords ctrl2 = COORDS_INIT; + + parse_path_default_xy (ctx, 4); + + ctrl1.x = 2 * ctx->cpx - ctx->rpx; + ctrl1.y = 2 * ctx->cpy - ctx->rpy; + ctrl2.x = ctx->rpx = ctx->params[0]; + ctrl2.y = ctx->rpy = ctx->params[1]; + coords.x = ctx->cpx = ctx->params[2]; + coords.y = ctx->cpy = ctx->params[3]; + + gimp_bezier_stroke_cubicto (ctx->stroke, &ctrl1, &ctrl2, &coords); + + ctx->param = 0; + } + break; + + case 'h': + /* horizontal lineto */ + if (ctx->param == 1) + { + coords.x = ctx->cpx = ctx->rpx = ctx->params[0]; + coords.y = ctx->cpy; + + gimp_bezier_stroke_lineto (ctx->stroke, &coords); + + ctx->param = 0; + } + break; + + case 'v': + /* vertical lineto */ + if (ctx->param == 1) + { + coords.x = ctx->cpx; + coords.y = ctx->cpy = ctx->rpy = ctx->params[0]; + + gimp_bezier_stroke_lineto (ctx->stroke, &coords); + + ctx->param = 0; + } + break; + + case 'q': + /* quadratic bezier curveto */ + if (ctx->param == 4 || final) + { + GimpCoords ctrl = COORDS_INIT; + + parse_path_default_xy (ctx, 4); + + ctrl.x = ctx->rpx = ctx->params[0]; + ctrl.y = ctx->rpy = ctx->params[1]; + coords.x = ctx->cpx = ctx->params[2]; + coords.y = ctx->cpy = ctx->params[3]; + + gimp_bezier_stroke_conicto (ctx->stroke, &ctrl, &coords); + + ctx->param = 0; + } + break; + + case 't': + /* truetype quadratic bezier curveto */ + if (ctx->param == 2 || final) + { + GimpCoords ctrl = COORDS_INIT; + + parse_path_default_xy (ctx, 2); + + ctrl.x = ctx->rpx = 2 * ctx->cpx - ctx->rpx; + ctrl.y = ctx->rpy = 2 * ctx->cpy - ctx->rpy; + coords.x = ctx->cpx = ctx->params[0]; + coords.y = ctx->cpy = ctx->params[1]; + + gimp_bezier_stroke_conicto (ctx->stroke, &ctrl, &coords); + + ctx->param = 0; + } + else if (final) + { + if (ctx->param > 2) + { + GimpCoords ctrl = COORDS_INIT; + + parse_path_default_xy (ctx, 4); + + ctrl.x = ctx->rpx = ctx->params[0]; + ctrl.y = ctx->rpy = ctx->params[1]; + coords.x = ctx->cpx = ctx->params[2]; + coords.y = ctx->cpy = ctx->params[3]; + + gimp_bezier_stroke_conicto (ctx->stroke, &ctrl, &coords); + } + else + { + parse_path_default_xy (ctx, 2); + + coords.x = ctx->cpx = ctx->rpx = ctx->params[0]; + coords.y = ctx->cpy = ctx->rpy = ctx->params[1]; + + gimp_bezier_stroke_lineto (ctx->stroke, &coords); + } + + ctx->param = 0; + } + break; + + case 'a': + if (ctx->param == 7 || final) + { + coords.x = ctx->cpx = ctx->rpx = ctx->params[5]; + coords.y = ctx->cpy = ctx->rpy = ctx->params[6]; + + gimp_bezier_stroke_arcto (ctx->stroke, + ctx->params[0], ctx->params[1], + gimp_deg_to_rad (ctx->params[2]), + ctx->params[3], ctx->params[4], + &coords); + ctx->param = 0; + } + break; + + default: + ctx->param = 0; + break; + } +} diff --git a/app/vectors/gimpvectors-import.h b/app/vectors/gimpvectors-import.h new file mode 100644 index 0000000..1d7f8a6 --- /dev/null +++ b/app/vectors/gimpvectors-import.h @@ -0,0 +1,44 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GimpVectors Import + * Copyright (C) 2003 Sven Neumann <sven@gimp.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_VECTORS_IMPORT_H__ +#define __GIMP_VECTORS_IMPORT_H__ + + +gboolean gimp_vectors_import_file (GimpImage *image, + GFile *file, + gboolean merge, + gboolean scale, + GimpVectors *parent, + gint position, + GList **ret_vectors, + GError **error); +gboolean gimp_vectors_import_buffer (GimpImage *image, + const gchar *buffer, + gsize len, + gboolean merge, + gboolean scale, + GimpVectors *parent, + gint position, + GList **ret_vectors, + GError **error); + + +#endif /* __GIMP_VECTORS_IMPORT_H__ */ diff --git a/app/vectors/gimpvectors-preview.c b/app/vectors/gimpvectors-preview.c new file mode 100644 index 0000000..988148a --- /dev/null +++ b/app/vectors/gimpvectors-preview.c @@ -0,0 +1,93 @@ +/* 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 "vectors-types.h" + +#include "libgimpmath/gimpmath.h" + +#include "core/gimpimage.h" +#include "core/gimptempbuf.h" + +#include "gimpstroke.h" +#include "gimpvectors.h" +#include "gimpvectors-preview.h" + + +/* public functions */ + +GimpTempBuf * +gimp_vectors_get_new_preview (GimpViewable *viewable, + GimpContext *context, + gint width, + gint height) +{ + GimpVectors *vectors; + GimpItem *item; + GimpStroke *cur_stroke; + gdouble xscale, yscale; + guchar *data; + GimpTempBuf *temp_buf; + + vectors = GIMP_VECTORS (viewable); + item = GIMP_ITEM (viewable); + + xscale = ((gdouble) width) / gimp_image_get_width (gimp_item_get_image (item)); + yscale = ((gdouble) height) / gimp_image_get_height (gimp_item_get_image (item)); + + temp_buf = gimp_temp_buf_new (width, height, babl_format ("Y' u8")); + data = gimp_temp_buf_get_data (temp_buf); + memset (data, 255, width * height); + + for (cur_stroke = gimp_vectors_stroke_get_next (vectors, NULL); + cur_stroke; + cur_stroke = gimp_vectors_stroke_get_next (vectors, cur_stroke)) + { + GArray *coords; + gboolean closed; + gint i; + + coords = gimp_stroke_interpolate (cur_stroke, 0.5, &closed); + + if (coords) + { + for (i = 0; i < coords->len; i++) + { + GimpCoords point; + gint x, y; + + point = g_array_index (coords, GimpCoords, i); + + x = ROUND (point.x * xscale); + y = ROUND (point.y * yscale); + + if (x >= 0 && y >= 0 && x < width && y < height) + data[y * width + x] = 0; + } + + g_array_free (coords, TRUE); + } + } + + return temp_buf; +} diff --git a/app/vectors/gimpvectors-preview.h b/app/vectors/gimpvectors-preview.h new file mode 100644 index 0000000..f934d8e --- /dev/null +++ b/app/vectors/gimpvectors-preview.h @@ -0,0 +1,32 @@ +/* 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_VECTORS_PREVIEW_H__ +#define __GIMP_VECTORS_PREVIEW_H__ + + +/* + * virtual function of GimpVectors -- don't call directly + */ + +GimpTempBuf * gimp_vectors_get_new_preview (GimpViewable *viewable, + GimpContext *context, + gint width, + gint height); + + +#endif /* __GIMP_VECTORS_PREVIEW_H__ */ diff --git a/app/vectors/gimpvectors-warp.c b/app/vectors/gimpvectors-warp.c new file mode 100644 index 0000000..aeb8dec --- /dev/null +++ b/app/vectors/gimpvectors-warp.c @@ -0,0 +1,210 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpvectors-warp.c + * Copyright (C) 2005 Bill Skaggs <weskaggs@primate.ucdavis.edu> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 "vectors-types.h" + +#include "libgimpmath/gimpmath.h" + +#include "core/gimp-utils.h" +#include "core/gimpcoords.h" + +#include "gimpanchor.h" +#include "gimpstroke.h" +#include "gimpvectors.h" +#include "gimpvectors-warp.h" + + +#define EPSILON 0.2 +#define DX 2.0 + + +static void gimp_stroke_warp_point (GimpStroke *stroke, + gdouble x, + gdouble y, + GimpCoords *point_warped, + gdouble y_offset, + gdouble x_len); + +static void gimp_vectors_warp_stroke (GimpVectors *vectors, + GimpStroke *stroke, + gdouble y_offset); + + +void +gimp_vectors_warp_point (GimpVectors *vectors, + GimpCoords *point, + GimpCoords *point_warped, + gdouble y_offset) +{ + gdouble x = point->x; + gdouble y = point->y; + gdouble len; + GList *list; + GimpStroke *stroke; + + for (list = vectors->strokes->head; + list; + list = g_list_next (list)) + { + stroke = list->data; + + len = gimp_vectors_stroke_get_length (vectors, stroke); + + if (x < len || ! list->next) + break; + + x -= len; + } + + if (! list) + { + point_warped->x = 0; + point_warped->y = 0; + return; + } + + gimp_stroke_warp_point (stroke, x, y, point_warped, y_offset, len); +} + +static void +gimp_stroke_warp_point (GimpStroke *stroke, + gdouble x, + gdouble y, + GimpCoords *point_warped, + gdouble y_offset, + gdouble x_len) +{ + GimpCoords point_zero = { 0, }; + GimpCoords point_minus = { 0, }; + GimpCoords point_plus = { 0, }; + gdouble slope; + gdouble dx, dy, nx, ny, len; + + if (x + DX >= x_len) + { + gdouble tx, ty; + + if (! gimp_stroke_get_point_at_dist (stroke, x_len, EPSILON, + &point_zero, &slope)) + { + point_warped->x = 0; + point_warped->y = 0; + return; + } + + point_warped->x = point_zero.x; + point_warped->y = point_zero.y; + + if (! gimp_stroke_get_point_at_dist (stroke, x_len - DX, EPSILON, + &point_minus, &slope)) + return; + + dx = point_zero.x - point_minus.x; + dy = point_zero.y - point_minus.y; + + len = hypot (dx, dy); + + if (len < 0.01) + return; + + tx = dx / len; + ty = dy / len; + + nx = - dy / len; + ny = dx / len; + + point_warped->x += tx * (x - x_len) + nx * (y - y_offset); + point_warped->y += ty * (x - x_len) + ny * (y - y_offset); + + return; + } + + if (! gimp_stroke_get_point_at_dist (stroke, x, EPSILON, + &point_zero, &slope)) + { + point_warped->x = 0; + point_warped->y = 0; + return; + } + + point_warped->x = point_zero.x; + point_warped->y = point_zero.y; + + if (! gimp_stroke_get_point_at_dist (stroke, x - DX, EPSILON, + &point_minus, &slope)) + return; + + if (! gimp_stroke_get_point_at_dist (stroke, x + DX, EPSILON, + &point_plus, &slope)) + return; + + dx = point_plus.x - point_minus.x; + dy = point_plus.y - point_minus.y; + + len = hypot (dx, dy); + + if (len < 0.01) + return; + + nx = - dy / len; + ny = dx / len; + + point_warped->x = point_zero.x + nx * (y - y_offset); + point_warped->y = point_zero.y + ny * (y - y_offset); +} + +static void +gimp_vectors_warp_stroke (GimpVectors *vectors, + GimpStroke *stroke, + gdouble y_offset) +{ + GList *list; + + for (list = stroke->anchors->head; list; list = g_list_next (list)) + { + GimpAnchor *anchor = list->data; + + gimp_vectors_warp_point (vectors, + &anchor->position, &anchor->position, + y_offset); + } +} + +void +gimp_vectors_warp_vectors (GimpVectors *vectors, + GimpVectors *vectors_in, + gdouble y_offset) +{ + GList *list; + + for (list = vectors_in->strokes->head; + list; + list = g_list_next (list)) + { + GimpStroke *stroke = list->data; + + gimp_vectors_warp_stroke (vectors, stroke, y_offset); + } +} diff --git a/app/vectors/gimpvectors-warp.h b/app/vectors/gimpvectors-warp.h new file mode 100644 index 0000000..12b8993 --- /dev/null +++ b/app/vectors/gimpvectors-warp.h @@ -0,0 +1,36 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpvectors-warp.h + * Copyright (C) 2005 Bill Skaggs <weskaggs@primate.ucdavis.edu> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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_VECTORS_WARP_H__ +#define __GIMP_VECTORS_WARP_H__ + + +void gimp_vectors_warp_point (GimpVectors *vectors, + GimpCoords *point, + GimpCoords *point_warped, + gdouble y_offset); + +void gimp_vectors_warp_vectors (GimpVectors *vectors, + GimpVectors *vectors_in, + gdouble yoffset); + + +#endif /* __GIMP_VECTORS_WARP_H__ */ + diff --git a/app/vectors/gimpvectors.c b/app/vectors/gimpvectors.c new file mode 100644 index 0000000..1a65eac --- /dev/null +++ b/app/vectors/gimpvectors.c @@ -0,0 +1,1255 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpvectors.c + * Copyright (C) 2002 Simon Budig <simon@gimp.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 <cairo.h> +#include <gegl.h> +#include <gdk-pixbuf/gdk-pixbuf.h> + +#include "libgimpcolor/gimpcolor.h" +#include "libgimpmath/gimpmath.h" + +#include "vectors-types.h" + +#include "core/gimp.h" +#include "core/gimp-transform-utils.h" +#include "core/gimpbezierdesc.h" +#include "core/gimpchannel-select.h" +#include "core/gimpcontainer.h" +#include "core/gimpcontext.h" +#include "core/gimpdrawable-fill.h" +#include "core/gimpdrawable-stroke.h" +#include "core/gimperror.h" +#include "core/gimpimage.h" +#include "core/gimpimage-undo-push.h" +#include "core/gimpmarshal.h" +#include "core/gimppaintinfo.h" +#include "core/gimpstrokeoptions.h" + +#include "paint/gimppaintcore-stroke.h" +#include "paint/gimppaintoptions.h" + +#include "gimpanchor.h" +#include "gimpstroke.h" +#include "gimpvectors.h" +#include "gimpvectors-preview.h" + +#include "gimp-intl.h" + + +enum +{ + FREEZE, + THAW, + LAST_SIGNAL +}; + + +static void gimp_vectors_finalize (GObject *object); + +static gint64 gimp_vectors_get_memsize (GimpObject *object, + gint64 *gui_size); + +static gboolean gimp_vectors_is_attached (GimpItem *item); +static GimpItemTree * gimp_vectors_get_tree (GimpItem *item); +static gboolean gimp_vectors_bounds (GimpItem *item, + gdouble *x, + gdouble *y, + gdouble *width, + gdouble *height); +static GimpItem * gimp_vectors_duplicate (GimpItem *item, + GType new_type); +static void gimp_vectors_convert (GimpItem *item, + GimpImage *dest_image, + GType old_type); +static void gimp_vectors_translate (GimpItem *item, + gdouble offset_x, + gdouble offset_y, + gboolean push_undo); +static void gimp_vectors_scale (GimpItem *item, + gint new_width, + gint new_height, + gint new_offset_x, + gint new_offset_y, + GimpInterpolationType interp_type, + GimpProgress *progress); +static void gimp_vectors_resize (GimpItem *item, + GimpContext *context, + GimpFillType fill_type, + gint new_width, + gint new_height, + gint offset_x, + gint offset_y); +static void gimp_vectors_flip (GimpItem *item, + GimpContext *context, + GimpOrientationType flip_type, + gdouble axis, + gboolean clip_result); +static void gimp_vectors_rotate (GimpItem *item, + GimpContext *context, + GimpRotationType rotate_type, + gdouble center_x, + gdouble center_y, + gboolean clip_result); +static void gimp_vectors_transform (GimpItem *item, + GimpContext *context, + const GimpMatrix3 *matrix, + GimpTransformDirection direction, + GimpInterpolationType interp_type, + GimpTransformResize clip_result, + GimpProgress *progress); +static GimpTransformResize + gimp_vectors_get_clip (GimpItem *item, + GimpTransformResize clip_result); +static gboolean gimp_vectors_fill (GimpItem *item, + GimpDrawable *drawable, + GimpFillOptions *fill_options, + gboolean push_undo, + GimpProgress *progress, + GError **error); +static gboolean gimp_vectors_stroke (GimpItem *item, + GimpDrawable *drawable, + GimpStrokeOptions *stroke_options, + gboolean push_undo, + GimpProgress *progress, + GError **error); +static void gimp_vectors_to_selection (GimpItem *item, + GimpChannelOps op, + gboolean antialias, + gboolean feather, + gdouble feather_radius_x, + gdouble feather_radius_y); + +static void gimp_vectors_real_freeze (GimpVectors *vectors); +static void gimp_vectors_real_thaw (GimpVectors *vectors); +static void gimp_vectors_real_stroke_add (GimpVectors *vectors, + GimpStroke *stroke); +static void gimp_vectors_real_stroke_remove (GimpVectors *vectors, + GimpStroke *stroke); +static GimpStroke * gimp_vectors_real_stroke_get (GimpVectors *vectors, + const GimpCoords *coord); +static GimpStroke *gimp_vectors_real_stroke_get_next(GimpVectors *vectors, + GimpStroke *prev); +static gdouble gimp_vectors_real_stroke_get_length (GimpVectors *vectors, + GimpStroke *prev); +static GimpAnchor * gimp_vectors_real_anchor_get (GimpVectors *vectors, + const GimpCoords *coord, + GimpStroke **ret_stroke); +static void gimp_vectors_real_anchor_delete (GimpVectors *vectors, + GimpAnchor *anchor); +static gdouble gimp_vectors_real_get_length (GimpVectors *vectors, + const GimpAnchor *start); +static gdouble gimp_vectors_real_get_distance (GimpVectors *vectors, + const GimpCoords *coord); +static gint gimp_vectors_real_interpolate (GimpVectors *vectors, + GimpStroke *stroke, + gdouble precision, + gint max_points, + GimpCoords *ret_coords); + +static GimpBezierDesc * gimp_vectors_make_bezier (GimpVectors *vectors); +static GimpBezierDesc * gimp_vectors_real_make_bezier (GimpVectors *vectors); + + +G_DEFINE_TYPE (GimpVectors, gimp_vectors, GIMP_TYPE_ITEM) + +#define parent_class gimp_vectors_parent_class + +static guint gimp_vectors_signals[LAST_SIGNAL] = { 0 }; + + +static void +gimp_vectors_class_init (GimpVectorsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass); + GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass); + GimpItemClass *item_class = GIMP_ITEM_CLASS (klass); + + gimp_vectors_signals[FREEZE] = + g_signal_new ("freeze", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GimpVectorsClass, freeze), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + gimp_vectors_signals[THAW] = + g_signal_new ("thaw", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpVectorsClass, thaw), + NULL, NULL, + gimp_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class->finalize = gimp_vectors_finalize; + + gimp_object_class->get_memsize = gimp_vectors_get_memsize; + + viewable_class->get_new_preview = gimp_vectors_get_new_preview; + viewable_class->default_icon_name = "gimp-path"; + + item_class->is_attached = gimp_vectors_is_attached; + item_class->get_tree = gimp_vectors_get_tree; + item_class->bounds = gimp_vectors_bounds; + item_class->duplicate = gimp_vectors_duplicate; + item_class->convert = gimp_vectors_convert; + item_class->translate = gimp_vectors_translate; + item_class->scale = gimp_vectors_scale; + item_class->resize = gimp_vectors_resize; + item_class->flip = gimp_vectors_flip; + item_class->rotate = gimp_vectors_rotate; + item_class->transform = gimp_vectors_transform; + item_class->get_clip = gimp_vectors_get_clip; + item_class->fill = gimp_vectors_fill; + item_class->stroke = gimp_vectors_stroke; + item_class->to_selection = gimp_vectors_to_selection; + item_class->default_name = _("Path"); + item_class->rename_desc = C_("undo-type", "Rename Path"); + item_class->translate_desc = C_("undo-type", "Move Path"); + item_class->scale_desc = C_("undo-type", "Scale Path"); + item_class->resize_desc = C_("undo-type", "Resize Path"); + item_class->flip_desc = C_("undo-type", "Flip Path"); + item_class->rotate_desc = C_("undo-type", "Rotate Path"); + item_class->transform_desc = C_("undo-type", "Transform Path"); + item_class->fill_desc = C_("undo-type", "Fill Path"); + item_class->stroke_desc = C_("undo-type", "Stroke Path"); + item_class->to_selection_desc = C_("undo-type", "Path to Selection"); + item_class->reorder_desc = C_("undo-type", "Reorder Path"); + item_class->raise_desc = C_("undo-type", "Raise Path"); + item_class->raise_to_top_desc = C_("undo-type", "Raise Path to Top"); + item_class->lower_desc = C_("undo-type", "Lower Path"); + item_class->lower_to_bottom_desc = C_("undo-type", "Lower Path to Bottom"); + item_class->raise_failed = _("Path cannot be raised higher."); + item_class->lower_failed = _("Path cannot be lowered more."); + + klass->freeze = gimp_vectors_real_freeze; + klass->thaw = gimp_vectors_real_thaw; + + klass->stroke_add = gimp_vectors_real_stroke_add; + klass->stroke_remove = gimp_vectors_real_stroke_remove; + klass->stroke_get = gimp_vectors_real_stroke_get; + klass->stroke_get_next = gimp_vectors_real_stroke_get_next; + klass->stroke_get_length = gimp_vectors_real_stroke_get_length; + + klass->anchor_get = gimp_vectors_real_anchor_get; + klass->anchor_delete = gimp_vectors_real_anchor_delete; + + klass->get_length = gimp_vectors_real_get_length; + klass->get_distance = gimp_vectors_real_get_distance; + klass->interpolate = gimp_vectors_real_interpolate; + + klass->make_bezier = gimp_vectors_real_make_bezier; +} + +static void +gimp_vectors_init (GimpVectors *vectors) +{ + gimp_item_set_visible (GIMP_ITEM (vectors), FALSE, FALSE); + + vectors->strokes = g_queue_new (); + vectors->stroke_to_list = g_hash_table_new (g_direct_hash, g_direct_equal); + vectors->last_stroke_ID = 0; + vectors->freeze_count = 0; + vectors->precision = 0.2; + + vectors->bezier_desc = NULL; + vectors->bounds_valid = FALSE; +} + +static void +gimp_vectors_finalize (GObject *object) +{ + GimpVectors *vectors = GIMP_VECTORS (object); + + if (vectors->bezier_desc) + { + gimp_bezier_desc_free (vectors->bezier_desc); + vectors->bezier_desc = NULL; + } + + if (vectors->strokes) + { + g_queue_free_full (vectors->strokes, (GDestroyNotify) g_object_unref); + vectors->strokes = NULL; + } + + if (vectors->stroke_to_list) + { + g_hash_table_destroy (vectors->stroke_to_list); + vectors->stroke_to_list = NULL; + } + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gint64 +gimp_vectors_get_memsize (GimpObject *object, + gint64 *gui_size) +{ + GimpVectors *vectors; + GList *list; + gint64 memsize = 0; + + vectors = GIMP_VECTORS (object); + + for (list = vectors->strokes->head; list; list = g_list_next (list)) + memsize += (gimp_object_get_memsize (GIMP_OBJECT (list->data), gui_size) + + sizeof (GList)); + + return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, + gui_size); +} + +static gboolean +gimp_vectors_is_attached (GimpItem *item) +{ + GimpImage *image = gimp_item_get_image (item); + + return (GIMP_IS_IMAGE (image) && + gimp_container_have (gimp_image_get_vectors (image), + GIMP_OBJECT (item))); +} + +static GimpItemTree * +gimp_vectors_get_tree (GimpItem *item) +{ + if (gimp_item_is_attached (item)) + { + GimpImage *image = gimp_item_get_image (item); + + return gimp_image_get_vectors_tree (image); + } + + return NULL; +} + +static gboolean +gimp_vectors_bounds (GimpItem *item, + gdouble *x, + gdouble *y, + gdouble *width, + gdouble *height) +{ + GimpVectors *vectors = GIMP_VECTORS (item); + + if (! vectors->bounds_valid) + { + GimpStroke *stroke; + + vectors->bounds_empty = TRUE; + vectors->bounds_x1 = vectors->bounds_x2 = 0.0; + vectors->bounds_y1 = vectors->bounds_y2 = 0.0; + + for (stroke = gimp_vectors_stroke_get_next (vectors, NULL); + stroke; + stroke = gimp_vectors_stroke_get_next (vectors, stroke)) + { + GArray *stroke_coords; + gboolean closed; + + stroke_coords = gimp_stroke_interpolate (stroke, 1.0, &closed); + + if (stroke_coords) + { + GimpCoords point; + gint i; + + if (vectors->bounds_empty && stroke_coords->len > 0) + { + point = g_array_index (stroke_coords, GimpCoords, 0); + + vectors->bounds_x1 = vectors->bounds_x2 = point.x; + vectors->bounds_y1 = vectors->bounds_y2 = point.y; + + vectors->bounds_empty = FALSE; + } + + for (i = 0; i < stroke_coords->len; i++) + { + point = g_array_index (stroke_coords, GimpCoords, i); + + vectors->bounds_x1 = MIN (vectors->bounds_x1, point.x); + vectors->bounds_y1 = MIN (vectors->bounds_y1, point.y); + vectors->bounds_x2 = MAX (vectors->bounds_x2, point.x); + vectors->bounds_y2 = MAX (vectors->bounds_y2, point.y); + } + + g_array_free (stroke_coords, TRUE); + } + } + + vectors->bounds_valid = TRUE; + } + + *x = vectors->bounds_x1; + *y = vectors->bounds_y1; + *width = vectors->bounds_x2 - vectors->bounds_x1; + *height = vectors->bounds_y2 - vectors->bounds_y1; + + return ! vectors->bounds_empty; +} + +static GimpItem * +gimp_vectors_duplicate (GimpItem *item, + GType new_type) +{ + GimpItem *new_item; + + g_return_val_if_fail (g_type_is_a (new_type, GIMP_TYPE_VECTORS), NULL); + + new_item = GIMP_ITEM_CLASS (parent_class)->duplicate (item, new_type); + + if (GIMP_IS_VECTORS (new_item)) + { + GimpVectors *vectors = GIMP_VECTORS (item); + GimpVectors *new_vectors = GIMP_VECTORS (new_item); + + gimp_vectors_copy_strokes (vectors, new_vectors); + } + + return new_item; +} + +static void +gimp_vectors_convert (GimpItem *item, + GimpImage *dest_image, + GType old_type) +{ + gimp_item_set_size (item, + gimp_image_get_width (dest_image), + gimp_image_get_height (dest_image)); + + GIMP_ITEM_CLASS (parent_class)->convert (item, dest_image, old_type); +} + +static void +gimp_vectors_translate (GimpItem *item, + gdouble offset_x, + gdouble offset_y, + gboolean push_undo) +{ + GimpVectors *vectors = GIMP_VECTORS (item); + GList *list; + + gimp_vectors_freeze (vectors); + + if (push_undo) + gimp_image_undo_push_vectors_mod (gimp_item_get_image (item), + _("Move Path"), + vectors); + + for (list = vectors->strokes->head; list; list = g_list_next (list)) + { + GimpStroke *stroke = list->data; + + gimp_stroke_translate (stroke, offset_x, offset_y); + } + + gimp_vectors_thaw (vectors); +} + +static void +gimp_vectors_scale (GimpItem *item, + gint new_width, + gint new_height, + gint new_offset_x, + gint new_offset_y, + GimpInterpolationType interpolation_type, + GimpProgress *progress) +{ + GimpVectors *vectors = GIMP_VECTORS (item); + GimpImage *image = gimp_item_get_image (item); + GList *list; + + gimp_vectors_freeze (vectors); + + if (gimp_item_is_attached (item)) + gimp_image_undo_push_vectors_mod (image, NULL, vectors); + + for (list = vectors->strokes->head; list; list = g_list_next (list)) + { + GimpStroke *stroke = list->data; + + gimp_stroke_scale (stroke, + (gdouble) new_width / (gdouble) gimp_item_get_width (item), + (gdouble) new_height / (gdouble) gimp_item_get_height (item)); + gimp_stroke_translate (stroke, new_offset_x, new_offset_y); + } + + GIMP_ITEM_CLASS (parent_class)->scale (item, + gimp_image_get_width (image), + gimp_image_get_height (image), + 0, 0, + interpolation_type, progress); + + gimp_vectors_thaw (vectors); +} + +static void +gimp_vectors_resize (GimpItem *item, + GimpContext *context, + GimpFillType fill_type, + gint new_width, + gint new_height, + gint offset_x, + gint offset_y) +{ + GimpVectors *vectors = GIMP_VECTORS (item); + GimpImage *image = gimp_item_get_image (item); + GList *list; + + gimp_vectors_freeze (vectors); + + if (gimp_item_is_attached (item)) + gimp_image_undo_push_vectors_mod (image, NULL, vectors); + + for (list = vectors->strokes->head; list; list = g_list_next (list)) + { + GimpStroke *stroke = list->data; + + gimp_stroke_translate (stroke, offset_x, offset_y); + } + + GIMP_ITEM_CLASS (parent_class)->resize (item, context, fill_type, + gimp_image_get_width (image), + gimp_image_get_height (image), + 0, 0); + + gimp_vectors_thaw (vectors); +} + +static void +gimp_vectors_flip (GimpItem *item, + GimpContext *context, + GimpOrientationType flip_type, + gdouble axis, + gboolean clip_result) +{ + GimpVectors *vectors = GIMP_VECTORS (item); + GList *list; + GimpMatrix3 matrix; + + gimp_matrix3_identity (&matrix); + gimp_transform_matrix_flip (&matrix, flip_type, axis); + + gimp_vectors_freeze (vectors); + + gimp_image_undo_push_vectors_mod (gimp_item_get_image (item), + _("Flip Path"), + vectors); + + for (list = vectors->strokes->head; list; list = g_list_next (list)) + { + GimpStroke *stroke = list->data; + + gimp_stroke_transform (stroke, &matrix, NULL); + } + + gimp_vectors_thaw (vectors); +} + +static void +gimp_vectors_rotate (GimpItem *item, + GimpContext *context, + GimpRotationType rotate_type, + gdouble center_x, + gdouble center_y, + gboolean clip_result) +{ + GimpVectors *vectors = GIMP_VECTORS (item); + GList *list; + GimpMatrix3 matrix; + + gimp_matrix3_identity (&matrix); + gimp_transform_matrix_rotate (&matrix, rotate_type, center_x, center_y); + + gimp_vectors_freeze (vectors); + + gimp_image_undo_push_vectors_mod (gimp_item_get_image (item), + _("Rotate Path"), + vectors); + + for (list = vectors->strokes->head; list; list = g_list_next (list)) + { + GimpStroke *stroke = list->data; + + gimp_stroke_transform (stroke, &matrix, NULL); + } + + gimp_vectors_thaw (vectors); +} + +static void +gimp_vectors_transform (GimpItem *item, + GimpContext *context, + const GimpMatrix3 *matrix, + GimpTransformDirection direction, + GimpInterpolationType interpolation_type, + GimpTransformResize clip_result, + GimpProgress *progress) +{ + GimpVectors *vectors = GIMP_VECTORS (item); + GimpMatrix3 local_matrix; + GQueue strokes; + GList *list; + + gimp_vectors_freeze (vectors); + + gimp_image_undo_push_vectors_mod (gimp_item_get_image (item), + _("Transform Path"), + vectors); + + local_matrix = *matrix; + + if (direction == GIMP_TRANSFORM_BACKWARD) + gimp_matrix3_invert (&local_matrix); + + g_queue_init (&strokes); + + while (! g_queue_is_empty (vectors->strokes)) + { + GimpStroke *stroke = g_queue_peek_head (vectors->strokes); + + g_object_ref (stroke); + + gimp_vectors_stroke_remove (vectors, stroke); + + gimp_stroke_transform (stroke, &local_matrix, &strokes); + + g_object_unref (stroke); + } + + vectors->last_stroke_ID = 0; + + for (list = strokes.head; list; list = g_list_next (list)) + { + GimpStroke *stroke = list->data; + + gimp_vectors_stroke_add (vectors, stroke); + + g_object_unref (stroke); + } + + g_queue_clear (&strokes); + + gimp_vectors_thaw (vectors); +} + +static GimpTransformResize +gimp_vectors_get_clip (GimpItem *item, + GimpTransformResize clip_result) +{ + return GIMP_TRANSFORM_RESIZE_ADJUST; +} + +static gboolean +gimp_vectors_fill (GimpItem *item, + GimpDrawable *drawable, + GimpFillOptions *fill_options, + gboolean push_undo, + GimpProgress *progress, + GError **error) +{ + GimpVectors *vectors = GIMP_VECTORS (item); + + if (g_queue_is_empty (vectors->strokes)) + { + g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED, + _("Not enough points to fill")); + return FALSE; + } + + return gimp_drawable_fill_vectors (drawable, fill_options, + vectors, push_undo, error); +} + +static gboolean +gimp_vectors_stroke (GimpItem *item, + GimpDrawable *drawable, + GimpStrokeOptions *stroke_options, + gboolean push_undo, + GimpProgress *progress, + GError **error) +{ + GimpVectors *vectors = GIMP_VECTORS (item); + gboolean retval = FALSE; + + if (g_queue_is_empty (vectors->strokes)) + { + g_set_error_literal (error, GIMP_ERROR, GIMP_FAILED, + _("Not enough points to stroke")); + return FALSE; + } + + switch (gimp_stroke_options_get_method (stroke_options)) + { + case GIMP_STROKE_LINE: + retval = gimp_drawable_stroke_vectors (drawable, stroke_options, + vectors, push_undo, error); + break; + + case GIMP_STROKE_PAINT_METHOD: + { + GimpPaintInfo *paint_info; + GimpPaintCore *core; + GimpPaintOptions *paint_options; + gboolean emulate_dynamics; + + paint_info = gimp_context_get_paint_info (GIMP_CONTEXT (stroke_options)); + + core = g_object_new (paint_info->paint_type, NULL); + + paint_options = gimp_stroke_options_get_paint_options (stroke_options); + emulate_dynamics = gimp_stroke_options_get_emulate_dynamics (stroke_options); + + retval = gimp_paint_core_stroke_vectors (core, drawable, + paint_options, + emulate_dynamics, + vectors, push_undo, error); + + g_object_unref (core); + } + break; + + default: + g_return_val_if_reached (FALSE); + } + + return retval; +} + +static void +gimp_vectors_to_selection (GimpItem *item, + GimpChannelOps op, + gboolean antialias, + gboolean feather, + gdouble feather_radius_x, + gdouble feather_radius_y) +{ + GimpVectors *vectors = GIMP_VECTORS (item); + GimpImage *image = gimp_item_get_image (item); + + gimp_channel_select_vectors (gimp_image_get_mask (image), + GIMP_ITEM_GET_CLASS (item)->to_selection_desc, + vectors, + op, antialias, + feather, feather_radius_x, feather_radius_x, + TRUE); +} + +static void +gimp_vectors_real_freeze (GimpVectors *vectors) +{ + /* release cached bezier representation */ + if (vectors->bezier_desc) + { + gimp_bezier_desc_free (vectors->bezier_desc); + vectors->bezier_desc = NULL; + } + + /* invalidate bounds */ + vectors->bounds_valid = FALSE; +} + +static void +gimp_vectors_real_thaw (GimpVectors *vectors) +{ + gimp_viewable_invalidate_preview (GIMP_VIEWABLE (vectors)); +} + + +/* public functions */ + +GimpVectors * +gimp_vectors_new (GimpImage *image, + const gchar *name) +{ + GimpVectors *vectors; + + g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL); + + vectors = GIMP_VECTORS (gimp_item_new (GIMP_TYPE_VECTORS, + image, name, + 0, 0, + gimp_image_get_width (image), + gimp_image_get_height (image))); + + return vectors; +} + +GimpVectors * +gimp_vectors_get_parent (GimpVectors *vectors) +{ + g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL); + + return GIMP_VECTORS (gimp_viewable_get_parent (GIMP_VIEWABLE (vectors))); +} + +void +gimp_vectors_freeze (GimpVectors *vectors) +{ + g_return_if_fail (GIMP_IS_VECTORS (vectors)); + + vectors->freeze_count++; + + if (vectors->freeze_count == 1) + g_signal_emit (vectors, gimp_vectors_signals[FREEZE], 0); +} + +void +gimp_vectors_thaw (GimpVectors *vectors) +{ + g_return_if_fail (GIMP_IS_VECTORS (vectors)); + g_return_if_fail (vectors->freeze_count > 0); + + vectors->freeze_count--; + + if (vectors->freeze_count == 0) + g_signal_emit (vectors, gimp_vectors_signals[THAW], 0); +} + +void +gimp_vectors_copy_strokes (GimpVectors *src_vectors, + GimpVectors *dest_vectors) +{ + g_return_if_fail (GIMP_IS_VECTORS (src_vectors)); + g_return_if_fail (GIMP_IS_VECTORS (dest_vectors)); + + gimp_vectors_freeze (dest_vectors); + + g_queue_free_full (dest_vectors->strokes, (GDestroyNotify) g_object_unref); + dest_vectors->strokes = g_queue_new (); + g_hash_table_remove_all (dest_vectors->stroke_to_list); + + dest_vectors->last_stroke_ID = 0; + + gimp_vectors_add_strokes (src_vectors, dest_vectors); + + gimp_vectors_thaw (dest_vectors); +} + + +void +gimp_vectors_add_strokes (GimpVectors *src_vectors, + GimpVectors *dest_vectors) +{ + GList *stroke; + + g_return_if_fail (GIMP_IS_VECTORS (src_vectors)); + g_return_if_fail (GIMP_IS_VECTORS (dest_vectors)); + + gimp_vectors_freeze (dest_vectors); + + for (stroke = src_vectors->strokes->head; + stroke != NULL; + stroke = g_list_next (stroke)) + { + GimpStroke *newstroke = gimp_stroke_duplicate (stroke->data); + + g_queue_push_tail (dest_vectors->strokes, newstroke); + + /* Also add to {stroke: GList node} map */ + g_hash_table_insert (dest_vectors->stroke_to_list, + newstroke, + g_queue_peek_tail_link (dest_vectors->strokes)); + + dest_vectors->last_stroke_ID++; + gimp_stroke_set_ID (newstroke, + dest_vectors->last_stroke_ID); + } + + gimp_vectors_thaw (dest_vectors); +} + + +void +gimp_vectors_stroke_add (GimpVectors *vectors, + GimpStroke *stroke) +{ + g_return_if_fail (GIMP_IS_VECTORS (vectors)); + g_return_if_fail (GIMP_IS_STROKE (stroke)); + + gimp_vectors_freeze (vectors); + + GIMP_VECTORS_GET_CLASS (vectors)->stroke_add (vectors, stroke); + + gimp_vectors_thaw (vectors); +} + +static void +gimp_vectors_real_stroke_add (GimpVectors *vectors, + GimpStroke *stroke) +{ + /* + * Don't prepend into vector->strokes. See ChangeLog 2003-05-21 + * --Mitch + */ + g_queue_push_tail (vectors->strokes, g_object_ref (stroke)); + + /* Also add to {stroke: GList node} map */ + g_hash_table_insert (vectors->stroke_to_list, + stroke, + g_queue_peek_tail_link (vectors->strokes)); + + vectors->last_stroke_ID++; + gimp_stroke_set_ID (stroke, vectors->last_stroke_ID); +} + +void +gimp_vectors_stroke_remove (GimpVectors *vectors, + GimpStroke *stroke) +{ + g_return_if_fail (GIMP_IS_VECTORS (vectors)); + g_return_if_fail (GIMP_IS_STROKE (stroke)); + + gimp_vectors_freeze (vectors); + + GIMP_VECTORS_GET_CLASS (vectors)->stroke_remove (vectors, stroke); + + gimp_vectors_thaw (vectors); +} + +static void +gimp_vectors_real_stroke_remove (GimpVectors *vectors, + GimpStroke *stroke) +{ + GList *list = g_hash_table_lookup (vectors->stroke_to_list, stroke); + + if (list) + { + g_queue_delete_link (vectors->strokes, list); + g_hash_table_remove (vectors->stroke_to_list, stroke); + g_object_unref (stroke); + } +} + +gint +gimp_vectors_get_n_strokes (GimpVectors *vectors) +{ + g_return_val_if_fail (GIMP_IS_VECTORS (vectors), 0); + + return g_queue_get_length (vectors->strokes); +} + + +GimpStroke * +gimp_vectors_stroke_get (GimpVectors *vectors, + const GimpCoords *coord) +{ + g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL); + + return GIMP_VECTORS_GET_CLASS (vectors)->stroke_get (vectors, coord); +} + +static GimpStroke * +gimp_vectors_real_stroke_get (GimpVectors *vectors, + const GimpCoords *coord) +{ + GimpStroke *minstroke = NULL; + gdouble mindist = G_MAXDOUBLE; + GList *list; + + for (list = vectors->strokes->head; list; list = g_list_next (list)) + { + GimpStroke *stroke = list->data; + GimpAnchor *anchor = gimp_stroke_anchor_get (stroke, coord); + + if (anchor) + { + gdouble dx = coord->x - anchor->position.x; + gdouble dy = coord->y - anchor->position.y; + + if (mindist > dx * dx + dy * dy) + { + mindist = dx * dx + dy * dy; + minstroke = stroke; + } + } + } + + return minstroke; +} + +GimpStroke * +gimp_vectors_stroke_get_by_ID (GimpVectors *vectors, + gint id) +{ + GList *list; + + g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL); + + for (list = vectors->strokes->head; list; list = g_list_next (list)) + { + if (gimp_stroke_get_ID (list->data) == id) + return list->data; + } + + return NULL; +} + + +GimpStroke * +gimp_vectors_stroke_get_next (GimpVectors *vectors, + GimpStroke *prev) +{ + g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL); + + return GIMP_VECTORS_GET_CLASS (vectors)->stroke_get_next (vectors, prev); +} + +static GimpStroke * +gimp_vectors_real_stroke_get_next (GimpVectors *vectors, + GimpStroke *prev) +{ + if (! prev) + { + return g_queue_peek_head (vectors->strokes); + } + else + { + GList *stroke = g_hash_table_lookup (vectors->stroke_to_list, prev); + + g_return_val_if_fail (stroke != NULL, NULL); + + return stroke->next ? stroke->next->data : NULL; + } +} + + +gdouble +gimp_vectors_stroke_get_length (GimpVectors *vectors, + GimpStroke *stroke) +{ + g_return_val_if_fail (GIMP_IS_VECTORS (vectors), 0.0); + g_return_val_if_fail (GIMP_IS_STROKE (stroke), 0.0); + + return GIMP_VECTORS_GET_CLASS (vectors)->stroke_get_length (vectors, stroke); +} + +static gdouble +gimp_vectors_real_stroke_get_length (GimpVectors *vectors, + GimpStroke *stroke) +{ + g_return_val_if_fail (GIMP_IS_VECTORS (vectors), 0.0); + g_return_val_if_fail (GIMP_IS_STROKE (stroke), 0.0); + + return gimp_stroke_get_length (stroke, vectors->precision); +} + + +GimpAnchor * +gimp_vectors_anchor_get (GimpVectors *vectors, + const GimpCoords *coord, + GimpStroke **ret_stroke) +{ + g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL); + + return GIMP_VECTORS_GET_CLASS (vectors)->anchor_get (vectors, coord, + ret_stroke); +} + +static GimpAnchor * +gimp_vectors_real_anchor_get (GimpVectors *vectors, + const GimpCoords *coord, + GimpStroke **ret_stroke) +{ + GimpAnchor *minanchor = NULL; + gdouble mindist = -1; + GList *list; + + for (list = vectors->strokes->head; list; list = g_list_next (list)) + { + GimpStroke *stroke = list->data; + GimpAnchor *anchor = gimp_stroke_anchor_get (stroke, coord); + + if (anchor) + { + gdouble dx = coord->x - anchor->position.x; + gdouble dy = coord->y - anchor->position.y; + + if (mindist > dx * dx + dy * dy || mindist < 0) + { + mindist = dx * dx + dy * dy; + minanchor = anchor; + + if (ret_stroke) + *ret_stroke = stroke; + } + } + } + + return minanchor; +} + + +void +gimp_vectors_anchor_delete (GimpVectors *vectors, + GimpAnchor *anchor) +{ + g_return_if_fail (GIMP_IS_VECTORS (vectors)); + g_return_if_fail (anchor != NULL); + + GIMP_VECTORS_GET_CLASS (vectors)->anchor_delete (vectors, anchor); +} + +static void +gimp_vectors_real_anchor_delete (GimpVectors *vectors, + GimpAnchor *anchor) +{ +} + + +void +gimp_vectors_anchor_select (GimpVectors *vectors, + GimpStroke *target_stroke, + GimpAnchor *anchor, + gboolean selected, + gboolean exclusive) +{ + GList *list; + + for (list = vectors->strokes->head; list; list = g_list_next (list)) + { + GimpStroke *stroke = list->data; + + gimp_stroke_anchor_select (stroke, + stroke == target_stroke ? anchor : NULL, + selected, exclusive); + } +} + + +gdouble +gimp_vectors_get_length (GimpVectors *vectors, + const GimpAnchor *start) +{ + g_return_val_if_fail (GIMP_IS_VECTORS (vectors), 0.0); + + return GIMP_VECTORS_GET_CLASS (vectors)->get_length (vectors, start); +} + +static gdouble +gimp_vectors_real_get_length (GimpVectors *vectors, + const GimpAnchor *start) +{ + g_printerr ("gimp_vectors_get_length: default implementation\n"); + + return 0; +} + + +gdouble +gimp_vectors_get_distance (GimpVectors *vectors, + const GimpCoords *coord) +{ + g_return_val_if_fail (GIMP_IS_VECTORS (vectors), 0.0); + + return GIMP_VECTORS_GET_CLASS (vectors)->get_distance (vectors, coord); +} + +static gdouble +gimp_vectors_real_get_distance (GimpVectors *vectors, + const GimpCoords *coord) +{ + g_printerr ("gimp_vectors_get_distance: default implementation\n"); + + return 0; +} + +gint +gimp_vectors_interpolate (GimpVectors *vectors, + GimpStroke *stroke, + gdouble precision, + gint max_points, + GimpCoords *ret_coords) +{ + g_return_val_if_fail (GIMP_IS_VECTORS (vectors), 0); + + return GIMP_VECTORS_GET_CLASS (vectors)->interpolate (vectors, stroke, + precision, max_points, + ret_coords); +} + +static gint +gimp_vectors_real_interpolate (GimpVectors *vectors, + GimpStroke *stroke, + gdouble precision, + gint max_points, + GimpCoords *ret_coords) +{ + g_printerr ("gimp_vectors_interpolate: default implementation\n"); + + return 0; +} + +const GimpBezierDesc * +gimp_vectors_get_bezier (GimpVectors *vectors) +{ + g_return_val_if_fail (GIMP_IS_VECTORS (vectors), NULL); + + if (! vectors->bezier_desc) + { + vectors->bezier_desc = gimp_vectors_make_bezier (vectors); + } + + return vectors->bezier_desc; +} + +static GimpBezierDesc * +gimp_vectors_make_bezier (GimpVectors *vectors) +{ + return GIMP_VECTORS_GET_CLASS (vectors)->make_bezier (vectors); +} + +static GimpBezierDesc * +gimp_vectors_real_make_bezier (GimpVectors *vectors) +{ + GimpStroke *stroke; + GArray *cmd_array; + GimpBezierDesc *ret_bezdesc = NULL; + + cmd_array = g_array_new (FALSE, FALSE, sizeof (cairo_path_data_t)); + + for (stroke = gimp_vectors_stroke_get_next (vectors, NULL); + stroke; + stroke = gimp_vectors_stroke_get_next (vectors, stroke)) + { + GimpBezierDesc *bezdesc = gimp_stroke_make_bezier (stroke); + + if (bezdesc) + { + cmd_array = g_array_append_vals (cmd_array, bezdesc->data, + bezdesc->num_data); + gimp_bezier_desc_free (bezdesc); + } + } + + if (cmd_array->len > 0) + ret_bezdesc = gimp_bezier_desc_new ((cairo_path_data_t *) cmd_array->data, + cmd_array->len); + + g_array_free (cmd_array, FALSE); + + return ret_bezdesc; +} diff --git a/app/vectors/gimpvectors.h b/app/vectors/gimpvectors.h new file mode 100644 index 0000000..a0d8a64 --- /dev/null +++ b/app/vectors/gimpvectors.h @@ -0,0 +1,184 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpvectors.h + * Copyright (C) 2002 Simon Budig <simon@gimp.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_VECTORS_H__ +#define __GIMP_VECTORS_H__ + +#include "core/gimpitem.h" + +#define GIMP_TYPE_VECTORS (gimp_vectors_get_type ()) +#define GIMP_VECTORS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VECTORS, GimpVectors)) +#define GIMP_VECTORS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VECTORS, GimpVectorsClass)) +#define GIMP_IS_VECTORS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_VECTORS)) +#define GIMP_IS_VECTORS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VECTORS)) +#define GIMP_VECTORS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VECTORS, GimpVectorsClass)) + + +typedef struct _GimpVectorsClass GimpVectorsClass; + +struct _GimpVectors +{ + GimpItem parent_instance; + + GQueue *strokes; /* Queue of GimpStrokes */ + GHashTable *stroke_to_list; /* Map from GimpStroke to strokes listnode */ + gint last_stroke_ID; + + gint freeze_count; + gdouble precision; + + GimpBezierDesc *bezier_desc; /* Cached bezier representation */ + + gboolean bounds_valid; /* Cached bounding box */ + gboolean bounds_empty; + gdouble bounds_x1; + gdouble bounds_y1; + gdouble bounds_x2; + gdouble bounds_y2; +}; + +struct _GimpVectorsClass +{ + GimpItemClass parent_class; + + /* signals */ + void (* freeze) (GimpVectors *vectors); + void (* thaw) (GimpVectors *vectors); + + /* virtual functions */ + void (* stroke_add) (GimpVectors *vectors, + GimpStroke *stroke); + void (* stroke_remove) (GimpVectors *vectors, + GimpStroke *stroke); + GimpStroke * (* stroke_get) (GimpVectors *vectors, + const GimpCoords *coord); + GimpStroke * (* stroke_get_next) (GimpVectors *vectors, + GimpStroke *prev); + gdouble (* stroke_get_length) (GimpVectors *vectors, + GimpStroke *stroke); + GimpAnchor * (* anchor_get) (GimpVectors *vectors, + const GimpCoords *coord, + GimpStroke **ret_stroke); + void (* anchor_delete) (GimpVectors *vectors, + GimpAnchor *anchor); + gdouble (* get_length) (GimpVectors *vectors, + const GimpAnchor *start); + gdouble (* get_distance) (GimpVectors *vectors, + const GimpCoords *coord); + gint (* interpolate) (GimpVectors *vectors, + GimpStroke *stroke, + gdouble precision, + gint max_points, + GimpCoords *ret_coords); + GimpBezierDesc * (* make_bezier) (GimpVectors *vectors); +}; + + +/* vectors utility functions */ + +GType gimp_vectors_get_type (void) G_GNUC_CONST; + +GimpVectors * gimp_vectors_new (GimpImage *image, + const gchar *name); + +GimpVectors * gimp_vectors_get_parent (GimpVectors *vectors); + +void gimp_vectors_freeze (GimpVectors *vectors); +void gimp_vectors_thaw (GimpVectors *vectors); + +void gimp_vectors_copy_strokes (GimpVectors *src_vectors, + GimpVectors *dest_vectors); +void gimp_vectors_add_strokes (GimpVectors *src_vectors, + GimpVectors *dest_vectors); + + +/* accessing / modifying the anchors */ + +GimpAnchor * gimp_vectors_anchor_get (GimpVectors *vectors, + const GimpCoords *coord, + GimpStroke **ret_stroke); + +/* prev == NULL: "first" anchor */ +GimpAnchor * gimp_vectors_anchor_get_next (GimpVectors *vectors, + const GimpAnchor *prev); + +/* type will be an xorable enum: + * VECTORS_NONE, VECTORS_FIX_ANGLE, VECTORS_FIX_RATIO, VECTORS_RESTRICT_ANGLE + * or so. + */ +void gimp_vectors_anchor_move_relative (GimpVectors *vectors, + GimpAnchor *anchor, + const GimpCoords *deltacoord, + gint type); +void gimp_vectors_anchor_move_absolute (GimpVectors *vectors, + GimpAnchor *anchor, + const GimpCoords *coord, + gint type); + +void gimp_vectors_anchor_delete (GimpVectors *vectors, + GimpAnchor *anchor); + +void gimp_vectors_anchor_select (GimpVectors *vectors, + GimpStroke *target_stroke, + GimpAnchor *anchor, + gboolean selected, + gboolean exclusive); + + +/* GimpStroke is a connected component of a GimpVectors object */ + +void gimp_vectors_stroke_add (GimpVectors *vectors, + GimpStroke *stroke); +void gimp_vectors_stroke_remove (GimpVectors *vectors, + GimpStroke *stroke); +gint gimp_vectors_get_n_strokes (GimpVectors *vectors); +GimpStroke * gimp_vectors_stroke_get (GimpVectors *vectors, + const GimpCoords *coord); +GimpStroke * gimp_vectors_stroke_get_by_ID (GimpVectors *vectors, + gint id); + +/* prev == NULL: "first" stroke */ +GimpStroke * gimp_vectors_stroke_get_next (GimpVectors *vectors, + GimpStroke *prev); +gdouble gimp_vectors_stroke_get_length (GimpVectors *vectors, + GimpStroke *stroke); + +/* accessing the shape of the curve */ + +gdouble gimp_vectors_get_length (GimpVectors *vectors, + const GimpAnchor *start); +gdouble gimp_vectors_get_distance (GimpVectors *vectors, + const GimpCoords *coord); + +/* returns the number of valid coordinates */ + +gint gimp_vectors_interpolate (GimpVectors *vectors, + GimpStroke *stroke, + gdouble precision, + gint max_points, + GimpCoords *ret_coords); + +/* usually overloaded */ + +/* returns a bezier representation */ +const GimpBezierDesc * gimp_vectors_get_bezier (GimpVectors *vectors); + + +#endif /* __GIMP_VECTORS_H__ */ diff --git a/app/vectors/gimpvectorsmodundo.c b/app/vectors/gimpvectorsmodundo.c new file mode 100644 index 0000000..b681ba9 --- /dev/null +++ b/app/vectors/gimpvectorsmodundo.c @@ -0,0 +1,141 @@ +/* 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 "vectors-types.h" + +#include "gimpvectors.h" +#include "gimpvectorsmodundo.h" + + +static void gimp_vectors_mod_undo_constructed (GObject *object); + +static gint64 gimp_vectors_mod_undo_get_memsize (GimpObject *object, + gint64 *gui_size); + +static void gimp_vectors_mod_undo_pop (GimpUndo *undo, + GimpUndoMode undo_mode, + GimpUndoAccumulator *accum); +static void gimp_vectors_mod_undo_free (GimpUndo *undo, + GimpUndoMode undo_mode); + + +G_DEFINE_TYPE (GimpVectorsModUndo, gimp_vectors_mod_undo, GIMP_TYPE_ITEM_UNDO) + +#define parent_class gimp_vectors_mod_undo_parent_class + + +static void +gimp_vectors_mod_undo_class_init (GimpVectorsModUndoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass); + GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass); + + object_class->constructed = gimp_vectors_mod_undo_constructed; + + gimp_object_class->get_memsize = gimp_vectors_mod_undo_get_memsize; + + undo_class->pop = gimp_vectors_mod_undo_pop; + undo_class->free = gimp_vectors_mod_undo_free; +} + +static void +gimp_vectors_mod_undo_init (GimpVectorsModUndo *undo) +{ +} + +static void +gimp_vectors_mod_undo_constructed (GObject *object) +{ + GimpVectorsModUndo *vectors_mod_undo = GIMP_VECTORS_MOD_UNDO (object); + GimpVectors *vectors; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_VECTORS (GIMP_ITEM_UNDO (object)->item)); + + vectors = GIMP_VECTORS (GIMP_ITEM_UNDO (object)->item); + + vectors_mod_undo->vectors = + GIMP_VECTORS (gimp_item_duplicate (GIMP_ITEM (vectors), + G_TYPE_FROM_INSTANCE (vectors))); +} + +static gint64 +gimp_vectors_mod_undo_get_memsize (GimpObject *object, + gint64 *gui_size) +{ + GimpVectorsModUndo *vectors_mod_undo = GIMP_VECTORS_MOD_UNDO (object); + gint64 memsize = 0; + + memsize += gimp_object_get_memsize (GIMP_OBJECT (vectors_mod_undo->vectors), + gui_size); + + return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, + gui_size); +} + +static void +gimp_vectors_mod_undo_pop (GimpUndo *undo, + GimpUndoMode undo_mode, + GimpUndoAccumulator *accum) +{ + GimpVectorsModUndo *vectors_mod_undo = GIMP_VECTORS_MOD_UNDO (undo); + GimpVectors *vectors = GIMP_VECTORS (GIMP_ITEM_UNDO (undo)->item); + GimpVectors *temp; + gint offset_x; + gint offset_y; + + GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum); + + temp = vectors_mod_undo->vectors; + + vectors_mod_undo->vectors = + GIMP_VECTORS (gimp_item_duplicate (GIMP_ITEM (vectors), + G_TYPE_FROM_INSTANCE (vectors))); + + gimp_vectors_freeze (vectors); + + gimp_vectors_copy_strokes (temp, vectors); + + gimp_item_get_offset (GIMP_ITEM (temp), &offset_x, &offset_y); + gimp_item_set_offset (GIMP_ITEM (vectors), offset_x, offset_y); + + gimp_item_set_size (GIMP_ITEM (vectors), + gimp_item_get_width (GIMP_ITEM (temp)), + gimp_item_get_height (GIMP_ITEM (temp))); + + g_object_unref (temp); + + gimp_vectors_thaw (vectors); +} + +static void +gimp_vectors_mod_undo_free (GimpUndo *undo, + GimpUndoMode undo_mode) +{ + GimpVectorsModUndo *vectors_mod_undo = GIMP_VECTORS_MOD_UNDO (undo); + + g_clear_object (&vectors_mod_undo->vectors); + + GIMP_UNDO_CLASS (parent_class)->free (undo, undo_mode); +} diff --git a/app/vectors/gimpvectorsmodundo.h b/app/vectors/gimpvectorsmodundo.h new file mode 100644 index 0000000..e63a0ef --- /dev/null +++ b/app/vectors/gimpvectorsmodundo.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_VECTORS_MOD_UNDO_H__ +#define __GIMP_VECTORS_MOD_UNDO_H__ + + +#include "core/gimpitemundo.h" + + +#define GIMP_TYPE_VECTORS_MOD_UNDO (gimp_vectors_mod_undo_get_type ()) +#define GIMP_VECTORS_MOD_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VECTORS_MOD_UNDO, GimpVectorsModUndo)) +#define GIMP_VECTORS_MOD_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VECTORS_MOD_UNDO, GimpVectorsModUndoClass)) +#define GIMP_IS_VECTORS_MOD_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_VECTORS_MOD_UNDO)) +#define GIMP_IS_VECTORS_MOD_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VECTORS_MOD_UNDO)) +#define GIMP_VECTORS_MOD_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VECTORS_MOD_UNDO, GimpVectorsModUndoClass)) + + +typedef struct _GimpVectorsModUndo GimpVectorsModUndo; +typedef struct _GimpVectorsModUndoClass GimpVectorsModUndoClass; + +struct _GimpVectorsModUndo +{ + GimpItemUndo parent_instance; + + GimpVectors *vectors; +}; + +struct _GimpVectorsModUndoClass +{ + GimpItemUndoClass parent_class; +}; + + +GType gimp_vectors_mod_undo_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_VECTORS_MOD_UNDO_H__ */ diff --git a/app/vectors/gimpvectorspropundo.c b/app/vectors/gimpvectorspropundo.c new file mode 100644 index 0000000..1a093df --- /dev/null +++ b/app/vectors/gimpvectorspropundo.c @@ -0,0 +1,94 @@ +/* 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 "vectors-types.h" + +#include "core/gimpimage.h" + +#include "gimpvectors.h" +#include "gimpvectorspropundo.h" + + +static void gimp_vectors_prop_undo_constructed (GObject *object); + +static void gimp_vectors_prop_undo_pop (GimpUndo *undo, + GimpUndoMode undo_mode, + GimpUndoAccumulator *accum); + + +G_DEFINE_TYPE (GimpVectorsPropUndo, gimp_vectors_prop_undo, GIMP_TYPE_ITEM_UNDO) + +#define parent_class gimp_vectors_prop_undo_parent_class + + +static void +gimp_vectors_prop_undo_class_init (GimpVectorsPropUndoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass); + + object_class->constructed = gimp_vectors_prop_undo_constructed; + + undo_class->pop = gimp_vectors_prop_undo_pop; +} + +static void +gimp_vectors_prop_undo_init (GimpVectorsPropUndo *undo) +{ +} + +static void +gimp_vectors_prop_undo_constructed (GObject *object) +{ + /* GimpVectors *vectors; */ + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_VECTORS (GIMP_ITEM_UNDO (object)->item)); + + /* vectors = GIMP_VECTORS (GIMP_ITEM_UNDO (object)->item); */ + + switch (GIMP_UNDO (object)->undo_type) + { + default: + gimp_assert_not_reached (); + } +} + +static void +gimp_vectors_prop_undo_pop (GimpUndo *undo, + GimpUndoMode undo_mode, + GimpUndoAccumulator *accum) +{ +#if 0 + GimpVectorsPropUndo *vectors_prop_undo = GIMP_VECTORS_PROP_UNDO (undo); + GimpVectors *vectors = GIMP_VECTORS (GIMP_ITEM_UNDO (undo)->item); +#endif + + GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum); + + switch (undo->undo_type) + { + default: + gimp_assert_not_reached (); + } +} diff --git a/app/vectors/gimpvectorspropundo.h b/app/vectors/gimpvectorspropundo.h new file mode 100644 index 0000000..8909226 --- /dev/null +++ b/app/vectors/gimpvectorspropundo.h @@ -0,0 +1,50 @@ +/* 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_VECTORS_PROP_UNDO_H__ +#define __GIMP_VECTORS_PROP_UNDO_H__ + + +#include "core/gimpitemundo.h" + + +#define GIMP_TYPE_VECTORS_PROP_UNDO (gimp_vectors_prop_undo_get_type ()) +#define GIMP_VECTORS_PROP_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VECTORS_PROP_UNDO, GimpVectorsPropUndo)) +#define GIMP_VECTORS_PROP_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VECTORS_PROP_UNDO, GimpVectorsPropUndoClass)) +#define GIMP_IS_VECTORS_PROP_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_VECTORS_PROP_UNDO)) +#define GIMP_IS_VECTORS_PROP_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VECTORS_PROP_UNDO)) +#define GIMP_VECTORS_PROP_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VECTORS_PROP_UNDO, GimpVectorsPropUndoClass)) + + +typedef struct _GimpVectorsPropUndo GimpVectorsPropUndo; +typedef struct _GimpVectorsPropUndoClass GimpVectorsPropUndoClass; + +struct _GimpVectorsPropUndo +{ + GimpItemUndo parent_instance; +}; + +struct _GimpVectorsPropUndoClass +{ + GimpItemUndoClass parent_class; +}; + + +GType gimp_vectors_prop_undo_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_VECTORS_PROP_UNDO_H__ */ diff --git a/app/vectors/gimpvectorsundo.c b/app/vectors/gimpvectorsundo.c new file mode 100644 index 0000000..646869f --- /dev/null +++ b/app/vectors/gimpvectorsundo.c @@ -0,0 +1,213 @@ +/* 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 "vectors-types.h" + +#include "core/gimpimage.h" + +#include "gimpvectors.h" +#include "gimpvectorsundo.h" + + +enum +{ + PROP_0, + PROP_PREV_PARENT, + PROP_PREV_POSITION, + PROP_PREV_VECTORS +}; + + +static void gimp_vectors_undo_constructed (GObject *object); +static void gimp_vectors_undo_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_vectors_undo_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static gint64 gimp_vectors_undo_get_memsize (GimpObject *object, + gint64 *gui_size); + +static void gimp_vectors_undo_pop (GimpUndo *undo, + GimpUndoMode undo_mode, + GimpUndoAccumulator *accum); + + +G_DEFINE_TYPE (GimpVectorsUndo, gimp_vectors_undo, GIMP_TYPE_ITEM_UNDO) + +#define parent_class gimp_vectors_undo_parent_class + + +static void +gimp_vectors_undo_class_init (GimpVectorsUndoClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass); + GimpUndoClass *undo_class = GIMP_UNDO_CLASS (klass); + + object_class->constructed = gimp_vectors_undo_constructed; + object_class->set_property = gimp_vectors_undo_set_property; + object_class->get_property = gimp_vectors_undo_get_property; + + gimp_object_class->get_memsize = gimp_vectors_undo_get_memsize; + + undo_class->pop = gimp_vectors_undo_pop; + + g_object_class_install_property (object_class, PROP_PREV_PARENT, + g_param_spec_object ("prev-parent", + NULL, NULL, + GIMP_TYPE_VECTORS, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_PREV_POSITION, + g_param_spec_int ("prev-position", NULL, NULL, + 0, G_MAXINT, 0, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_PREV_VECTORS, + g_param_spec_object ("prev-vectors", NULL, NULL, + GIMP_TYPE_VECTORS, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +gimp_vectors_undo_init (GimpVectorsUndo *undo) +{ +} + +static void +gimp_vectors_undo_constructed (GObject *object) +{ + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_VECTORS (GIMP_ITEM_UNDO (object)->item)); +} + +static void +gimp_vectors_undo_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpVectorsUndo *vectors_undo = GIMP_VECTORS_UNDO (object); + + switch (property_id) + { + case PROP_PREV_PARENT: + vectors_undo->prev_parent = g_value_get_object (value); + break; + case PROP_PREV_POSITION: + vectors_undo->prev_position = g_value_get_int (value); + break; + case PROP_PREV_VECTORS: + vectors_undo->prev_vectors = g_value_get_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_vectors_undo_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpVectorsUndo *vectors_undo = GIMP_VECTORS_UNDO (object); + + switch (property_id) + { + case PROP_PREV_PARENT: + g_value_set_object (value, vectors_undo->prev_parent); + break; + case PROP_PREV_POSITION: + g_value_set_int (value, vectors_undo->prev_position); + break; + case PROP_PREV_VECTORS: + g_value_set_object (value, vectors_undo->prev_vectors); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static gint64 +gimp_vectors_undo_get_memsize (GimpObject *object, + gint64 *gui_size) +{ + GimpItemUndo *item_undo = GIMP_ITEM_UNDO (object); + gint64 memsize = 0; + + if (! gimp_item_is_attached (item_undo->item)) + memsize += gimp_object_get_memsize (GIMP_OBJECT (item_undo->item), + gui_size); + + return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object, + gui_size); +} + +static void +gimp_vectors_undo_pop (GimpUndo *undo, + GimpUndoMode undo_mode, + GimpUndoAccumulator *accum) +{ + GimpVectorsUndo *vectors_undo = GIMP_VECTORS_UNDO (undo); + GimpVectors *vectors = GIMP_VECTORS (GIMP_ITEM_UNDO (undo)->item); + + GIMP_UNDO_CLASS (parent_class)->pop (undo, undo_mode, accum); + + if ((undo_mode == GIMP_UNDO_MODE_UNDO && + undo->undo_type == GIMP_UNDO_VECTORS_ADD) || + (undo_mode == GIMP_UNDO_MODE_REDO && + undo->undo_type == GIMP_UNDO_VECTORS_REMOVE)) + { + /* remove vectors */ + + /* record the current parent and position */ + vectors_undo->prev_parent = gimp_vectors_get_parent (vectors); + vectors_undo->prev_position = gimp_item_get_index (GIMP_ITEM (vectors)); + + gimp_image_remove_vectors (undo->image, vectors, FALSE, + vectors_undo->prev_vectors); + } + else + { + /* restore vectors */ + + /* record the active vectors */ + vectors_undo->prev_vectors = gimp_image_get_active_vectors (undo->image); + + gimp_image_add_vectors (undo->image, vectors, + vectors_undo->prev_parent, + vectors_undo->prev_position, FALSE); + } +} diff --git a/app/vectors/gimpvectorsundo.h b/app/vectors/gimpvectorsundo.h new file mode 100644 index 0000000..9b54346 --- /dev/null +++ b/app/vectors/gimpvectorsundo.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_VECTORS_UNDO_H__ +#define __GIMP_VECTORS_UNDO_H__ + + +#include "core/gimpitemundo.h" + + +#define GIMP_TYPE_VECTORS_UNDO (gimp_vectors_undo_get_type ()) +#define GIMP_VECTORS_UNDO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_VECTORS_UNDO, GimpVectorsUndo)) +#define GIMP_VECTORS_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_VECTORS_UNDO, GimpVectorsUndoClass)) +#define GIMP_IS_VECTORS_UNDO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_VECTORS_UNDO)) +#define GIMP_IS_VECTORS_UNDO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_VECTORS_UNDO)) +#define GIMP_VECTORS_UNDO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_VECTORS_UNDO, GimpVectorsUndoClass)) + + +typedef struct _GimpVectorsUndo GimpVectorsUndo; +typedef struct _GimpVectorsUndoClass GimpVectorsUndoClass; + +struct _GimpVectorsUndo +{ + GimpItemUndo parent_instance; + + GimpVectors *prev_parent; + gint prev_position; /* former position in list */ + GimpVectors *prev_vectors; /* previous active vectors */ +}; + +struct _GimpVectorsUndoClass +{ + GimpItemUndoClass parent_class; +}; + + +GType gimp_vectors_undo_get_type (void) G_GNUC_CONST; + + +#endif /* __GIMP_VECTORS_UNDO_H__ */ diff --git a/app/vectors/vectors-enums.h b/app/vectors/vectors-enums.h new file mode 100644 index 0000000..c0c603b --- /dev/null +++ b/app/vectors/vectors-enums.h @@ -0,0 +1,46 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * vectors-enums.h + * Copyright (C) 2006 Simon Budig <simon@gimp.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 __VECTORS_ENUMS_H__ +#define __VECTORS_ENUMS_H__ + + +typedef enum +{ + GIMP_ANCHOR_ANCHOR, + GIMP_ANCHOR_CONTROL +} GimpAnchorType; + +typedef enum +{ + GIMP_ANCHOR_FEATURE_NONE, + GIMP_ANCHOR_FEATURE_EDGE, + GIMP_ANCHOR_FEATURE_ALIGNED, + GIMP_ANCHOR_FEATURE_SYMMETRIC +} GimpAnchorFeatureType; + +typedef enum +{ + EXTEND_SIMPLE, + EXTEND_EDITABLE +} GimpVectorExtendMode; + + +#endif /* __VECTORS_ENUMS_H__ */ diff --git a/app/vectors/vectors-types.h b/app/vectors/vectors-types.h new file mode 100644 index 0000000..7c0d2d3 --- /dev/null +++ b/app/vectors/vectors-types.h @@ -0,0 +1,37 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * vectors-types.h + * Copyright (C) 2002 Simon Budig <simon@gimp.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 __VECTORS_TYPES_H__ +#define __VECTORS_TYPES_H__ + + +#include "core/core-types.h" + +#include "vectors/vectors-enums.h" + + +typedef struct _GimpAnchor GimpAnchor; + +typedef struct _GimpVectors GimpVectors; +typedef struct _GimpStroke GimpStroke; +typedef struct _GimpBezierStroke GimpBezierStroke; + + +#endif /* __VECTORS_TYPES_H__ */ |