diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 03:13:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 03:13:10 +0000 |
commit | 3c57dd931145d43f2b0aef96c4d178135956bf91 (patch) | |
tree | 3de698981e9f0cc2c4f9569b19a5f3595e741f6b /app/xcf | |
parent | Initial commit. (diff) | |
download | gimp-3c57dd931145d43f2b0aef96c4d178135956bf91.tar.xz gimp-3c57dd931145d43f2b0aef96c4d178135956bf91.zip |
Adding upstream version 2.10.36.upstream/2.10.36
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'app/xcf')
-rw-r--r-- | app/xcf/Makefile.am | 31 | ||||
-rw-r--r-- | app/xcf/Makefile.in | 951 | ||||
-rw-r--r-- | app/xcf/xcf-load.c | 3246 | ||||
-rw-r--r-- | app/xcf/xcf-load.h | 27 | ||||
-rw-r--r-- | app/xcf/xcf-private.h | 118 | ||||
-rw-r--r-- | app/xcf/xcf-read.c | 274 | ||||
-rw-r--r-- | app/xcf/xcf-read.h | 53 | ||||
-rw-r--r-- | app/xcf/xcf-save.c | 2271 | ||||
-rw-r--r-- | app/xcf/xcf-save.h | 27 | ||||
-rw-r--r-- | app/xcf/xcf-seek.c | 53 | ||||
-rw-r--r-- | app/xcf/xcf-seek.h | 27 | ||||
-rw-r--r-- | app/xcf/xcf-utils.c | 53 | ||||
-rw-r--r-- | app/xcf/xcf-utils.h | 26 | ||||
-rw-r--r-- | app/xcf/xcf-write.c | 339 | ||||
-rw-r--r-- | app/xcf/xcf-write.h | 64 | ||||
-rw-r--r-- | app/xcf/xcf.c | 516 | ||||
-rw-r--r-- | app/xcf/xcf.h | 38 |
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__ */ |