summaryrefslogtreecommitdiffstats
path: root/app/xcf
diff options
context:
space:
mode:
Diffstat (limited to 'app/xcf')
-rw-r--r--app/xcf/Makefile.am31
-rw-r--r--app/xcf/Makefile.in951
-rw-r--r--app/xcf/xcf-load.c3246
-rw-r--r--app/xcf/xcf-load.h27
-rw-r--r--app/xcf/xcf-private.h118
-rw-r--r--app/xcf/xcf-read.c274
-rw-r--r--app/xcf/xcf-read.h53
-rw-r--r--app/xcf/xcf-save.c2271
-rw-r--r--app/xcf/xcf-save.h27
-rw-r--r--app/xcf/xcf-seek.c53
-rw-r--r--app/xcf/xcf-seek.h27
-rw-r--r--app/xcf/xcf-utils.c53
-rw-r--r--app/xcf/xcf-utils.h26
-rw-r--r--app/xcf/xcf-write.c339
-rw-r--r--app/xcf/xcf-write.h64
-rw-r--r--app/xcf/xcf.c516
-rw-r--r--app/xcf/xcf.h38
17 files changed, 8114 insertions, 0 deletions
diff --git a/app/xcf/Makefile.am b/app/xcf/Makefile.am
new file mode 100644
index 0000000..a95a87c
--- /dev/null
+++ b/app/xcf/Makefile.am
@@ -0,0 +1,31 @@
+## Process this file with automake to produce Makefile.in
+
+AM_CPPFLAGS = \
+ -DG_LOG_DOMAIN=\"Gimp-XCF\" \
+ -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 = libappxcf.a
+
+libappxcf_a_SOURCES = \
+ xcf.c \
+ xcf.h \
+ xcf-load.c \
+ xcf-load.h \
+ xcf-read.c \
+ xcf-read.h \
+ xcf-private.h \
+ xcf-save.c \
+ xcf-save.h \
+ xcf-seek.c \
+ xcf-seek.h \
+ xcf-utils.c \
+ xcf-utils.h \
+ xcf-write.c \
+ xcf-write.h
diff --git a/app/xcf/Makefile.in b/app/xcf/Makefile.in
new file mode 100644
index 0000000..25b195f
--- /dev/null
+++ b/app/xcf/Makefile.in
@@ -0,0 +1,951 @@
+# 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/xcf
+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 =
+libappxcf_a_AR = $(AR) $(ARFLAGS)
+libappxcf_a_LIBADD =
+am_libappxcf_a_OBJECTS = xcf.$(OBJEXT) xcf-load.$(OBJEXT) \
+ xcf-read.$(OBJEXT) xcf-save.$(OBJEXT) xcf-seek.$(OBJEXT) \
+ xcf-utils.$(OBJEXT) xcf-write.$(OBJEXT)
+libappxcf_a_OBJECTS = $(am_libappxcf_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)/xcf-load.Po ./$(DEPDIR)/xcf-read.Po \
+ ./$(DEPDIR)/xcf-save.Po ./$(DEPDIR)/xcf-seek.Po \
+ ./$(DEPDIR)/xcf-utils.Po ./$(DEPDIR)/xcf-write.Po \
+ ./$(DEPDIR)/xcf.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 = $(libappxcf_a_SOURCES)
+DIST_SOURCES = $(libappxcf_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-XCF\" \
+ -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 = libappxcf.a
+libappxcf_a_SOURCES = \
+ xcf.c \
+ xcf.h \
+ xcf-load.c \
+ xcf-load.h \
+ xcf-read.c \
+ xcf-read.h \
+ xcf-private.h \
+ xcf-save.c \
+ xcf-save.h \
+ xcf-seek.c \
+ xcf-seek.h \
+ xcf-utils.c \
+ xcf-utils.h \
+ xcf-write.c \
+ xcf-write.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/xcf/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu app/xcf/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)
+
+libappxcf.a: $(libappxcf_a_OBJECTS) $(libappxcf_a_DEPENDENCIES) $(EXTRA_libappxcf_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libappxcf.a
+ $(AM_V_AR)$(libappxcf_a_AR) libappxcf.a $(libappxcf_a_OBJECTS) $(libappxcf_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libappxcf.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xcf-load.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xcf-read.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xcf-save.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xcf-seek.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xcf-utils.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xcf-write.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/xcf.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)/xcf-load.Po
+ -rm -f ./$(DEPDIR)/xcf-read.Po
+ -rm -f ./$(DEPDIR)/xcf-save.Po
+ -rm -f ./$(DEPDIR)/xcf-seek.Po
+ -rm -f ./$(DEPDIR)/xcf-utils.Po
+ -rm -f ./$(DEPDIR)/xcf-write.Po
+ -rm -f ./$(DEPDIR)/xcf.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)/xcf-load.Po
+ -rm -f ./$(DEPDIR)/xcf-read.Po
+ -rm -f ./$(DEPDIR)/xcf-save.Po
+ -rm -f ./$(DEPDIR)/xcf-seek.Po
+ -rm -f ./$(DEPDIR)/xcf-utils.Po
+ -rm -f ./$(DEPDIR)/xcf-write.Po
+ -rm -f ./$(DEPDIR)/xcf.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/xcf/xcf-load.c b/app/xcf/xcf-load.c
new file mode 100644
index 0000000..da196bc
--- /dev/null
+++ b/app/xcf/xcf-load.c
@@ -0,0 +1,3246 @@
+/* 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 <zlib.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core/core-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "gegl/gimp-babl.h"
+#include "gegl/gimp-gegl-tile-compat.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontainer.h"
+#include "core/gimpdrawable-private.h" /* eek */
+#include "core/gimpgrid.h"
+#include "core/gimpgrouplayer.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-colormap.h"
+#include "core/gimpimage-grid.h"
+#include "core/gimpimage-guides.h"
+#include "core/gimpimage-metadata.h"
+#include "core/gimpimage-private.h"
+#include "core/gimpimage-sample-points.h"
+#include "core/gimpimage-symmetry.h"
+#include "core/gimpimage-undo.h"
+#include "core/gimpitemstack.h"
+#include "core/gimplayer-floating-selection.h"
+#include "core/gimplayer-new.h"
+#include "core/gimplayermask.h"
+#include "core/gimpparasitelist.h"
+#include "core/gimpprogress.h"
+#include "core/gimpselection.h"
+#include "core/gimpsymmetry.h"
+#include "core/gimptemplate.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "text/gimptextlayer.h"
+#include "text/gimptextlayer-xcf.h"
+
+#include "vectors/gimpanchor.h"
+#include "vectors/gimpstroke.h"
+#include "vectors/gimpbezierstroke.h"
+#include "vectors/gimpvectors.h"
+#include "vectors/gimpvectors-compat.h"
+
+#include "xcf-private.h"
+#include "xcf-load.h"
+#include "xcf-read.h"
+#include "xcf-seek.h"
+#include "xcf-utils.h"
+
+#include "gimp-log.h"
+#include "gimp-intl.h"
+
+
+#define MAX_XCF_PARASITE_DATA_LEN (256L * 1024 * 1024)
+
+/* #define GIMP_XCF_PATH_DEBUG */
+
+
+static void xcf_load_add_masks (GimpImage *image);
+static gboolean xcf_load_image_props (XcfInfo *info,
+ GimpImage *image);
+static gboolean xcf_load_layer_props (XcfInfo *info,
+ GimpImage *image,
+ GimpLayer **layer,
+ GList **item_path,
+ gboolean *apply_mask,
+ gboolean *edit_mask,
+ gboolean *show_mask,
+ guint32 *text_layer_flags,
+ guint32 *group_layer_flags);
+static gboolean xcf_check_layer_props (XcfInfo *info,
+ GList **item_path,
+ gboolean *is_group_layer,
+ gboolean *is_text_layer);
+static gboolean xcf_load_channel_props (XcfInfo *info,
+ GimpImage *image,
+ GimpChannel **channel);
+static gboolean xcf_load_prop (XcfInfo *info,
+ PropType *prop_type,
+ guint32 *prop_size);
+static GimpLayer * xcf_load_layer (XcfInfo *info,
+ GimpImage *image,
+ GList **item_path);
+static GimpChannel * xcf_load_channel (XcfInfo *info,
+ GimpImage *image);
+static GimpLayerMask * xcf_load_layer_mask (XcfInfo *info,
+ GimpImage *image);
+static gboolean xcf_load_buffer (XcfInfo *info,
+ GeglBuffer *buffer);
+static gboolean xcf_load_level (XcfInfo *info,
+ GeglBuffer *buffer);
+static gboolean xcf_load_tile (XcfInfo *info,
+ GeglBuffer *buffer,
+ GeglRectangle *tile_rect,
+ const Babl *format);
+static gboolean xcf_load_tile_rle (XcfInfo *info,
+ GeglBuffer *buffer,
+ GeglRectangle *tile_rect,
+ const Babl *format,
+ gint data_length);
+static gboolean xcf_load_tile_zlib (XcfInfo *info,
+ GeglBuffer *buffer,
+ GeglRectangle *tile_rect,
+ const Babl *format,
+ gint data_length);
+static GimpParasite * xcf_load_parasite (XcfInfo *info);
+static gboolean xcf_load_old_paths (XcfInfo *info,
+ GimpImage *image);
+static gboolean xcf_load_old_path (XcfInfo *info,
+ GimpImage *image);
+static gboolean xcf_load_vectors (XcfInfo *info,
+ GimpImage *image);
+static gboolean xcf_load_vector (XcfInfo *info,
+ GimpImage *image);
+
+static gboolean xcf_skip_unknown_prop (XcfInfo *info,
+ gsize size);
+
+static gboolean xcf_item_path_is_parent (GList *path,
+ GList *parent_path);
+static void xcf_fix_item_path (GimpLayer *layer,
+ GList **path,
+ GList *broken_paths);
+
+#define xcf_progress_update(info) G_STMT_START \
+ { \
+ if (info->progress) \
+ gimp_progress_pulse (info->progress); \
+ } G_STMT_END
+
+
+GimpImage *
+xcf_load_image (Gimp *gimp,
+ XcfInfo *info,
+ GError **error)
+{
+ GimpImage *image = NULL;
+ const GimpParasite *parasite;
+ gboolean has_metadata = FALSE;
+ goffset saved_pos;
+ goffset offset;
+ gint width;
+ gint height;
+ gint image_type;
+ GimpPrecision precision = GIMP_PRECISION_U8_GAMMA;
+ gint num_successful_elements = 0;
+ gint n_broken_layers = 0;
+ gint n_broken_channels = 0;
+ GList *broken_paths = NULL;
+ GList *group_layers = NULL;
+ GList *syms;
+ GList *iter;
+
+ /* read in the image width, height and type */
+ xcf_read_int32 (info, (guint32 *) &width, 1);
+ xcf_read_int32 (info, (guint32 *) &height, 1);
+ xcf_read_int32 (info, (guint32 *) &image_type, 1);
+ if (image_type < GIMP_RGB || image_type > GIMP_INDEXED)
+ goto hard_error;
+
+ /* Be lenient with corrupt image dimensions.
+ * Hopefully layer dimensions will be valid. */
+ if (width <= 0 || height <= 0 ||
+ width > GIMP_MAX_IMAGE_SIZE || height > GIMP_MAX_IMAGE_SIZE)
+ {
+ GIMP_LOG (XCF, "Invalid image size %d x %d, setting to 1x1.", width, height);
+ width = 1;
+ height = 1;
+ }
+
+ if (info->file_version >= 4)
+ {
+ gint p;
+
+ xcf_read_int32 (info, (guint32 *) &p, 1);
+
+ if (info->file_version == 4)
+ {
+ switch (p)
+ {
+ case 0: precision = GIMP_PRECISION_U8_GAMMA; break;
+ case 1: precision = GIMP_PRECISION_U16_GAMMA; break;
+ case 2: precision = GIMP_PRECISION_U32_LINEAR; break;
+ case 3: precision = GIMP_PRECISION_HALF_LINEAR; break;
+ case 4: precision = GIMP_PRECISION_FLOAT_LINEAR; break;
+ default:
+ goto hard_error;
+ }
+ }
+ else if (info->file_version == 5 ||
+ info->file_version == 6)
+ {
+ switch (p)
+ {
+ case 100: precision = GIMP_PRECISION_U8_LINEAR; break;
+ case 150: precision = GIMP_PRECISION_U8_GAMMA; break;
+ case 200: precision = GIMP_PRECISION_U16_LINEAR; break;
+ case 250: precision = GIMP_PRECISION_U16_GAMMA; break;
+ case 300: precision = GIMP_PRECISION_U32_LINEAR; break;
+ case 350: precision = GIMP_PRECISION_U32_GAMMA; break;
+ case 400: precision = GIMP_PRECISION_HALF_LINEAR; break;
+ case 450: precision = GIMP_PRECISION_HALF_GAMMA; break;
+ case 500: precision = GIMP_PRECISION_FLOAT_LINEAR; break;
+ case 550: precision = GIMP_PRECISION_FLOAT_GAMMA; break;
+ default:
+ goto hard_error;
+ }
+ }
+ else
+ {
+ precision = p;
+ }
+ }
+
+ GIMP_LOG (XCF, "version=%d, width=%d, height=%d, image_type=%d, precision=%d",
+ info->file_version, width, height, image_type, precision);
+
+ if (! gimp_babl_is_valid (image_type, precision))
+ {
+ gimp_message_literal (gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_ERROR,
+ _("Invalid image mode and precision combination."));
+ goto hard_error;
+ }
+
+ image = gimp_create_image (gimp, width, height, image_type, precision,
+ FALSE);
+
+ gimp_image_undo_disable (image);
+
+ xcf_progress_update (info);
+
+ /* read the image properties */
+ if (! xcf_load_image_props (info, image))
+ goto hard_error;
+
+ GIMP_LOG (XCF, "image props loaded");
+
+ /* check for a GimpGrid parasite */
+ parasite = gimp_image_parasite_find (GIMP_IMAGE (image),
+ gimp_grid_parasite_name ());
+ if (parasite)
+ {
+ GimpGrid *grid = gimp_grid_from_parasite (parasite);
+
+ if (grid)
+ {
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ gimp_parasite_list_remove (private->parasites,
+ gimp_parasite_name (parasite));
+
+ gimp_image_set_grid (GIMP_IMAGE (image), grid, FALSE);
+ g_object_unref (grid);
+ }
+ }
+
+ /* check for a metadata parasite */
+ parasite = gimp_image_parasite_find (GIMP_IMAGE (image),
+ "gimp-image-metadata");
+ if (parasite)
+ {
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ GimpMetadata *metadata = NULL;
+ const gchar *meta_string;
+
+ meta_string = (gchar *) gimp_parasite_data (parasite);
+
+ if (meta_string)
+ metadata = gimp_metadata_deserialize (meta_string);
+
+ if (metadata)
+ {
+ has_metadata = TRUE;
+
+ gimp_image_set_metadata (image, metadata, FALSE);
+ g_object_unref (metadata);
+ }
+
+ gimp_parasite_list_remove (private->parasites,
+ gimp_parasite_name (parasite));
+ }
+
+ /* check for symmetry parasites */
+ syms = gimp_image_symmetry_list ();
+ for (iter = syms; iter; iter = g_list_next (iter))
+ {
+ GType type = (GType) iter->data;
+ gchar *parasite_name = gimp_symmetry_parasite_name (type);
+
+ parasite = gimp_image_parasite_find (image,
+ parasite_name);
+ g_free (parasite_name);
+ if (parasite)
+ {
+ GimpSymmetry *sym = gimp_symmetry_from_parasite (parasite,
+ image,
+ type);
+
+ if (sym)
+ {
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ gimp_parasite_list_remove (private->parasites,
+ gimp_parasite_name (parasite));
+
+ gimp_image_symmetry_add (image, sym);
+
+ g_signal_emit_by_name (sym, "active-changed", NULL);
+ if (sym->active)
+ gimp_image_set_active_symmetry (image, type);
+ }
+ }
+ }
+ g_list_free (syms);
+
+ /* migrate the old "exif-data" parasite */
+ parasite = gimp_image_parasite_find (GIMP_IMAGE (image),
+ "exif-data");
+ if (parasite)
+ {
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ if (has_metadata)
+ {
+ g_printerr ("xcf-load: inconsistent metadata discovered: XCF file "
+ "has both 'gimp-image-metadata' and 'exif-data' "
+ "parasites, dropping old 'exif-data'\n");
+ }
+ else
+ {
+ GimpMetadata *metadata = gimp_image_get_metadata (image);
+ GError *my_error = NULL;
+
+ if (metadata)
+ g_object_ref (metadata);
+ else
+ metadata = gimp_metadata_new ();
+
+ if (! gimp_metadata_set_from_exif (metadata,
+ gimp_parasite_data (parasite),
+ gimp_parasite_data_size (parasite),
+ &my_error))
+ {
+ gimp_message (gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ _("Corrupt 'exif-data' parasite discovered.\n"
+ "Exif data could not be migrated: %s"),
+ my_error->message);
+ g_clear_error (&my_error);
+ }
+ else
+ {
+ gimp_image_set_metadata (image, metadata, FALSE);
+ }
+
+ g_object_unref (metadata);
+ }
+
+ gimp_parasite_list_remove (private->parasites,
+ gimp_parasite_name (parasite));
+ }
+
+ /* migrate the old "gimp-metadata" parasite */
+ parasite = gimp_image_parasite_find (GIMP_IMAGE (image),
+ "gimp-metadata");
+ if (parasite)
+ {
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ const gchar *xmp_data = gimp_parasite_data (parasite);
+ gint xmp_length = gimp_parasite_data_size (parasite);
+
+ if (has_metadata)
+ {
+ g_printerr ("xcf-load: inconsistent metadata discovered: XCF file "
+ "has both 'gimp-image-metadata' and 'gimp-metadata' "
+ "parasites, dropping old 'gimp-metadata'\n");
+ }
+ else if (xmp_length < 14 ||
+ strncmp (xmp_data, "GIMP_XMP_1", 10) != 0)
+ {
+ gimp_message (gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ _("Corrupt 'gimp-metadata' parasite discovered.\n"
+ "XMP data could not be migrated."));
+ }
+ else
+ {
+ GimpMetadata *metadata = gimp_image_get_metadata (image);
+ GError *my_error = NULL;
+
+ if (metadata)
+ g_object_ref (metadata);
+ else
+ metadata = gimp_metadata_new ();
+
+ if (! gimp_metadata_set_from_xmp (metadata,
+ (const guint8 *) xmp_data + 10,
+ xmp_length - 10,
+ &my_error))
+ {
+ /* XMP metadata from 2.8.x or earlier can be really messed up.
+ * Let's make the message more user friendly so they will
+ * understand that we can't do anything about it.
+ * See issue #987. */
+ gimp_message (gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ _("Corrupt XMP metadata saved by an older version of "
+ "GIMP could not be converted and will be ignored.\n"
+ "If you don't know what XMP is, you most likely don't "
+ "need it. Reported error: %s."),
+ my_error->message);
+ g_clear_error (&my_error);
+ }
+ else
+ {
+ gimp_image_set_metadata (image, metadata, FALSE);
+ }
+
+ g_object_unref (metadata);
+ }
+
+ gimp_parasite_list_remove (private->parasites,
+ gimp_parasite_name (parasite));
+ }
+
+ /* check for a gimp-xcf-compatibility-mode parasite */
+ parasite = gimp_image_parasite_find (GIMP_IMAGE (image),
+ "gimp-xcf-compatibility-mode");
+ if (parasite)
+ {
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+
+ /* just ditch it, it's unused but shouldn't be re-saved */
+ gimp_parasite_list_remove (private->parasites,
+ gimp_parasite_name (parasite));
+ }
+
+ xcf_progress_update (info);
+
+ while (TRUE)
+ {
+ GimpLayer *layer;
+ GList *item_path = NULL;
+
+ /* read in the offset of the next layer */
+ xcf_read_offset (info, &offset, 1);
+
+ /* if the offset is 0 then we are at the end
+ * of the layer list.
+ */
+ if (offset == 0)
+ break;
+
+ /* save the current position as it is where the
+ * next layer offset is stored.
+ */
+ saved_pos = info->cp;
+
+ if (offset < saved_pos)
+ {
+ GIMP_LOG (XCF, "Invalid layer offset: %" G_GOFFSET_FORMAT
+ " at offset: %" G_GOFFSET_FORMAT, offset, saved_pos);
+ goto error;
+ }
+
+ /* seek to the layer offset */
+ if (! xcf_seek_pos (info, offset, NULL))
+ goto error;
+
+ /* read in the layer */
+ layer = xcf_load_layer (info, image, &item_path);
+ if (! layer)
+ {
+ n_broken_layers++;
+
+ if (! xcf_seek_pos (info, saved_pos, NULL))
+ {
+ if (item_path)
+ g_list_free (item_path);
+
+ goto error;
+ }
+
+ /* Don't just stop at the first broken layer. Load as much as
+ * possible.
+ */
+ if (! item_path)
+ {
+ GimpContainer *layers = gimp_image_get_layers (image);
+
+ item_path = g_list_prepend (NULL,
+ GUINT_TO_POINTER (gimp_container_get_n_children (layers)));
+
+ broken_paths = g_list_prepend (broken_paths, item_path);
+ }
+
+ continue;
+ }
+
+ if (broken_paths && item_path)
+ {
+ /* Item paths may be a problem when layers are missing. */
+ xcf_fix_item_path (layer, &item_path, broken_paths);
+ }
+
+ num_successful_elements++;
+
+ xcf_progress_update (info);
+
+ /* suspend layer-group size updates */
+ if (GIMP_IS_GROUP_LAYER (layer))
+ {
+ GimpGroupLayer *group = GIMP_GROUP_LAYER (layer);
+
+ group_layers = g_list_prepend (group_layers, group);
+
+ gimp_group_layer_suspend_resize (group, FALSE);
+ }
+
+ /* add the layer to the image if its not the floating selection */
+ if (layer != info->floating_sel)
+ {
+ GimpContainer *layers = gimp_image_get_layers (image);
+ GimpContainer *container;
+ GimpLayer *parent;
+
+ if (item_path)
+ {
+ if (info->floating_sel)
+ {
+ /* there is a floating selection, but it will get
+ * added after all layers are loaded, so toplevel
+ * layer indices are off-by-one. Adjust item paths
+ * accordingly:
+ */
+ gint toplevel_index;
+
+ toplevel_index = GPOINTER_TO_UINT (item_path->data);
+
+ toplevel_index--;
+
+ item_path->data = GUINT_TO_POINTER (toplevel_index);
+ }
+
+ parent = GIMP_LAYER
+ (gimp_item_stack_get_parent_by_path (GIMP_ITEM_STACK (layers),
+ item_path,
+ NULL));
+
+ container = gimp_viewable_get_children (GIMP_VIEWABLE (parent));
+
+ g_list_free (item_path);
+ }
+ else
+ {
+ parent = NULL;
+ container = layers;
+ }
+
+ gimp_image_add_layer (image, layer,
+ parent,
+ gimp_container_get_n_children (container),
+ FALSE);
+ }
+
+ /* restore the saved position so we'll be ready to
+ * read the next offset.
+ */
+ if (! xcf_seek_pos (info, saved_pos, NULL))
+ goto error;
+ }
+
+ /* resume layer-group size updates, in reverse order */
+ for (iter = group_layers; iter; iter = g_list_next (iter))
+ {
+ GimpGroupLayer *group = iter->data;
+
+ gimp_group_layer_resume_resize (group, FALSE);
+ }
+ g_clear_pointer (&group_layers, g_list_free);
+
+ if (broken_paths)
+ {
+ g_list_free_full (broken_paths, (GDestroyNotify) g_list_free);
+ broken_paths = NULL;
+ }
+
+ while (TRUE)
+ {
+ GimpChannel *channel;
+
+ /* read in the offset of the next channel */
+ xcf_read_offset (info, &offset, 1);
+
+ /* if the offset is 0 then we are at the end
+ * of the channel list.
+ */
+ if (offset == 0)
+ break;
+
+ /* save the current position as it is where the
+ * next channel offset is stored.
+ */
+ saved_pos = info->cp;
+
+ if (offset < saved_pos)
+ {
+ GIMP_LOG (XCF, "Invalid channel offset: %" G_GOFFSET_FORMAT
+ " at offset: % "G_GOFFSET_FORMAT, offset, saved_pos);
+ goto error;
+ }
+
+ /* seek to the channel offset */
+ if (! xcf_seek_pos (info, offset, NULL))
+ goto error;
+
+ /* read in the channel */
+ channel = xcf_load_channel (info, image);
+ if (!channel)
+ {
+ n_broken_channels++;
+ GIMP_LOG (XCF, "Failed to load channel.");
+
+ if (! xcf_seek_pos (info, saved_pos, NULL))
+ goto error;
+
+ continue;
+ }
+
+ num_successful_elements++;
+
+ xcf_progress_update (info);
+
+ /* add the channel to the image if its not the selection */
+ if (channel != gimp_image_get_mask (image))
+ gimp_image_add_channel (image, channel,
+ NULL, /* FIXME tree */
+ gimp_container_get_n_children (gimp_image_get_channels (image)),
+ FALSE);
+
+ /* restore the saved position so we'll be ready to
+ * read the next offset.
+ */
+ if (! xcf_seek_pos (info, saved_pos, NULL))
+ goto error;
+ }
+
+ if (n_broken_layers == 0 && n_broken_channels == 0)
+ xcf_load_add_masks (image);
+
+ if (info->floating_sel && info->floating_sel_drawable)
+ floating_sel_attach (info->floating_sel, info->floating_sel_drawable);
+
+ if (info->active_layer)
+ gimp_image_set_active_layer (image, info->active_layer);
+
+ if (info->active_channel)
+ gimp_image_set_active_channel (image, info->active_channel);
+
+ if (info->file)
+ gimp_image_set_file (image, info->file);
+
+ if (info->tattoo_state > 0)
+ gimp_image_set_tattoo_state (image, info->tattoo_state);
+
+ if (n_broken_layers > 0 || n_broken_channels > 0)
+ goto error;
+
+ gimp_image_undo_enable (image);
+
+ return image;
+
+ error:
+ if (num_successful_elements == 0)
+ goto hard_error;
+
+ g_clear_pointer (&group_layers, g_list_free);
+
+ if (broken_paths)
+ {
+ g_list_free_full (broken_paths, (GDestroyNotify) g_list_free);
+ broken_paths = NULL;
+ }
+
+ gimp_message_literal (gimp, G_OBJECT (info->progress), GIMP_MESSAGE_WARNING,
+ _("This XCF file is corrupt! I have loaded as much "
+ "of it as I can, but it is incomplete."));
+
+ xcf_load_add_masks (image);
+
+ gimp_image_undo_enable (image);
+
+ return image;
+
+ hard_error:
+ g_clear_pointer (&group_layers, g_list_free);
+
+ if (broken_paths)
+ {
+ g_list_free_full (broken_paths, (GDestroyNotify) g_list_free);
+ broken_paths = NULL;
+ }
+
+ g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("This XCF file is corrupt! I could not even "
+ "salvage any partial image data from it."));
+
+ g_clear_object (&image);
+
+ return NULL;
+}
+
+static void
+xcf_load_add_masks (GimpImage *image)
+{
+ GList *layers;
+ GList *list;
+
+ layers = gimp_image_get_layer_list (image);
+
+ for (list = layers; list; list = g_list_next (list))
+ {
+ GimpLayer *layer = list->data;
+ GimpLayerMask *mask;
+
+ mask = g_object_get_data (G_OBJECT (layer), "gimp-layer-mask");
+
+ if (mask)
+ {
+ gboolean apply_mask;
+ gboolean edit_mask;
+ gboolean show_mask;
+
+ apply_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (layer),
+ "gimp-layer-mask-apply"));
+ edit_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (layer),
+ "gimp-layer-mask-edit"));
+ show_mask = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (layer),
+ "gimp-layer-mask-show"));
+
+ gimp_layer_add_mask (layer, mask, FALSE, NULL);
+
+ gimp_layer_set_apply_mask (layer, apply_mask, FALSE);
+ gimp_layer_set_edit_mask (layer, edit_mask);
+ gimp_layer_set_show_mask (layer, show_mask, FALSE);
+
+ g_object_set_data (G_OBJECT (layer), "gimp-layer-mask", NULL);
+ g_object_set_data (G_OBJECT (layer), "gimp-layer-mask-apply", NULL);
+ g_object_set_data (G_OBJECT (layer), "gimp-layer-mask-edit", NULL);
+ g_object_set_data (G_OBJECT (layer), "gimp-layer-mask-show", NULL);
+ }
+ }
+
+ g_list_free (layers);
+}
+
+static gboolean
+xcf_load_image_props (XcfInfo *info,
+ GimpImage *image)
+{
+ PropType prop_type;
+ guint32 prop_size;
+
+ while (TRUE)
+ {
+ if (! xcf_load_prop (info, &prop_type, &prop_size))
+ return FALSE;
+
+ switch (prop_type)
+ {
+ case PROP_END:
+ return TRUE;
+
+ case PROP_COLORMAP:
+ {
+ guint32 n_colors;
+ guchar cmap[GIMP_IMAGE_COLORMAP_SIZE];
+
+ xcf_read_int32 (info, &n_colors, 1);
+
+ if (n_colors > (GIMP_IMAGE_COLORMAP_SIZE / 3))
+ {
+ gimp_message (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_ERROR,
+ "Maximum colormap size (%d) exceeded",
+ GIMP_IMAGE_COLORMAP_SIZE);
+ return FALSE;
+ }
+
+ if (info->file_version == 0)
+ {
+ gint i;
+
+ gimp_message_literal (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ _("XCF warning: version 0 of XCF file format\n"
+ "did not save indexed colormaps correctly.\n"
+ "Substituting grayscale map."));
+
+ if (! xcf_seek_pos (info, info->cp + n_colors, NULL))
+ return FALSE;
+
+ for (i = 0; i < n_colors; i++)
+ {
+ cmap[i * 3 + 0] = i;
+ cmap[i * 3 + 1] = i;
+ cmap[i * 3 + 2] = i;
+ }
+ }
+ else
+ {
+ xcf_read_int8 (info, cmap, n_colors * 3);
+ }
+
+ /* only set color map if image is indexed, this is just
+ * sanity checking to make sure gimp doesn't end up with
+ * an image state that is impossible.
+ */
+ if (gimp_image_get_base_type (image) == GIMP_INDEXED)
+ gimp_image_set_colormap (image, cmap, n_colors, FALSE);
+
+ GIMP_LOG (XCF, "prop colormap n_colors=%d", n_colors);
+ }
+ break;
+
+ case PROP_COMPRESSION:
+ {
+ guint8 compression;
+
+ xcf_read_int8 (info, (guint8 *) &compression, 1);
+
+ if ((compression != COMPRESS_NONE) &&
+ (compression != COMPRESS_RLE) &&
+ (compression != COMPRESS_ZLIB) &&
+ (compression != COMPRESS_FRACTAL))
+ {
+ gimp_message (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_ERROR,
+ "Unknown compression type: %d",
+ (gint) compression);
+ return FALSE;
+ }
+
+ info->compression = compression;
+
+ gimp_image_set_xcf_compression (image,
+ compression >= COMPRESS_ZLIB);
+
+ GIMP_LOG (XCF, "prop compression=%d", compression);
+ }
+ break;
+
+ case PROP_GUIDES:
+ {
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ gint32 position;
+ gint8 orientation;
+ gint i, nguides;
+
+ nguides = prop_size / (4 + 1);
+ for (i = 0; i < nguides; i++)
+ {
+ xcf_read_int32 (info, (guint32 *) &position, 1);
+ xcf_read_int8 (info, (guint8 *) &orientation, 1);
+
+ /* skip -1 guides from old XCFs */
+ if (position < 0)
+ continue;
+
+ GIMP_LOG (XCF, "prop guide orientation=%d position=%d",
+ orientation, position);
+
+ switch (orientation)
+ {
+ case XCF_ORIENTATION_HORIZONTAL:
+ gimp_image_add_hguide (image, position, FALSE);
+ break;
+
+ case XCF_ORIENTATION_VERTICAL:
+ gimp_image_add_vguide (image, position, FALSE);
+ break;
+
+ default:
+ gimp_message_literal (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ "Guide orientation out of range in XCF file");
+ continue;
+ }
+ }
+
+ /* this is silly as the order of guides doesn't really matter,
+ * but it restores the list to its original order, which
+ * cannot be wrong --Mitch
+ */
+ private->guides = g_list_reverse (private->guides);
+ }
+ break;
+
+ case PROP_SAMPLE_POINTS:
+ {
+ gint n_sample_points, i;
+
+ n_sample_points = prop_size / (5 * 4);
+ for (i = 0; i < n_sample_points; i++)
+ {
+ GimpSamplePoint *sample_point;
+ gint32 x, y;
+ GimpColorPickMode pick_mode;
+ guint32 padding[2] = { 0, };
+
+ xcf_read_int32 (info, (guint32 *) &x, 1);
+ xcf_read_int32 (info, (guint32 *) &y, 1);
+ xcf_read_int32 (info, (guint32 *) &pick_mode, 1);
+ xcf_read_int32 (info, (guint32 *) padding, 2);
+
+ GIMP_LOG (XCF, "prop sample point x=%d y=%d mode=%d",
+ x, y, pick_mode);
+
+ if (pick_mode > GIMP_COLOR_PICK_MODE_LAST)
+ pick_mode = GIMP_COLOR_PICK_MODE_PIXEL;
+
+ sample_point = gimp_image_add_sample_point_at_pos (image,
+ x, y, FALSE);
+ gimp_image_set_sample_point_pick_mode (image, sample_point,
+ pick_mode, FALSE);
+ }
+ }
+ break;
+
+ case PROP_OLD_SAMPLE_POINTS:
+ {
+ gint32 x, y;
+ gint i, n_sample_points;
+
+ /* if there are already sample points, we loaded the new
+ * prop before
+ */
+ if (gimp_image_get_sample_points (image))
+ {
+ if (! xcf_skip_unknown_prop (info, prop_size))
+ return FALSE;
+
+ break;
+ }
+
+ n_sample_points = prop_size / (4 + 4);
+ for (i = 0; i < n_sample_points; i++)
+ {
+ xcf_read_int32 (info, (guint32 *) &x, 1);
+ xcf_read_int32 (info, (guint32 *) &y, 1);
+
+ GIMP_LOG (XCF, "prop old sample point x=%d y=%d", x, y);
+
+ gimp_image_add_sample_point_at_pos (image, x, y, FALSE);
+ }
+ }
+ break;
+
+ case PROP_RESOLUTION:
+ {
+ gfloat xres, yres;
+
+ xcf_read_float (info, &xres, 1);
+ xcf_read_float (info, &yres, 1);
+
+ GIMP_LOG (XCF, "prop resolution x=%f y=%f", xres, yres);
+
+ if (xres < GIMP_MIN_RESOLUTION || xres > GIMP_MAX_RESOLUTION ||
+ yres < GIMP_MIN_RESOLUTION || yres > GIMP_MAX_RESOLUTION)
+ {
+ GimpTemplate *template = image->gimp->config->default_image;
+
+ gimp_message_literal (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ "Warning, resolution out of range in XCF file");
+ xres = gimp_template_get_resolution_x (template);
+ yres = gimp_template_get_resolution_y (template);
+ }
+
+ gimp_image_set_resolution (image, xres, yres);
+ }
+ break;
+
+ case PROP_TATTOO:
+ {
+ xcf_read_int32 (info, &info->tattoo_state, 1);
+
+ GIMP_LOG (XCF, "prop tattoo state=%d", info->tattoo_state);
+ }
+ break;
+
+ case PROP_PARASITES:
+ {
+ goffset base = info->cp;
+
+ while (info->cp - base < prop_size)
+ {
+ GimpParasite *p = xcf_load_parasite (info);
+ GError *error = NULL;
+
+ if (! p)
+ {
+ gimp_message (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ "Invalid image parasite found. "
+ "Possibly corrupt XCF file.");
+
+ xcf_seek_pos (info, base + prop_size, NULL);
+ continue;
+ }
+
+ if (! gimp_image_parasite_validate (image, p, &error))
+ {
+ gimp_message (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ "Warning, invalid image parasite in XCF file: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+ else
+ {
+ gimp_image_parasite_attach (image, p, FALSE);
+ }
+
+ gimp_parasite_free (p);
+ }
+
+ if (info->cp - base != prop_size)
+ gimp_message_literal (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ "Error while loading an image's parasites");
+ }
+ break;
+
+ case PROP_UNIT:
+ {
+ guint32 unit;
+
+ xcf_read_int32 (info, &unit, 1);
+
+ GIMP_LOG (XCF, "prop unit=%d", unit);
+
+ if ((unit <= GIMP_UNIT_PIXEL) ||
+ (unit >= gimp_unit_get_number_of_built_in_units ()))
+ {
+ gimp_message_literal (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ "Warning, unit out of range in XCF file, "
+ "falling back to inches");
+ unit = GIMP_UNIT_INCH;
+ }
+
+ gimp_image_set_unit (image, unit);
+ }
+ break;
+
+ case PROP_PATHS:
+ {
+ goffset base = info->cp;
+
+ if (! xcf_load_old_paths (info, image))
+ xcf_seek_pos (info, base + prop_size, NULL);
+ }
+ break;
+
+ case PROP_USER_UNIT:
+ {
+ gchar *unit_strings[5];
+ float factor;
+ guint32 digits;
+ GimpUnit unit;
+ gint num_units;
+ gint i;
+
+ xcf_read_float (info, &factor, 1);
+ xcf_read_int32 (info, &digits, 1);
+ xcf_read_string (info, unit_strings, 5);
+
+ for (i = 0; i < 5; i++)
+ if (unit_strings[i] == NULL)
+ unit_strings[i] = g_strdup ("");
+
+ num_units = gimp_unit_get_number_of_units ();
+
+ for (unit = gimp_unit_get_number_of_built_in_units ();
+ unit < num_units; unit++)
+ {
+ /* if the factor and the identifier match some unit
+ * in unitrc, use the unitrc unit
+ */
+ if ((ABS (gimp_unit_get_factor (unit) - factor) < 1e-5) &&
+ (strcmp (unit_strings[0],
+ gimp_unit_get_identifier (unit)) == 0))
+ {
+ break;
+ }
+ }
+
+ /* no match */
+ if (unit == num_units)
+ unit = gimp_unit_new (unit_strings[0],
+ factor,
+ digits,
+ unit_strings[1],
+ unit_strings[2],
+ unit_strings[3],
+ unit_strings[4]);
+
+ gimp_image_set_unit (image, unit);
+
+ for (i = 0; i < 5; i++)
+ g_free (unit_strings[i]);
+ }
+ break;
+
+ case PROP_VECTORS:
+ {
+ goffset base = info->cp;
+
+ if (xcf_load_vectors (info, image))
+ {
+ if (base + prop_size != info->cp)
+ {
+ g_printerr ("Mismatch in PROP_VECTORS size: "
+ "skipping %" G_GOFFSET_FORMAT " bytes.\n",
+ base + prop_size - info->cp);
+ xcf_seek_pos (info, base + prop_size, NULL);
+ }
+ }
+ else
+ {
+ /* skip silently since we don't understand the format and
+ * xcf_load_vectors already explained what was wrong
+ */
+ xcf_seek_pos (info, base + prop_size, NULL);
+ }
+ }
+ break;
+
+ default:
+#ifdef GIMP_UNSTABLE
+ g_printerr ("unexpected/unknown image property: %d (skipping)\n",
+ prop_type);
+#endif
+ if (! xcf_skip_unknown_prop (info, prop_size))
+ return FALSE;
+ break;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+xcf_load_layer_props (XcfInfo *info,
+ GimpImage *image,
+ GimpLayer **layer,
+ GList **item_path,
+ gboolean *apply_mask,
+ gboolean *edit_mask,
+ gboolean *show_mask,
+ guint32 *text_layer_flags,
+ guint32 *group_layer_flags)
+{
+ PropType prop_type;
+ guint32 prop_size;
+
+ while (TRUE)
+ {
+ if (! xcf_load_prop (info, &prop_type, &prop_size))
+ return FALSE;
+
+ switch (prop_type)
+ {
+ case PROP_END:
+ return TRUE;
+
+ case PROP_ACTIVE_LAYER:
+ info->active_layer = *layer;
+ break;
+
+ case PROP_FLOATING_SELECTION:
+ info->floating_sel = *layer;
+ xcf_read_offset (info, &info->floating_sel_offset, 1);
+ break;
+
+ case PROP_OPACITY:
+ {
+ guint32 opacity;
+
+ xcf_read_int32 (info, &opacity, 1);
+
+ gimp_layer_set_opacity (*layer, (gdouble) opacity / 255.0, FALSE);
+ }
+ break;
+
+ case PROP_FLOAT_OPACITY:
+ {
+ gfloat opacity;
+
+ xcf_read_float (info, &opacity, 1);
+
+ gimp_layer_set_opacity (*layer, opacity, FALSE);
+ }
+ break;
+
+ case PROP_VISIBLE:
+ {
+ gboolean visible;
+
+ xcf_read_int32 (info, (guint32 *) &visible, 1);
+
+ gimp_item_set_visible (GIMP_ITEM (*layer), visible, FALSE);
+ }
+ break;
+
+ case PROP_LINKED:
+ {
+ gboolean linked;
+
+ xcf_read_int32 (info, (guint32 *) &linked, 1);
+
+ gimp_item_set_linked (GIMP_ITEM (*layer), linked, FALSE);
+ }
+ break;
+
+ case PROP_COLOR_TAG:
+ {
+ GimpColorTag color_tag;
+
+ xcf_read_int32 (info, (guint32 *) &color_tag, 1);
+
+ gimp_item_set_color_tag (GIMP_ITEM (*layer), color_tag, FALSE);
+ }
+ break;
+
+ case PROP_LOCK_CONTENT:
+ {
+ gboolean lock_content;
+
+ xcf_read_int32 (info, (guint32 *) &lock_content, 1);
+
+ if (gimp_item_can_lock_content (GIMP_ITEM (*layer)))
+ gimp_item_set_lock_content (GIMP_ITEM (*layer),
+ lock_content, FALSE);
+ }
+ break;
+
+ case PROP_LOCK_ALPHA:
+ {
+ gboolean lock_alpha;
+
+ xcf_read_int32 (info, (guint32 *) &lock_alpha, 1);
+
+ if (gimp_layer_can_lock_alpha (*layer))
+ gimp_layer_set_lock_alpha (*layer, lock_alpha, FALSE);
+ }
+ break;
+
+ case PROP_LOCK_POSITION:
+ {
+ gboolean lock_position;
+
+ xcf_read_int32 (info, (guint32 *) &lock_position, 1);
+
+ if (gimp_item_can_lock_position (GIMP_ITEM (*layer)))
+ gimp_item_set_lock_position (GIMP_ITEM (*layer),
+ lock_position, FALSE);
+ }
+ break;
+
+ case PROP_APPLY_MASK:
+ xcf_read_int32 (info, (guint32 *) apply_mask, 1);
+ break;
+
+ case PROP_EDIT_MASK:
+ xcf_read_int32 (info, (guint32 *) edit_mask, 1);
+ break;
+
+ case PROP_SHOW_MASK:
+ xcf_read_int32 (info, (guint32 *) show_mask, 1);
+ break;
+
+ case PROP_OFFSETS:
+ {
+ gint32 offset_x;
+ gint32 offset_y;
+
+ xcf_read_int32 (info, (guint32 *) &offset_x, 1);
+ xcf_read_int32 (info, (guint32 *) &offset_y, 1);
+
+ if (offset_x < -GIMP_MAX_IMAGE_SIZE ||
+ offset_x > GIMP_MAX_IMAGE_SIZE)
+ {
+ g_printerr ("unexpected item offset_x (%d) in XCF, "
+ "setting to 0\n", offset_x);
+ offset_x = 0;
+ }
+
+ if (offset_y < -GIMP_MAX_IMAGE_SIZE ||
+ offset_y > GIMP_MAX_IMAGE_SIZE)
+ {
+ g_printerr ("unexpected item offset_y (%d) in XCF, "
+ "setting to 0\n", offset_y);
+ offset_y = 0;
+ }
+
+ gimp_item_set_offset (GIMP_ITEM (*layer), offset_x, offset_y);
+ }
+ break;
+
+ case PROP_MODE:
+ {
+ GimpLayerMode mode;
+
+ xcf_read_int32 (info, (guint32 *) &mode, 1);
+
+ if (mode == GIMP_LAYER_MODE_OVERLAY_LEGACY)
+ mode = GIMP_LAYER_MODE_SOFTLIGHT_LEGACY;
+
+ gimp_layer_set_mode (*layer, mode, FALSE);
+ }
+ break;
+
+ case PROP_BLEND_SPACE:
+ {
+ gint32 blend_space;
+
+ xcf_read_int32 (info, (guint32 *) &blend_space, 1);
+
+ /* if blend_space < 0 it was originally AUTO, and its negative is
+ * the actual value AUTO used to map to at the time the file was
+ * saved. if AUTO still maps to the same value, keep using AUTO
+ * for the property; otherwise, use the concrete value.
+ */
+ if (blend_space < 0)
+ {
+ GimpLayerMode mode = gimp_layer_get_mode (*layer);
+
+ blend_space = -blend_space;
+
+ if (blend_space == gimp_layer_mode_get_blend_space (mode))
+ blend_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+ else
+ GIMP_LOG (XCF, "BLEND_SPACE: AUTO => %d", blend_space);
+ }
+
+ gimp_layer_set_blend_space (*layer, blend_space, FALSE);
+ }
+ break;
+
+ case PROP_COMPOSITE_SPACE:
+ {
+ gint32 composite_space;
+
+ xcf_read_int32 (info, (guint32 *) &composite_space, 1);
+
+ /* if composite_space < 0 it was originally AUTO, and its negative
+ * is the actual value AUTO used to map to at the time the file was
+ * saved. if AUTO still maps to the same value, keep using AUTO
+ * for the property; otherwise, use the concrete value.
+ */
+ if (composite_space < 0)
+ {
+ GimpLayerMode mode = gimp_layer_get_mode (*layer);
+
+ composite_space = -composite_space;
+
+ if (composite_space == gimp_layer_mode_get_composite_space (mode))
+ composite_space = GIMP_LAYER_COLOR_SPACE_AUTO;
+ else
+ GIMP_LOG (XCF, "COMPOSITE_SPACE: AUTO => %d", composite_space);
+ }
+
+ gimp_layer_set_composite_space (*layer, composite_space, FALSE);
+ }
+ break;
+
+ case PROP_COMPOSITE_MODE:
+ {
+ gint32 composite_mode;
+
+ xcf_read_int32 (info, (guint32 *) &composite_mode, 1);
+
+ /* if composite_mode < 0 it was originally AUTO, and its negative
+ * is the actual value AUTO used to map to at the time the file was
+ * saved. if AUTO still maps to the same value, keep using AUTO
+ * for the property; otherwise, use the concrete value.
+ */
+ if (composite_mode < 0)
+ {
+ GimpLayerMode mode = gimp_layer_get_mode (*layer);
+
+ composite_mode = -composite_mode;
+
+ if (composite_mode == gimp_layer_mode_get_composite_mode (mode))
+ composite_mode = GIMP_LAYER_COMPOSITE_AUTO;
+ else
+ GIMP_LOG (XCF, "COMPOSITE_MODE: AUTO => %d", composite_mode);
+ }
+
+ gimp_layer_set_composite_mode (*layer, composite_mode, FALSE);
+ }
+ break;
+
+ case PROP_TATTOO:
+ {
+ GimpTattoo tattoo;
+
+ xcf_read_int32 (info, (guint32 *) &tattoo, 1);
+
+ gimp_item_set_tattoo (GIMP_ITEM (*layer), tattoo);
+ }
+ break;
+
+ case PROP_PARASITES:
+ {
+ goffset base = info->cp;
+
+ while (info->cp - base < prop_size)
+ {
+ GimpParasite *p = xcf_load_parasite (info);
+ GError *error = NULL;
+
+ if (! p)
+ return FALSE;
+
+ if (! gimp_item_parasite_validate (GIMP_ITEM (*layer), p, &error))
+ {
+ gimp_message (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ "Warning, invalid layer parasite in XCF file: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+ else
+ {
+ gimp_item_parasite_attach (GIMP_ITEM (*layer), p, FALSE);
+ }
+
+ gimp_parasite_free (p);
+ }
+
+ if (info->cp - base != prop_size)
+ gimp_message_literal (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ "Error while loading a layer's parasites");
+ }
+ break;
+
+ case PROP_TEXT_LAYER_FLAGS:
+ xcf_read_int32 (info, text_layer_flags, 1);
+ break;
+
+ case PROP_GROUP_ITEM:
+ {
+ GimpLayer *group;
+ gboolean is_active_layer;
+
+ /* We're going to delete *layer, Don't leave its pointers
+ * in @info. After that, we'll restore them back with the
+ * new pointer. See bug #767873.
+ */
+ is_active_layer = (*layer == info->active_layer);
+ if (is_active_layer)
+ info->active_layer = NULL;
+
+ if (*layer == info->floating_sel)
+ info->floating_sel = NULL;
+
+ group = gimp_group_layer_new (image);
+
+ gimp_object_set_name (GIMP_OBJECT (group),
+ gimp_object_get_name (*layer));
+
+ g_object_ref_sink (*layer);
+ g_object_unref (*layer);
+ *layer = group;
+
+ if (is_active_layer)
+ info->active_layer = *layer;
+
+ /* Don't restore info->floating_sel because group layers
+ * can't be floating selections
+ */
+ }
+ break;
+
+ case PROP_ITEM_PATH:
+ {
+ goffset base = info->cp;
+ GList *path = NULL;
+
+ while (info->cp - base < prop_size)
+ {
+ guint32 index;
+
+ if (xcf_read_int32 (info, &index, 1) != 4)
+ {
+ g_list_free (path);
+ return FALSE;
+ }
+
+ path = g_list_append (path, GUINT_TO_POINTER (index));
+ }
+
+ *item_path = path;
+ }
+ break;
+
+ case PROP_GROUP_ITEM_FLAGS:
+ xcf_read_int32 (info, group_layer_flags, 1);
+ break;
+
+ default:
+#ifdef GIMP_UNSTABLE
+ g_printerr ("unexpected/unknown layer property: %d (skipping)\n",
+ prop_type);
+#endif
+ if (! xcf_skip_unknown_prop (info, prop_size))
+ return FALSE;
+ break;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+xcf_check_layer_props (XcfInfo *info,
+ GList **item_path,
+ gboolean *is_group_layer,
+ gboolean *is_text_layer)
+{
+ PropType prop_type;
+ guint32 prop_size;
+
+ g_return_val_if_fail (*is_group_layer == FALSE, FALSE);
+ g_return_val_if_fail (*is_text_layer == FALSE, FALSE);
+
+ while (TRUE)
+ {
+ if (! xcf_load_prop (info, &prop_type, &prop_size))
+ return FALSE;
+
+ switch (prop_type)
+ {
+ case PROP_END:
+ return TRUE;
+
+ case PROP_TEXT_LAYER_FLAGS:
+ *is_text_layer = TRUE;
+
+ if (! xcf_skip_unknown_prop (info, prop_size))
+ return FALSE;
+ break;
+
+ case PROP_GROUP_ITEM:
+ case PROP_GROUP_ITEM_FLAGS:
+ *is_group_layer = TRUE;
+
+ if (! xcf_skip_unknown_prop (info, prop_size))
+ return FALSE;
+ break;
+
+ case PROP_ITEM_PATH:
+ {
+ goffset base = info->cp;
+ GList *path = NULL;
+
+ while (info->cp - base < prop_size)
+ {
+ guint32 index;
+
+ if (xcf_read_int32 (info, &index, 1) != 4)
+ {
+ g_list_free (path);
+ return FALSE;
+ }
+
+ path = g_list_append (path, GUINT_TO_POINTER (index));
+ }
+
+ *item_path = path;
+ }
+ break;
+
+ case PROP_ACTIVE_LAYER:
+ case PROP_FLOATING_SELECTION:
+ case PROP_OPACITY:
+ case PROP_FLOAT_OPACITY:
+ case PROP_VISIBLE:
+ case PROP_LINKED:
+ case PROP_COLOR_TAG:
+ case PROP_LOCK_CONTENT:
+ case PROP_LOCK_ALPHA:
+ case PROP_LOCK_POSITION:
+ case PROP_APPLY_MASK:
+ case PROP_EDIT_MASK:
+ case PROP_SHOW_MASK:
+ case PROP_OFFSETS:
+ case PROP_MODE:
+ case PROP_BLEND_SPACE:
+ case PROP_COMPOSITE_SPACE:
+ case PROP_COMPOSITE_MODE:
+ case PROP_TATTOO:
+ case PROP_PARASITES:
+ if (! xcf_skip_unknown_prop (info, prop_size))
+ return FALSE;
+ /* Just ignore for now. */
+ break;
+
+ default:
+#ifdef GIMP_UNSTABLE
+ g_printerr ("unexpected/unknown layer property: %d (skipping)\n",
+ prop_type);
+#endif
+ if (! xcf_skip_unknown_prop (info, prop_size))
+ return FALSE;
+ break;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+xcf_load_channel_props (XcfInfo *info,
+ GimpImage *image,
+ GimpChannel **channel)
+{
+ PropType prop_type;
+ guint32 prop_size;
+
+ while (TRUE)
+ {
+ if (! xcf_load_prop (info, &prop_type, &prop_size))
+ return FALSE;
+
+ switch (prop_type)
+ {
+ case PROP_END:
+ return TRUE;
+
+ case PROP_ACTIVE_CHANNEL:
+ info->active_channel = *channel;
+ break;
+
+ case PROP_SELECTION:
+ {
+ GimpChannel *mask;
+
+ /* We're going to delete *channel, Don't leave its pointer
+ * in @info. See bug #767873.
+ */
+ if (*channel == info->active_channel)
+ info->active_channel = NULL;
+
+ mask =
+ gimp_selection_new (image,
+ gimp_item_get_width (GIMP_ITEM (*channel)),
+ gimp_item_get_height (GIMP_ITEM (*channel)));
+ gimp_image_take_mask (image, mask);
+
+ gimp_drawable_steal_buffer (GIMP_DRAWABLE (mask),
+ GIMP_DRAWABLE (*channel));
+ g_object_unref (*channel);
+ *channel = mask;
+
+ /* Don't restore info->active_channel because the
+ * selection can't be the active channel
+ */
+ }
+ break;
+
+ case PROP_OPACITY:
+ {
+ guint32 opacity;
+
+ xcf_read_int32 (info, &opacity, 1);
+
+ gimp_channel_set_opacity (*channel, opacity / 255.0, FALSE);
+ }
+ break;
+
+ case PROP_FLOAT_OPACITY:
+ {
+ gfloat opacity;
+
+ xcf_read_float (info, &opacity, 1);
+
+ gimp_channel_set_opacity (*channel, opacity, FALSE);
+ }
+ break;
+
+ case PROP_VISIBLE:
+ {
+ gboolean visible;
+
+ xcf_read_int32 (info, (guint32 *) &visible, 1);
+
+ gimp_item_set_visible (GIMP_ITEM (*channel), visible, FALSE);
+ }
+ break;
+
+ case PROP_COLOR_TAG:
+ {
+ GimpColorTag color_tag;
+
+ xcf_read_int32 (info, (guint32 *) &color_tag, 1);
+
+ gimp_item_set_color_tag (GIMP_ITEM (*channel), color_tag, FALSE);
+ }
+ break;
+
+ case PROP_LINKED:
+ {
+ gboolean linked;
+
+ xcf_read_int32 (info, (guint32 *) &linked, 1);
+
+ gimp_item_set_linked (GIMP_ITEM (*channel), linked, FALSE);
+ }
+ break;
+
+ case PROP_LOCK_CONTENT:
+ {
+ gboolean lock_content;
+
+ xcf_read_int32 (info, (guint32 *) &lock_content, 1);
+
+ if (gimp_item_can_lock_content (GIMP_ITEM (*channel)))
+ gimp_item_set_lock_content (GIMP_ITEM (*channel),
+ lock_content, FALSE);
+ }
+ break;
+
+ case PROP_LOCK_POSITION:
+ {
+ gboolean lock_position;
+
+ xcf_read_int32 (info, (guint32 *) &lock_position, 1);
+
+ if (gimp_item_can_lock_position (GIMP_ITEM (*channel)))
+ gimp_item_set_lock_position (GIMP_ITEM (*channel),
+ lock_position, FALSE);
+ }
+ break;
+
+ case PROP_SHOW_MASKED:
+ {
+ gboolean show_masked;
+
+ xcf_read_int32 (info, (guint32 *) &show_masked, 1);
+
+ gimp_channel_set_show_masked (*channel, show_masked);
+ }
+ break;
+
+ case PROP_COLOR:
+ {
+ guchar col[3];
+
+ xcf_read_int8 (info, (guint8 *) col, 3);
+
+ gimp_rgb_set_uchar (&(*channel)->color, col[0], col[1], col[2]);
+ }
+ break;
+
+ case PROP_FLOAT_COLOR:
+ {
+ gfloat col[3];
+
+ xcf_read_float (info, col, 3);
+
+ gimp_rgb_set (&(*channel)->color, col[0], col[1], col[2]);
+ }
+ break;
+
+ case PROP_TATTOO:
+ {
+ GimpTattoo tattoo;
+
+ xcf_read_int32 (info, (guint32 *) &tattoo, 1);
+
+ gimp_item_set_tattoo (GIMP_ITEM (*channel), tattoo);
+ }
+ break;
+
+ case PROP_PARASITES:
+ {
+ goffset base = info->cp;
+
+ while ((info->cp - base) < prop_size)
+ {
+ GimpParasite *p = xcf_load_parasite (info);
+ GError *error = NULL;
+
+ if (! p)
+ return FALSE;
+
+ if (! gimp_item_parasite_validate (GIMP_ITEM (*channel), p,
+ &error))
+ {
+ gimp_message (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ "Warning, invalid channel parasite in XCF file: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+ else
+ {
+ gimp_item_parasite_attach (GIMP_ITEM (*channel), p, FALSE);
+ }
+
+ gimp_parasite_free (p);
+ }
+
+ if (info->cp - base != prop_size)
+ gimp_message_literal (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ "Error while loading a channel's parasites");
+ }
+ break;
+
+ default:
+#ifdef GIMP_UNSTABLE
+ g_printerr ("unexpected/unknown channel property: %d (skipping)\n",
+ prop_type);
+#endif
+ if (! xcf_skip_unknown_prop (info, prop_size))
+ return FALSE;
+ break;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+xcf_load_prop (XcfInfo *info,
+ PropType *prop_type,
+ guint32 *prop_size)
+{
+ if (G_UNLIKELY (xcf_read_int32 (info, (guint32 *) prop_type, 1) != 4))
+ return FALSE;
+
+ if (G_UNLIKELY (xcf_read_int32 (info, (guint32 *) prop_size, 1) != 4))
+ return FALSE;
+
+ GIMP_LOG (XCF, "prop type=%d size=%u", *prop_type, *prop_size);
+
+ return TRUE;
+}
+
+static GimpLayer *
+xcf_load_layer (XcfInfo *info,
+ GimpImage *image,
+ GList **item_path)
+{
+ GimpLayer *layer;
+ GimpLayerMask *layer_mask;
+ goffset hierarchy_offset;
+ goffset layer_mask_offset;
+ gboolean apply_mask = TRUE;
+ gboolean edit_mask = FALSE;
+ gboolean show_mask = FALSE;
+ gboolean active;
+ gboolean floating;
+ guint32 group_layer_flags = 0;
+ guint32 text_layer_flags = 0;
+ gint width;
+ gint height;
+ gint type;
+ GimpImageBaseType base_type;
+ gboolean has_alpha;
+ const Babl *format;
+ gboolean is_fs_drawable;
+ gchar *name;
+ goffset cur_offset;
+
+ /* check and see if this is the drawable the floating selection
+ * is attached to. if it is then we'll do the attachment in our caller.
+ */
+ is_fs_drawable = (info->cp == info->floating_sel_offset);
+
+ /* read in the layer width, height, type and name */
+ xcf_read_int32 (info, (guint32 *) &width, 1);
+ xcf_read_int32 (info, (guint32 *) &height, 1);
+ xcf_read_int32 (info, (guint32 *) &type, 1);
+ xcf_read_string (info, &name, 1);
+
+ GIMP_LOG (XCF, "width=%d, height=%d, type=%d, name='%s'",
+ width, height, type, name);
+
+ switch (type)
+ {
+ case GIMP_RGB_IMAGE:
+ base_type = GIMP_RGB;
+ has_alpha = FALSE;
+ break;
+
+ case GIMP_RGBA_IMAGE:
+ base_type = GIMP_RGB;
+ has_alpha = TRUE;
+ break;
+
+ case GIMP_GRAY_IMAGE:
+ base_type = GIMP_GRAY;
+ has_alpha = FALSE;
+ break;
+
+ case GIMP_GRAYA_IMAGE:
+ base_type = GIMP_GRAY;
+ has_alpha = TRUE;
+ break;
+
+ case GIMP_INDEXED_IMAGE:
+ base_type = GIMP_INDEXED;
+ has_alpha = FALSE;
+ break;
+
+ case GIMP_INDEXEDA_IMAGE:
+ base_type = GIMP_INDEXED;
+ has_alpha = TRUE;
+ break;
+
+ default:
+ return NULL;
+ }
+
+ if (width <= 0 || height <= 0 ||
+ width > GIMP_MAX_IMAGE_SIZE || height > GIMP_MAX_IMAGE_SIZE)
+ {
+ gboolean is_group_layer = FALSE;
+ gboolean is_text_layer = FALSE;
+ goffset saved_pos;
+
+ saved_pos = info->cp;
+ /* Load item path and check if this is a group or text layer. */
+ xcf_check_layer_props (info, item_path, &is_group_layer, &is_text_layer);
+ if ((is_text_layer || is_group_layer) &&
+ xcf_seek_pos (info, saved_pos, NULL))
+ {
+ /* Something is wrong, but leave a chance to the layer because
+ * anyway group and text layer depends on their contents.
+ */
+ width = height = 1;
+ g_clear_pointer (item_path, g_list_free);
+ }
+ else
+ {
+ return NULL;
+ }
+ }
+
+ /* do not use gimp_image_get_layer_format() because it might
+ * be the floating selection of a channel or mask
+ */
+ format = gimp_image_get_format (image, base_type,
+ gimp_image_get_precision (image),
+ has_alpha);
+
+ /* create a new layer */
+ layer = gimp_layer_new (image, width, height,
+ format, name,
+ GIMP_OPACITY_OPAQUE, GIMP_LAYER_MODE_NORMAL);
+ g_free (name);
+ if (! layer)
+ return NULL;
+
+ /* read in the layer properties */
+ if (! xcf_load_layer_props (info, image, &layer, item_path,
+ &apply_mask, &edit_mask, &show_mask,
+ &text_layer_flags, &group_layer_flags))
+ goto error;
+
+ GIMP_LOG (XCF, "layer props loaded");
+
+ xcf_progress_update (info);
+
+ /* call the evil text layer hack that might change our layer pointer */
+ active = (info->active_layer == layer);
+ floating = (info->floating_sel == layer);
+
+ if (gimp_text_layer_xcf_load_hack (&layer))
+ {
+ gimp_text_layer_set_xcf_flags (GIMP_TEXT_LAYER (layer),
+ text_layer_flags);
+
+ if (active)
+ info->active_layer = layer;
+ if (floating)
+ info->floating_sel = layer;
+ }
+
+ /* read the hierarchy and layer mask offsets */
+ cur_offset = info->cp;
+ xcf_read_offset (info, &hierarchy_offset, 1);
+ xcf_read_offset (info, &layer_mask_offset, 1);
+
+ /* read in the hierarchy (ignore it for group layers, both as an
+ * optimization and because the hierarchy's extents don't match
+ * the group layer's tiles)
+ */
+ if (! gimp_viewable_get_children (GIMP_VIEWABLE (layer)))
+ {
+ if (hierarchy_offset < cur_offset)
+ {
+ GIMP_LOG (XCF, "Invalid layer hierarchy offset!");
+ goto error;
+ }
+ if (! xcf_seek_pos (info, hierarchy_offset, NULL))
+ goto error;
+
+ GIMP_LOG (XCF, "loading buffer");
+
+ if (! xcf_load_buffer (info,
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (layer))))
+ goto error;
+
+ GIMP_LOG (XCF, "buffer loaded");
+
+ xcf_progress_update (info);
+ }
+ else
+ {
+ gboolean expanded = group_layer_flags & XCF_GROUP_ITEM_EXPANDED;
+
+ gimp_viewable_set_expanded (GIMP_VIEWABLE (layer), expanded);
+ }
+
+ /* read in the layer mask */
+ if (layer_mask_offset != 0)
+ {
+ if (layer_mask_offset < cur_offset)
+ {
+ GIMP_LOG (XCF, "Invalid layer mask offset!");
+ goto error;
+ }
+ if (! xcf_seek_pos (info, layer_mask_offset, NULL))
+ goto error;
+
+ layer_mask = xcf_load_layer_mask (info, image);
+ if (! layer_mask)
+ goto error;
+
+ xcf_progress_update (info);
+
+ /* don't add the layer mask yet, that won't work for group
+ * layers which update their size automatically; instead
+ * attach it so it can be added when all layers are loaded
+ */
+ g_object_set_data_full (G_OBJECT (layer), "gimp-layer-mask",
+ g_object_ref_sink (layer_mask),
+ (GDestroyNotify) g_object_unref);
+ g_object_set_data (G_OBJECT (layer), "gimp-layer-mask-apply",
+ GINT_TO_POINTER (apply_mask));
+ g_object_set_data (G_OBJECT (layer), "gimp-layer-mask-edit",
+ GINT_TO_POINTER (edit_mask));
+ g_object_set_data (G_OBJECT (layer), "gimp-layer-mask-show",
+ GINT_TO_POINTER (show_mask));
+ }
+
+ /* attach the floating selection... */
+ if (is_fs_drawable)
+ info->floating_sel_drawable = GIMP_DRAWABLE (layer);
+
+ return layer;
+
+ error:
+ if (info->active_layer == layer)
+ info->active_layer = NULL;
+
+ if (info->floating_sel == layer)
+ info->floating_sel = NULL;
+
+ if (info->floating_sel_drawable == GIMP_DRAWABLE (layer))
+ info->floating_sel_drawable = NULL;
+
+ g_object_unref (layer);
+
+ return NULL;
+}
+
+static GimpChannel *
+xcf_load_channel (XcfInfo *info,
+ GimpImage *image)
+{
+ GimpChannel *channel;
+ goffset hierarchy_offset;
+ gint width;
+ gint height;
+ gboolean is_fs_drawable;
+ gchar *name;
+ GimpRGB color = { 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE };
+ goffset cur_offset;
+
+ /* check and see if this is the drawable the floating selection
+ * is attached to. if it is then we'll do the attachment in our caller.
+ */
+ is_fs_drawable = (info->cp == info->floating_sel_offset);
+
+ /* read in the layer width, height and name */
+ xcf_read_int32 (info, (guint32 *) &width, 1);
+ xcf_read_int32 (info, (guint32 *) &height, 1);
+ if (width <= 0 || height <= 0 ||
+ width > GIMP_MAX_IMAGE_SIZE || height > GIMP_MAX_IMAGE_SIZE)
+ {
+ GIMP_LOG (XCF, "Invalid channel size %d x %d.", width, height);
+ return NULL;
+ }
+
+ xcf_read_string (info, &name, 1);
+ GIMP_LOG (XCF, "Channel width=%d, height=%d, name='%s'",
+ width, height, name);
+
+ /* create a new channel */
+ channel = gimp_channel_new (image, width, height, name, &color);
+ g_free (name);
+ if (!channel)
+ return NULL;
+
+ /* read in the channel properties */
+ if (! xcf_load_channel_props (info, image, &channel))
+ goto error;
+
+ xcf_progress_update (info);
+
+ /* read the hierarchy offset */
+ cur_offset = info->cp;
+ xcf_read_offset (info, &hierarchy_offset, 1);
+
+ if (hierarchy_offset < cur_offset)
+ {
+ GIMP_LOG (XCF, "Invalid hierarchy offset!");
+ goto error;
+ }
+
+ /* read in the hierarchy */
+ if (! xcf_seek_pos (info, hierarchy_offset, NULL))
+ goto error;
+
+ if (! xcf_load_buffer (info,
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (channel))))
+ goto error;
+
+ xcf_progress_update (info);
+
+ if (is_fs_drawable)
+ info->floating_sel_drawable = GIMP_DRAWABLE (channel);
+
+ return channel;
+
+ error:
+ /* don't unref the selection of a partially loaded XCF */
+ if (channel != gimp_image_get_mask (image))
+ {
+ if (info->active_channel == channel)
+ info->active_channel = NULL;
+
+ if (info->floating_sel_drawable == GIMP_DRAWABLE (channel))
+ info->floating_sel_drawable = NULL;
+
+ g_object_unref (channel);
+ }
+
+ return NULL;
+}
+
+static GimpLayerMask *
+xcf_load_layer_mask (XcfInfo *info,
+ GimpImage *image)
+{
+ GimpLayerMask *layer_mask;
+ GimpChannel *channel;
+ goffset hierarchy_offset;
+ gint width;
+ gint height;
+ gboolean is_fs_drawable;
+ gchar *name;
+ GimpRGB color = { 0.0, 0.0, 0.0, GIMP_OPACITY_OPAQUE };
+ goffset cur_offset;
+
+ /* check and see if this is the drawable the floating selection
+ * is attached to. if it is then we'll do the attachment in our caller.
+ */
+ is_fs_drawable = (info->cp == info->floating_sel_offset);
+
+ /* read in the layer width, height and name */
+ xcf_read_int32 (info, (guint32 *) &width, 1);
+ xcf_read_int32 (info, (guint32 *) &height, 1);
+ if (width <= 0 || height <= 0 ||
+ width > GIMP_MAX_IMAGE_SIZE || height > GIMP_MAX_IMAGE_SIZE)
+ {
+ GIMP_LOG (XCF, "Invalid layer mask size %d x %d.", width, height);
+ return NULL;
+ }
+
+ xcf_read_string (info, &name, 1);
+ GIMP_LOG (XCF, "Layer mask width=%d, height=%d, name='%s'",
+ width, height, name);
+
+ /* create a new layer mask */
+ layer_mask = gimp_layer_mask_new (image, width, height, name, &color);
+ g_free (name);
+ if (! layer_mask)
+ return NULL;
+
+ /* read in the layer_mask properties */
+ channel = GIMP_CHANNEL (layer_mask);
+ if (! xcf_load_channel_props (info, image, &channel))
+ goto error;
+
+ xcf_progress_update (info);
+
+ /* read the hierarchy offset */
+ cur_offset = info->cp;
+ xcf_read_offset (info, &hierarchy_offset, 1);
+
+ if (hierarchy_offset < cur_offset)
+ {
+ GIMP_LOG (XCF, "Invalid hierarchy offset!");
+ goto error;
+ }
+
+ /* read in the hierarchy */
+ if (! xcf_seek_pos (info, hierarchy_offset, NULL))
+ goto error;
+
+ if (! xcf_load_buffer (info,
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (layer_mask))))
+ goto error;
+
+ xcf_progress_update (info);
+
+ /* attach the floating selection... */
+ if (is_fs_drawable)
+ info->floating_sel_drawable = GIMP_DRAWABLE (layer_mask);
+
+ return layer_mask;
+
+ error:
+ if (info->active_channel == GIMP_CHANNEL (layer_mask))
+ info->active_channel = NULL;
+
+ if (info->floating_sel_drawable == GIMP_DRAWABLE (layer_mask))
+ info->floating_sel_drawable = NULL;
+
+ g_object_unref (layer_mask);
+
+ return NULL;
+}
+
+static gboolean
+xcf_load_buffer (XcfInfo *info,
+ GeglBuffer *buffer)
+{
+ const Babl *format;
+ goffset offset;
+ gint width;
+ gint height;
+ gint bpp;
+ goffset cur_offset;
+
+ format = gegl_buffer_get_format (buffer);
+
+ xcf_read_int32 (info, (guint32 *) &width, 1);
+ xcf_read_int32 (info, (guint32 *) &height, 1);
+ xcf_read_int32 (info, (guint32 *) &bpp, 1);
+
+ /* make sure the values in the file correspond to the values
+ * calculated when the GeglBuffer was created.
+ */
+ if (width != gegl_buffer_get_width (buffer) ||
+ height != gegl_buffer_get_height (buffer) ||
+ bpp != babl_format_get_bytes_per_pixel (format))
+ return FALSE;
+
+ cur_offset = info->cp;
+ xcf_read_offset (info, &offset, 1); /* top level */
+
+ if (offset < cur_offset)
+ {
+ GIMP_LOG (XCF, "Invalid buffer offset!");
+ return FALSE;
+ }
+
+ /* seek to the level offset */
+ if (! xcf_seek_pos (info, offset, NULL))
+ return FALSE;
+
+ /* read in the level */
+ if (! xcf_load_level (info, buffer))
+ return FALSE;
+
+ /* discard levels below first.
+ */
+
+ return TRUE;
+}
+
+
+static gboolean
+xcf_load_level (XcfInfo *info,
+ GeglBuffer *buffer)
+{
+ const Babl *format;
+ gint bpp;
+ goffset saved_pos;
+ goffset offset;
+ goffset offset2;
+ goffset max_data_length;
+ gint n_tile_rows;
+ gint n_tile_cols;
+ guint ntiles;
+ gint width;
+ gint height;
+ gint i;
+ gint fail;
+
+ format = gegl_buffer_get_format (buffer);
+ bpp = babl_format_get_bytes_per_pixel (format);
+
+ xcf_read_int32 (info, (guint32 *) &width, 1);
+ xcf_read_int32 (info, (guint32 *) &height, 1);
+
+ if (width != gegl_buffer_get_width (buffer) ||
+ height != gegl_buffer_get_height (buffer))
+ return FALSE;
+
+ /* maximal allowable size of on-disk tile data. make it somewhat bigger than
+ * the uncompressed tile size, to allow for the possibility of negative
+ * compression.
+ */
+ max_data_length = XCF_TILE_WIDTH * XCF_TILE_HEIGHT * bpp *
+ XCF_TILE_MAX_DATA_LENGTH_FACTOR /* = 1.5, currently */;
+
+ /* read in the first tile offset.
+ * if it is '0', then this tile level is empty
+ * and we can simply return.
+ */
+ xcf_read_offset (info, &offset, 1);
+ if (offset == 0)
+ return TRUE;
+
+ n_tile_rows = gimp_gegl_buffer_get_n_tile_rows (buffer, XCF_TILE_HEIGHT);
+ n_tile_cols = gimp_gegl_buffer_get_n_tile_cols (buffer, XCF_TILE_WIDTH);
+
+ ntiles = n_tile_rows * n_tile_cols;
+ for (i = 0; i < ntiles; i++)
+ {
+ GeglRectangle rect;
+
+ fail = FALSE;
+
+ if (offset == 0)
+ {
+ gimp_message_literal (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_ERROR,
+ "not enough tiles found in level");
+ return FALSE;
+ }
+
+ /* save the current position as it is where the
+ * next tile offset is stored.
+ */
+ saved_pos = info->cp;
+
+ /* read in the offset of the next tile so we can calculate the amount
+ * of data needed for this tile
+ */
+ xcf_read_offset (info, &offset2, 1);
+
+ /* if the offset is 0 then we need to read in the maximum possible
+ * allowing for negative compression
+ */
+ if (offset2 == 0)
+ offset2 = offset + max_data_length;
+
+ /* seek to the tile offset */
+ if (! xcf_seek_pos (info, offset, NULL))
+ return FALSE;
+
+ if (offset2 < offset || offset2 - offset > max_data_length)
+ {
+ gimp_message (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_ERROR,
+ "invalid tile data length: %" G_GOFFSET_FORMAT,
+ offset2 - offset);
+ return FALSE;
+ }
+
+ /* get buffer rectangle to write to */
+ gimp_gegl_buffer_get_tile_rect (buffer,
+ XCF_TILE_WIDTH, XCF_TILE_HEIGHT,
+ i, &rect);
+
+ GIMP_LOG (XCF, "loading tile %d/%d", i + 1, ntiles);
+
+ /* read in the tile */
+ switch (info->compression)
+ {
+ case COMPRESS_NONE:
+ if (! xcf_load_tile (info, buffer, &rect, format))
+ fail = TRUE;
+ break;
+ case COMPRESS_RLE:
+ if (! xcf_load_tile_rle (info, buffer, &rect, format,
+ offset2 - offset))
+ fail = TRUE;
+ break;
+ case COMPRESS_ZLIB:
+ if (! xcf_load_tile_zlib (info, buffer, &rect, format,
+ offset2 - offset))
+ fail = TRUE;
+ break;
+ case COMPRESS_FRACTAL:
+ g_printerr ("xcf: fractal compression unimplemented. "
+ "Possibly corrupt XCF file.");
+ fail = TRUE;
+ break;
+ default:
+ g_printerr ("xcf: unknown compression. "
+ "Possibly corrupt XCF file.");
+ fail = TRUE;
+ break;
+ }
+
+ if (fail)
+ return FALSE;
+
+ GIMP_LOG (XCF, "loaded tile %d/%d", i + 1, ntiles);
+
+ /* restore the saved position so we'll be ready to
+ * read the next offset.
+ */
+ if (!xcf_seek_pos (info, saved_pos, NULL))
+ return FALSE;
+
+ /* read in the offset of the next tile */
+ xcf_read_offset (info, &offset, 1);
+ }
+
+ if (offset != 0)
+ {
+ gimp_message (info->gimp, G_OBJECT (info->progress), GIMP_MESSAGE_ERROR,
+ "encountered garbage after reading level: %" G_GOFFSET_FORMAT,
+ offset);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+xcf_load_tile (XcfInfo *info,
+ GeglBuffer *buffer,
+ GeglRectangle *tile_rect,
+ const Babl *format)
+{
+ gint bpp = babl_format_get_bytes_per_pixel (format);
+ gint tile_size = bpp * tile_rect->width * tile_rect->height;
+ guchar *tile_data = g_alloca (tile_size);
+
+ if (info->file_version <= 11)
+ {
+ xcf_read_int8 (info, tile_data, tile_size);
+ }
+ else
+ {
+ gint n_components = babl_format_get_n_components (format);
+
+ xcf_read_component (info, bpp / n_components, tile_data,
+ tile_size / bpp * n_components);
+ }
+
+ if (! xcf_data_is_zero (tile_data, tile_size))
+ {
+ gegl_buffer_set (buffer, tile_rect, 0, format, tile_data,
+ GEGL_AUTO_ROWSTRIDE);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+xcf_load_tile_rle (XcfInfo *info,
+ GeglBuffer *buffer,
+ GeglRectangle *tile_rect,
+ const Babl *format,
+ gint data_length)
+{
+ gint bpp = babl_format_get_bytes_per_pixel (format);
+ gint tile_size = bpp * tile_rect->width * tile_rect->height;
+ guchar *tile_data = g_alloca (tile_size);
+ guchar nonzero = FALSE;
+ gsize bytes_read;
+ gint i;
+ guchar *xcfdata;
+ guchar *xcfodata;
+ guchar *xcfdatalimit;
+
+ /* Workaround for bug #357809: avoid crashing on g_malloc() and skip
+ * this tile (return TRUE without storing data) as if it did not
+ * contain any data. It is better than returning FALSE, which would
+ * skip the whole hierarchy while there may still be some valid
+ * tiles in the file.
+ */
+ if (data_length <= 0)
+ return TRUE;
+
+ xcfdata = xcfodata = g_alloca (data_length);
+
+ /* we have to read directly instead of xcf_read_* because we may be
+ * reading past the end of the file here
+ */
+ g_input_stream_read_all (info->input, xcfdata, data_length,
+ &bytes_read, NULL, NULL);
+ info->cp += bytes_read;
+
+ if (bytes_read == 0)
+ return TRUE;
+
+ xcfdatalimit = &xcfodata[bytes_read - 1];
+
+ for (i = 0; i < bpp; i++)
+ {
+ guchar *data = tile_data + i;
+ gint size = tile_rect->width * tile_rect->height;
+ gint count = 0;
+ guchar val;
+ gint length;
+ gint j;
+
+ while (size > 0)
+ {
+ if (xcfdata > xcfdatalimit)
+ {
+ goto bogus_rle;
+ }
+
+ val = *xcfdata++;
+
+ length = val;
+ if (length >= 128)
+ {
+ length = 255 - (length - 1);
+ if (length == 128)
+ {
+ if (xcfdata >= xcfdatalimit)
+ {
+ goto bogus_rle;
+ }
+
+ length = (*xcfdata << 8) + xcfdata[1];
+ xcfdata += 2;
+ }
+
+ count += length;
+ size -= length;
+
+ if (size < 0)
+ {
+ goto bogus_rle;
+ }
+
+ if (&xcfdata[length-1] > xcfdatalimit)
+ {
+ goto bogus_rle;
+ }
+
+ while (length-- > 0)
+ {
+ *data = *xcfdata++;
+ nonzero |= *data;
+ data += bpp;
+ }
+ }
+ else
+ {
+ length += 1;
+ if (length == 128)
+ {
+ if (xcfdata >= xcfdatalimit)
+ {
+ goto bogus_rle;
+ }
+
+ length = (*xcfdata << 8) + xcfdata[1];
+ xcfdata += 2;
+ }
+
+ count += length;
+ size -= length;
+
+ if (size < 0)
+ {
+ goto bogus_rle;
+ }
+
+ if (xcfdata > xcfdatalimit)
+ {
+ goto bogus_rle;
+ }
+
+ val = *xcfdata++;
+ nonzero |= val;
+
+ for (j = 0; j < length; j++)
+ {
+ *data = val;
+ data += bpp;
+ }
+ }
+ }
+ }
+
+ if (nonzero)
+ {
+ if (info->file_version >= 12)
+ {
+ gint n_components = babl_format_get_n_components (format);
+
+ xcf_read_from_be (bpp / n_components, tile_data,
+ tile_size / bpp * n_components);
+ }
+
+ gegl_buffer_set (buffer, tile_rect, 0, format, tile_data,
+ GEGL_AUTO_ROWSTRIDE);
+ }
+
+ return TRUE;
+
+ bogus_rle:
+ return FALSE;
+}
+
+static gboolean
+xcf_load_tile_zlib (XcfInfo *info,
+ GeglBuffer *buffer,
+ GeglRectangle *tile_rect,
+ const Babl *format,
+ gint data_length)
+{
+ z_stream strm;
+ int action;
+ int status;
+ gint bpp = babl_format_get_bytes_per_pixel (format);
+ gint tile_size = bpp * tile_rect->width * tile_rect->height;
+ guchar *tile_data = g_alloca (tile_size);
+ gsize bytes_read;
+ guchar *xcfdata;
+
+ /* Workaround for bug #357809: avoid crashing on g_malloc() and skip
+ * this tile (return TRUE without storing data) as if it did not
+ * contain any data. It is better than returning FALSE, which would
+ * skip the whole hierarchy while there may still be some valid
+ * tiles in the file.
+ */
+ if (data_length <= 0)
+ return TRUE;
+
+ xcfdata = g_alloca (data_length);
+
+ /* we have to read directly instead of xcf_read_* because we may be
+ * reading past the end of the file here
+ */
+ g_input_stream_read_all (info->input, xcfdata, data_length,
+ &bytes_read, NULL, NULL);
+ info->cp += bytes_read;
+
+ if (bytes_read == 0)
+ return TRUE;
+
+ strm.next_out = tile_data;
+ strm.avail_out = tile_size;
+
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.next_in = xcfdata;
+ strm.avail_in = bytes_read;
+
+ /* Initialize the stream decompression. */
+ status = inflateInit (&strm);
+ if (status != Z_OK)
+ return FALSE;
+
+ action = Z_NO_FLUSH;
+
+ while (status == Z_OK)
+ {
+ if (strm.avail_in == 0)
+ {
+ action = Z_FINISH;
+ }
+
+ status = inflate (&strm, action);
+
+ if (status == Z_STREAM_END)
+ {
+ /* All the data was successfully decoded. */
+ break;
+ }
+ else if (status == Z_BUF_ERROR)
+ {
+ g_printerr ("xcf: decompressed tile bigger than the expected size.");
+ inflateEnd (&strm);
+ return FALSE;
+ }
+ else if (status != Z_OK)
+ {
+ g_printerr ("xcf: tile decompression failed: %s", zError (status));
+ inflateEnd (&strm);
+ return FALSE;
+ }
+ }
+
+ if (! xcf_data_is_zero (tile_data, tile_size))
+ {
+ if (info->file_version >= 12)
+ {
+ gint n_components = babl_format_get_n_components (format);
+
+ xcf_read_from_be (bpp / n_components, tile_data,
+ tile_size / bpp * n_components);
+ }
+
+ gegl_buffer_set (buffer, tile_rect, 0, format, tile_data,
+ GEGL_AUTO_ROWSTRIDE);
+ }
+
+ inflateEnd (&strm);
+
+ return TRUE;
+}
+
+static GimpParasite *
+xcf_load_parasite (XcfInfo *info)
+{
+ GimpParasite *parasite = NULL;
+ gchar *name;
+ guint32 flags;
+ guint32 size, size_read;
+ gpointer data;
+
+ xcf_read_string (info, &name, 1);
+ xcf_read_int32 (info, &flags, 1);
+ xcf_read_int32 (info, &size, 1);
+
+ GIMP_LOG (XCF, "Parasite name: %s, flags: %d, size: %d", name, flags, size);
+
+ if (size > MAX_XCF_PARASITE_DATA_LEN)
+ {
+ g_printerr ("Maximum parasite data length (%ld bytes) exceeded. "
+ "Possibly corrupt XCF file.", MAX_XCF_PARASITE_DATA_LEN);
+ g_free (name);
+ return NULL;
+ }
+
+ if (!name)
+ {
+ g_printerr ("Parasite has no name! Possibly corrupt XCF file.\n");
+ return NULL;
+ }
+
+ data = g_new (gchar, size);
+ size_read = xcf_read_int8 (info, data, size);
+
+ if (size_read != size)
+ {
+ g_printerr ("Incorrect parasite data size: read %u bytes instead of %u. "
+ "Possibly corrupt XCF file.\n",
+ size_read, size);
+ }
+ else
+ {
+ parasite = gimp_parasite_new (name, flags, size, data);
+ }
+
+ g_free (name);
+ g_free (data);
+
+ return parasite;
+}
+
+static gboolean
+xcf_load_old_paths (XcfInfo *info,
+ GimpImage *image)
+{
+ guint32 num_paths;
+ guint32 last_selected_row;
+ GimpVectors *active_vectors;
+
+ xcf_read_int32 (info, &last_selected_row, 1);
+ xcf_read_int32 (info, &num_paths, 1);
+
+ GIMP_LOG (XCF, "Number of old paths: %u", num_paths);
+
+ while (num_paths-- > 0)
+ if (! xcf_load_old_path (info, image))
+ return FALSE;
+
+ active_vectors =
+ GIMP_VECTORS (gimp_container_get_child_by_index (gimp_image_get_vectors (image),
+ last_selected_row));
+
+ if (active_vectors)
+ gimp_image_set_active_vectors (image, active_vectors);
+
+ return TRUE;
+}
+
+static gboolean
+xcf_load_old_path (XcfInfo *info,
+ GimpImage *image)
+{
+ gchar *name;
+ guint32 locked;
+ guint8 state;
+ guint32 closed;
+ guint32 num_points;
+ guint32 version; /* changed from num_paths */
+ GimpTattoo tattoo = 0;
+ GimpVectors *vectors;
+ GimpVectorsCompatPoint *points;
+ gint i;
+
+ xcf_read_string (info, &name, 1);
+ xcf_read_int32 (info, &locked, 1);
+ xcf_read_int8 (info, &state, 1);
+ xcf_read_int32 (info, &closed, 1);
+ xcf_read_int32 (info, &num_points, 1);
+ xcf_read_int32 (info, &version, 1);
+
+ if (version == 2)
+ {
+ guint32 dummy;
+
+ /* Had extra type field and points are stored as doubles */
+ xcf_read_int32 (info, (guint32 *) &dummy, 1);
+ }
+ else if (version == 3)
+ {
+ guint32 dummy;
+
+ /* Has extra tattoo field */
+ xcf_read_int32 (info, (guint32 *) &dummy, 1);
+ xcf_read_int32 (info, (guint32 *) &tattoo, 1);
+ }
+ else if (version != 1)
+ {
+ g_printerr ("Unknown path type (version: %u). Possibly corrupt XCF file.\n", version);
+
+ return FALSE;
+ }
+
+ /* skip empty compatibility paths */
+ if (num_points == 0)
+ {
+ g_free (name);
+ return FALSE;
+ }
+
+ points = g_new0 (GimpVectorsCompatPoint, num_points);
+
+ for (i = 0; i < num_points; i++)
+ {
+ if (version == 1)
+ {
+ gint32 x;
+ gint32 y;
+
+ xcf_read_int32 (info, &points[i].type, 1);
+ xcf_read_int32 (info, (guint32 *) &x, 1);
+ xcf_read_int32 (info, (guint32 *) &y, 1);
+
+ points[i].x = x;
+ points[i].y = y;
+ }
+ else
+ {
+ gfloat x;
+ gfloat y;
+
+ xcf_read_int32 (info, &points[i].type, 1);
+ xcf_read_float (info, &x, 1);
+ xcf_read_float (info, &y, 1);
+
+ points[i].x = x;
+ points[i].y = y;
+ }
+ }
+
+ vectors = gimp_vectors_compat_new (image, name, points, num_points, closed);
+
+ g_free (name);
+ g_free (points);
+
+ gimp_item_set_linked (GIMP_ITEM (vectors), locked, FALSE);
+
+ if (tattoo)
+ gimp_item_set_tattoo (GIMP_ITEM (vectors), tattoo);
+
+ gimp_image_add_vectors (image, vectors,
+ NULL, /* can't be a tree */
+ gimp_container_get_n_children (gimp_image_get_vectors (image)),
+ FALSE);
+
+ return TRUE;
+}
+
+static gboolean
+xcf_load_vectors (XcfInfo *info,
+ GimpImage *image)
+{
+ guint32 version;
+ guint32 active_index;
+ guint32 num_paths;
+ GimpVectors *active_vectors;
+
+#ifdef GIMP_XCF_PATH_DEBUG
+ g_printerr ("xcf_load_vectors\n");
+#endif
+
+ xcf_read_int32 (info, &version, 1);
+
+ if (version != 1)
+ {
+ gimp_message (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ "Unknown vectors version: %d (skipping)", version);
+ return FALSE;
+ }
+
+ xcf_read_int32 (info, &active_index, 1);
+ xcf_read_int32 (info, &num_paths, 1);
+
+#ifdef GIMP_XCF_PATH_DEBUG
+ g_printerr ("%d paths (active: %d)\n", num_paths, active_index);
+#endif
+
+ while (num_paths-- > 0)
+ if (! xcf_load_vector (info, image))
+ return FALSE;
+
+ /* FIXME tree */
+ active_vectors =
+ GIMP_VECTORS (gimp_container_get_child_by_index (gimp_image_get_vectors (image),
+ active_index));
+
+ if (active_vectors)
+ gimp_image_set_active_vectors (image, active_vectors);
+
+#ifdef GIMP_XCF_PATH_DEBUG
+ g_printerr ("xcf_load_vectors: loaded %d bytes\n", info->cp - base);
+#endif
+ return TRUE;
+}
+
+static gboolean
+xcf_load_vector (XcfInfo *info,
+ GimpImage *image)
+{
+ gchar *name;
+ GimpTattoo tattoo = 0;
+ guint32 visible;
+ guint32 linked;
+ guint32 num_parasites;
+ guint32 num_strokes;
+ GimpVectors *vectors;
+ gint i;
+
+#ifdef GIMP_XCF_PATH_DEBUG
+ g_printerr ("xcf_load_vector\n");
+#endif
+
+ xcf_read_string (info, &name, 1);
+ xcf_read_int32 (info, &tattoo, 1);
+ xcf_read_int32 (info, &visible, 1);
+ xcf_read_int32 (info, &linked, 1);
+ xcf_read_int32 (info, &num_parasites, 1);
+ xcf_read_int32 (info, &num_strokes, 1);
+
+#ifdef GIMP_XCF_PATH_DEBUG
+ g_printerr ("name: %s, tattoo: %d, visible: %d, linked: %d, "
+ "num_parasites %d, num_strokes %d\n",
+ name, tattoo, visible, linked, num_parasites, num_strokes);
+#endif
+
+ vectors = gimp_vectors_new (image, name);
+ g_free (name);
+
+ gimp_item_set_visible (GIMP_ITEM (vectors), visible, FALSE);
+ gimp_item_set_linked (GIMP_ITEM (vectors), linked, FALSE);
+
+ if (tattoo)
+ gimp_item_set_tattoo (GIMP_ITEM (vectors), tattoo);
+
+ for (i = 0; i < num_parasites; i++)
+ {
+ GimpParasite *parasite = xcf_load_parasite (info);
+ GError *error = NULL;
+
+ if (! parasite)
+ return FALSE;
+
+ if (! gimp_item_parasite_validate (GIMP_ITEM (vectors), parasite, &error))
+ {
+ gimp_message (info->gimp, G_OBJECT (info->progress),
+ GIMP_MESSAGE_WARNING,
+ "Warning, invalid vectors parasite in XCF file: %s",
+ error->message);
+ g_clear_error (&error);
+ }
+ else
+ {
+ gimp_item_parasite_attach (GIMP_ITEM (vectors), parasite, FALSE);
+ }
+
+ gimp_parasite_free (parasite);
+ }
+
+ for (i = 0; i < num_strokes; i++)
+ {
+ guint32 stroke_type_id;
+ guint32 closed;
+ guint32 num_axes;
+ guint32 num_control_points;
+ guint32 type;
+ gfloat coords[10] = GIMP_COORDS_DEFAULT_VALUES;
+ GimpStroke *stroke;
+ gint j;
+
+ GimpValueArray *control_points;
+ GValue value = G_VALUE_INIT;
+ GimpAnchor anchor = { { 0, } };
+ GType stroke_type;
+
+ g_value_init (&value, GIMP_TYPE_ANCHOR);
+
+ xcf_read_int32 (info, &stroke_type_id, 1);
+ xcf_read_int32 (info, &closed, 1);
+ xcf_read_int32 (info, &num_axes, 1);
+ xcf_read_int32 (info, &num_control_points, 1);
+
+#ifdef GIMP_XCF_PATH_DEBUG
+ g_printerr ("stroke_type: %d, closed: %d, num_axes %d, len %d\n",
+ stroke_type_id, closed, num_axes, num_control_points);
+#endif
+
+ switch (stroke_type_id)
+ {
+ case XCF_STROKETYPE_BEZIER_STROKE:
+ stroke_type = GIMP_TYPE_BEZIER_STROKE;
+ break;
+
+ default:
+ g_printerr ("skipping unknown stroke type\n");
+ xcf_seek_pos (info,
+ info->cp + 4 * num_axes * num_control_points,
+ NULL);
+ continue;
+ }
+
+ if (num_axes < 2 || num_axes > 6)
+ {
+ g_printerr ("bad number of axes in stroke description\n");
+ return FALSE;
+ }
+
+ control_points = gimp_value_array_new (num_control_points);
+
+ anchor.selected = FALSE;
+
+ for (j = 0; j < num_control_points; j++)
+ {
+ xcf_read_int32 (info, &type, 1);
+ xcf_read_float (info, coords, num_axes);
+
+ anchor.type = type;
+ anchor.position.x = coords[0];
+ anchor.position.y = coords[1];
+ anchor.position.pressure = coords[2];
+ anchor.position.xtilt = coords[3];
+ anchor.position.ytilt = coords[4];
+ anchor.position.wheel = coords[5];
+
+ g_value_set_boxed (&value, &anchor);
+ gimp_value_array_append (control_points, &value);
+
+#ifdef GIMP_XCF_PATH_DEBUG
+ g_printerr ("Anchor: %d, (%f, %f, %f, %f, %f, %f)\n", type,
+ coords[0], coords[1], coords[2], coords[3],
+ coords[4], coords[5]);
+#endif
+ }
+
+ g_value_unset (&value);
+
+ stroke = g_object_new (stroke_type,
+ "closed", closed,
+ "control-points", control_points,
+ NULL);
+
+ gimp_vectors_stroke_add (vectors, stroke);
+
+ g_object_unref (stroke);
+ gimp_value_array_unref (control_points);
+ }
+
+ gimp_image_add_vectors (image, vectors,
+ NULL, /* FIXME tree */
+ gimp_container_get_n_children (gimp_image_get_vectors (image)),
+ FALSE);
+
+ return TRUE;
+}
+
+static gboolean
+xcf_skip_unknown_prop (XcfInfo *info,
+ gsize size)
+{
+ guint8 buf[16];
+ guint amount;
+
+ while (size > 0)
+ {
+ if (g_input_stream_is_closed (info->input))
+ return FALSE;
+
+ amount = MIN (16, size);
+ amount = xcf_read_int8 (info, buf, amount);
+ if (amount == 0)
+ return FALSE;
+
+ size -= amount;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+xcf_item_path_is_parent (GList *path,
+ GList *parent_path)
+{
+ GList *iter = path;
+ GList *parent_iter = parent_path;
+
+ if (g_list_length (parent_path) >= g_list_length (path))
+ return FALSE;
+
+ while (iter && parent_iter)
+ {
+ if (iter->data != parent_iter->data)
+ return FALSE;
+
+ iter = iter->next;
+ parent_iter = parent_iter->next;
+ }
+
+ return TRUE;
+}
+
+static void
+xcf_fix_item_path (GimpLayer *layer,
+ GList **path,
+ GList *broken_paths)
+{
+ GList *iter;
+
+ for (iter = broken_paths; iter; iter = iter->next)
+ {
+ if (xcf_item_path_is_parent (*path, iter->data))
+ {
+ /* Not much to do when the absent path is a parent. */
+ g_printerr ("%s: layer '%s' moved to layer tree root because of missing parent.",
+ G_STRFUNC, gimp_object_get_name (layer));
+ g_clear_pointer (path, g_list_free);
+ return;
+ }
+ }
+
+ /* Check if a parent of path, or path itself is on the same
+ * tree level as any broken path; and if so, and if the broken path is
+ * in a lower position in the item group, decrement it.
+ */
+ for (iter = broken_paths; iter; iter = iter->next)
+ {
+ GList *broken_path = iter->data;
+ GList *iter1 = *path;
+ GList *iter2 = broken_path;
+
+ if (g_list_length (broken_path) > g_list_length (*path))
+ continue;
+
+ while (iter1 && iter2)
+ {
+ if (iter2->next && iter1->data != iter2->data)
+ /* Paths diverged before reaching iter2 leaf. */
+ break;
+
+ if (iter2->next)
+ {
+ iter1 = iter1->next;
+ iter2 = iter2->next;
+ continue;
+ }
+
+ if (GPOINTER_TO_UINT (iter2->data) < GPOINTER_TO_UINT (iter1->data))
+ iter1->data = GUINT_TO_POINTER (GPOINTER_TO_UINT (iter1->data) - 1);
+
+ break;
+ }
+ }
+}
diff --git a/app/xcf/xcf-load.h b/app/xcf/xcf-load.h
new file mode 100644
index 0000000..94881d0
--- /dev/null
+++ b/app/xcf/xcf-load.h
@@ -0,0 +1,27 @@
+/* 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 __XCF_LOAD_H__
+#define __XCF_LOAD_H__
+
+
+GimpImage * xcf_load_image (Gimp *gimp,
+ XcfInfo *info,
+ GError **error);
+
+
+#endif /* __XCF_LOAD_H__ */
diff --git a/app/xcf/xcf-private.h b/app/xcf/xcf-private.h
new file mode 100644
index 0000000..b7490d8
--- /dev/null
+++ b/app/xcf/xcf-private.h
@@ -0,0 +1,118 @@
+/* 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 __XCF_PRIVATE_H__
+#define __XCF_PRIVATE_H__
+
+
+#define XCF_TILE_WIDTH 64
+#define XCF_TILE_HEIGHT 64
+#define XCF_TILE_MAX_DATA_LENGTH_FACTOR 1.5
+
+typedef enum
+{
+ PROP_END = 0,
+ PROP_COLORMAP = 1,
+ PROP_ACTIVE_LAYER = 2,
+ PROP_ACTIVE_CHANNEL = 3,
+ PROP_SELECTION = 4,
+ PROP_FLOATING_SELECTION = 5,
+ PROP_OPACITY = 6,
+ PROP_MODE = 7,
+ PROP_VISIBLE = 8,
+ PROP_LINKED = 9,
+ PROP_LOCK_ALPHA = 10,
+ PROP_APPLY_MASK = 11,
+ PROP_EDIT_MASK = 12,
+ PROP_SHOW_MASK = 13,
+ PROP_SHOW_MASKED = 14,
+ PROP_OFFSETS = 15,
+ PROP_COLOR = 16,
+ PROP_COMPRESSION = 17,
+ PROP_GUIDES = 18,
+ PROP_RESOLUTION = 19,
+ PROP_TATTOO = 20,
+ PROP_PARASITES = 21,
+ PROP_UNIT = 22,
+ PROP_PATHS = 23,
+ PROP_USER_UNIT = 24,
+ PROP_VECTORS = 25,
+ PROP_TEXT_LAYER_FLAGS = 26,
+ PROP_OLD_SAMPLE_POINTS = 27,
+ PROP_LOCK_CONTENT = 28,
+ PROP_GROUP_ITEM = 29,
+ PROP_ITEM_PATH = 30,
+ PROP_GROUP_ITEM_FLAGS = 31,
+ PROP_LOCK_POSITION = 32,
+ PROP_FLOAT_OPACITY = 33,
+ PROP_COLOR_TAG = 34,
+ PROP_COMPOSITE_MODE = 35,
+ PROP_COMPOSITE_SPACE = 36,
+ PROP_BLEND_SPACE = 37,
+ PROP_FLOAT_COLOR = 38,
+ PROP_SAMPLE_POINTS = 39,
+} PropType;
+
+typedef enum
+{
+ COMPRESS_NONE = 0,
+ COMPRESS_RLE = 1,
+ COMPRESS_ZLIB = 2, /* unused */
+ COMPRESS_FRACTAL = 3 /* unused */
+} XcfCompressionType;
+
+typedef enum
+{
+ XCF_ORIENTATION_HORIZONTAL = 1,
+ XCF_ORIENTATION_VERTICAL = 2
+} XcfOrientationType;
+
+typedef enum
+{
+ XCF_STROKETYPE_STROKE = 0,
+ XCF_STROKETYPE_BEZIER_STROKE = 1
+} XcfStrokeType;
+
+typedef enum
+{
+ XCF_GROUP_ITEM_EXPANDED = 1
+} XcfGroupItemFlagsType;
+
+typedef struct _XcfInfo XcfInfo;
+
+struct _XcfInfo
+{
+ Gimp *gimp;
+ GimpProgress *progress;
+ GInputStream *input;
+ GOutputStream *output;
+ GSeekable *seekable;
+ goffset cp;
+ gint bytes_per_offset;
+ GFile *file;
+ GimpTattoo tattoo_state;
+ GimpLayer *active_layer;
+ GimpChannel *active_channel;
+ GimpDrawable *floating_sel_drawable;
+ GimpLayer *floating_sel;
+ goffset floating_sel_offset;
+ XcfCompressionType compression;
+ gint file_version;
+};
+
+
+#endif /* __XCF_PRIVATE_H__ */
diff --git a/app/xcf/xcf-read.c b/app/xcf/xcf-read.c
new file mode 100644
index 0000000..1c53436
--- /dev/null
+++ b/app/xcf/xcf-read.c
@@ -0,0 +1,274 @@
+/* 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 <gio/gio.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core/core-types.h"
+
+#include "xcf-private.h"
+#include "xcf-read.h"
+
+#include "gimp-intl.h"
+
+
+#define MAX_XCF_STRING_LEN (16L * 1024 * 1024)
+
+
+guint
+xcf_read_int8 (XcfInfo *info,
+ guint8 *data,
+ gint count)
+{
+ gsize bytes_read = 0;
+
+ /* we allow for 'data == NULL && count == 0', which g_input_stream_read_all()
+ * rejects.
+ */
+ if (count > 0)
+ {
+ g_input_stream_read_all (info->input, data, count,
+ &bytes_read, NULL, NULL);
+
+ info->cp += bytes_read;
+ }
+
+ return bytes_read;
+}
+
+guint
+xcf_read_int16 (XcfInfo *info,
+ guint16 *data,
+ gint count)
+{
+ guint total = 0;
+
+ if (count > 0)
+ {
+ total += xcf_read_int8 (info, (guint8 *) data, count * 2);
+
+ while (count--)
+ {
+ *data = g_ntohs (*data);
+ data++;
+ }
+ }
+
+ return total;
+}
+
+guint
+xcf_read_int32 (XcfInfo *info,
+ guint32 *data,
+ gint count)
+{
+ guint total = 0;
+
+ if (count > 0)
+ {
+ total += xcf_read_int8 (info, (guint8 *) data, count * 4);
+
+ while (count--)
+ {
+ *data = g_ntohl (*data);
+ data++;
+ }
+ }
+
+ return total;
+}
+
+guint
+xcf_read_int64 (XcfInfo *info,
+ guint64 *data,
+ gint count)
+{
+ guint total = 0;
+
+ if (count > 0)
+ {
+ total += xcf_read_int8 (info, (guint8 *) data, count * 8);
+
+ while (count--)
+ {
+ *data = GINT64_FROM_BE (*data);
+ data++;
+ }
+ }
+
+ return total;
+}
+
+guint
+xcf_read_offset (XcfInfo *info,
+ goffset *data,
+ gint count)
+{
+ guint total = 0;
+
+ if (count > 0)
+ {
+ if (info->bytes_per_offset == 4)
+ {
+ gint32 *int_offsets = g_alloca (count * sizeof (gint32));
+
+ total += xcf_read_int8 (info, (guint8 *) int_offsets, count * 4);
+
+ while (count--)
+ {
+ *data = g_ntohl (*int_offsets);
+ int_offsets++;
+ data++;
+ }
+ }
+ else
+ {
+ total += xcf_read_int8 (info, (guint8 *) data, count * 8);
+
+ while (count--)
+ {
+ *data = GINT64_FROM_BE (*data);
+ data++;
+ }
+ }
+ }
+
+ return total;
+}
+
+guint
+xcf_read_float (XcfInfo *info,
+ gfloat *data,
+ gint count)
+{
+ return xcf_read_int32 (info, (guint32 *) ((void *) data), count);
+}
+
+guint
+xcf_read_string (XcfInfo *info,
+ gchar **data,
+ gint count)
+{
+ guint total = 0;
+ gint i;
+
+ for (i = 0; i < count; i++)
+ {
+ guint32 tmp;
+
+ total += xcf_read_int32 (info, &tmp, 1);
+
+ if (tmp > MAX_XCF_STRING_LEN)
+ {
+ g_warning ("Maximum string length (%ld bytes) exceeded. "
+ "Possibly corrupt XCF file.", MAX_XCF_STRING_LEN);
+ data[i] = NULL;
+ }
+ else if (tmp > 0)
+ {
+ gchar *str;
+
+ str = g_new (gchar, tmp);
+ total += xcf_read_int8 (info, (guint8*) str, tmp);
+
+ if (str[tmp - 1] != '\0')
+ str[tmp - 1] = '\0';
+
+ data[i] = gimp_any_to_utf8 (str, -1,
+ _("Invalid UTF-8 string in XCF file"));
+
+ g_free (str);
+ }
+ else
+ {
+ data[i] = NULL;
+ }
+ }
+
+ return total;
+}
+
+guint
+xcf_read_component (XcfInfo *info,
+ gint bpc,
+ guint8 *data,
+ gint count)
+{
+ switch (bpc)
+ {
+ case 1:
+ return xcf_read_int8 (info, data, count);
+
+ case 2:
+ return xcf_read_int16 (info, (guint16 *) data, count);
+
+ case 4:
+ return xcf_read_int32 (info, (guint32 *) data, count);
+
+ case 8:
+ return xcf_read_int64 (info, (guint64 *) data, count);
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+void
+xcf_read_from_be (gint bpc,
+ guint8 *data,
+ gint count)
+{
+ gint i;
+
+ switch (bpc)
+ {
+ case 1:
+ break;
+
+ case 2:
+ {
+ guint16 *d = (guint16 *) data;
+
+ for (i = 0; i < count; i++)
+ d[i] = g_ntohs (d[i]);
+ }
+ break;
+
+ case 4:
+ {
+ guint32 *d = (guint32 *) data;
+
+ for (i = 0; i < count; i++)
+ d[i] = g_ntohl (d[i]);
+ }
+ break;
+
+ case 8:
+ {
+ guint64 *d = (guint64 *) data;
+
+ for (i = 0; i < count; i++)
+ d[i] = GINT64_FROM_BE (d[i]);
+ }
+ break;
+ }
+}
diff --git a/app/xcf/xcf-read.h b/app/xcf/xcf-read.h
new file mode 100644
index 0000000..2a75a76
--- /dev/null
+++ b/app/xcf/xcf-read.h
@@ -0,0 +1,53 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __XCF_READ_H__
+#define __XCF_READ_H__
+
+
+guint xcf_read_int8 (XcfInfo *info,
+ guint8 *data,
+ gint count);
+guint xcf_read_int16 (XcfInfo *info,
+ guint16 *data,
+ gint count);
+guint xcf_read_int32 (XcfInfo *info,
+ guint32 *data,
+ gint count);
+guint xcf_read_int64 (XcfInfo *info,
+ guint64 *data,
+ gint count);
+guint xcf_read_offset (XcfInfo *info,
+ goffset *data,
+ gint count);
+guint xcf_read_float (XcfInfo *info,
+ gfloat *data,
+ gint count);
+guint xcf_read_string (XcfInfo *info,
+ gchar **data,
+ gint count);
+guint xcf_read_component (XcfInfo *info,
+ gint bpc,
+ guint8 *data,
+ gint count);
+
+void xcf_read_from_be (gint bpc,
+ guint8 *data,
+ gint count);
+
+
+#endif /* __XCF_READ_H__ */
diff --git a/app/xcf/xcf-save.c b/app/xcf/xcf-save.c
new file mode 100644
index 0000000..a1c4f71
--- /dev/null
+++ b/app/xcf/xcf-save.c
@@ -0,0 +1,2271 @@
+/* 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 <zlib.h>
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpcolor/gimpcolor.h"
+
+#include "core/core-types.h"
+
+#include "gegl/gimp-babl-compat.h"
+#include "gegl/gimp-gegl-tile-compat.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontainer.h"
+#include "core/gimpchannel.h"
+#include "core/gimpdrawable.h"
+#include "core/gimpgrid.h"
+#include "core/gimpguide.h"
+#include "core/gimpimage.h"
+#include "core/gimpimage-colormap.h"
+#include "core/gimpimage-grid.h"
+#include "core/gimpimage-guides.h"
+#include "core/gimpimage-metadata.h"
+#include "core/gimpimage-private.h"
+#include "core/gimpimage-sample-points.h"
+#include "core/gimpimage-symmetry.h"
+#include "core/gimplayer.h"
+#include "core/gimplayermask.h"
+#include "core/gimpparasitelist.h"
+#include "core/gimpprogress.h"
+#include "core/gimpsamplepoint.h"
+#include "core/gimpsymmetry.h"
+
+#include "operations/layer-modes/gimp-layer-modes.h"
+
+#include "text/gimptextlayer.h"
+#include "text/gimptextlayer-xcf.h"
+
+#include "vectors/gimpanchor.h"
+#include "vectors/gimpstroke.h"
+#include "vectors/gimpbezierstroke.h"
+#include "vectors/gimpvectors.h"
+#include "vectors/gimpvectors-compat.h"
+
+#include "xcf-private.h"
+#include "xcf-read.h"
+#include "xcf-save.h"
+#include "xcf-seek.h"
+#include "xcf-write.h"
+
+#include "gimp-intl.h"
+
+
+static gboolean xcf_save_image_props (XcfInfo *info,
+ GimpImage *image,
+ GError **error);
+static gboolean xcf_save_layer_props (XcfInfo *info,
+ GimpImage *image,
+ GimpLayer *layer,
+ GError **error);
+static gboolean xcf_save_channel_props (XcfInfo *info,
+ GimpImage *image,
+ GimpChannel *channel,
+ GError **error);
+static gboolean xcf_save_prop (XcfInfo *info,
+ GimpImage *image,
+ PropType prop_type,
+ GError **error,
+ ...);
+static gboolean xcf_save_layer (XcfInfo *info,
+ GimpImage *image,
+ GimpLayer *layer,
+ GError **error);
+static gboolean xcf_save_channel (XcfInfo *info,
+ GimpImage *image,
+ GimpChannel *channel,
+ GError **error);
+static gboolean xcf_save_buffer (XcfInfo *info,
+ GeglBuffer *buffer,
+ GError **error);
+static gboolean xcf_save_level (XcfInfo *info,
+ GeglBuffer *buffer,
+ GError **error);
+static gboolean xcf_save_tile (XcfInfo *info,
+ GeglBuffer *buffer,
+ GeglRectangle *tile_rect,
+ const Babl *format,
+ GError **error);
+static gboolean xcf_save_tile_rle (XcfInfo *info,
+ GeglBuffer *buffer,
+ GeglRectangle *tile_rect,
+ const Babl *format,
+ guchar *rlebuf,
+ GError **error);
+static gboolean xcf_save_tile_zlib (XcfInfo *info,
+ GeglBuffer *buffer,
+ GeglRectangle *tile_rect,
+ const Babl *format,
+ GError **error);
+static gboolean xcf_save_parasite (XcfInfo *info,
+ GimpParasite *parasite,
+ GError **error);
+static gboolean xcf_save_parasite_list (XcfInfo *info,
+ GimpParasiteList *parasite,
+ GError **error);
+static gboolean xcf_save_old_paths (XcfInfo *info,
+ GimpImage *image,
+ GError **error);
+static gboolean xcf_save_vectors (XcfInfo *info,
+ GimpImage *image,
+ GError **error);
+
+
+/* private convenience macros */
+#define xcf_write_int32_check_error(info, data, count) G_STMT_START { \
+ xcf_write_int32 (info, data, count, &tmp_error); \
+ if (tmp_error) \
+ { \
+ g_propagate_error (error, tmp_error); \
+ return FALSE; \
+ } \
+ } G_STMT_END
+
+#define xcf_write_offset_check_error(info, data, count) G_STMT_START { \
+ xcf_write_offset (info, data, count, &tmp_error); \
+ if (tmp_error) \
+ { \
+ g_propagate_error (error, tmp_error); \
+ return FALSE; \
+ } \
+ } G_STMT_END
+
+#define xcf_write_zero_offset_check_error(info, count) G_STMT_START { \
+ xcf_write_zero_offset (info, count, &tmp_error); \
+ if (tmp_error) \
+ { \
+ g_propagate_error (error, tmp_error); \
+ return FALSE; \
+ } \
+ } G_STMT_END
+
+#define xcf_write_int8_check_error(info, data, count) G_STMT_START { \
+ xcf_write_int8 (info, data, count, &tmp_error); \
+ if (tmp_error) \
+ { \
+ g_propagate_error (error, tmp_error); \
+ return FALSE; \
+ } \
+ } G_STMT_END
+
+#define xcf_write_float_check_error(info, data, count) G_STMT_START { \
+ xcf_write_float (info, data, count, &tmp_error); \
+ if (tmp_error) \
+ { \
+ g_propagate_error (error, tmp_error); \
+ return FALSE; \
+ } \
+ } G_STMT_END
+
+#define xcf_write_string_check_error(info, data, count) G_STMT_START { \
+ xcf_write_string (info, data, count, &tmp_error); \
+ if (tmp_error) \
+ { \
+ g_propagate_error (error, tmp_error); \
+ return FALSE; \
+ } \
+ } G_STMT_END
+
+#define xcf_write_component_check_error(info, bpc, data, count) G_STMT_START { \
+ xcf_write_component (info, bpc, data, count, &tmp_error); \
+ if (tmp_error) \
+ { \
+ g_propagate_error (error, tmp_error); \
+ return FALSE; \
+ } \
+ } G_STMT_END
+
+#define xcf_write_prop_type_check_error(info, prop_type) G_STMT_START { \
+ guint32 _prop_int32 = prop_type; \
+ xcf_write_int32_check_error (info, &_prop_int32, 1); \
+ } G_STMT_END
+
+#define xcf_check_error(x) G_STMT_START { \
+ if (! (x)) \
+ return FALSE; \
+ } G_STMT_END
+
+#define xcf_progress_update(info) G_STMT_START \
+ { \
+ progress++; \
+ if (info->progress) \
+ gimp_progress_set_value (info->progress, \
+ (gdouble) progress / (gdouble) max_progress); \
+ } G_STMT_END
+
+
+gboolean
+xcf_save_image (XcfInfo *info,
+ GimpImage *image,
+ GError **error)
+{
+ GList *all_layers;
+ GList *all_channels;
+ GList *list;
+ goffset saved_pos;
+ goffset offset;
+ guint32 value;
+ guint n_layers;
+ guint n_channels;
+ guint progress = 0;
+ guint max_progress;
+ gchar version_tag[16];
+ GError *tmp_error = NULL;
+
+ /* write out the tag information for the image */
+ if (info->file_version > 0)
+ {
+ g_snprintf (version_tag, sizeof (version_tag),
+ "gimp xcf v%03d", info->file_version);
+ }
+ else
+ {
+ strcpy (version_tag, "gimp xcf file");
+ }
+
+ xcf_write_int8_check_error (info, (guint8 *) version_tag, 14);
+
+ /* write out the width, height and image type information for the image */
+ value = gimp_image_get_width (image);
+ xcf_write_int32_check_error (info, (guint32 *) &value, 1);
+
+ value = gimp_image_get_height (image);
+ xcf_write_int32_check_error (info, (guint32 *) &value, 1);
+
+ value = gimp_image_get_base_type (image);
+ xcf_write_int32_check_error (info, &value, 1);
+
+ if (info->file_version >= 4)
+ {
+ value = gimp_image_get_precision (image);
+ xcf_write_int32_check_error (info, &value, 1);
+ }
+
+ /* determine the number of layers and channels in the image */
+ all_layers = gimp_image_get_layer_list (image);
+ all_channels = gimp_image_get_channel_list (image);
+
+ /* check and see if we have to save out the selection */
+ if (! gimp_channel_is_empty (gimp_image_get_mask (image)))
+ {
+ all_channels = g_list_append (all_channels, gimp_image_get_mask (image));
+ }
+
+ n_layers = (guint) g_list_length (all_layers);
+ n_channels = (guint) g_list_length (all_channels);
+
+ max_progress = 1 + n_layers + n_channels;
+
+ /* write the property information for the image */
+ xcf_check_error (xcf_save_image_props (info, image, error));
+
+ xcf_progress_update (info);
+
+ /* 'saved_pos' is the next slot in the offset table */
+ saved_pos = info->cp;
+
+ /* write an empty offset table */
+ xcf_write_zero_offset_check_error (info, n_layers + n_channels + 2);
+
+ /* 'offset' is where we will write the next layer or channel */
+ offset = info->cp;
+
+ for (list = all_layers; list; list = g_list_next (list))
+ {
+ GimpLayer *layer = list->data;
+
+ /* seek back to the next slot in the offset table and write the
+ * offset of the layer
+ */
+ xcf_check_error (xcf_seek_pos (info, saved_pos, error));
+ xcf_write_offset_check_error (info, &offset, 1);
+
+ /* remember the next slot in the offset table */
+ saved_pos = info->cp;
+
+ /* seek to the layer offset and save the layer */
+ xcf_check_error (xcf_seek_pos (info, offset, error));
+ xcf_check_error (xcf_save_layer (info, image, layer, error));
+
+ /* the next layer's offset is after the layer we just wrote */
+ offset = info->cp;
+
+ xcf_progress_update (info);
+ }
+
+ /* skip a '0' in the offset table to indicate the end of the layer
+ * offsets
+ */
+ saved_pos += info->bytes_per_offset;
+
+ for (list = all_channels; list; list = g_list_next (list))
+ {
+ GimpChannel *channel = list->data;
+
+ /* seek back to the next slot in the offset table and write the
+ * offset of the channel
+ */
+ xcf_check_error (xcf_seek_pos (info, saved_pos, error));
+ xcf_write_offset_check_error (info, &offset, 1);
+
+ /* remember the next slot in the offset table */
+ saved_pos = info->cp;
+
+ /* seek to the channel offset and save the channel */
+ xcf_check_error (xcf_seek_pos (info, offset, error));
+ xcf_check_error (xcf_save_channel (info, image, channel, error));
+
+ /* the next channels's offset is after the channel we just wrote */
+ offset = info->cp;
+
+ xcf_progress_update (info);
+ }
+
+ /* there is already a '0' at the end of the offset table to indicate
+ * the end of the channel offsets
+ */
+
+ g_list_free (all_layers);
+ g_list_free (all_channels);
+
+ return ! g_output_stream_is_closed (info->output);
+}
+
+static gboolean
+xcf_save_image_props (XcfInfo *info,
+ GimpImage *image,
+ GError **error)
+{
+ GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image);
+ GimpParasite *grid_parasite = NULL;
+ GimpParasite *meta_parasite = NULL;
+ GList *symmetry_parasites = NULL;
+ GList *iter;
+ GimpUnit unit = gimp_image_get_unit (image);
+ gdouble xres;
+ gdouble yres;
+
+ gimp_image_get_resolution (image, &xres, &yres);
+
+ /* check and see if we should save the colormap property */
+ if (gimp_image_get_colormap (image))
+ xcf_check_error (xcf_save_prop (info, image, PROP_COLORMAP, error,
+ gimp_image_get_colormap_size (image),
+ gimp_image_get_colormap (image)));
+
+ if (info->compression != COMPRESS_NONE)
+ xcf_check_error (xcf_save_prop (info, image, PROP_COMPRESSION, error,
+ info->compression));
+
+ if (gimp_image_get_guides (image))
+ xcf_check_error (xcf_save_prop (info, image, PROP_GUIDES, error,
+ gimp_image_get_guides (image)));
+
+ if (gimp_image_get_sample_points (image))
+ {
+ /* save the new property before the old one, so loading can skip
+ * the latter
+ */
+ xcf_check_error (xcf_save_prop (info, image, PROP_SAMPLE_POINTS, error,
+ gimp_image_get_sample_points (image)));
+ xcf_check_error (xcf_save_prop (info, image, PROP_OLD_SAMPLE_POINTS, error,
+ gimp_image_get_sample_points (image)));
+ }
+
+ xcf_check_error (xcf_save_prop (info, image, PROP_RESOLUTION, error,
+ xres, yres));
+
+ xcf_check_error (xcf_save_prop (info, image, PROP_TATTOO, error,
+ gimp_image_get_tattoo_state (image)));
+
+ if (unit < gimp_unit_get_number_of_built_in_units ())
+ xcf_check_error (xcf_save_prop (info, image, PROP_UNIT, error, unit));
+
+ if (gimp_container_get_n_children (gimp_image_get_vectors (image)) > 0)
+ {
+ if (gimp_vectors_compat_is_compatible (image))
+ xcf_check_error (xcf_save_prop (info, image, PROP_PATHS, error));
+ else
+ xcf_check_error (xcf_save_prop (info, image, PROP_VECTORS, error));
+ }
+
+ if (unit >= gimp_unit_get_number_of_built_in_units ())
+ xcf_check_error (xcf_save_prop (info, image, PROP_USER_UNIT, error, unit));
+
+ if (gimp_image_get_grid (image))
+ {
+ GimpGrid *grid = gimp_image_get_grid (image);
+
+ grid_parasite = gimp_grid_to_parasite (grid);
+ gimp_parasite_list_add (private->parasites, grid_parasite);
+ }
+
+ if (gimp_image_get_metadata (image))
+ {
+ GimpMetadata *metadata = gimp_image_get_metadata (image);
+ gchar *meta_string;
+
+ meta_string = gimp_metadata_serialize (metadata);
+
+ if (meta_string)
+ {
+ meta_parasite = gimp_parasite_new ("gimp-image-metadata",
+ GIMP_PARASITE_PERSISTENT,
+ strlen (meta_string) + 1,
+ meta_string);
+ gimp_parasite_list_add (private->parasites, meta_parasite);
+ g_free (meta_string);
+ }
+ }
+
+ if (g_list_length (gimp_image_symmetry_get (image)))
+ {
+ GimpParasite *parasite = NULL;
+ GimpSymmetry *symmetry;
+
+ for (iter = gimp_image_symmetry_get (image); iter; iter = g_list_next (iter))
+ {
+ symmetry = GIMP_SYMMETRY (iter->data);
+ if (G_TYPE_FROM_INSTANCE (symmetry) == GIMP_TYPE_SYMMETRY)
+ /* Do not save the identity symmetry. */
+ continue;
+ parasite = gimp_symmetry_to_parasite (GIMP_SYMMETRY (iter->data));
+ gimp_parasite_list_add (private->parasites, parasite);
+ symmetry_parasites = g_list_prepend (symmetry_parasites, parasite);
+ }
+ }
+
+ if (gimp_parasite_list_length (private->parasites) > 0)
+ {
+ xcf_check_error (xcf_save_prop (info, image, PROP_PARASITES, error,
+ private->parasites));
+ }
+
+ if (grid_parasite)
+ {
+ gimp_parasite_list_remove (private->parasites,
+ gimp_parasite_name (grid_parasite));
+ gimp_parasite_free (grid_parasite);
+ }
+
+ if (meta_parasite)
+ {
+ gimp_parasite_list_remove (private->parasites,
+ gimp_parasite_name (meta_parasite));
+ gimp_parasite_free (meta_parasite);
+ }
+
+ for (iter = symmetry_parasites; iter; iter = g_list_next (iter))
+ {
+ GimpParasite *parasite = iter->data;
+
+ gimp_parasite_list_remove (private->parasites,
+ gimp_parasite_name (parasite));
+ }
+ g_list_free_full (symmetry_parasites,
+ (GDestroyNotify) gimp_parasite_free);
+
+ xcf_check_error (xcf_save_prop (info, image, PROP_END, error));
+
+ return TRUE;
+}
+
+static gboolean
+xcf_save_layer_props (XcfInfo *info,
+ GimpImage *image,
+ GimpLayer *layer,
+ GError **error)
+{
+ GimpParasiteList *parasites;
+ gint offset_x;
+ gint offset_y;
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (layer)))
+ xcf_check_error (xcf_save_prop (info, image, PROP_GROUP_ITEM, error));
+
+ if (gimp_viewable_get_parent (GIMP_VIEWABLE (layer)))
+ {
+ GList *path;
+
+ path = gimp_item_get_path (GIMP_ITEM (layer));
+ xcf_check_error (xcf_save_prop (info, image, PROP_ITEM_PATH, error,
+ path));
+ g_list_free (path);
+ }
+
+ if (layer == gimp_image_get_active_layer (image))
+ xcf_check_error (xcf_save_prop (info, image, PROP_ACTIVE_LAYER, error));
+
+ if (layer == gimp_image_get_floating_selection (image))
+ {
+ info->floating_sel_drawable = gimp_layer_get_floating_sel_drawable (layer);
+ xcf_check_error (xcf_save_prop (info, image, PROP_FLOATING_SELECTION,
+ error));
+ }
+
+ xcf_check_error (xcf_save_prop (info, image, PROP_OPACITY, error,
+ gimp_layer_get_opacity (layer)));
+ xcf_check_error (xcf_save_prop (info, image, PROP_FLOAT_OPACITY, error,
+ gimp_layer_get_opacity (layer)));
+ xcf_check_error (xcf_save_prop (info, image, PROP_VISIBLE, error,
+ gimp_item_get_visible (GIMP_ITEM (layer))));
+ xcf_check_error (xcf_save_prop (info, image, PROP_LINKED, error,
+ gimp_item_get_linked (GIMP_ITEM (layer))));
+ xcf_check_error (xcf_save_prop (info, image, PROP_COLOR_TAG, error,
+ gimp_item_get_color_tag (GIMP_ITEM (layer))));
+ xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_CONTENT, error,
+ gimp_item_get_lock_content (GIMP_ITEM (layer))));
+ xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_ALPHA, error,
+ gimp_layer_get_lock_alpha (layer)));
+ xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_POSITION, error,
+ gimp_item_get_lock_position (GIMP_ITEM (layer))));
+
+ if (gimp_layer_get_mask (layer))
+ {
+ xcf_check_error (xcf_save_prop (info, image, PROP_APPLY_MASK, error,
+ gimp_layer_get_apply_mask (layer)));
+ xcf_check_error (xcf_save_prop (info, image, PROP_EDIT_MASK, error,
+ gimp_layer_get_edit_mask (layer)));
+ xcf_check_error (xcf_save_prop (info, image, PROP_SHOW_MASK, error,
+ gimp_layer_get_show_mask (layer)));
+ }
+ else
+ {
+ xcf_check_error (xcf_save_prop (info, image, PROP_APPLY_MASK, error,
+ FALSE));
+ xcf_check_error (xcf_save_prop (info, image, PROP_EDIT_MASK, error,
+ FALSE));
+ xcf_check_error (xcf_save_prop (info, image, PROP_SHOW_MASK, error,
+ FALSE));
+ }
+
+ gimp_item_get_offset (GIMP_ITEM (layer), &offset_x, &offset_y);
+
+ xcf_check_error (xcf_save_prop (info, image, PROP_OFFSETS, error,
+ offset_x, offset_y));
+ xcf_check_error (xcf_save_prop (info, image, PROP_MODE, error,
+ gimp_layer_get_mode (layer)));
+ xcf_check_error (xcf_save_prop (info, image, PROP_BLEND_SPACE, error,
+ gimp_layer_get_mode (layer),
+ gimp_layer_get_blend_space (layer)));
+ xcf_check_error (xcf_save_prop (info, image, PROP_COMPOSITE_SPACE, error,
+ gimp_layer_get_mode (layer),
+ gimp_layer_get_composite_space (layer)));
+ xcf_check_error (xcf_save_prop (info, image, PROP_COMPOSITE_MODE, error,
+ gimp_layer_get_mode (layer),
+ gimp_layer_get_composite_mode (layer)));
+ xcf_check_error (xcf_save_prop (info, image, PROP_TATTOO, error,
+ gimp_item_get_tattoo (GIMP_ITEM (layer))));
+
+ if (GIMP_IS_TEXT_LAYER (layer) && GIMP_TEXT_LAYER (layer)->text)
+ {
+ GimpTextLayer *text_layer = GIMP_TEXT_LAYER (layer);
+ guint32 flags = gimp_text_layer_get_xcf_flags (text_layer);
+
+ gimp_text_layer_xcf_save_prepare (text_layer);
+
+ if (flags)
+ xcf_check_error (xcf_save_prop (info,
+ image, PROP_TEXT_LAYER_FLAGS, error,
+ flags));
+ }
+
+ if (gimp_viewable_get_children (GIMP_VIEWABLE (layer)))
+ {
+ gint32 flags = 0;
+
+ if (gimp_viewable_get_expanded (GIMP_VIEWABLE (layer)))
+ flags |= XCF_GROUP_ITEM_EXPANDED;
+
+ xcf_check_error (xcf_save_prop (info,
+ image, PROP_GROUP_ITEM_FLAGS, error,
+ flags));
+ }
+
+ parasites = gimp_item_get_parasites (GIMP_ITEM (layer));
+
+ if (gimp_parasite_list_length (parasites) > 0)
+ {
+ xcf_check_error (xcf_save_prop (info, image, PROP_PARASITES, error,
+ parasites));
+ }
+
+ xcf_check_error (xcf_save_prop (info, image, PROP_END, error));
+
+ return TRUE;
+}
+
+static gboolean
+xcf_save_channel_props (XcfInfo *info,
+ GimpImage *image,
+ GimpChannel *channel,
+ GError **error)
+{
+ GimpParasiteList *parasites;
+
+ if (channel == gimp_image_get_active_channel (image))
+ xcf_check_error (xcf_save_prop (info, image, PROP_ACTIVE_CHANNEL, error));
+
+ if (channel == gimp_image_get_mask (image))
+ xcf_check_error (xcf_save_prop (info, image, PROP_SELECTION, error));
+
+ xcf_check_error (xcf_save_prop (info, image, PROP_OPACITY, error,
+ gimp_channel_get_opacity (channel)));
+ xcf_check_error (xcf_save_prop (info, image, PROP_FLOAT_OPACITY, error,
+ gimp_channel_get_opacity (channel)));
+ xcf_check_error (xcf_save_prop (info, image, PROP_VISIBLE, error,
+ gimp_item_get_visible (GIMP_ITEM (channel))));
+ xcf_check_error (xcf_save_prop (info, image, PROP_LINKED, error,
+ gimp_item_get_linked (GIMP_ITEM (channel))));
+ xcf_check_error (xcf_save_prop (info, image, PROP_COLOR_TAG, error,
+ gimp_item_get_color_tag (GIMP_ITEM (channel))));
+ xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_CONTENT, error,
+ gimp_item_get_lock_content (GIMP_ITEM (channel))));
+ xcf_check_error (xcf_save_prop (info, image, PROP_LOCK_POSITION, error,
+ gimp_item_get_lock_position (GIMP_ITEM (channel))));
+ xcf_check_error (xcf_save_prop (info, image, PROP_SHOW_MASKED, error,
+ gimp_channel_get_show_masked (channel)));
+ xcf_check_error (xcf_save_prop (info, image, PROP_COLOR, error,
+ &channel->color));
+ xcf_check_error (xcf_save_prop (info, image, PROP_FLOAT_COLOR, error,
+ &channel->color));
+ xcf_check_error (xcf_save_prop (info, image, PROP_TATTOO, error,
+ gimp_item_get_tattoo (GIMP_ITEM (channel))));
+
+ parasites = gimp_item_get_parasites (GIMP_ITEM (channel));
+
+ if (gimp_parasite_list_length (parasites) > 0)
+ {
+ xcf_check_error (xcf_save_prop (info, image, PROP_PARASITES, error,
+ parasites));
+ }
+
+ xcf_check_error (xcf_save_prop (info, image, PROP_END, error));
+
+ return TRUE;
+}
+
+static gboolean
+xcf_save_prop (XcfInfo *info,
+ GimpImage *image,
+ PropType prop_type,
+ GError **error,
+ ...)
+{
+ guint32 size;
+ va_list args;
+ GError *tmp_error = NULL;
+
+ va_start (args, error);
+
+ switch (prop_type)
+ {
+ case PROP_END:
+ size = 0;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+ break;
+
+ case PROP_COLORMAP:
+ {
+ guint32 n_colors = va_arg (args, guint32);
+ guchar *colors = va_arg (args, guchar *);
+
+ size = 4 + n_colors * 3;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, &n_colors, 1);
+ xcf_write_int8_check_error (info, colors, n_colors * 3);
+ }
+ break;
+
+ case PROP_ACTIVE_LAYER:
+ case PROP_ACTIVE_CHANNEL:
+ case PROP_SELECTION:
+ case PROP_GROUP_ITEM:
+ size = 0;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+ break;
+
+ case PROP_FLOATING_SELECTION:
+ size = info->bytes_per_offset;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ info->floating_sel_offset = info->cp;
+ xcf_write_zero_offset_check_error (info, 1);
+ break;
+
+ case PROP_OPACITY:
+ {
+ gdouble opacity = va_arg (args, gdouble);
+ guint32 uint_opacity = opacity * 255.999;
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, &uint_opacity, 1);
+ }
+ break;
+
+ case PROP_FLOAT_OPACITY:
+ {
+ gfloat opacity = va_arg (args, gdouble);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_float_check_error (info, &opacity, 1);
+ }
+ break;
+
+ case PROP_MODE:
+ {
+ gint32 mode = va_arg (args, gint32);
+
+ size = 4;
+
+ if (mode == GIMP_LAYER_MODE_OVERLAY_LEGACY)
+ mode = GIMP_LAYER_MODE_SOFTLIGHT_LEGACY;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, (guint32 *) &mode, 1);
+ }
+ break;
+
+ case PROP_BLEND_SPACE:
+ {
+ GimpLayerMode mode = va_arg (args, GimpLayerMode);
+ gint32 blend_space = va_arg (args, gint32);
+
+ G_STATIC_ASSERT (GIMP_LAYER_COLOR_SPACE_AUTO == 0);
+
+ /* if blend_space is AUTO, save the negative of the actual value AUTO
+ * maps to for the given mode, so that we can correctly load it even if
+ * the mapping changes in the future.
+ */
+ if (blend_space == GIMP_LAYER_COLOR_SPACE_AUTO)
+ blend_space = -gimp_layer_mode_get_blend_space (mode);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, (guint32 *) &blend_space, 1);
+ }
+ break;
+
+ case PROP_COMPOSITE_SPACE:
+ {
+ GimpLayerMode mode = va_arg (args, GimpLayerMode);
+ gint32 composite_space = va_arg (args, gint32);
+
+ G_STATIC_ASSERT (GIMP_LAYER_COLOR_SPACE_AUTO == 0);
+
+ /* if composite_space is AUTO, save the negative of the actual value
+ * AUTO maps to for the given mode, so that we can correctly load it
+ * even if the mapping changes in the future.
+ */
+ if (composite_space == GIMP_LAYER_COLOR_SPACE_AUTO)
+ composite_space = -gimp_layer_mode_get_composite_space (mode);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, (guint32 *) &composite_space, 1);
+ }
+ break;
+
+ case PROP_COMPOSITE_MODE:
+ {
+ GimpLayerMode mode = va_arg (args, GimpLayerMode);
+ gint32 composite_mode = va_arg (args, gint32);
+
+ G_STATIC_ASSERT (GIMP_LAYER_COMPOSITE_AUTO == 0);
+
+ /* if composite_mode is AUTO, save the negative of the actual value
+ * AUTO maps to for the given mode, so that we can correctly load it
+ * even if the mapping changes in the future.
+ */
+ if (composite_mode == GIMP_LAYER_COMPOSITE_AUTO)
+ composite_mode = -gimp_layer_mode_get_composite_mode (mode);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, (guint32 *) &composite_mode, 1);
+ }
+ break;
+
+ case PROP_VISIBLE:
+ {
+ guint32 visible = va_arg (args, guint32);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, &visible, 1);
+ }
+ break;
+
+ case PROP_LINKED:
+ {
+ guint32 linked = va_arg (args, guint32);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, &linked, 1);
+ }
+ break;
+
+ case PROP_COLOR_TAG:
+ {
+ guint32 color_tag = va_arg (args, gint32);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, &color_tag, 1);
+ }
+ break;
+
+ case PROP_LOCK_CONTENT:
+ {
+ guint32 lock_content = va_arg (args, guint32);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, &lock_content, 1);
+ }
+ break;
+
+ case PROP_LOCK_ALPHA:
+ {
+ guint32 lock_alpha = va_arg (args, guint32);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, &lock_alpha, 1);
+ }
+ break;
+
+ case PROP_LOCK_POSITION:
+ {
+ guint32 lock_position = va_arg (args, guint32);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, &lock_position, 1);
+ }
+ break;
+
+ case PROP_APPLY_MASK:
+ {
+ guint32 apply_mask = va_arg (args, guint32);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, &apply_mask, 1);
+ }
+ break;
+
+ case PROP_EDIT_MASK:
+ {
+ guint32 edit_mask = va_arg (args, guint32);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, &edit_mask, 1);
+ }
+ break;
+
+ case PROP_SHOW_MASK:
+ {
+ guint32 show_mask = va_arg (args, guint32);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, &show_mask, 1);
+ }
+ break;
+
+ case PROP_SHOW_MASKED:
+ {
+ guint32 show_masked = va_arg (args, guint32);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, &show_masked, 1);
+ }
+ break;
+
+ case PROP_OFFSETS:
+ {
+ gint32 offsets[2];
+
+ offsets[0] = va_arg (args, gint32);
+ offsets[1] = va_arg (args, gint32);
+
+ size = 8;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, (guint32 *) offsets, 2);
+ }
+ break;
+
+ case PROP_COLOR:
+ {
+ GimpRGB *color = va_arg (args, GimpRGB *);
+ guchar col[3];
+
+ gimp_rgb_get_uchar (color, &col[0], &col[1], &col[2]);
+
+ size = 3;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int8_check_error (info, col, 3);
+ }
+ break;
+
+ case PROP_FLOAT_COLOR:
+ {
+ GimpRGB *color = va_arg (args, GimpRGB *);
+ gfloat col[3];
+
+ col[0] = color->r;
+ col[1] = color->g;
+ col[2] = color->b;
+
+ size = 3 * 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_float_check_error (info, col, 3);
+ }
+ break;
+
+ case PROP_COMPRESSION:
+ {
+ guint8 compression = (guint8) va_arg (args, guint32);
+
+ size = 1;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int8_check_error (info, &compression, 1);
+ }
+ break;
+
+ case PROP_GUIDES:
+ {
+ GList *guides = va_arg (args, GList *);
+ gint n_guides = g_list_length (guides);
+ GList *iter;
+
+ for (iter = guides; iter; iter = g_list_next (iter))
+ {
+ /* Do not save custom guides. */
+ if (gimp_guide_is_custom (GIMP_GUIDE (iter->data)))
+ n_guides--;
+ }
+
+ if (n_guides > 0)
+ {
+ size = n_guides * (4 + 1);
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ for (; guides; guides = g_list_next (guides))
+ {
+ GimpGuide *guide = guides->data;
+ gint32 position = gimp_guide_get_position (guide);
+ gint8 orientation;
+
+ if (gimp_guide_is_custom (guide))
+ continue;
+
+ switch (gimp_guide_get_orientation (guide))
+ {
+ case GIMP_ORIENTATION_HORIZONTAL:
+ orientation = XCF_ORIENTATION_HORIZONTAL;
+ break;
+
+ case GIMP_ORIENTATION_VERTICAL:
+ orientation = XCF_ORIENTATION_VERTICAL;
+ break;
+
+ default:
+ g_warning ("%s: skipping guide with bad orientation",
+ G_STRFUNC);
+ continue;
+ }
+
+ xcf_write_int32_check_error (info, (guint32 *) &position, 1);
+ xcf_write_int8_check_error (info, (guint8 *) &orientation, 1);
+ }
+ }
+ }
+ break;
+
+ case PROP_SAMPLE_POINTS:
+ {
+ GList *sample_points = va_arg (args, GList *);
+ gint n_sample_points = g_list_length (sample_points);
+
+ size = n_sample_points * (5 * 4);
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ for (; sample_points; sample_points = g_list_next (sample_points))
+ {
+ GimpSamplePoint *sample_point = sample_points->data;
+ gint32 x, y;
+ GimpColorPickMode pick_mode;
+ guint32 padding[2] = { 0, };
+
+ gimp_sample_point_get_position (sample_point, &x, &y);
+ pick_mode = gimp_sample_point_get_pick_mode (sample_point);
+
+ xcf_write_int32_check_error (info, (guint32 *) &x, 1);
+ xcf_write_int32_check_error (info, (guint32 *) &y, 1);
+ xcf_write_int32_check_error (info, (guint32 *) &pick_mode, 1);
+ xcf_write_int32_check_error (info, (guint32 *) padding, 2);
+ }
+ }
+ break;
+
+ case PROP_OLD_SAMPLE_POINTS:
+ {
+ GList *sample_points = va_arg (args, GList *);
+ gint n_sample_points = g_list_length (sample_points);
+
+ size = n_sample_points * (4 + 4);
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ for (; sample_points; sample_points = g_list_next (sample_points))
+ {
+ GimpSamplePoint *sample_point = sample_points->data;
+ gint32 x, y;
+
+ gimp_sample_point_get_position (sample_point, &x, &y);
+
+ xcf_write_int32_check_error (info, (guint32 *) &x, 1);
+ xcf_write_int32_check_error (info, (guint32 *) &y, 1);
+ }
+ }
+ break;
+
+ case PROP_RESOLUTION:
+ {
+ gfloat resolution[2];
+
+ resolution[0] = va_arg (args, double);
+ resolution[1] = va_arg (args, double);
+
+ size = 2 * 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_float_check_error (info, resolution, 2);
+ }
+ break;
+
+ case PROP_TATTOO:
+ {
+ guint32 tattoo = va_arg (args, guint32);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, &tattoo, 1);
+ }
+ break;
+
+ case PROP_PARASITES:
+ {
+ GimpParasiteList *list = va_arg (args, GimpParasiteList *);
+
+ if (gimp_parasite_list_persistent_length (list) > 0)
+ {
+ goffset base;
+ goffset pos;
+
+ size = 0;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+
+ /* because we don't know how much room the parasite list
+ * will take we save the file position and write the
+ * length later
+ */
+ pos = info->cp;
+ xcf_write_int32_check_error (info, &size, 1);
+
+ base = info->cp;
+
+ xcf_check_error (xcf_save_parasite_list (info, list, error));
+
+ size = info->cp - base;
+
+ /* go back to the saved position and write the length */
+ xcf_check_error (xcf_seek_pos (info, pos, error));
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_check_error (xcf_seek_pos (info, base + size, error));
+ }
+ }
+ break;
+
+ case PROP_UNIT:
+ {
+ guint32 unit = va_arg (args, guint32);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, &unit, 1);
+ }
+ break;
+
+ case PROP_PATHS:
+ {
+ goffset base;
+ goffset pos;
+
+ size = 0;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+
+ /* because we don't know how much room the paths list will
+ * take we save the file position and write the length later
+ */
+ pos = info->cp;
+ xcf_write_int32_check_error (info, &size, 1);
+
+ base = info->cp;
+
+ xcf_check_error (xcf_save_old_paths (info, image, error));
+
+ size = info->cp - base;
+
+ /* go back to the saved position and write the length */
+ xcf_check_error (xcf_seek_pos (info, pos, error));
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_check_error (xcf_seek_pos (info, base + size, error));
+ }
+ break;
+
+ case PROP_USER_UNIT:
+ {
+ GimpUnit unit = va_arg (args, guint32);
+ const gchar *unit_strings[5];
+ gfloat factor;
+ guint32 digits;
+
+ /* write the entire unit definition */
+ unit_strings[0] = gimp_unit_get_identifier (unit);
+ factor = gimp_unit_get_factor (unit);
+ digits = gimp_unit_get_digits (unit);
+ unit_strings[1] = gimp_unit_get_symbol (unit);
+ unit_strings[2] = gimp_unit_get_abbreviation (unit);
+ unit_strings[3] = gimp_unit_get_singular (unit);
+ unit_strings[4] = gimp_unit_get_plural (unit);
+
+ size =
+ 2 * 4 +
+ strlen (unit_strings[0]) ? strlen (unit_strings[0]) + 5 : 4 +
+ strlen (unit_strings[1]) ? strlen (unit_strings[1]) + 5 : 4 +
+ strlen (unit_strings[2]) ? strlen (unit_strings[2]) + 5 : 4 +
+ strlen (unit_strings[3]) ? strlen (unit_strings[3]) + 5 : 4 +
+ strlen (unit_strings[4]) ? strlen (unit_strings[4]) + 5 : 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_float_check_error (info, &factor, 1);
+ xcf_write_int32_check_error (info, &digits, 1);
+ xcf_write_string_check_error (info, (gchar **) unit_strings, 5);
+ }
+ break;
+
+ case PROP_VECTORS:
+ {
+ goffset base;
+ goffset pos;
+
+ size = 0;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+
+ /* because we don't know how much room the paths list will
+ * take we save the file position and write the length later
+ */
+ pos = info->cp;
+ xcf_write_int32_check_error (info, &size, 1);
+
+ base = info->cp;
+
+ xcf_check_error (xcf_save_vectors (info, image, error));
+
+ size = info->cp - base;
+
+ /* go back to the saved position and write the length */
+ xcf_check_error (xcf_seek_pos (info, pos, error));
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_check_error (xcf_seek_pos (info, base + size, error));
+ }
+ break;
+
+ case PROP_TEXT_LAYER_FLAGS:
+ {
+ guint32 flags = va_arg (args, guint32);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ xcf_write_int32_check_error (info, &flags, 1);
+ }
+ break;
+
+ case PROP_ITEM_PATH:
+ {
+ GList *path = va_arg (args, GList *);
+
+ size = 4 * g_list_length (path);
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+
+ while (path)
+ {
+ guint32 index = GPOINTER_TO_UINT (path->data);
+
+ xcf_write_int32_check_error (info, &index, 1);
+
+ path = g_list_next (path);
+ }
+ }
+ break;
+
+ case PROP_GROUP_ITEM_FLAGS:
+ {
+ guint32 flags = va_arg (args, guint32);
+
+ size = 4;
+
+ xcf_write_prop_type_check_error (info, prop_type);
+ xcf_write_int32_check_error (info, &size, 1);
+ xcf_write_int32_check_error (info, &flags, 1);
+ }
+ break;
+ }
+
+ va_end (args);
+
+ return TRUE;
+}
+
+static gboolean
+xcf_save_layer (XcfInfo *info,
+ GimpImage *image,
+ GimpLayer *layer,
+ GError **error)
+{
+ goffset saved_pos;
+ goffset offset;
+ guint32 value;
+ const gchar *string;
+ GError *tmp_error = NULL;
+
+ /* check and see if this is the drawable that the floating
+ * selection is attached to.
+ */
+ if (GIMP_DRAWABLE (layer) == info->floating_sel_drawable)
+ {
+ saved_pos = info->cp;
+ xcf_check_error (xcf_seek_pos (info, info->floating_sel_offset, error));
+ xcf_write_offset_check_error (info, &saved_pos, 1);
+ xcf_check_error (xcf_seek_pos (info, saved_pos, error));
+ }
+
+ /* write out the width, height and image type information for the layer */
+ value = gimp_item_get_width (GIMP_ITEM (layer));
+ xcf_write_int32_check_error (info, &value, 1);
+
+ value = gimp_item_get_height (GIMP_ITEM (layer));
+ xcf_write_int32_check_error (info, &value, 1);
+
+ value = gimp_babl_format_get_image_type (gimp_drawable_get_format (GIMP_DRAWABLE (layer)));
+ xcf_write_int32_check_error (info, &value, 1);
+
+ /* write out the layers name */
+ string = gimp_object_get_name (layer);
+ xcf_write_string_check_error (info, (gchar **) &string, 1);
+
+ /* write out the layer properties */
+ xcf_save_layer_props (info, image, layer, error);
+
+ /* write out the layer tile hierarchy */
+ offset = info->cp + 2 * info->bytes_per_offset;
+ xcf_write_offset_check_error (info, &offset, 1);
+
+ saved_pos = info->cp;
+
+ /* write a zero layer mask offset */
+ xcf_write_zero_offset_check_error (info, 1);
+
+ xcf_check_error (xcf_save_buffer (info,
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (layer)),
+ error));
+
+ offset = info->cp;
+
+ /* write out the layer mask */
+ if (gimp_layer_get_mask (layer))
+ {
+ GimpLayerMask *mask = gimp_layer_get_mask (layer);
+
+ xcf_check_error (xcf_seek_pos (info, saved_pos, error));
+ xcf_write_offset_check_error (info, &offset, 1);
+
+ xcf_check_error (xcf_seek_pos (info, offset, error));
+ xcf_check_error (xcf_save_channel (info, image, GIMP_CHANNEL (mask),
+ error));
+ }
+
+ return TRUE;
+}
+
+static gboolean
+xcf_save_channel (XcfInfo *info,
+ GimpImage *image,
+ GimpChannel *channel,
+ GError **error)
+{
+ goffset saved_pos;
+ goffset offset;
+ guint32 value;
+ const gchar *string;
+ GError *tmp_error = NULL;
+
+ /* check and see if this is the drawable that the floating
+ * selection is attached to.
+ */
+ if (GIMP_DRAWABLE (channel) == info->floating_sel_drawable)
+ {
+ saved_pos = info->cp;
+ xcf_check_error (xcf_seek_pos (info, info->floating_sel_offset, error));
+ xcf_write_offset_check_error (info, &saved_pos, 1);
+ xcf_check_error (xcf_seek_pos (info, saved_pos, error));
+ }
+
+ /* write out the width and height information for the channel */
+ value = gimp_item_get_width (GIMP_ITEM (channel));
+ xcf_write_int32_check_error (info, &value, 1);
+
+ value = gimp_item_get_height (GIMP_ITEM (channel));
+ xcf_write_int32_check_error (info, &value, 1);
+
+ /* write out the channels name */
+ string = gimp_object_get_name (channel);
+ xcf_write_string_check_error (info, (gchar **) &string, 1);
+
+ /* write out the channel properties */
+ xcf_save_channel_props (info, image, channel, error);
+
+ /* write out the channel tile hierarchy */
+ offset = info->cp + info->bytes_per_offset;
+ xcf_write_offset_check_error (info, &offset, 1);
+
+ xcf_check_error (xcf_save_buffer (info,
+ gimp_drawable_get_buffer (GIMP_DRAWABLE (channel)),
+ error));
+
+ return TRUE;
+}
+
+static gint
+xcf_calc_levels (gint size,
+ gint tile_size)
+{
+ gint levels;
+
+ levels = 1;
+ while (size > tile_size)
+ {
+ size /= 2;
+ levels += 1;
+ }
+
+ return levels;
+}
+
+
+static gboolean
+xcf_save_buffer (XcfInfo *info,
+ GeglBuffer *buffer,
+ GError **error)
+{
+ const Babl *format;
+ goffset saved_pos;
+ goffset offset;
+ guint32 width;
+ guint32 height;
+ guint32 bpp;
+ gint i;
+ gint nlevels;
+ gint tmp1, tmp2;
+ GError *tmp_error = NULL;
+
+ format = gegl_buffer_get_format (buffer);
+
+ width = gegl_buffer_get_width (buffer);
+ height = gegl_buffer_get_height (buffer);
+ bpp = babl_format_get_bytes_per_pixel (format);
+
+ xcf_write_int32_check_error (info, (guint32 *) &width, 1);
+ xcf_write_int32_check_error (info, (guint32 *) &height, 1);
+ xcf_write_int32_check_error (info, (guint32 *) &bpp, 1);
+
+ tmp1 = xcf_calc_levels (width, XCF_TILE_WIDTH);
+ tmp2 = xcf_calc_levels (height, XCF_TILE_HEIGHT);
+ nlevels = MAX (tmp1, tmp2);
+
+ /* 'saved_pos' is the next slot in the offset table */
+ saved_pos = info->cp;
+
+ /* write an empty offset table */
+ xcf_write_zero_offset_check_error (info, nlevels + 1);
+
+ /* 'offset' is where we will write the next level */
+ offset = info->cp;
+
+ for (i = 0; i < nlevels; i++)
+ {
+ /* seek back to the next slot in the offset table and write the
+ * offset of the level
+ */
+ xcf_check_error (xcf_seek_pos (info, saved_pos, error));
+ xcf_write_offset_check_error (info, &offset, 1);
+
+ /* remember the next slot in the offset table */
+ saved_pos = info->cp;
+
+ /* seek to the level offset and save the level */
+ xcf_check_error (xcf_seek_pos (info, offset, error));
+
+ if (i == 0)
+ {
+ /* write out the level. */
+ xcf_check_error (xcf_save_level (info, buffer, error));
+ }
+ else
+ {
+ /* fake an empty level */
+ tmp1 = 0;
+ width /= 2;
+ height /= 2;
+ xcf_write_int32_check_error (info, (guint32 *) &width, 1);
+ xcf_write_int32_check_error (info, (guint32 *) &height, 1);
+
+ /* NOTE: this should be an offset, not an int32! however...
+ * since there are already 64-bit-offsets XCFs out there in
+ * which this field is 32-bit, and since it's not actually
+ * being used, we're going to keep this field 32-bit for the
+ * dummy levels, to remain consistent. if we ever make use
+ * of levels above the first, we should turn this field into
+ * an offset, and bump the xcf version.
+ */
+ xcf_write_int32_check_error (info, (guint32 *) &tmp1, 1);
+ }
+
+ /* the next level's offset if after the level we just wrote */
+ offset = info->cp;
+ }
+
+ /* there is already a '0' at the end of the offset table to indicate
+ * the end of the level offsets
+ */
+
+ return TRUE;
+}
+
+static gboolean
+xcf_save_level (XcfInfo *info,
+ GeglBuffer *buffer,
+ GError **error)
+{
+ const Babl *format;
+ goffset *offset_table;
+ goffset *next_offset;
+ goffset saved_pos;
+ goffset offset;
+ goffset max_data_length;
+ guint32 width;
+ guint32 height;
+ gint bpp;
+ gint n_tile_rows;
+ gint n_tile_cols;
+ guint ntiles;
+ gint i;
+ guchar *rlebuf = NULL;
+ GError *tmp_error = NULL;
+
+ format = gegl_buffer_get_format (buffer);
+
+ width = gegl_buffer_get_width (buffer);
+ height = gegl_buffer_get_height (buffer);
+ bpp = babl_format_get_bytes_per_pixel (format);
+
+ xcf_write_int32_check_error (info, (guint32 *) &width, 1);
+ xcf_write_int32_check_error (info, (guint32 *) &height, 1);
+
+ saved_pos = info->cp;
+
+ /* maximal allowable size of on-disk tile data. make it somewhat bigger than
+ * the uncompressed tile size, to allow for the possibility of negative
+ * compression. xcf_load_level() enforces this limit.
+ */
+ max_data_length = XCF_TILE_WIDTH * XCF_TILE_HEIGHT * bpp *
+ XCF_TILE_MAX_DATA_LENGTH_FACTOR /* = 1.5, currently */;
+
+ /* allocate a temporary buffer to store the rle data before it is
+ * written to disk
+ */
+ if (info->compression == COMPRESS_RLE)
+ rlebuf = g_alloca (max_data_length);
+
+ n_tile_rows = gimp_gegl_buffer_get_n_tile_rows (buffer, XCF_TILE_HEIGHT);
+ n_tile_cols = gimp_gegl_buffer_get_n_tile_cols (buffer, XCF_TILE_WIDTH);
+
+ ntiles = n_tile_rows * n_tile_cols;
+
+ /* allocate an offset table so we don't have to seek back after each
+ * tile, see bug #686862. allocate ntiles + 1 slots because a zero
+ * offset indicates the offset table's end.
+ * Do not use g_alloca since it may cause Stack Overflow on
+ * large images, see issue #6138.
+ */
+ offset_table = g_malloc0 ((ntiles + 1) * sizeof (goffset));
+ next_offset = offset_table;
+
+ /* 'saved_pos' is the offset of the tile offset table */
+ saved_pos = info->cp;
+
+ /* write an empty offset table */
+ xcf_write_zero_offset_check_error (info, ntiles + 1);
+
+ /* 'offset' is where we will write the next tile */
+ offset = info->cp;
+
+ for (i = 0; i < ntiles; i++)
+ {
+ GeglRectangle rect;
+
+ /* store the offset in the table and increment the next pointer */
+ *next_offset++ = offset;
+
+ gimp_gegl_buffer_get_tile_rect (buffer,
+ XCF_TILE_WIDTH, XCF_TILE_HEIGHT,
+ i, &rect);
+
+ /* write out the tile. */
+ switch (info->compression)
+ {
+ case COMPRESS_NONE:
+ xcf_check_error (xcf_save_tile (info, buffer, &rect, format,
+ error));
+ break;
+ case COMPRESS_RLE:
+ xcf_check_error (xcf_save_tile_rle (info, buffer, &rect, format,
+ rlebuf, error));
+ break;
+ case COMPRESS_ZLIB:
+ xcf_check_error (xcf_save_tile_zlib (info, buffer, &rect, format,
+ error));
+ break;
+ case COMPRESS_FRACTAL:
+ g_warning ("xcf: fractal compression unimplemented");
+ g_free (offset_table);
+ return FALSE;
+ }
+
+ /* make sure the on-disk tile data didn't end up being too big.
+ * xcf_load_level() would refuse to load the file if it did.
+ */
+ if (info->cp < offset || info->cp - offset > max_data_length)
+ {
+ g_message ("xcf: invalid tile data length: %" G_GOFFSET_FORMAT,
+ info->cp - offset);
+ g_free (offset_table);
+ return FALSE;
+ }
+
+ /* the next tile's offset is after the tile we just wrote */
+ offset = info->cp;
+ }
+
+ /* seek back to the offset table and write it */
+ xcf_check_error (xcf_seek_pos (info, saved_pos, error));
+ xcf_write_offset_check_error (info, offset_table, ntiles + 1);
+
+ /* seek to the end of the file */
+ xcf_check_error (xcf_seek_pos (info, offset, error));
+
+ g_free (offset_table);
+
+ return TRUE;
+}
+
+static gboolean
+xcf_save_tile (XcfInfo *info,
+ GeglBuffer *buffer,
+ GeglRectangle *tile_rect,
+ const Babl *format,
+ GError **error)
+{
+ gint bpp = babl_format_get_bytes_per_pixel (format);
+ gint tile_size = bpp * tile_rect->width * tile_rect->height;
+ guchar *tile_data = g_alloca (tile_size);
+ GError *tmp_error = NULL;
+
+ gegl_buffer_get (buffer, tile_rect, 1.0, format, tile_data,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ if (info->file_version <= 11)
+ {
+ xcf_write_int8_check_error (info, tile_data, tile_size);
+ }
+ else
+ {
+ gint n_components = babl_format_get_n_components (format);
+
+ xcf_write_component_check_error (info, bpp / n_components, tile_data,
+ tile_size / bpp * n_components);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+xcf_save_tile_rle (XcfInfo *info,
+ GeglBuffer *buffer,
+ GeglRectangle *tile_rect,
+ const Babl *format,
+ guchar *rlebuf,
+ GError **error)
+{
+ gint bpp = babl_format_get_bytes_per_pixel (format);
+ gint tile_size = bpp * tile_rect->width * tile_rect->height;
+ guchar *tile_data = g_alloca (tile_size);
+ gint len = 0;
+ gint i, j;
+ GError *tmp_error = NULL;
+
+ gegl_buffer_get (buffer, tile_rect, 1.0, format, tile_data,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ if (info->file_version >= 12)
+ {
+ gint n_components = babl_format_get_n_components (format);
+
+ xcf_write_to_be (bpp / n_components, tile_data,
+ tile_size / bpp * n_components);
+ }
+
+ for (i = 0; i < bpp; i++)
+ {
+ const guchar *data = tile_data + i;
+ gint state = 0;
+ gint length = 0;
+ gint count = 0;
+ gint size = tile_rect->width * tile_rect->height;
+ guint last = -1;
+
+ while (size > 0)
+ {
+ switch (state)
+ {
+ case 0:
+ /* in state 0 we try to find a long sequence of
+ * matching values.
+ */
+ if ((length == 32768) ||
+ ((size - length) <= 0) ||
+ ((length > 1) && (last != *data)))
+ {
+ count += length;
+
+ if (length >= 128)
+ {
+ rlebuf[len++] = 127;
+ rlebuf[len++] = (length >> 8);
+ rlebuf[len++] = length & 0x00FF;
+ rlebuf[len++] = last;
+ }
+ else
+ {
+ rlebuf[len++] = length - 1;
+ rlebuf[len++] = last;
+ }
+
+ size -= length;
+ length = 0;
+ }
+ else if ((length == 1) && (last != *data))
+ {
+ state = 1;
+ }
+ break;
+
+ case 1:
+ /* in state 1 we try and find a long sequence of
+ * non-matching values.
+ */
+ if ((length == 32768) ||
+ ((size - length) == 0) ||
+ ((length > 0) && (last == *data) &&
+ ((size - length) == 1 || last == data[bpp])))
+ {
+ const guchar *t;
+
+ /* if we came here because of a new run, backup one */
+ if (!((length == 32768) || ((size - length) == 0)))
+ {
+ length--;
+ data -= bpp;
+ }
+
+ count += length;
+ state = 0;
+
+ if (length >= 128)
+ {
+ rlebuf[len++] = 255 - 127;
+ rlebuf[len++] = (length >> 8);
+ rlebuf[len++] = length & 0x00FF;
+ }
+ else
+ {
+ rlebuf[len++] = 255 - (length - 1);
+ }
+
+ t = data - length * bpp;
+
+ for (j = 0; j < length; j++)
+ {
+ rlebuf[len++] = *t;
+ t += bpp;
+ }
+
+ size -= length;
+ length = 0;
+ }
+ break;
+ }
+
+ if (size > 0)
+ {
+ length += 1;
+ last = *data;
+ data += bpp;
+ }
+ }
+
+ if (count != (tile_rect->width * tile_rect->height))
+ g_message ("xcf: uh oh! xcf rle tile saving error: %d", count);
+ }
+
+ xcf_write_int8_check_error (info, rlebuf, len);
+
+ return TRUE;
+}
+
+static gboolean
+xcf_save_tile_zlib (XcfInfo *info,
+ GeglBuffer *buffer,
+ GeglRectangle *tile_rect,
+ const Babl *format,
+ GError **error)
+{
+ gint bpp = babl_format_get_bytes_per_pixel (format);
+ gint tile_size = bpp * tile_rect->width * tile_rect->height;
+ guchar *tile_data = g_alloca (tile_size);
+ /* The buffer for compressed data. */
+ guchar *buf = g_alloca (tile_size);
+ GError *tmp_error = NULL;
+ z_stream strm;
+ int action;
+ int status;
+
+ gegl_buffer_get (buffer, tile_rect, 1.0, format, tile_data,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ if (info->file_version >= 12)
+ {
+ gint n_components = babl_format_get_n_components (format);
+
+ xcf_write_to_be (bpp / n_components, tile_data,
+ tile_size / bpp * n_components);
+ }
+
+ /* allocate deflate state */
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+
+ status = deflateInit (&strm, Z_DEFAULT_COMPRESSION);
+ if (status != Z_OK)
+ return FALSE;
+
+ strm.next_in = tile_data;
+ strm.avail_in = tile_size;
+ strm.next_out = buf;
+ strm.avail_out = tile_size;
+
+ action = Z_NO_FLUSH;
+
+ while (status == Z_OK || status == Z_BUF_ERROR)
+ {
+ if (strm.avail_in == 0)
+ {
+ /* Finish the encoding. */
+ action = Z_FINISH;
+ }
+
+ status = deflate (&strm, action);
+
+ if (status == Z_STREAM_END || status == Z_BUF_ERROR)
+ {
+ size_t write_size = tile_size - strm.avail_out;
+
+ xcf_write_int8_check_error (info, buf, write_size);
+
+ /* Reset next_out and avail_out. */
+ strm.next_out = buf;
+ strm.avail_out = tile_size;
+ }
+ else if (status != Z_OK)
+ {
+ g_printerr ("xcf: tile compression failed: %s", zError (status));
+ deflateEnd (&strm);
+ return FALSE;
+ }
+ }
+
+ deflateEnd (&strm);
+ return TRUE;
+}
+
+static gboolean
+xcf_save_parasite (XcfInfo *info,
+ GimpParasite *parasite,
+ GError **error)
+{
+ if (gimp_parasite_is_persistent (parasite))
+ {
+ guint32 value;
+ const gchar *string;
+ GError *tmp_error = NULL;
+
+ string = gimp_parasite_name (parasite);
+ xcf_write_string_check_error (info, (gchar **) &string, 1);
+
+ value = gimp_parasite_flags (parasite);
+ xcf_write_int32_check_error (info, &value, 1);
+
+ value = gimp_parasite_data_size (parasite);
+ xcf_write_int32_check_error (info, &value, 1);
+
+ xcf_write_int8_check_error (info,
+ gimp_parasite_data (parasite),
+ gimp_parasite_data_size (parasite));
+ }
+
+ return TRUE;
+}
+
+typedef struct
+{
+ XcfInfo *info;
+ GError *error;
+} XcfParasiteData;
+
+static void
+xcf_save_parasite_func (gchar *key,
+ GimpParasite *parasite,
+ XcfParasiteData *data)
+{
+ if (! data->error)
+ xcf_save_parasite (data->info, parasite, &data->error);
+}
+
+static gboolean
+xcf_save_parasite_list (XcfInfo *info,
+ GimpParasiteList *list,
+ GError **error)
+{
+ XcfParasiteData data;
+
+ data.info = info;
+ data.error = NULL;
+
+ gimp_parasite_list_foreach (list, (GHFunc) xcf_save_parasite_func, &data);
+
+ if (data.error)
+ {
+ g_propagate_error (error, data.error);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+xcf_save_old_paths (XcfInfo *info,
+ GimpImage *image,
+ GError **error)
+{
+ GimpVectors *active_vectors;
+ guint32 num_paths;
+ guint32 active_index = 0;
+ GList *list;
+ GError *tmp_error = NULL;
+
+ /* Write out the following:-
+ *
+ * last_selected_row (gint)
+ * number_of_paths (gint)
+ *
+ * then each path:-
+ */
+
+ num_paths = gimp_container_get_n_children (gimp_image_get_vectors (image));
+
+ active_vectors = gimp_image_get_active_vectors (image);
+
+ if (active_vectors)
+ active_index = gimp_container_get_child_index (gimp_image_get_vectors (image),
+ GIMP_OBJECT (active_vectors));
+
+ xcf_write_int32_check_error (info, &active_index, 1);
+ xcf_write_int32_check_error (info, &num_paths, 1);
+
+ for (list = gimp_image_get_vectors_iter (image);
+ list;
+ list = g_list_next (list))
+ {
+ GimpVectors *vectors = list->data;
+ gchar *name;
+ guint32 locked;
+ guint8 state;
+ guint32 version;
+ guint32 pathtype;
+ guint32 tattoo;
+ GimpVectorsCompatPoint *points;
+ guint32 num_points;
+ guint32 closed;
+ gint i;
+
+ /*
+ * name (string)
+ * locked (gint)
+ * state (gchar)
+ * closed (gint)
+ * number points (gint)
+ * version (gint)
+ * pathtype (gint)
+ * tattoo (gint)
+ * then each point.
+ */
+
+ points = gimp_vectors_compat_get_points (vectors,
+ (gint32 *) &num_points,
+ (gint32 *) &closed);
+
+ /* if no points are generated because of a faulty path we should
+ * skip saving the path - this is unfortunately impossible, because
+ * we already saved the number of paths and I won't start seeking
+ * around to fix that cruft */
+
+ name = (gchar *) gimp_object_get_name (vectors);
+ locked = gimp_item_get_linked (GIMP_ITEM (vectors));
+ state = closed ? 4 : 2; /* EDIT : ADD (editing state, 1.2 compat) */
+ version = 3;
+ pathtype = 1; /* BEZIER (1.2 compat) */
+ tattoo = gimp_item_get_tattoo (GIMP_ITEM (vectors));
+
+ xcf_write_string_check_error (info, &name, 1);
+ xcf_write_int32_check_error (info, &locked, 1);
+ xcf_write_int8_check_error (info, &state, 1);
+ xcf_write_int32_check_error (info, &closed, 1);
+ xcf_write_int32_check_error (info, &num_points, 1);
+ xcf_write_int32_check_error (info, &version, 1);
+ xcf_write_int32_check_error (info, &pathtype, 1);
+ xcf_write_int32_check_error (info, &tattoo, 1);
+
+ for (i = 0; i < num_points; i++)
+ {
+ gfloat x;
+ gfloat y;
+
+ x = points[i].x;
+ y = points[i].y;
+
+ /*
+ * type (gint)
+ * x (gfloat)
+ * y (gfloat)
+ */
+
+ xcf_write_int32_check_error (info, &points[i].type, 1);
+ xcf_write_float_check_error (info, &x, 1);
+ xcf_write_float_check_error (info, &y, 1);
+ }
+
+ g_free (points);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+xcf_save_vectors (XcfInfo *info,
+ GimpImage *image,
+ GError **error)
+{
+ GimpVectors *active_vectors;
+ guint32 version = 1;
+ guint32 active_index = 0;
+ guint32 num_paths;
+ GList *list;
+ GList *stroke_list;
+ GError *tmp_error = NULL;
+
+ /* Write out the following:-
+ *
+ * version (gint)
+ * active_index (gint)
+ * num_paths (gint)
+ *
+ * then each path:-
+ */
+
+ active_vectors = gimp_image_get_active_vectors (image);
+
+ if (active_vectors)
+ active_index = gimp_container_get_child_index (gimp_image_get_vectors (image),
+ GIMP_OBJECT (active_vectors));
+
+ num_paths = gimp_container_get_n_children (gimp_image_get_vectors (image));
+
+ xcf_write_int32_check_error (info, &version, 1);
+ xcf_write_int32_check_error (info, &active_index, 1);
+ xcf_write_int32_check_error (info, &num_paths, 1);
+
+ for (list = gimp_image_get_vectors_iter (image);
+ list;
+ list = g_list_next (list))
+ {
+ GimpVectors *vectors = list->data;
+ GimpParasiteList *parasites;
+ const gchar *name;
+ guint32 tattoo;
+ guint32 visible;
+ guint32 linked;
+ guint32 num_parasites;
+ guint32 num_strokes;
+
+ /*
+ * name (string)
+ * tattoo (gint)
+ * visible (gint)
+ * linked (gint)
+ * num_parasites (gint)
+ * num_strokes (gint)
+ *
+ * then each parasite
+ * then each stroke
+ */
+
+ name = gimp_object_get_name (vectors);
+ visible = gimp_item_get_visible (GIMP_ITEM (vectors));
+ linked = gimp_item_get_linked (GIMP_ITEM (vectors));
+ tattoo = gimp_item_get_tattoo (GIMP_ITEM (vectors));
+ parasites = gimp_item_get_parasites (GIMP_ITEM (vectors));
+ num_parasites = gimp_parasite_list_persistent_length (parasites);
+ num_strokes = g_queue_get_length (vectors->strokes);
+
+ xcf_write_string_check_error (info, (gchar **) &name, 1);
+ xcf_write_int32_check_error (info, &tattoo, 1);
+ xcf_write_int32_check_error (info, &visible, 1);
+ xcf_write_int32_check_error (info, &linked, 1);
+ xcf_write_int32_check_error (info, &num_parasites, 1);
+ xcf_write_int32_check_error (info, &num_strokes, 1);
+
+ xcf_check_error (xcf_save_parasite_list (info, parasites, error));
+
+ for (stroke_list = g_list_first (vectors->strokes->head);
+ stroke_list;
+ stroke_list = g_list_next (stroke_list))
+ {
+ GimpStroke *stroke = stroke_list->data;
+ guint32 stroke_type;
+ guint32 closed;
+ guint32 num_axes;
+ GArray *control_points;
+ gint i;
+
+ guint32 type;
+ gfloat coords[6];
+
+ /*
+ * stroke_type (gint)
+ * closed (gint)
+ * num_axes (gint)
+ * num_control_points (gint)
+ *
+ * then each control point.
+ */
+
+ if (GIMP_IS_BEZIER_STROKE (stroke))
+ {
+ stroke_type = XCF_STROKETYPE_BEZIER_STROKE;
+ num_axes = 2; /* hardcoded, might be increased later */
+ }
+ else
+ {
+ g_printerr ("Skipping unknown stroke type!\n");
+ continue;
+ }
+
+ control_points = gimp_stroke_control_points_get (stroke,
+ (gint32 *) &closed);
+
+ xcf_write_int32_check_error (info, &stroke_type, 1);
+ xcf_write_int32_check_error (info, &closed, 1);
+ xcf_write_int32_check_error (info, &num_axes, 1);
+ xcf_write_int32_check_error (info, &control_points->len, 1);
+
+ for (i = 0; i < control_points->len; i++)
+ {
+ GimpAnchor *anchor;
+
+ anchor = & (g_array_index (control_points, GimpAnchor, i));
+
+ type = anchor->type;
+ coords[0] = anchor->position.x;
+ coords[1] = anchor->position.y;
+ coords[2] = anchor->position.pressure;
+ coords[3] = anchor->position.xtilt;
+ coords[4] = anchor->position.ytilt;
+ coords[5] = anchor->position.wheel;
+
+ /*
+ * type (gint)
+ *
+ * the first num_axis elements of:
+ * [0] x (gfloat)
+ * [1] y (gfloat)
+ * [2] pressure (gfloat)
+ * [3] xtilt (gfloat)
+ * [4] ytilt (gfloat)
+ * [5] wheel (gfloat)
+ */
+
+ xcf_write_int32_check_error (info, &type, 1);
+ xcf_write_float_check_error (info, coords, num_axes);
+ }
+
+ g_array_free (control_points, TRUE);
+ }
+ }
+
+ return TRUE;
+}
diff --git a/app/xcf/xcf-save.h b/app/xcf/xcf-save.h
new file mode 100644
index 0000000..d3af169
--- /dev/null
+++ b/app/xcf/xcf-save.h
@@ -0,0 +1,27 @@
+/* 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 __XCF_SAVE_H__
+#define __XCF_SAVE_H__
+
+
+gboolean xcf_save_image (XcfInfo *info,
+ GimpImage *image,
+ GError **error);
+
+
+#endif /* __XCF_SAVE_H__ */
diff --git a/app/xcf/xcf-seek.c b/app/xcf/xcf-seek.c
new file mode 100644
index 0000000..26fcc27
--- /dev/null
+++ b/app/xcf/xcf-seek.c
@@ -0,0 +1,53 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "core/core-types.h"
+
+#include "xcf-private.h"
+#include "xcf-seek.h"
+
+#include "gimp-intl.h"
+
+
+gboolean
+xcf_seek_pos (XcfInfo *info,
+ goffset pos,
+ GError **error)
+{
+ if (info->cp != pos)
+ {
+ GError *my_error = NULL;
+
+ info->cp = pos;
+
+ if (! g_seekable_seek (info->seekable, info->cp, G_SEEK_SET,
+ NULL, &my_error))
+ {
+ g_propagate_prefixed_error (error, my_error,
+ _("Could not seek in XCF file: "));
+ return FALSE;
+ }
+
+ gimp_assert (info->cp == g_seekable_tell (info->seekable));
+ }
+
+ return TRUE;
+}
diff --git a/app/xcf/xcf-seek.h b/app/xcf/xcf-seek.h
new file mode 100644
index 0000000..02d629f
--- /dev/null
+++ b/app/xcf/xcf-seek.h
@@ -0,0 +1,27 @@
+/* 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 __XCF_SEEK_H__
+#define __XCF_SEEK_H__
+
+
+gboolean xcf_seek_pos (XcfInfo *info,
+ goffset pos,
+ GError **error);
+
+
+#endif /* __XCF_SEEK_H__ */
diff --git a/app/xcf/xcf-utils.c b/app/xcf/xcf-utils.c
new file mode 100644
index 0000000..726cad2
--- /dev/null
+++ b/app/xcf/xcf-utils.c
@@ -0,0 +1,53 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "xcf-utils.h"
+
+
+gboolean
+xcf_data_is_zero (const void *data,
+ gint size)
+{
+ const guint8 *data8;
+ const guint64 *data64;
+
+ for (data8 = data; size > 0 && (guintptr) data8 % 8 != 0; data8++, size--)
+ {
+ if (*data8)
+ return FALSE;
+ }
+
+ for (data64 = (gpointer) data8; size >= 8; data64++, size -= 8)
+ {
+ if (*data64)
+ return FALSE;
+ }
+
+ for (data8 = (gpointer) data64; size > 0; data8++, size--)
+ {
+ if (*data8)
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/app/xcf/xcf-utils.h b/app/xcf/xcf-utils.h
new file mode 100644
index 0000000..5f4673f
--- /dev/null
+++ b/app/xcf/xcf-utils.h
@@ -0,0 +1,26 @@
+/* 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 __XCF_UTILS_H__
+#define __XCF_UTILS_H__
+
+
+gboolean xcf_data_is_zero (const void *data,
+ gint size);
+
+
+#endif /* __XCF_UTILS_H__ */
diff --git a/app/xcf/xcf-write.c b/app/xcf/xcf-write.c
new file mode 100644
index 0000000..a154bf5
--- /dev/null
+++ b/app/xcf/xcf-write.c
@@ -0,0 +1,339 @@
+/* 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 <gio/gio.h>
+
+#include "core/core-types.h"
+
+#include "xcf-private.h"
+#include "xcf-write.h"
+
+#include "gimp-intl.h"
+
+
+guint
+xcf_write_int8 (XcfInfo *info,
+ const guint8 *data,
+ gint count,
+ GError **error)
+{
+ GError *my_error = NULL;
+ gsize bytes_written = 0;
+
+ /* we allow for 'data == NULL && count == 0', which
+ * g_output_stream_write_all() rejects.
+ */
+ if (count > 0)
+ {
+ if (! g_output_stream_write_all (info->output, data, count,
+ &bytes_written, NULL, &my_error))
+ {
+ g_propagate_prefixed_error (error, my_error,
+ _("Error writing XCF: "));
+ }
+
+ info->cp += bytes_written;
+ }
+
+ return bytes_written;
+}
+
+guint
+xcf_write_int16 (XcfInfo *info,
+ const guint16 *data,
+ gint count,
+ GError **error)
+{
+ GError *tmp_error = NULL;
+ gint i;
+
+ if (count > 0)
+ {
+ for (i = 0; i < count; i++)
+ {
+ guint16 tmp = g_htons (data[i]);
+
+ xcf_write_int8 (info, (const guint8 *) &tmp, 2, &tmp_error);
+
+ if (tmp_error)
+ {
+ g_propagate_error (error, tmp_error);
+
+ return i * 2;
+ }
+ }
+ }
+
+ return count * 2;
+}
+
+guint
+xcf_write_int32 (XcfInfo *info,
+ const guint32 *data,
+ gint count,
+ GError **error)
+{
+ GError *tmp_error = NULL;
+ gint i;
+
+ if (count > 0)
+ {
+ for (i = 0; i < count; i++)
+ {
+ guint32 tmp = g_htonl (data[i]);
+
+ xcf_write_int8 (info, (const guint8 *) &tmp, 4, &tmp_error);
+
+ if (tmp_error)
+ {
+ g_propagate_error (error, tmp_error);
+
+ return i * 4;
+ }
+ }
+ }
+
+ return count * 4;
+}
+
+guint
+xcf_write_int64 (XcfInfo *info,
+ const guint64 *data,
+ gint count,
+ GError **error)
+{
+ GError *tmp_error = NULL;
+ gint i;
+
+ if (count > 0)
+ {
+ for (i = 0; i < count; i++)
+ {
+ guint64 tmp = GINT64_TO_BE (data[i]);
+
+ xcf_write_int8 (info, (const guint8 *) &tmp, 8, &tmp_error);
+
+ if (tmp_error)
+ {
+ g_propagate_error (error, tmp_error);
+
+ return i * 8;
+ }
+ }
+ }
+
+ return count * 8;
+}
+
+guint
+xcf_write_offset (XcfInfo *info,
+ const goffset *data,
+ gint count,
+ GError **error)
+{
+ if (count > 0)
+ {
+ gint i;
+
+ for (i = 0; i < count; i++)
+ {
+ GError *tmp_error = NULL;
+
+ if (info->bytes_per_offset == 4)
+ {
+ guint32 tmp = g_htonl (data[i]);
+
+ xcf_write_int8 (info, (const guint8 *) &tmp, 4, &tmp_error);
+ }
+ else
+ {
+ gint64 tmp = GINT64_TO_BE (data[i]);
+
+ xcf_write_int8 (info, (const guint8 *) &tmp, 8, &tmp_error);
+ }
+
+ if (tmp_error)
+ {
+ g_propagate_error (error, tmp_error);
+
+ return i * info->bytes_per_offset;
+ }
+ }
+ }
+
+ return count * info->bytes_per_offset;
+}
+
+guint
+xcf_write_zero_offset (XcfInfo *info,
+ gint count,
+ GError **error)
+{
+ if (count > 0)
+ {
+ guint8 *tmp;
+ guint bytes_written = 0;
+
+ tmp = g_try_malloc (count * info->bytes_per_offset);
+ if (! tmp)
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error writing XCF: failed to allocate %d bytes of memory."),
+ count * info->bytes_per_offset);
+ }
+ else
+ {
+ memset (tmp, 0, count * info->bytes_per_offset);
+
+ bytes_written = xcf_write_int8 (info, (const guint8 *) tmp,
+ count * info->bytes_per_offset, error);
+ g_free (tmp);
+ }
+
+ return bytes_written;
+ }
+
+ return 0;
+}
+
+guint
+xcf_write_float (XcfInfo *info,
+ const gfloat *data,
+ gint count,
+ GError **error)
+{
+ return xcf_write_int32 (info,
+ (const guint32 *)((gconstpointer) data), count,
+ error);
+}
+
+guint
+xcf_write_string (XcfInfo *info,
+ gchar **data,
+ gint count,
+ GError **error)
+{
+ GError *tmp_error = NULL;
+ guint total = 0;
+ gint i;
+
+ for (i = 0; i < count; i++)
+ {
+ guint32 tmp;
+
+ if (data[i])
+ tmp = strlen (data[i]) + 1;
+ else
+ tmp = 0;
+
+ xcf_write_int32 (info, &tmp, 1, &tmp_error);
+
+ if (tmp_error)
+ {
+ g_propagate_error (error, tmp_error);
+ return total;
+ }
+
+ if (tmp > 0)
+ xcf_write_int8 (info, (const guint8 *) data[i], tmp, &tmp_error);
+
+ if (tmp_error)
+ {
+ g_propagate_error (error, tmp_error);
+ return total;
+ }
+
+ total += 4 + tmp;
+ }
+
+ return total;
+}
+
+guint
+xcf_write_component (XcfInfo *info,
+ gint bpc,
+ const guint8 *data,
+ gint count,
+ GError **error)
+{
+ switch (bpc)
+ {
+ case 1:
+ return xcf_write_int8 (info, data, count, error);
+
+ case 2:
+ return xcf_write_int16 (info, (const guint16 *) data, count, error);
+
+ case 4:
+ return xcf_write_int32 (info, (const guint32 *) data, count, error);
+
+ case 8:
+ return xcf_write_int64 (info, (const guint64 *) data, count, error);
+
+ default:
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("Error writing XCF: unsupported BPC when writing pixel: %d"),
+ bpc);
+ }
+
+ return 0;
+}
+
+void
+xcf_write_to_be (gint bpc,
+ guint8 *data,
+ gint count)
+{
+ gint i;
+
+ switch (bpc)
+ {
+ case 1:
+ break;
+
+ case 2:
+ {
+ guint16 *d = (guint16 *) data;
+
+ for (i = 0; i < count; i++)
+ d[i] = g_htons (d[i]);
+ }
+ break;
+
+ case 4:
+ {
+ guint32 *d = (guint32 *) data;
+
+ for (i = 0; i < count; i++)
+ d[i] = g_htonl (d[i]);
+ }
+ break;
+
+ case 8:
+ {
+ guint64 *d = (guint64 *) data;
+
+ for (i = 0; i < count; i++)
+ d[i] = GINT64_TO_BE (d[i]);
+ }
+ break;
+ }
+}
diff --git a/app/xcf/xcf-write.h b/app/xcf/xcf-write.h
new file mode 100644
index 0000000..63ec15d
--- /dev/null
+++ b/app/xcf/xcf-write.h
@@ -0,0 +1,64 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __XCF_WRITE_H__
+#define __XCF_WRITE_H__
+
+
+guint xcf_write_int8 (XcfInfo *info,
+ const guint8 *data,
+ gint count,
+ GError **error);
+guint xcf_write_int16 (XcfInfo *info,
+ const guint16 *data,
+ gint count,
+ GError **error);
+guint xcf_write_int32 (XcfInfo *info,
+ const guint32 *data,
+ gint count,
+ GError **error);
+guint xcf_write_int64 (XcfInfo *info,
+ const guint64 *data,
+ gint count,
+ GError **error);
+guint xcf_write_offset (XcfInfo *info,
+ const goffset *data,
+ gint count,
+ GError **error);
+guint xcf_write_zero_offset (XcfInfo *info,
+ gint count,
+ GError **error);
+guint xcf_write_float (XcfInfo *info,
+ const gfloat *data,
+ gint count,
+ GError **error);
+guint xcf_write_string (XcfInfo *info,
+ gchar **data,
+ gint count,
+ GError **error);
+guint xcf_write_component (XcfInfo *info,
+ gint bpc,
+ const guint8 *data,
+ gint count,
+ GError **error);
+
+void xcf_write_to_be (gint bpc,
+ guint8 *data,
+ gint count);
+
+
+#endif /* __XCF_WRITE_H__ */
diff --git a/app/xcf/xcf.c b/app/xcf/xcf.c
new file mode 100644
index 0000000..254da08
--- /dev/null
+++ b/app/xcf/xcf.c
@@ -0,0 +1,516 @@
+/* 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 <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <glib/gstdio.h>
+#include <gegl.h>
+
+#include "libgimpbase/gimpbase.h"
+
+#include "core/core-types.h"
+
+#include "core/gimp.h"
+#include "core/gimpimage.h"
+#include "core/gimpparamspecs.h"
+#include "core/gimpprogress.h"
+
+#include "plug-in/gimppluginmanager.h"
+#include "plug-in/gimppluginprocedure.h"
+
+#include "xcf.h"
+#include "xcf-private.h"
+#include "xcf-load.h"
+#include "xcf-read.h"
+#include "xcf-save.h"
+
+#include "gimp-intl.h"
+
+
+typedef GimpImage * GimpXcfLoaderFunc (Gimp *gimp,
+ XcfInfo *info,
+ GError **error);
+
+
+static GimpValueArray * xcf_load_invoker (GimpProcedure *procedure,
+ Gimp *gimp,
+ GimpContext *context,
+ GimpProgress *progress,
+ const GimpValueArray *args,
+ GError **error);
+static GimpValueArray * xcf_save_invoker (GimpProcedure *procedure,
+ Gimp *gimp,
+ GimpContext *context,
+ GimpProgress *progress,
+ const GimpValueArray *args,
+ GError **error);
+
+
+static GimpXcfLoaderFunc * const xcf_loaders[] =
+{
+ xcf_load_image, /* version 0 */
+ xcf_load_image, /* version 1 */
+ xcf_load_image, /* version 2 */
+ xcf_load_image, /* version 3 */
+ xcf_load_image, /* version 4 */
+ xcf_load_image, /* version 5 */
+ xcf_load_image, /* version 6 */
+ xcf_load_image, /* version 7 */
+ xcf_load_image, /* version 8 */
+ xcf_load_image, /* version 9 */
+ xcf_load_image, /* version 10 */
+ xcf_load_image, /* version 11 */
+ xcf_load_image, /* version 12 */
+ xcf_load_image /* version 13 */
+};
+
+
+void
+xcf_init (Gimp *gimp)
+{
+ GimpPlugInProcedure *proc;
+ GFile *file;
+ GimpProcedure *procedure;
+
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+
+ /* So this is sort of a hack, but its better than it was before. To
+ * do this right there would be a file load-save handler type and
+ * the whole interface would change but there isn't, and currently
+ * the plug-in structure contains all the load-save info, so it
+ * makes sense to use that for the XCF load/save handlers, even
+ * though they are internal. The only thing it requires is using a
+ * PlugInProcDef struct. -josh
+ */
+
+ /* gimp-xcf-save */
+ file = g_file_new_for_path ("gimp-xcf-save");
+ procedure = gimp_plug_in_procedure_new (GIMP_PLUGIN, file);
+ g_object_unref (file);
+
+ procedure->proc_type = GIMP_INTERNAL;
+ procedure->marshal_func = xcf_save_invoker;
+
+ proc = GIMP_PLUG_IN_PROCEDURE (procedure);
+ proc->menu_label = g_strdup (N_("GIMP XCF image"));
+ gimp_plug_in_procedure_set_icon (proc, GIMP_ICON_TYPE_ICON_NAME,
+ (const guint8 *) "gimp-wilber",
+ strlen ("gimp-wilber") + 1);
+ gimp_plug_in_procedure_set_image_types (proc, "RGB*, GRAY*, INDEXED*");
+ gimp_plug_in_procedure_set_file_proc (proc, "xcf", "", NULL);
+ gimp_plug_in_procedure_set_mime_types (proc, "image/x-xcf");
+ gimp_plug_in_procedure_set_handles_uri (proc);
+
+ gimp_object_set_static_name (GIMP_OBJECT (procedure), "gimp-xcf-save");
+ gimp_procedure_set_static_strings (procedure,
+ "gimp-xcf-save",
+ "Saves file in the .xcf file format",
+ "The XCF file format has been designed "
+ "specifically for loading and saving "
+ "tiled and layered images in GIMP. "
+ "This procedure will save the specified "
+ "image in the xcf file format.",
+ "Spencer Kimball & Peter Mattis",
+ "Spencer Kimball & Peter Mattis",
+ "1995-1996",
+ NULL);
+
+ gimp_procedure_add_argument (procedure,
+ gimp_param_spec_int32 ("dummy-param",
+ "Dummy Param",
+ "Dummy parameter",
+ G_MININT32, G_MAXINT32, 0,
+ GIMP_PARAM_READWRITE));
+ gimp_procedure_add_argument (procedure,
+ gimp_param_spec_image_id ("image",
+ "Image",
+ "Input image",
+ gimp, FALSE,
+ GIMP_PARAM_READWRITE));
+ gimp_procedure_add_argument (procedure,
+ gimp_param_spec_drawable_id ("drawable",
+ "Drawable",
+ "Active drawable of input image",
+ gimp, TRUE,
+ GIMP_PARAM_READWRITE));
+ gimp_procedure_add_argument (procedure,
+ gimp_param_spec_string ("filename",
+ "Filename",
+ "The name of the file "
+ "to save the image in, "
+ "in URI format and "
+ "UTF-8 encoding",
+ TRUE, FALSE, TRUE,
+ NULL,
+ GIMP_PARAM_READWRITE));
+ gimp_procedure_add_argument (procedure,
+ gimp_param_spec_string ("raw-filename",
+ "Raw filename",
+ "The basename of the "
+ "file, in UTF-8",
+ FALSE, FALSE, TRUE,
+ NULL,
+ GIMP_PARAM_READWRITE));
+ gimp_plug_in_manager_add_procedure (gimp->plug_in_manager, proc);
+ g_object_unref (procedure);
+
+ /* gimp-xcf-load */
+ file = g_file_new_for_path ("gimp-xcf-load");
+ procedure = gimp_plug_in_procedure_new (GIMP_PLUGIN, file);
+ g_object_unref (file);
+
+ procedure->proc_type = GIMP_INTERNAL;
+ procedure->marshal_func = xcf_load_invoker;
+
+ proc = GIMP_PLUG_IN_PROCEDURE (procedure);
+ proc->menu_label = g_strdup (N_("GIMP XCF image"));
+ gimp_plug_in_procedure_set_icon (proc, GIMP_ICON_TYPE_ICON_NAME,
+ (const guint8 *) "gimp-wilber",
+ strlen ("gimp-wilber") + 1);
+ gimp_plug_in_procedure_set_image_types (proc, NULL);
+ gimp_plug_in_procedure_set_file_proc (proc, "xcf", "",
+ "0,string,gimp\\040xcf\\040");
+ gimp_plug_in_procedure_set_mime_types (proc, "image/x-xcf");
+ gimp_plug_in_procedure_set_handles_uri (proc);
+
+ gimp_object_set_static_name (GIMP_OBJECT (procedure), "gimp-xcf-load");
+ gimp_procedure_set_static_strings (procedure,
+ "gimp-xcf-load",
+ "Loads file saved in the .xcf file format",
+ "The XCF file format has been designed "
+ "specifically for loading and saving "
+ "tiled and layered images in GIMP. "
+ "This procedure will load the specified "
+ "file.",
+ "Spencer Kimball & Peter Mattis",
+ "Spencer Kimball & Peter Mattis",
+ "1995-1996",
+ NULL);
+
+ gimp_procedure_add_argument (procedure,
+ gimp_param_spec_int32 ("dummy-param",
+ "Dummy Param",
+ "Dummy parameter",
+ G_MININT32, G_MAXINT32, 0,
+ GIMP_PARAM_READWRITE));
+ gimp_procedure_add_argument (procedure,
+ gimp_param_spec_string ("filename",
+ "Filename",
+ "The name of the file "
+ "to load, in the "
+ "on-disk character "
+ "set and encoding",
+ TRUE, FALSE, TRUE,
+ NULL,
+ GIMP_PARAM_READWRITE));
+ gimp_procedure_add_argument (procedure,
+ gimp_param_spec_string ("raw-filename",
+ "Raw filename",
+ "The basename of the "
+ "file, in UTF-8",
+ FALSE, FALSE, TRUE,
+ NULL,
+ GIMP_PARAM_READWRITE));
+
+ gimp_procedure_add_return_value (procedure,
+ gimp_param_spec_image_id ("image",
+ "Image",
+ "Output image",
+ gimp, FALSE,
+ GIMP_PARAM_READWRITE));
+ gimp_plug_in_manager_add_procedure (gimp->plug_in_manager, proc);
+ g_object_unref (procedure);
+}
+
+void
+xcf_exit (Gimp *gimp)
+{
+ g_return_if_fail (GIMP_IS_GIMP (gimp));
+}
+
+GimpImage *
+xcf_load_stream (Gimp *gimp,
+ GInputStream *input,
+ GFile *input_file,
+ GimpProgress *progress,
+ GError **error)
+{
+ XcfInfo info = { 0, };
+ const gchar *filename;
+ GimpImage *image = NULL;
+ gchar id[14];
+ gboolean success;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
+ g_return_val_if_fail (G_IS_INPUT_STREAM (input), NULL);
+ g_return_val_if_fail (input_file == NULL || G_IS_FILE (input_file), NULL);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ if (input_file)
+ filename = gimp_file_get_utf8_name (input_file);
+ else
+ filename = _("Memory Stream");
+
+ info.gimp = gimp;
+ info.input = input;
+ info.seekable = G_SEEKABLE (input);
+ info.bytes_per_offset = 4;
+ info.progress = progress;
+ info.file = input_file;
+ info.compression = COMPRESS_NONE;
+
+ if (progress)
+ gimp_progress_start (progress, FALSE, _("Opening '%s'"), filename);
+
+ success = TRUE;
+
+ xcf_read_int8 (&info, (guint8 *) id, 14);
+
+ if (! g_str_has_prefix (id, "gimp xcf "))
+ {
+ success = FALSE;
+ }
+ else if (strcmp (id + 9, "file") == 0)
+ {
+ info.file_version = 0;
+ }
+ else if (id[9] == 'v' &&
+ id[13] == '\0')
+ {
+ info.file_version = atoi (id + 10);
+ }
+ else
+ {
+ success = FALSE;
+ }
+
+ if (info.file_version >= 11)
+ info.bytes_per_offset = 8;
+
+ if (success)
+ {
+ if (info.file_version >= 0 &&
+ info.file_version < G_N_ELEMENTS (xcf_loaders))
+ {
+ image = (*(xcf_loaders[info.file_version])) (gimp, &info, error);
+
+ if (! image)
+ success = FALSE;
+
+ g_input_stream_close (info.input, NULL, NULL);
+ }
+ else
+ {
+ g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+ _("XCF error: unsupported XCF file version %d "
+ "encountered"), info.file_version);
+ success = FALSE;
+ }
+ }
+
+ if (progress)
+ gimp_progress_end (progress);
+
+ return image;
+}
+
+gboolean
+xcf_save_stream (Gimp *gimp,
+ GimpImage *image,
+ GOutputStream *output,
+ GFile *output_file,
+ GimpProgress *progress,
+ GError **error)
+{
+ XcfInfo info = { 0, };
+ const gchar *filename;
+ gboolean success = FALSE;
+ GError *my_error = NULL;
+ GCancellable *cancellable;
+
+ g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
+ g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
+ g_return_val_if_fail (G_IS_OUTPUT_STREAM (output), FALSE);
+ g_return_val_if_fail (output_file == NULL || G_IS_FILE (output_file), FALSE);
+ g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ if (output_file)
+ filename = gimp_file_get_utf8_name (output_file);
+ else
+ filename = _("Memory Stream");
+
+ info.gimp = gimp;
+ info.output = output;
+ info.seekable = G_SEEKABLE (output);
+ info.bytes_per_offset = 4;
+ info.progress = progress;
+ info.file = output_file;
+
+ if (gimp_image_get_xcf_compression (image))
+ info.compression = COMPRESS_ZLIB;
+ else
+ info.compression = COMPRESS_RLE;
+
+ info.file_version = gimp_image_get_xcf_version (image,
+ info.compression ==
+ COMPRESS_ZLIB,
+ NULL, NULL, NULL);
+
+ if (info.file_version >= 11)
+ info.bytes_per_offset = 8;
+
+ if (progress)
+ gimp_progress_start (progress, FALSE, _("Saving '%s'"), filename);
+
+ success = xcf_save_image (&info, image, &my_error);
+
+ cancellable = g_cancellable_new ();
+ if (success)
+ {
+ if (progress)
+ gimp_progress_set_text (progress, _("Closing '%s'"), filename);
+ }
+ else
+ {
+ /* When closing the stream, the image will be actually saved,
+ * unless we properly cancel it with a GCancellable.
+ * Not closing the stream is not an option either, as this will
+ * happen anyway when finalizing the output.
+ * So let's make sure now that we don't overwrite the XCF file
+ * when an error occurred.
+ */
+ g_cancellable_cancel (cancellable);
+ }
+ success = g_output_stream_close (info.output, cancellable, &my_error);
+ g_object_unref (cancellable);
+
+ if (! success && my_error)
+ g_propagate_prefixed_error (error, my_error,
+ _("Error writing '%s': "), filename);
+
+ if (progress)
+ gimp_progress_end (progress);
+
+ return success;
+}
+
+
+/* private functions */
+
+static GimpValueArray *
+xcf_load_invoker (GimpProcedure *procedure,
+ Gimp *gimp,
+ GimpContext *context,
+ GimpProgress *progress,
+ const GimpValueArray *args,
+ GError **error)
+{
+ GimpValueArray *return_vals;
+ GimpImage *image = NULL;
+ const gchar *uri;
+ GFile *file;
+ GInputStream *input;
+ GError *my_error = NULL;
+
+ gimp_set_busy (gimp);
+
+ uri = g_value_get_string (gimp_value_array_index (args, 1));
+ file = g_file_new_for_uri (uri);
+
+ input = G_INPUT_STREAM (g_file_read (file, NULL, &my_error));
+
+ if (input)
+ {
+ image = xcf_load_stream (gimp, input, file, progress, error);
+
+ g_object_unref (input);
+ }
+ else
+ {
+ g_propagate_prefixed_error (error, my_error,
+ _("Could not open '%s' for reading: "),
+ gimp_file_get_utf8_name (file));
+ }
+
+ g_object_unref (file);
+
+ return_vals = gimp_procedure_get_return_values (procedure, image != NULL,
+ error ? *error : NULL);
+
+ if (image)
+ gimp_value_set_image (gimp_value_array_index (return_vals, 1), image);
+
+ gimp_unset_busy (gimp);
+
+ return return_vals;
+}
+
+static GimpValueArray *
+xcf_save_invoker (GimpProcedure *procedure,
+ Gimp *gimp,
+ GimpContext *context,
+ GimpProgress *progress,
+ const GimpValueArray *args,
+ GError **error)
+{
+ GimpValueArray *return_vals;
+ GimpImage *image;
+ const gchar *uri;
+ GFile *file;
+ GOutputStream *output;
+ gboolean success = FALSE;
+ GError *my_error = NULL;
+
+ gimp_set_busy (gimp);
+
+ image = gimp_value_get_image (gimp_value_array_index (args, 1), gimp);
+ uri = g_value_get_string (gimp_value_array_index (args, 3));
+ file = g_file_new_for_uri (uri);
+
+ output = G_OUTPUT_STREAM (g_file_replace (file,
+ NULL, FALSE, G_FILE_CREATE_NONE,
+ NULL, &my_error));
+
+ if (output)
+ {
+ success = xcf_save_stream (gimp, image, output, file, progress, error);
+
+ g_object_unref (output);
+ }
+ else
+ {
+ g_propagate_prefixed_error (error, my_error,
+ _("Error creating '%s': "),
+ gimp_file_get_utf8_name (file));
+ }
+
+ g_object_unref (file);
+
+ return_vals = gimp_procedure_get_return_values (procedure, success,
+ error ? *error : NULL);
+
+ gimp_unset_busy (gimp);
+
+ return return_vals;
+}
diff --git a/app/xcf/xcf.h b/app/xcf/xcf.h
new file mode 100644
index 0000000..b3c5e41
--- /dev/null
+++ b/app/xcf/xcf.h
@@ -0,0 +1,38 @@
+/* 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 __XCF_H__
+#define __XCF_H__
+
+
+void xcf_init (Gimp *gimp);
+void xcf_exit (Gimp *gimp);
+
+GimpImage * xcf_load_stream (Gimp *gimp,
+ GInputStream *input,
+ GFile *input_file,
+ GimpProgress *progress,
+ GError **error);
+
+gboolean xcf_save_stream (Gimp *gimp,
+ GimpImage *image,
+ GOutputStream *output,
+ GFile *output_file,
+ GimpProgress *progress,
+ GError **error);
+
+#endif /* __XCF_H__ */