diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:30:19 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:30:19 +0000 |
commit | 5c1676dfe6d2f3c837a5e074117b45613fd29a72 (patch) | |
tree | cbffb45144febf451e54061db2b21395faf94bfe /plug-ins/file-psd | |
parent | Initial commit. (diff) | |
download | gimp-5c1676dfe6d2f3c837a5e074117b45613fd29a72.tar.xz gimp-5c1676dfe6d2f3c837a5e074117b45613fd29a72.zip |
Adding upstream version 2.10.34.upstream/2.10.34upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plug-ins/file-psd')
-rw-r--r-- | plug-ins/file-psd/Makefile.am | 74 | ||||
-rw-r--r-- | plug-ins/file-psd/Makefile.in | 1048 | ||||
-rw-r--r-- | plug-ins/file-psd/TODO.txt | 54 | ||||
-rw-r--r-- | plug-ins/file-psd/new-resource-ids.txt | 23 | ||||
-rw-r--r-- | plug-ins/file-psd/psd-image-res-load.c | 1611 | ||||
-rw-r--r-- | plug-ins/file-psd/psd-image-res-load.h | 43 | ||||
-rw-r--r-- | plug-ins/file-psd/psd-layer-res-load.c | 944 | ||||
-rw-r--r-- | plug-ins/file-psd/psd-layer-res-load.h | 35 | ||||
-rw-r--r-- | plug-ins/file-psd/psd-load.c | 2807 | ||||
-rw-r--r-- | plug-ins/file-psd/psd-load.h | 32 | ||||
-rw-r--r-- | plug-ins/file-psd/psd-save.c | 2097 | ||||
-rw-r--r-- | plug-ins/file-psd/psd-save.h | 27 | ||||
-rw-r--r-- | plug-ins/file-psd/psd-thumb-load.c | 309 | ||||
-rw-r--r-- | plug-ins/file-psd/psd-thumb-load.h | 31 | ||||
-rw-r--r-- | plug-ins/file-psd/psd-util.c | 933 | ||||
-rw-r--r-- | plug-ins/file-psd/psd-util.h | 87 | ||||
-rw-r--r-- | plug-ins/file-psd/psd.c | 385 | ||||
-rw-r--r-- | plug-ins/file-psd/psd.h | 688 |
18 files changed, 11228 insertions, 0 deletions
diff --git a/plug-ins/file-psd/Makefile.am b/plug-ins/file-psd/Makefile.am new file mode 100644 index 0000000..10ce648 --- /dev/null +++ b/plug-ins/file-psd/Makefile.am @@ -0,0 +1,74 @@ +## Process this file with automake to produce Makefile.in + +libgimpui = $(top_builddir)/libgimp/libgimpui-$(GIMP_API_VERSION).la +libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la +libgimpwidgets = $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la +libgimp = $(top_builddir)/libgimp/libgimp-$(GIMP_API_VERSION).la +libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la +libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la +libgimpmath = $(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la + +if OS_WIN32 +mwindows = -mwindows +else +libm = -lm +endif + +if HAVE_WINDRES +include $(top_srcdir)/build/windows/gimprc-plug-ins.rule +file_psd_RC = file-psd.rc.o +endif + +AM_LDFLAGS = $(mwindows) + +libexecdir = $(gimpplugindir)/plug-ins/file-psd + +libexec_PROGRAMS = \ + file-psd + +file_psd_SOURCES = \ + psd.c \ + psd.h \ + psd-util.c \ + psd-util.h \ + psd-load.c \ + psd-load.h \ + psd-save.c \ + psd-save.h \ + psd-thumb-load.c \ + psd-thumb-load.h \ + psd-image-res-load.c \ + psd-image-res-load.h \ + psd-layer-res-load.c \ + psd-layer-res-load.h + +EXTRA_DIST = \ + TODO.txt \ + new-resource-ids.txt + +AM_CPPFLAGS = \ + -DG_LOG_DOMAIN=\"file-psd\" \ + -I$(top_srcdir) \ + $(GTK_CFLAGS) \ + $(EXIF_CFLAGS) \ + $(GEGL_CFLAGS) \ + -I$(includedir) + +file_psd_LDADD = \ + $(libm) \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpconfig) \ + $(libgimp) \ + $(libgimpcolor) \ + $(libgimpmath) \ + $(libgimpbase) \ + $(JPEG_LIBS) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(EXIF_LIBS) \ + $(IPTCDATA_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(Z_LIBS) \ + $(file_psd_RC) diff --git a/plug-ins/file-psd/Makefile.in b/plug-ins/file-psd/Makefile.in new file mode 100644 index 0000000..54d5598 --- /dev/null +++ b/plug-ins/file-psd/Makefile.in @@ -0,0 +1,1048 @@ +# 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@ + +# Version resources for Microsoft Windows + +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@ +libexec_PROGRAMS = file-psd$(EXEEXT) +subdir = plug-ins/file-psd +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 = +am__installdirs = "$(DESTDIR)$(libexecdir)" +PROGRAMS = $(libexec_PROGRAMS) +am_file_psd_OBJECTS = psd.$(OBJEXT) psd-util.$(OBJEXT) \ + psd-load.$(OBJEXT) psd-save.$(OBJEXT) psd-thumb-load.$(OBJEXT) \ + psd-image-res-load.$(OBJEXT) psd-layer-res-load.$(OBJEXT) +file_psd_OBJECTS = $(am_file_psd_OBJECTS) +am__DEPENDENCIES_1 = +file_psd_DEPENDENCIES = $(am__DEPENDENCIES_1) $(libgimpui) \ + $(libgimpwidgets) $(libgimpconfig) $(libgimp) $(libgimpcolor) \ + $(libgimpmath) $(libgimpbase) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(file_psd_RC) +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 = +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)/psd-image-res-load.Po \ + ./$(DEPDIR)/psd-layer-res-load.Po ./$(DEPDIR)/psd-load.Po \ + ./$(DEPDIR)/psd-save.Po ./$(DEPDIR)/psd-thumb-load.Po \ + ./$(DEPDIR)/psd-util.Po ./$(DEPDIR)/psd.Po +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +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 = $(file_psd_SOURCES) +DIST_SOURCES = $(file_psd_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)/build/windows/gimprc-plug-ins.rule \ + $(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 = $(gimpplugindir)/plug-ins/file-psd +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@ +libgimpui = $(top_builddir)/libgimp/libgimpui-$(GIMP_API_VERSION).la +libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la +libgimpwidgets = $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la +libgimp = $(top_builddir)/libgimp/libgimp-$(GIMP_API_VERSION).la +libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la +libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la +libgimpmath = $(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la +@OS_WIN32_TRUE@mwindows = -mwindows +@OS_WIN32_FALSE@libm = -lm +@HAVE_WINDRES_TRUE@GIMPPLUGINRC = $(top_builddir)/build/windows/gimp-plug-ins.rc +@HAVE_WINDRES_TRUE@file_psd_RC = file-psd.rc.o +AM_LDFLAGS = $(mwindows) +file_psd_SOURCES = \ + psd.c \ + psd.h \ + psd-util.c \ + psd-util.h \ + psd-load.c \ + psd-load.h \ + psd-save.c \ + psd-save.h \ + psd-thumb-load.c \ + psd-thumb-load.h \ + psd-image-res-load.c \ + psd-image-res-load.h \ + psd-layer-res-load.c \ + psd-layer-res-load.h + +EXTRA_DIST = \ + TODO.txt \ + new-resource-ids.txt + +AM_CPPFLAGS = \ + -DG_LOG_DOMAIN=\"file-psd\" \ + -I$(top_srcdir) \ + $(GTK_CFLAGS) \ + $(EXIF_CFLAGS) \ + $(GEGL_CFLAGS) \ + -I$(includedir) + +file_psd_LDADD = \ + $(libm) \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpconfig) \ + $(libgimp) \ + $(libgimpcolor) \ + $(libgimpmath) \ + $(libgimpbase) \ + $(JPEG_LIBS) \ + $(GTK_LIBS) \ + $(GEGL_LIBS) \ + $(EXIF_LIBS) \ + $(IPTCDATA_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(Z_LIBS) \ + $(file_psd_RC) + +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/build/windows/gimprc-plug-ins.rule $(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 plug-ins/file-psd/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu plug-ins/file-psd/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_srcdir)/build/windows/gimprc-plug-ins.rule $(am__empty): + +$(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): +install-libexecPROGRAMS: $(libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(libexecdir)" && rm -f $$files + +clean-libexecPROGRAMS: + @list='$(libexec_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +file-psd$(EXEEXT): $(file_psd_OBJECTS) $(file_psd_DEPENDENCIES) $(EXTRA_file_psd_DEPENDENCIES) + @rm -f file-psd$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(file_psd_OBJECTS) $(file_psd_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/psd-image-res-load.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/psd-layer-res-load.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/psd-load.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/psd-save.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/psd-thumb-load.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/psd-util.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/psd.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 $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(libexecdir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +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-libexecPROGRAMS clean-libtool \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/psd-image-res-load.Po + -rm -f ./$(DEPDIR)/psd-layer-res-load.Po + -rm -f ./$(DEPDIR)/psd-load.Po + -rm -f ./$(DEPDIR)/psd-save.Po + -rm -f ./$(DEPDIR)/psd-thumb-load.Po + -rm -f ./$(DEPDIR)/psd-util.Po + -rm -f ./$(DEPDIR)/psd.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-libexecPROGRAMS + +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)/psd-image-res-load.Po + -rm -f ./$(DEPDIR)/psd-layer-res-load.Po + -rm -f ./$(DEPDIR)/psd-load.Po + -rm -f ./$(DEPDIR)/psd-save.Po + -rm -f ./$(DEPDIR)/psd-thumb-load.Po + -rm -f ./$(DEPDIR)/psd-util.Po + -rm -f ./$(DEPDIR)/psd.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: uninstall-libexecPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-generic clean-libexecPROGRAMS clean-libtool \ + 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-libexecPROGRAMS \ + 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 uninstall-libexecPROGRAMS + +.PRECIOUS: Makefile + + +# `windres` seems a very stupid tool and it breaks with double shlashes +# in parameter paths. Strengthen the rule a little. +@HAVE_WINDRES_TRUE@%.rc.o: +@HAVE_WINDRES_TRUE@ $(WINDRES) --define ORIGINALFILENAME_STR="$*$(EXEEXT)" \ +@HAVE_WINDRES_TRUE@ --define INTERNALNAME_STR="$*" \ +@HAVE_WINDRES_TRUE@ --define TOP_SRCDIR="`echo $(top_srcdir) | sed 's*//*/*'`" \ +@HAVE_WINDRES_TRUE@ -I"`echo $(top_srcdir)/app | sed 's%/\+%/%'`" \ +@HAVE_WINDRES_TRUE@ -I"`echo $(top_builddir)/app | sed 's%/\+%/%'`"\ +@HAVE_WINDRES_TRUE@ -I"`echo $(top_builddir) | sed 's%/\+%/%'`"\ +@HAVE_WINDRES_TRUE@ $(GIMPPLUGINRC) $@ + +# 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/plug-ins/file-psd/TODO.txt b/plug-ins/file-psd/TODO.txt new file mode 100644 index 0000000..f02ff74 --- /dev/null +++ b/plug-ins/file-psd/TODO.txt @@ -0,0 +1,54 @@ +Load +==== +Photoshop 2.0 and lower files are not supported due to lack of +file specs and test files. + +Add text names for color modes + +Parasite for layer blending ranges per channel? + +Read & use global mask data + +Check for block over-run in read_channel_data + +Process errors from readers in read_channel_data + +Invert layer mask - channel_set_show_masked or invert mask + parasite + +decode more image resources + +Read in layer resources and process adjustment layers etc. +add parasites as required - should be similar to image resources + +add layer support for: + +Text layers + +vector masks + +Image resources: +================ + +1005 - resolution + width and height units dropped. + +1007 - Display info + save color space & color as parasite if unable to display. + +1008 - caption - Add to XMP data block. + +1025 - Working path (not saved) - Load + +1026 - Layers Group Info - ?Load + +1033 1036 + Better error handling for Jpeg decompression. Check for JPEG being included + Custom error function. + +1041 - Honour ICC untagged option + +2000-2998 - Paths + Add initial fill rule and clipboard parasites. + +2999 - Clipping path + Add as parasite to path record. diff --git a/plug-ins/file-psd/new-resource-ids.txt b/plug-ins/file-psd/new-resource-ids.txt new file mode 100644 index 0000000..561559a --- /dev/null +++ b/plug-ins/file-psd/new-resource-ids.txt @@ -0,0 +1,23 @@ +Unknown resource ID's seen in PSD files but not yet identified. + +8BIM 1061 (0x0425) +8BIM 1062 (0x0426) + +8BIM 1064 (0x0428) + +8BIM 1069 (0x042d) + +8BIM 1072 (0x0430) + +8BIM 1077 (0x0435) + +MeSa 7002 (0x165a) +MeSa 7003 (0x165b) + +8BIM 4000 (0x0fa0) +8BIM 4001 (0x0fa1) +8BIM 4002 (0x0fa2) +8BIM 4003 (0x0fa3) +8BIM 4004 (0x0fa4) +8BIM 4005 (0x0fa5) +8BIM 4006 (0x0fa6) diff --git a/plug-ins/file-psd/psd-image-res-load.c b/plug-ins/file-psd/psd-image-res-load.c new file mode 100644 index 0000000..a634bc6 --- /dev/null +++ b/plug-ins/file-psd/psd-image-res-load.c @@ -0,0 +1,1611 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GIMP PSD Plug-in + * Copyright 2007 by John Marshall + * + * 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/>. + */ + +/* ----- Known Image Resource Block Types ----- + All image resources not otherwise handled, including unknown types + are added as image parasites. + The data is attached as-is from the file (i.e. in big endian order). + + PSD_PS2_IMAGE_INFO = 1000, Dropped * 0x03e8 - Obsolete - ps 2.0 image info * + PSD_MAC_PRINT_INFO = 1001, PS Only * 0x03e9 - Optional - Mac print manager print info record * + PSD_PS2_COLOR_TAB = 1003, Dropped * 0x03eb - Obsolete - ps 2.0 indexed color table * + PSD_RESN_INFO = 1005, Loaded * 0x03ed - ResolutionInfo structure * + PSD_ALPHA_NAMES = 1006, Loaded * 0x03ee - Alpha channel names * + PSD_DISPLAY_INFO = 1007, Loaded * 0x03ef - DisplayInfo structure * + PSD_CAPTION = 1008, Loaded * 0x03f0 - Optional - Caption string * + PSD_BORDER_INFO = 1009, * 0x03f1 - Border info * + PSD_BACKGROUND_COL = 1010, * 0x03f2 - Background color * + PSD_PRINT_FLAGS = 1011, * 0x03f3 - Print flags * + PSD_GREY_HALFTONE = 1012, * 0x03f4 - Greyscale and multichannel halftoning info * + PSD_COLOR_HALFTONE = 1013, * 0x03f5 - Color halftoning info * + PSD_DUOTONE_HALFTONE = 1014, * 0x03f6 - Duotone halftoning info * + PSD_GREY_XFER = 1015, * 0x03f7 - Greyscale and multichannel transfer functions * + PSD_COLOR_XFER = 1016, * 0x03f8 - Color transfer functions * + PSD_DUOTONE_XFER = 1017, * 0x03f9 - Duotone transfer functions * + PSD_DUOTONE_INFO = 1018, * 0x03fa - Duotone image information * + PSD_EFFECTIVE_BW = 1019, * 0x03fb - Effective black & white values for dot range * + PSD_OBSOLETE_01 = 1020, Dropped * 0x03fc - Obsolete * + PSD_EPS_OPT = 1021, * 0x03fd - EPS options * + PSD_QUICK_MASK = 1022, Loaded * 0x03fe - Quick mask info * + PSD_OBSOLETE_02 = 1023, Dropped * 0x03ff - Obsolete * + PSD_LAYER_STATE = 1024, Loaded * 0x0400 - Layer state info * + PSD_WORKING_PATH = 1025, * 0x0401 - Working path (not saved) * + PSD_LAYER_GROUP = 1026, * 0x0402 - Layers group info * + PSD_OBSOLETE_03 = 1027, Dropped * 0x0403 - Obsolete * + PSD_IPTC_NAA_DATA = 1028, Loaded * 0x0404 - IPTC-NAA record (IMV4.pdf) * + PSD_IMAGE_MODE_RAW = 1029, * 0x0405 - Image mode for raw format files * + PSD_JPEG_QUAL = 1030, PS Only * 0x0406 - JPEG quality * + PSD_GRID_GUIDE = 1032, Loaded * 0x0408 - Grid & guide info * + PSD_THUMB_RES = 1033, Special * 0x0409 - Thumbnail resource * + PSD_COPYRIGHT_FLG = 1034, * 0x040a - Copyright flag * + PSD_URL = 1035, * 0x040b - URL string * + PSD_THUMB_RES2 = 1036, Special * 0x040c - Thumbnail resource * + PSD_GLOBAL_ANGLE = 1037, * 0x040d - Global angle * + PSD_COLOR_SAMPLER = 1038, * 0x040e - Color samplers resource * + PSD_ICC_PROFILE = 1039, Loaded * 0x040f - ICC Profile * + PSD_WATERMARK = 1040, * 0x0410 - Watermark * + PSD_ICC_UNTAGGED = 1041, * 0x0411 - Do not use ICC profile flag * + PSD_EFFECTS_VISIBLE = 1042, * 0x0412 - Show hide all effects layers * + PSD_SPOT_HALFTONE = 1043, * 0x0413 - Spot halftone * + PSD_DOC_IDS = 1044, * 0x0414 - Document specific IDs * + PSD_ALPHA_NAMES_UNI = 1045, Loaded * 0x0415 - Unicode alpha names * + PSD_IDX_COL_TAB_CNT = 1046, Loaded * 0x0416 - Indexed color table count * + PSD_IDX_TRANSPARENT = 1047, * 0x0417 - Index of transparent color (if any) * + PSD_GLOBAL_ALT = 1049, * 0x0419 - Global altitude * + PSD_SLICES = 1050, * 0x041a - Slices * + PSD_WORKFLOW_URL_UNI = 1051, * 0x041b - Workflow URL - Unicode string * + PSD_JUMP_TO_XPEP = 1052, * 0x041c - Jump to XPEP (?) * + PSD_ALPHA_ID = 1053, Loaded * 0x041d - Alpha IDs * + PSD_URL_LIST_UNI = 1054, * 0x041e - URL list - unicode * + PSD_VERSION_INFO = 1057, * 0x0421 - Version info * + PSD_EXIF_DATA = 1058, Loaded * 0x0422 - Exif data block 1 * + PSD_EXIF_DATA_3 = 1059 * 0X0423 - Exif data block 3 (?) * + PSD_XMP_DATA = 1060, Loaded * 0x0424 - XMP data block * + PSD_CAPTION_DIGEST = 1061, * 0x0425 - Caption digest * + PSD_PRINT_SCALE = 1062, * 0x0426 - Print scale * + PSD_PIXEL_AR = 1064, * 0x0428 - Pixel aspect ratio * + PSD_LAYER_COMPS = 1065, * 0x0429 - Layer comps * + PSD_ALT_DUOTONE_COLOR = 1066, * 0x042A - Alternative Duotone colors * + PSD_ALT_SPOT_COLOR = 1067, * 0x042B - Alternative Spot colors * + PSD_LAYER_SELECT_ID = 1069, * 0x042D - Layer selection ID * + PSD_HDR_TONING_INFO = 1070, * 0x042E - HDR toning information * + PSD_PRINT_INFO_SCALE = 1071, * 0x042F - Print scale * + PSD_LAYER_GROUP_E_ID = 1072, * 0x0430 - Layer group(s) enabled ID * + PSD_COLOR_SAMPLER_NEW = 1073, * 0x0431 - Color sampler resource for ps CS3 and higher PSD files * + PSD_MEASURE_SCALE = 1074, * 0x0432 - Measurement scale * + PSD_TIMELINE_INFO = 1075, * 0x0433 - Timeline information * + PSD_SHEET_DISCLOSE = 1076, * 0x0434 - Sheet discloser * + PSD_DISPLAY_INFO_NEW = 1077, Loaded * 0x0435 - DisplayInfo structure for ps CS3 and higher PSD files * + PSD_ONION_SKINS = 1078, * 0x0436 - Onion skins * + PSD_COUNT_INFO = 1080, * 0x0438 - Count information* + PSD_PRINT_INFO = 1082, * 0x043A - Print information added in ps CS5* + PSD_PRINT_STYLE = 1083, * 0x043B - Print style * + PSD_MAC_NSPRINTINFO = 1084, * 0x043C - Mac NSPrintInfo* + PSD_WIN_DEVMODE = 1085, * 0x043D - Windows DEVMODE * + PSD_AUTO_SAVE_PATH = 1086, * 0x043E - Auto save file path * + PSD_AUTO_SAVE_FORMAT = 1087, * 0x043F - Auto save format * + PSD_PATH_INFO_FIRST = 2000, Loaded * 0x07d0 - First path info block * + PSD_PATH_INFO_LAST = 2998, Loaded * 0x0bb6 - Last path info block * + PSD_CLIPPING_PATH = 2999, * 0x0bb7 - Name of clipping path * + PSD_PLUGIN_R_FIRST = 4000, * 0x0FA0 - First plugin resource * + PSD_PLUGIN_R_LAST = 4999, * 0x1387 - Last plugin resource * + PSD_IMAGEREADY_VARS = 7000, PS Only * 0x1B58 - Imageready variables * + PSD_IMAGEREADY_DATA = 7001, PS Only * 0x1B59 - Imageready data sets * + PSD_LIGHTROOM_WORK = 8000, PS Only * 0x1F40 - Lightroom workflow * + PSD_PRINT_FLAGS_2 = 10000 * 0x2710 - Print flags * +*/ + +#include "config.h" + +#include <string.h> +#include <errno.h> + +#include <glib/gstdio.h> +#include <libgimp/gimp.h> + +#include <jpeglib.h> +#include <jerror.h> + +#ifdef HAVE_IPTCDATA +#include <libiptcdata/iptc-data.h> +#endif /* HAVE_IPTCDATA */ + +#include "psd.h" +#include "psd-util.h" +#include "psd-image-res-load.h" + +#include "libgimp/stdplugins-intl.h" + +#define EXIF_HEADER_SIZE 8 + +/* Local function prototypes */ +static gint load_resource_unknown (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error); + +static gint load_resource_ps_only (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error); + +static gint load_resource_1005 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error); + +static gint load_resource_1006 (const PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error); + +static gint load_resource_1007 (const PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error); + +static gint load_resource_1008 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error); + +static gint load_resource_1022 (const PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error); + +static gint load_resource_1024 (const PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error); + +static gint load_resource_1028 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error); + +static gint load_resource_1032 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error); + +static gint load_resource_1033 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error); + +static gint load_resource_1039 (const PSDimageres *res_a, + PSDimage *img_a, + gint32 image_id, + FILE *f, + GError **error); + +static gint load_resource_1045 (const PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error); + +static gint load_resource_1046 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error); + +static gint load_resource_1053 (const PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error); + +static gint load_resource_1058 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error); + +static gint load_resource_1077 (const PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error); + +static gint load_resource_2000 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error); + +/* Public Functions */ +gint +get_image_resource_header (PSDimageres *res_a, + FILE *f, + GError **error) +{ + gint32 read_len; + gint32 write_len; + gchar *name; + + if (fread (&res_a->type, 4, 1, f) < 1 + || fread (&res_a->id, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + res_a->id = GUINT16_FROM_BE (res_a->id); + name = fread_pascal_string (&read_len, &write_len, 2, f, error); + if (*error) + return -1; + if (name != NULL) + g_strlcpy (res_a->name, name, write_len + 1); + else + res_a->name[0] = 0x0; + g_free (name); + if (fread (&res_a->data_len, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + res_a->data_len = GUINT32_FROM_BE (res_a->data_len); + res_a->data_start = ftell (f); + + IFDBG(2) g_debug ("Type: %.4s, id: %d, start: %d, len: %d", + res_a->type, res_a->id, res_a->data_start, res_a->data_len); + + return 0; +} + +gint +load_image_resource (PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + gboolean *resolution_loaded, + gboolean *profile_loaded, + GError **error) +{ + gint pad; + + /* Set file position to start of image resource data block */ + if (fseek (f, res_a->data_start, SEEK_SET) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + /* Process image resource blocks */ + if (memcmp (res_a->type, "8BIM", 4) != 0 && + memcmp (res_a->type, "MeSa", 4) !=0) + { + IFDBG(1) g_debug ("Unknown image resource type signature %.4s", + res_a->type); + } + else + { + switch (res_a->id) + { + case PSD_PS2_IMAGE_INFO: + case PSD_PS2_COLOR_TAB: + case PSD_OBSOLETE_01: + case PSD_OBSOLETE_02: + case PSD_OBSOLETE_03: + /* Drop obsolete image resource blocks */ + IFDBG(2) g_debug ("Obsolete image resource block: %d", + res_a->id); + break; + + case PSD_THUMB_RES: + case PSD_THUMB_RES2: + /* Drop thumbnails from standard file load */ + IFDBG(2) g_debug ("Thumbnail resource block: %d", + res_a->id); + break; + + case PSD_MAC_PRINT_INFO: + case PSD_JPEG_QUAL: + /* Save photoshop resources with no meaning for GIMP + as image parasites */ + load_resource_ps_only (res_a, image_id, f, error); + break; + + case PSD_RESN_INFO: + if (! load_resource_1005 (res_a, image_id, f, error)) + *resolution_loaded = TRUE; + break; + + case PSD_ALPHA_NAMES: + if (! img_a->merged_image_only) + load_resource_1006 (res_a, image_id, img_a, f, error); + break; + + case PSD_DISPLAY_INFO: + load_resource_1007 (res_a, image_id, img_a, f, error); + break; + + case PSD_CAPTION: + load_resource_1008 (res_a, image_id, f, error); + break; + + case PSD_QUICK_MASK: + if (! img_a->merged_image_only) + load_resource_1022 (res_a, image_id, img_a, f, error); + break; + + case PSD_LAYER_STATE: + if (! img_a->merged_image_only) + load_resource_1024 (res_a, image_id, img_a, f, error); + break; + + case PSD_WORKING_PATH: + if (! img_a->merged_image_only) + load_resource_2000 (res_a, image_id, f, error); + break; + + case PSD_IPTC_NAA_DATA: + load_resource_1028 (res_a, image_id, f, error); + break; + + case PSD_GRID_GUIDE: + if (! img_a->merged_image_only) + load_resource_1032 (res_a, image_id, f, error); + break; + + case PSD_ICC_PROFILE: + if (! load_resource_1039 (res_a, img_a, image_id, f, error)) + *profile_loaded = TRUE; + break; + + case PSD_ALPHA_NAMES_UNI: + if (! img_a->merged_image_only) + load_resource_1045 (res_a, image_id, img_a, f, error); + break; + + case PSD_IDX_COL_TAB_CNT: + load_resource_1046 (res_a, image_id, f, error); + break; + + case PSD_ALPHA_ID: + if (! img_a->merged_image_only) + load_resource_1053 (res_a, image_id, img_a, f, error); + break; + + case PSD_EXIF_DATA: + load_resource_1058 (res_a, image_id, f, error); + break; + + case PSD_XMP_DATA: + break; + + case PSD_DISPLAY_INFO_NEW: + load_resource_1077 (res_a, image_id, img_a, f, error); + break; + + default: + if (res_a->id >= 2000 && + res_a->id < 2999) + load_resource_2000 (res_a, image_id, f, error); + else + load_resource_unknown (res_a, image_id, f, error); + } + } + + /* Image blocks are null padded to even length */ + if (res_a->data_len % 2 == 0) + pad = 0; + else + pad = 1; + + /* Set file position to end of image resource block */ + if (fseek (f, res_a->data_start + res_a->data_len + pad, SEEK_SET) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + return 0; +} + +gint +load_thumbnail_resource (PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error) +{ + gint rtn = 0; + gint pad; + + /* Set file position to start of image resource data block */ + if (fseek (f, res_a->data_start, SEEK_SET) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + /* Process image resource blocks */ + if (res_a->id == PSD_THUMB_RES + || res_a->id == PSD_THUMB_RES2) + { + /* Load thumbnails from standard file load */ + load_resource_1033 (res_a, image_id, f, error); + rtn = 1; + } + + /* Image blocks are null padded to even length */ + if (res_a->data_len % 2 == 0) + pad = 0; + else + pad = 1; + + /* Set file position to end of image resource block */ + if (fseek (f, res_a->data_start + res_a->data_len + pad, SEEK_SET) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + return rtn; +} + +/* Private Functions */ + +static gint +load_resource_unknown (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error) +{ + /* Unknown image resources attached as parasites to re-save later */ + GimpParasite *parasite; + gchar *data; + gchar *name; + + IFDBG(2) g_debug ("Process unknown image resource block: %d", res_a->id); + + data = g_malloc (res_a->data_len); + if (res_a->data_len > 0 && fread (data, res_a->data_len, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + g_free (data); + return -1; + } + + name = g_strdup_printf ("psd-image-resource-%.4s-%.4x", + res_a->type, res_a->id); + IFDBG(2) g_debug ("Parasite name: %s", name); + + parasite = gimp_parasite_new (name, 0, res_a->data_len, data); + gimp_image_attach_parasite (image_id, parasite); + gimp_parasite_free (parasite); + g_free (data); + g_free (name); + + return 0; +} + +static gint +load_resource_ps_only (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error) +{ + /* Save photoshop resources with no meaning for GIMP as image parasites + to re-save later */ + GimpParasite *parasite; + gchar *data; + gchar *name; + + IFDBG(3) g_debug ("Process image resource block: %d", res_a->id); + + data = g_malloc (res_a->data_len); + if (fread (data, res_a->data_len, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + g_free (data); + return -1; + } + + name = g_strdup_printf ("psd-image-resource-%.4s-%.4x", + res_a->type, res_a->id); + IFDBG(2) g_debug ("Parasite name: %s", name); + + parasite = gimp_parasite_new (name, 0, res_a->data_len, data); + gimp_image_attach_parasite (image_id, parasite); + gimp_parasite_free (parasite); + g_free (data); + g_free (name); + + return 0; +} + +static gint +load_resource_1005 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error) +{ + /* Load image resolution and unit of measure */ + + /* FIXME width unit and height unit unused at present */ + + ResolutionInfo res_info; + GimpUnit image_unit; + + IFDBG(2) g_debug ("Process image resource block 1005: Resolution Info"); + + if (fread (&res_info.hRes, 4, 1, f) < 1 + || fread (&res_info.hResUnit, 2, 1, f) < 1 + || fread (&res_info.widthUnit, 2, 1, f) < 1 + || fread (&res_info.vRes, 4, 1, f) < 1 + || fread (&res_info.vResUnit, 2, 1, f) < 1 + || fread (&res_info.heightUnit, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + res_info.hRes = GINT32_FROM_BE (res_info.hRes); + res_info.hResUnit = GINT16_FROM_BE (res_info.hResUnit); + res_info.widthUnit = GINT16_FROM_BE (res_info.widthUnit); + res_info.vRes = GINT32_FROM_BE (res_info.vRes); + res_info.vResUnit = GINT16_FROM_BE (res_info.vResUnit); + res_info.heightUnit = GINT16_FROM_BE (res_info.heightUnit); + + IFDBG(3) g_debug ("Resolution: %d, %d, %d, %d, %d, %d", + res_info.hRes, + res_info.hResUnit, + res_info.widthUnit, + res_info.vRes, + res_info.vResUnit, + res_info.heightUnit); + + /* Resolution always recorded as pixels / inch in a fixed point implied + decimal int32 with 16 bits before point and 16 after (i.e. cast as + double and divide resolution by 2^16 */ + gimp_image_set_resolution (image_id, + res_info.hRes / 65536.0, res_info.vRes / 65536.0); + + /* GIMP only has one display unit so use ps horizontal resolution unit */ + switch (res_info.hResUnit) + { + case PSD_RES_INCH: + image_unit = GIMP_UNIT_INCH; + break; + case PSD_RES_CM: + image_unit = GIMP_UNIT_MM; + break; + default: + image_unit = GIMP_UNIT_INCH; + } + + gimp_image_set_unit (image_id, image_unit); + + return 0; +} + +static gint +load_resource_1006 (const PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error) +{ + /* Load alpha channel names stored as a series of pascal strings + unpadded between strings */ + + gchar *str; + gint32 block_rem; + gint32 read_len; + gint32 write_len; + + IFDBG(2) g_debug ("Process image resource block 1006: Alpha Channel Names"); + + if (img_a->alpha_names) + { + IFDBG(3) g_debug ("Alpha names loaded from unicode resource block"); + return 0; + } + + img_a->alpha_names = g_ptr_array_new (); + + block_rem = res_a->data_len; + while (block_rem > 1) + { + str = fread_pascal_string (&read_len, &write_len, 1, f, error); + if (*error) + return -1; + IFDBG(3) g_debug ("String: %s, %d, %d", str, read_len, write_len); + if (write_len >= 0) + { + g_ptr_array_add (img_a->alpha_names, (gpointer) str); + } + block_rem -= read_len; + } + + return 0; +} + +static gint +load_resource_1007 (const PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error) +{ + /* Load alpha channel display info */ + + DisplayInfo dsp_info; + CMColor ps_color; + GimpRGB gimp_rgb; + GimpHSV gimp_hsv; + GimpCMYK gimp_cmyk; + gint16 tot_rec; + gint cidx; + + IFDBG(2) g_debug ("Process image resource block 1007: Display Info"); + tot_rec = res_a->data_len / 14; + if (tot_rec == 0) + return 0; + + img_a->alpha_display_info = g_new (PSDchanneldata *, tot_rec); + img_a->alpha_display_count = tot_rec; + for (cidx = 0; cidx < tot_rec; ++cidx) + { + if (fread (&dsp_info.colorSpace, 2, 1, f) < 1 + || fread (&dsp_info.color, 8, 1, f) < 1 + || fread (&dsp_info.opacity, 2, 1, f) < 1 + || fread (&dsp_info.kind, 1, 1, f) < 1 + || fread (&dsp_info.padding, 1, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + dsp_info.colorSpace = GINT16_FROM_BE (dsp_info.colorSpace); + ps_color.cmyk.cyan = GUINT16_FROM_BE (dsp_info.color[0]); + ps_color.cmyk.magenta = GUINT16_FROM_BE (dsp_info.color[1]); + ps_color.cmyk.yellow = GUINT16_FROM_BE (dsp_info.color[2]); + ps_color.cmyk.black = GUINT16_FROM_BE (dsp_info.color[3]); + dsp_info.opacity = GINT16_FROM_BE (dsp_info.opacity); + + switch (dsp_info.colorSpace) + { + case PSD_CS_RGB: + gimp_rgb_set (&gimp_rgb, ps_color.rgb.red / 65535.0, + ps_color.rgb.green / 65535.0, + ps_color.rgb.blue / 65535.0); + break; + + case PSD_CS_HSB: + gimp_hsv_set (&gimp_hsv, ps_color.hsv.hue / 65535.0, + ps_color.hsv.saturation / 65535.0, + ps_color.hsv.value / 65535.0); + gimp_hsv_to_rgb (&gimp_hsv, &gimp_rgb); + break; + + case PSD_CS_CMYK: + gimp_cmyk_set (&gimp_cmyk, 1.0 - ps_color.cmyk.cyan / 65535.0, + 1.0 - ps_color.cmyk.magenta / 65535.0, + 1.0 - ps_color.cmyk.yellow / 65535.0, + 1.0 - ps_color.cmyk.black / 65535.0); + gimp_cmyk_to_rgb (&gimp_cmyk, &gimp_rgb); + break; + + case PSD_CS_GRAYSCALE: + gimp_rgb_set (&gimp_rgb, ps_color.gray.gray / 10000.0, + ps_color.gray.gray / 10000.0, + ps_color.gray.gray / 10000.0); + break; + + case PSD_CS_FOCOLTONE: + case PSD_CS_TRUMATCH: + case PSD_CS_HKS: + case PSD_CS_LAB: + case PSD_CS_PANTONE: + case PSD_CS_TOYO: + case PSD_CS_DIC: + case PSD_CS_ANPA: + default: + if (CONVERSION_WARNINGS) + g_message ("Unsupported color space: %d", + dsp_info.colorSpace); + gimp_rgb_set (&gimp_rgb, 1.0, 0.0, 0.0); + } + + gimp_rgb_set_alpha (&gimp_rgb, 1.0); + + IFDBG(2) g_debug ("PS cSpace: %d, col: %d %d %d %d, opacity: %d, kind: %d", + dsp_info.colorSpace, ps_color.cmyk.cyan, ps_color.cmyk.magenta, + ps_color.cmyk.yellow, ps_color.cmyk.black, dsp_info.opacity, + dsp_info.kind); + + IFDBG(2) g_debug ("cSpace: %d, col: %g %g %g, opacity: %d, kind: %d", + dsp_info.colorSpace, gimp_rgb.r * 255 , gimp_rgb.g * 255, + gimp_rgb.b * 255, dsp_info.opacity, dsp_info.kind); + + img_a->alpha_display_info[cidx] = g_malloc0 (sizeof (PSDchanneldata)); + img_a->alpha_display_info[cidx]->gimp_color = gimp_rgb; + img_a->alpha_display_info[cidx]->opacity = dsp_info.opacity; + img_a->alpha_display_info[cidx]->ps_kind = dsp_info.kind; + img_a->alpha_display_info[cidx]->ps_cspace = dsp_info.colorSpace; + img_a->alpha_display_info[cidx]->ps_color = ps_color; + } + + return 0; +} + +static gint +load_resource_1008 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error) +{ + /* Load image caption */ + GimpParasite *parasite; + gchar *caption; + gint32 read_len; + gint32 write_len; + + IFDBG(2) g_debug ("Process image resource block: 1008: Caption"); + caption = fread_pascal_string (&read_len, &write_len, 1, f, error); + if (*error) + return -1; + + IFDBG(3) g_debug ("Caption: %s", caption); + parasite = gimp_parasite_new (GIMP_PARASITE_COMMENT, GIMP_PARASITE_PERSISTENT, + write_len, caption); + gimp_image_attach_parasite (image_id, parasite); + gimp_parasite_free (parasite); + g_free (caption); + + return 0; +} + +static gint +load_resource_1022 (const PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error) +{ + /* Load quick mask info */ + gboolean quick_mask_empty; /* Quick mask initially empty */ + + IFDBG(2) g_debug ("Process image resource block: 1022: Quick Mask"); + + if (fread (&img_a->quick_mask_id, 2, 1, f) < 1 + || fread (&quick_mask_empty, 1, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + img_a->quick_mask_id = GUINT16_FROM_BE (img_a->quick_mask_id); + + IFDBG(3) g_debug ("Quick mask channel: %d, empty: %d", + img_a->quick_mask_id, + quick_mask_empty); + + return 0; +} + +static gint +load_resource_1024 (const PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error) +{ + /* Load image layer state - current active layer counting from bottom up */ + IFDBG(2) g_debug ("Process image resource block: 1024: Layer State"); + + if (fread (&img_a->layer_state, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + img_a->layer_state = GUINT16_FROM_BE (img_a->layer_state); + + return 0; +} + +static gint +load_resource_1028 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error) +{ + /* Load IPTC data block */ + +#ifdef HAVE_IPTCDATA + IptcData *iptc_data; + guchar *iptc_buf; + guint iptc_buf_len; +#else + gchar *name; +#endif /* HAVE_IPTCDATA */ + + GimpParasite *parasite; + gchar *res_data; + + IFDBG(2) g_debug ("Process image resource block: 1028: IPTC data"); + + res_data = g_malloc (res_a->data_len); + if (fread (res_data, res_a->data_len, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + g_free (res_data); + return -1; + } + +#ifdef HAVE_IPTCDATA + /* Load IPTC data structure */ + iptc_data = iptc_data_new_from_data (res_data, res_a->data_len); + IFDBG (3) iptc_data_dump (iptc_data, 0); + + /* Store resource data as a GIMP IPTC parasite */ + IFDBG (2) g_debug ("Processing IPTC data as GIMP IPTC parasite"); + /* Serialize IPTC data */ + iptc_data_save (iptc_data, &iptc_buf, &iptc_buf_len); + if (iptc_buf_len > 0) + { + parasite = gimp_parasite_new (GIMP_PARASITE_IPTC, + GIMP_PARASITE_PERSISTENT, + iptc_buf_len, iptc_buf); + gimp_image_attach_parasite (image_id, parasite); + gimp_parasite_free (parasite); + } + + iptc_data_unref (iptc_data); + g_free (iptc_buf); + +#else + /* Store resource data as a standard psd parasite */ + IFDBG (2) g_debug ("Processing IPTC data as psd parasite"); + name = g_strdup_printf ("psd-image-resource-%.4s-%.4x", + res_a->type, res_a->id); + IFDBG(3) g_debug ("Parasite name: %s", name); + + parasite = gimp_parasite_new (name, 0, res_a->data_len, res_data); + gimp_image_attach_parasite (image_id, parasite); + gimp_parasite_free (parasite); + g_free (name); + +#endif /* HAVE_IPTCDATA */ + + g_free (res_data); + return 0; +} + +static gint +load_resource_1032 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error) +{ + /* Load grid and guides */ + + /* Grid info is not used (CS2 or earlier) */ + + GuideHeader hdr; + GuideResource guide; + gint i; + + IFDBG(2) g_debug ("Process image resource block 1032: Grid and Guide Info"); + + if (fread (&hdr.fVersion, 4, 1, f) < 1 + || fread (&hdr.fGridCycleV, 4, 1, f) < 1 + || fread (&hdr.fGridCycleH, 4, 1, f) < 1 + || fread (&hdr.fGuideCount, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + hdr.fVersion = GUINT32_FROM_BE (hdr.fVersion); + hdr.fGridCycleV = GUINT32_FROM_BE (hdr.fGridCycleV); + hdr.fGridCycleH = GUINT32_FROM_BE (hdr.fGridCycleH); + hdr.fGuideCount = GUINT32_FROM_BE (hdr.fGuideCount); + + IFDBG(3) g_debug ("Grids & Guides: %d, %d, %d, %d", + hdr.fVersion, + hdr.fGridCycleV, + hdr.fGridCycleH, + hdr.fGuideCount); + + for (i = 0; i < hdr.fGuideCount; ++i) + { + if (fread (&guide.fLocation, 4, 1, f) < 1 + || fread (&guide.fDirection, 1, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + guide.fLocation = GUINT32_FROM_BE (guide.fLocation); + guide.fLocation /= 32; + + IFDBG(3) g_debug ("Guide: %d px, %d", + guide.fLocation, + guide.fDirection); + + if (guide.fDirection == PSD_VERTICAL) + gimp_image_add_vguide (image_id, guide.fLocation); + else + gimp_image_add_hguide (image_id, guide.fLocation); + } + + return 0; +} + +static gint +load_resource_1033 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error) +{ + /* Load thumbnail image */ + + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + + ThumbnailInfo thumb_info; + GeglBuffer *buffer; + const Babl *format; + gint32 layer_id; + guchar *buf; + guchar *rgb_buf; + guchar **rowbuf; + gint i; + + IFDBG(2) g_debug ("Process image resource block %d: Thumbnail Image", res_a->id); + + /* Read thumbnail resource header info */ + if (fread (&thumb_info.format, 4, 1, f) < 1 + || fread (&thumb_info.width, 4, 1, f) < 1 + || fread (&thumb_info.height, 4, 1, f) < 1 + || fread (&thumb_info.widthbytes, 4, 1, f) < 1 + || fread (&thumb_info.size, 4, 1, f) < 1 + || fread (&thumb_info.compressedsize, 4, 1, f) < 1 + || fread (&thumb_info.bitspixel, 2, 1, f) < 1 + || fread (&thumb_info.planes, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + thumb_info.format = GINT32_FROM_BE (thumb_info.format); + thumb_info.width = GINT32_FROM_BE (thumb_info.width); + thumb_info.height = GINT32_FROM_BE (thumb_info.height); + thumb_info.widthbytes = GINT32_FROM_BE (thumb_info.widthbytes); + thumb_info.size = GINT32_FROM_BE (thumb_info.size); + thumb_info.compressedsize = GINT32_FROM_BE (thumb_info.compressedsize); + thumb_info.bitspixel = GINT16_FROM_BE (thumb_info.bitspixel); + thumb_info.planes = GINT16_FROM_BE (thumb_info.planes); + + IFDBG(2) g_debug ("\nThumbnail:\n" + "\tFormat: %d\n" + "\tDimensions: %d x %d\n", + thumb_info.format, + thumb_info.width, + thumb_info.height); + + if (thumb_info.format != 1) + { + IFDBG(1) g_debug ("Unknown thumbnail format %d", thumb_info.format); + return -1; + } + + /* Load Jpeg RGB thumbnail info */ + + /* Step 1: Allocate and initialize JPEG decompression object */ + cinfo.err = jpeg_std_error (&jerr); + jpeg_create_decompress (&cinfo); + + /* Step 2: specify data source (eg, a file) */ + jpeg_stdio_src(&cinfo, f); + + /* Step 3: read file parameters with jpeg_read_header() */ + jpeg_read_header (&cinfo, TRUE); + + /* Step 4: set parameters for decompression */ + + + /* Step 5: Start decompressor */ + jpeg_start_decompress (&cinfo); + + /* temporary buffers */ + buf = g_new (guchar, cinfo.output_height * cinfo.output_width + * cinfo.output_components); + if (res_a->id == PSD_THUMB_RES) + rgb_buf = g_new (guchar, cinfo.output_height * cinfo.output_width + * cinfo.output_components); + else + rgb_buf = NULL; + rowbuf = g_new (guchar *, cinfo.output_height); + + for (i = 0; i < cinfo.output_height; ++i) + rowbuf[i] = buf + cinfo.output_width * cinfo.output_components * i; + + /* Create image layer */ + gimp_image_resize (image_id, cinfo.output_width, cinfo.output_height, 0, 0); + layer_id = gimp_layer_new (image_id, _("Background"), + cinfo.output_width, + cinfo.output_height, + GIMP_RGB_IMAGE, + 100, + gimp_image_get_default_new_layer_mode (image_id)); + buffer = gimp_drawable_get_buffer (layer_id); + format = babl_format ("R'G'B' u8"); + + /* Step 6: while (scan lines remain to be read) */ + /* jpeg_read_scanlines(...); */ + while (cinfo.output_scanline < cinfo.output_height) + { + jpeg_read_scanlines (&cinfo, + (JSAMPARRAY) &rowbuf[cinfo.output_scanline], 1); + } + + if (res_a->id == PSD_THUMB_RES) /* Order is BGR for resource 1033 */ + { + guchar *dst = rgb_buf; + guchar *src = buf; + + for (i = 0; i < gegl_buffer_get_width (buffer) * gegl_buffer_get_height (buffer); ++i) + { + guchar r, g, b; + + r = *(src++); + g = *(src++); + b = *(src++); + *(dst++) = b; + *(dst++) = g; + *(dst++) = r; + } + } + + gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, + gegl_buffer_get_width (buffer), + gegl_buffer_get_height (buffer)), + 0, format, rgb_buf ? rgb_buf : buf, GEGL_AUTO_ROWSTRIDE); + + /* Step 7: Finish decompression */ + jpeg_finish_decompress (&cinfo); + /* We can ignore the return value since suspension is not possible + * with the stdio data source. + */ + + /* Step 8: Release JPEG decompression object */ + jpeg_destroy_decompress (&cinfo); + + /* free up the temporary buffers */ + g_free (rowbuf); + g_free (buf); + g_free (rgb_buf); + + /* At this point you may want to check to see whether any + * corrupt-data warnings occurred (test whether + * jerr.num_warnings is nonzero). + */ + gimp_image_insert_layer (image_id, layer_id, -1, 0); + g_object_unref (buffer); + + return 0; +} + +static gint +load_resource_1039 (const PSDimageres *res_a, + PSDimage *img_a, + gint32 image_id, + FILE *f, + GError **error) +{ + /* Load ICC profile */ + GimpColorProfile *profile; + gchar *icc_profile; + + IFDBG(2) g_debug ("Process image resource block: 1039: ICC Profile"); + + icc_profile = g_malloc (res_a->data_len); + if (fread (icc_profile, res_a->data_len, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + g_free (icc_profile); + return -1; + } + + profile = gimp_color_profile_new_from_icc_profile ((guint8 *) icc_profile, + res_a->data_len, + NULL); + if (profile) + { + if (img_a->color_mode == PSD_CMYK && + gimp_color_profile_is_cmyk (profile)) + { + img_a->cmyk_profile = profile; + } + else + { + gimp_image_set_color_profile (image_id, profile); + g_object_unref (profile); + } + } + + g_free (icc_profile); + + return 0; +} + +static gint +load_resource_1045 (const PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error) +{ + /* Load alpha channel names stored as a series of unicode strings + in a GPtrArray */ + + gchar *str; + gint32 block_rem; + gint32 read_len; + gint32 write_len; + + IFDBG(2) g_debug ("Process image resource block 1045: Unicode Alpha Channel Names"); + + if (img_a->alpha_names) + { + gint i; + IFDBG(3) g_debug ("Deleting localised alpha channel names"); + for (i = 0; i < img_a->alpha_names->len; ++i) + { + str = g_ptr_array_index (img_a->alpha_names, i); + g_free (str); + } + g_ptr_array_free (img_a->alpha_names, TRUE); + } + + img_a->alpha_names = g_ptr_array_new (); + + block_rem = res_a->data_len; + while (block_rem > 1) + { + str = fread_unicode_string (&read_len, &write_len, 1, f, error); + if (*error) + return -1; + + IFDBG(3) g_debug ("String: %s, %d, %d", str, read_len, write_len); + if (write_len >= 0) + { + g_ptr_array_add (img_a->alpha_names, (gpointer) str); + } + block_rem -= read_len; + } + + return 0; +} + +static gint +load_resource_1046 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error) +{ + /* Load indexed color table count */ + guchar *cmap; + gint32 cmap_count = 0; + gint16 index_count = 0; + + IFDBG(2) g_debug ("Process image resource block: 1046: Indexed Color Table Count"); + + if (fread (&index_count, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + index_count = GINT16_FROM_BE (index_count); + + IFDBG(3) g_debug ("Indexed color table count: %d", index_count); + /* FIXME - check that we have indexed image */ + if (index_count && index_count < 256) + { + cmap = gimp_image_get_colormap (image_id, &cmap_count); + if (cmap && index_count < cmap_count) + gimp_image_set_colormap (image_id, cmap, index_count); + g_free (cmap); + } + return 0; +} + +static gint +load_resource_1053 (const PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error) +{ + /* Load image alpha channel ids (tattoos) */ + gint16 tot_rec; + gint16 cidx; + + IFDBG(2) g_debug ("Process image resource block: 1053: Channel ID"); + + tot_rec = res_a->data_len / 4; + if (tot_rec ==0) + return 0; + + img_a->alpha_id = g_malloc (sizeof (img_a->alpha_id) * tot_rec); + img_a->alpha_id_count = tot_rec; + for (cidx = 0; cidx < tot_rec; ++cidx) + { + if (fread (&img_a->alpha_id[cidx], 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + img_a->alpha_id[cidx] = GUINT32_FROM_BE (img_a->alpha_id[cidx]); + + IFDBG(3) g_debug ("Channel id: %d", img_a->alpha_id[cidx]); + } + + return 0; +} + +static gint +load_resource_1058 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error) +{ + gchar *name; + + GimpParasite *parasite; + gchar *res_data; + + IFDBG(2) g_debug ("Process image resource block: 1058: Exif data"); + + res_data = g_malloc (res_a->data_len); + if (fread (res_data, res_a->data_len, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + g_free (res_data); + return -1; + } + + /* Store resource data as a standard psd parasite */ + IFDBG (2) g_debug ("Processing exif data as psd parasite"); + name = g_strdup_printf ("psd-image-resource-%.4s-%.4x", + res_a->type, res_a->id); + IFDBG(3) g_debug ("Parasite name: %s", name); + + parasite = gimp_parasite_new (name, 0, res_a->data_len, res_data); + gimp_image_attach_parasite (image_id, parasite); + gimp_parasite_free (parasite); + g_free (name); + + g_free (res_data); + return 0; +} + +static gint +load_resource_1077 (const PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error) +{ + /* Load alpha channel display info */ + + DisplayInfoNew dsp_info; + CMColor ps_color; + GimpRGB gimp_rgb; + GimpHSV gimp_hsv; + GimpCMYK gimp_cmyk; + gint16 tot_rec; + gint cidx; + + IFDBG(2) g_debug ("Process image resource block 1077: Display Info New"); + + /* For now, skip first 4 bytes since intention is unclear. Seems to be + a version number that is always one, but who knows. */ + fseek (f, 4, SEEK_CUR); + + tot_rec = res_a->data_len / 13; + if (tot_rec == 0) + return 0; + + img_a->alpha_display_info = g_new (PSDchanneldata *, tot_rec); + img_a->alpha_display_count = tot_rec; + for (cidx = 0; cidx < tot_rec; ++cidx) + { + if (fread (&dsp_info.colorSpace, 2, 1, f) < 1 + || fread (&dsp_info.color, 8, 1, f) < 1 + || fread (&dsp_info.opacity, 2, 1, f) < 1 + || fread (&dsp_info.mode, 1, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + dsp_info.colorSpace = GINT16_FROM_BE (dsp_info.colorSpace); + ps_color.cmyk.cyan = GUINT16_FROM_BE (dsp_info.color[0]); + ps_color.cmyk.magenta = GUINT16_FROM_BE (dsp_info.color[1]); + ps_color.cmyk.yellow = GUINT16_FROM_BE (dsp_info.color[2]); + ps_color.cmyk.black = GUINT16_FROM_BE (dsp_info.color[3]); + dsp_info.opacity = GINT16_FROM_BE (dsp_info.opacity); + + switch (dsp_info.colorSpace) + { + case PSD_CS_RGB: + gimp_rgb_set (&gimp_rgb, ps_color.rgb.red / 65535.0, + ps_color.rgb.green / 65535.0, + ps_color.rgb.blue / 65535.0); + break; + + case PSD_CS_HSB: + gimp_hsv_set (&gimp_hsv, ps_color.hsv.hue / 65535.0, + ps_color.hsv.saturation / 65535.0, + ps_color.hsv.value / 65535.0); + gimp_hsv_to_rgb (&gimp_hsv, &gimp_rgb); + break; + + case PSD_CS_CMYK: + gimp_cmyk_set (&gimp_cmyk, 1.0 - ps_color.cmyk.cyan / 65535.0, + 1.0 - ps_color.cmyk.magenta / 65535.0, + 1.0 - ps_color.cmyk.yellow / 65535.0, + 1.0 - ps_color.cmyk.black / 65535.0); + gimp_cmyk_to_rgb (&gimp_cmyk, &gimp_rgb); + break; + + case PSD_CS_GRAYSCALE: + gimp_rgb_set (&gimp_rgb, ps_color.gray.gray / 10000.0, + ps_color.gray.gray / 10000.0, + ps_color.gray.gray / 10000.0); + break; + + case PSD_CS_FOCOLTONE: + case PSD_CS_TRUMATCH: + case PSD_CS_HKS: + case PSD_CS_LAB: + case PSD_CS_PANTONE: + case PSD_CS_TOYO: + case PSD_CS_DIC: + case PSD_CS_ANPA: + default: + if (CONVERSION_WARNINGS) + g_message ("Unsupported color space: %d", + dsp_info.colorSpace); + gimp_rgb_set (&gimp_rgb, 1.0, 0.0, 0.0); + } + + gimp_rgb_set_alpha (&gimp_rgb, 1.0); + + IFDBG(2) g_debug ("PS cSpace: %d, col: %d %d %d %d, opacity: %d, mode: %d", + dsp_info.colorSpace, ps_color.cmyk.cyan, ps_color.cmyk.magenta, + ps_color.cmyk.yellow, ps_color.cmyk.black, dsp_info.opacity, + dsp_info.mode); + + IFDBG(2) g_debug ("cSpace: %d, col: %g %g %g, opacity: %d, mode: %d", + dsp_info.colorSpace, gimp_rgb.r * 255 , gimp_rgb.g * 255, + gimp_rgb.b * 255, dsp_info.opacity, dsp_info.mode); + + img_a->alpha_display_info[cidx] = g_malloc0 (sizeof (PSDchanneldata)); + img_a->alpha_display_info[cidx]->gimp_color = gimp_rgb; + img_a->alpha_display_info[cidx]->opacity = dsp_info.opacity; + img_a->alpha_display_info[cidx]->ps_mode = dsp_info.mode; + img_a->alpha_display_info[cidx]->ps_cspace = dsp_info.colorSpace; + img_a->alpha_display_info[cidx]->ps_color = ps_color; + } + + return 0; +} + +static gint +load_resource_2000 (const PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error) +{ + gdouble *controlpoints; + gint32 x[3]; + gint32 y[3]; + gint32 vector_id = -1; + gint16 type; + gint16 init_fill; + gint16 num_rec; + gint16 path_rec; + gint16 cntr; + gint image_width; + gint image_height; + gint i; + gboolean closed; + + /* Load path data from image resources 2000-2998 */ + + IFDBG(2) g_debug ("Process image resource block: %d :Path data", res_a->id); + path_rec = res_a->data_len / 26; + if (path_rec ==0) + return 0; + + if (fread (&type, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + type = GINT16_FROM_BE (type); + if (type != PSD_PATH_FILL_RULE) + { + IFDBG(1) g_debug ("Unexpected path record type: %d", type); + return -1; + } + + if (fseek (f, 24, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + path_rec--; + if (path_rec ==0) + return 0; + + image_width = gimp_image_width (image_id); + image_height = gimp_image_height (image_id); + + /* Create path */ + if (res_a->id == PSD_WORKING_PATH) + { + /* use "Working Path" for the path name to match the Photoshop display */ + vector_id = gimp_vectors_new (image_id, "Working Path"); + } + else + { + /* Use the name stored in the PSD to name the path */ + vector_id = gimp_vectors_new (image_id, res_a->name); + } + + gimp_image_insert_vectors (image_id, vector_id, -1, -1); + + while (path_rec > 0) + { + if (fread (&type, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + type = GINT16_FROM_BE (type); + IFDBG(3) g_debug ("Path record type %d", type); + + if (type == PSD_PATH_FILL_RULE) + { + if (fseek (f, 24, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + } + + else if (type == PSD_PATH_FILL_INIT) + { + if (fread (&init_fill, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + if (fseek (f, 22, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + } + + else if (type == PSD_PATH_CL_LEN + || type == PSD_PATH_OP_LEN) + { + if (fread (&num_rec, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + num_rec = GINT16_FROM_BE (num_rec); + if (num_rec > path_rec) + { + psd_set_error (feof (f), errno, error); + return - 1; + } + IFDBG(3) g_debug ("Num path records %d", num_rec); + + if (type == PSD_PATH_CL_LEN) + closed = TRUE; + else + closed = FALSE; + cntr = 0; + controlpoints = g_malloc (sizeof (gdouble) * num_rec * 6); + if (fseek (f, 22, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + g_free (controlpoints); + return -1; + } + + while (num_rec > 0) + { + if (fread (&type, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + type = GINT16_FROM_BE (type); + IFDBG(3) g_debug ("Path record type %d", type); + + if (type == PSD_PATH_CL_LNK + || type == PSD_PATH_CL_UNLNK + || type == PSD_PATH_OP_LNK + || type == PSD_PATH_OP_UNLNK) + { + if (fread (&y[0], 4, 1, f) < 1 + || fread (&x[0], 4, 1, f) < 1 + || fread (&y[1], 4, 1, f) < 1 + || fread (&x[1], 4, 1, f) < 1 + || fread (&y[2], 4, 1, f) < 1 + || fread (&x[2], 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + for (i = 0; i < 3; ++i) + { + x[i] = GINT32_FROM_BE (x[i]); + controlpoints[cntr] = x[i] / 16777216.0 * image_width; + cntr++; + y[i] = GINT32_FROM_BE (y[i]); + controlpoints[cntr] = y[i] / 16777216.0 * image_height; + cntr++; + } + IFDBG(3) g_debug ("Path points (%d,%d), (%d,%d), (%d,%d)", + x[0], y[0], x[1], y[1], x[2], y[2]); + } + else + { + IFDBG(1) g_debug ("Unexpected path type record %d", type); + if (fseek (f, 24, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + } + path_rec--; + num_rec--; + } + /* Add sub-path */ + gimp_vectors_stroke_new_from_points (vector_id, + GIMP_VECTORS_STROKE_TYPE_BEZIER, + cntr, controlpoints, closed); + g_free (controlpoints); + } + + else + { + if (fseek (f, 24, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + } + + path_rec--; + } + + return 0; +} diff --git a/plug-ins/file-psd/psd-image-res-load.h b/plug-ins/file-psd/psd-image-res-load.h new file mode 100644 index 0000000..c981a6b --- /dev/null +++ b/plug-ins/file-psd/psd-image-res-load.h @@ -0,0 +1,43 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GIMP PSD Plug-in + * Copyright 2007 by John Marshall + * + * 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 __PSD_IMAGE_RES_LOAD_H__ +#define __PSD_IMAGE_RES_LOAD_H__ + + +gint get_image_resource_header (PSDimageres *res_a, + FILE *f, + GError **error); + +gint load_image_resource (PSDimageres *res_a, + gint32 image_id, + PSDimage *img_a, + FILE *f, + gboolean *resolution_loaded, + gboolean *profile_loaded, + GError **error); + +gint load_thumbnail_resource (PSDimageres *res_a, + gint32 image_id, + FILE *f, + GError **error); + + +#endif /* __PSD_IMAGE_RES_LOAD_H__ */ diff --git a/plug-ins/file-psd/psd-layer-res-load.c b/plug-ins/file-psd/psd-layer-res-load.c new file mode 100644 index 0000000..d2207b3 --- /dev/null +++ b/plug-ins/file-psd/psd-layer-res-load.c @@ -0,0 +1,944 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GIMP PSD Plug-in + * Copyright 2007 by John Marshall + * + * 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/>. + */ + +/* ----- Known Layer Resource Block Types ----- + All layer resources not otherwise handled, including unknown types + are dropped with a warning. + + * Adjustment layer IDs * + PSD_LADJ_LEVEL "levl" Drop Layer * Adjustment layer - levels (PS4) * + PSD_LADJ_CURVE "curv" Drop Layer * Adjustment layer - curves (PS4) * + PSD_LADJ_BRIGHTNESS "brit" Drop Layer * Adjustment layer - brightness contrast (PS4) * + PSD_LADJ_BALANCE "blnc" Drop Layer * Adjustment layer - color balance (PS4) * + PSD_LADJ_BLACK_WHITE "blwh" Drop Layer * Adjustment layer - black & white (PS10) * + PSD_LADJ_HUE "hue " Drop Layer * Adjustment layer - old hue saturation (PS4) * + PSD_LADJ_HUE2 "hue2" Drop Layer * Adjustment layer - hue saturation (PS5) * + PSD_LADJ_SELECTIVE "selc" Drop Layer * Adjustment layer - selective color (PS4) * + PSD_LADJ_MIXER "mixr" Drop Layer * Adjustment layer - channel mixer (PS9) * + PSD_LADJ_GRAD_MAP "grdm" Drop Layer * Adjustment layer - gradient map (PS9) * + PSD_LADJ_PHOTO_FILT "phfl" Drop Layer * Adjustment layer - photo filter (PS9) * + PSD_LADJ_EXPOSURE "expA" Drop Layer * Adjustment layer - exposure (PS10) * + PSD_LADJ_INVERT "nvrt" Drop Layer * Adjustment layer - invert (PS4) * + PSD_LADJ_THRESHOLD "thrs" Drop Layer * Adjustment layer - threshold (PS4) * + PSD_LADJ_POSTERIZE "post" Drop Layer * Adjustment layer - posterize (PS4) * + PSD_LADJ_VIBRANCE "vibA" - * Adjustment layer - vibrance (PS10) * + PSD_LADJ_COLOR_LOOKUP "clrL" - * Adjustment layer - color lookup (PS13) * + + * Fill Layer IDs * + PSD_LFIL_SOLID "SoCo" - * Solid color sheet setting (PS6) * + PSD_LFIL_PATTERN "PtFl" - * Pattern fill setting (PS6) * + PSD_LFIL_GRADIENT "GdFl" - * Gradient fill setting (PS6) * + + * Effects Layer IDs * + PSD_LFX_FX "lrFX" - * Effects layer info (PS5) * + PSD_LFX_FX2 "lfx2" - * Object based effects layer info (PS6) * + + * Type Tool Layers * + PSD_LTYP_TYPE "tySh" - * Type tool layer (PS5) * + PSD_LTYP_TYPE2 "TySh" - * Type tool object setting (PS6) * + + * Layer Properties * + PSD_LPRP_UNICODE "luni" Loaded * Unicode layer name (PS5) * + PSD_LPRP_SOURCE "lnsr" Loaded * Layer name source setting (PS6) * + PSD_LPRP_ID "lyid" Loaded * Layer ID (PS5) * + PSD_LPRP_BLEND_CLIP "clbl" - * Blend clipping elements (PS6) * + PSD_LPRP_BLEND_INT "infx" - * Blend interior elements (PS6) * + PSD_LPRP_KNOCKOUT "knko" - * Knockout setting (PS6) * + PSD_LPRP_PROTECT "lspf" - * Protected setting (PS6) * + PSD_LPRP_COLOR "lclr" - * Sheet color setting (PS6) * + PSD_LPRP_REF_POINT "fxrp" - * Reference point (PS6) * + PSD_LPRP_VERSION "lyvr" Loaded * Layer version (PS7) * + + * Vector mask * + PSD_LMSK_VMASK "vmsk" - * Vector mask setting (PS6) * + + * Parasites * + PSD_LPAR_ANNOTATE "Anno" - * Annotation (PS6) * + + * Other * + PSD_LOTH_PATTERN "Patt" - * Patterns (PS6) * + PSD_LOTH_GRADIENT "grdm" - * Gradient settings (PS6) * + PSD_LOTH_SECTION "lsct" Loaded * Section divider setting (PS6) (Layer Groups) * + PSD_LOTH_SECTION2 "lsdk" Loaded * Nested section divider setting (CS5) (Layer Groups) * + PSD_LOTH_RESTRICT "brst" - * Channel blending restriction setting (PS6) * + PSD_LOTH_FOREIGN_FX "ffxi" - * Foreign effect ID (PS6) * + PSD_LOTH_PATT_DATA "shpa" - * Pattern data (PS6) * + PSD_LOTH_META_DATA "shmd" - * Meta data setting (PS6) * + PSD_LOTH_LAYER_DATA "layr" - * Layer data (PS6) * + PSD_LOTH_CONTENT_GEN "CgEd" - * Content generator extra data (PS12) * + PSD_LOTH_TEXT_ENGINE "Txt2" - * Text engine data (PS10) * + PSD_LOTH_PATH_NAME "pths" - * Unicode path name (PS13) * + PSD_LOTH_ANIMATION_FX "anFX" - * Animation effects (PS13) * + PSD_LOTH_FILTER_MASK "FMsk" - * Filter mask (PS10) * + PSD_LOTH_VECTOR_STROKE "vscg" - * Vector stroke data (PS13) * + PSD_LOTH_ALIGN_RENDER "sn2P" - * Aligned rendering flag (?) * + PSD_LOTH_USER_MASK "LMsk" - * User mask (?) * + + * Effects layer resource IDs * + PSD_LFX_COMMON "cmnS" - * Effects layer - common state (PS5) * + PSD_LFX_DROP_SDW "dsdw" - * Effects layer - drop shadow (PS5) * + PSD_LFX_INNER_SDW "isdw" - * Effects layer - inner shadow (PS5) * + PSD_LFX_OUTER_GLW "oglw" - * Effects layer - outer glow (PS5) * + PSD_LFX_INNER_GLW "iglw" - * Effects layer - inner glow (PS5) * + PSD_LFX_BEVEL "bevl" - * Effects layer - bevel (PS5) * + + * New stuff temporarily until I can get them sorted out * + + * Placed Layer * + PSD_LPL_PLACE_LAYER "plLd" - * Placed layer (?) * + PSD_LPL_PLACE_LAYER_NEW "SoLd" - * Placed layer (PS10) * + + * Linked Layer * + PSD_LLL_LINKED_LAYER "lnkD" - * Linked layer (?) * + PSD_LLL_LINKED_LAYER_2 "lnk2" - * Linked layer 2nd key * + PSD_LLL_LINKED_LAYER_3 "lnk3" - * Linked layer 3rd key * + + * Merged Transparency * + PSD_LMT_MERGE_TRANS "Mtrn" - * Merged transparency save flag (?) * + PSD_LMT_MERGE_TRANS_16 "Mt16" - * Merged transparency save flag 2 * + PSD_LMT_MERGE_TRANS_32 "Mt32" - * Merged transparency save flag 3 * + + * Filter Effects * + PSD_LFFX_FILTER_FX "FXid" - * Filter effects (?) * + PSD_LFFX_FILTER_FX_2 "FEid" - * Filter effects 2 * +*/ + +#include "config.h" + +#include <string.h> +#include <errno.h> + +#include <glib/gstdio.h> +#include <libgimp/gimp.h> + +#include "psd.h" +#include "psd-util.h" +#include "psd-layer-res-load.h" + +#include "libgimp/stdplugins-intl.h" + +/* Local function prototypes */ +static gint load_resource_unknown (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error); + +static gint load_resource_ladj (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error); + +static gint load_resource_lfil (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error); + +static gint load_resource_lfx (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error); + +static gint load_resource_ltyp (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error); + +static gint load_resource_luni (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error); + +static gint load_resource_lyid (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error); + +static gint load_resource_lclr (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error); + +static gint load_resource_lsct (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error); + +static gint load_resource_lrfx (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error); + +static gint load_resource_lyvr (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error); + +static gint load_resource_lnsr (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error); + +/* Public Functions */ +gint +get_layer_resource_header (PSDlayerres *res_a, + FILE *f, + GError **error) +{ + if (fread (res_a->sig, 4, 1, f) < 1 + || fread (res_a->key, 4, 1, f) < 1 + || fread (&res_a->data_len, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + res_a->data_len = GUINT32_FROM_BE (res_a->data_len); + res_a->data_start = ftell (f); + + IFDBG(2) g_debug ("Sig: %.4s, key: %.4s, start: %d, len: %d", + res_a->sig, res_a->key, res_a->data_start, res_a->data_len); + + return 0; +} + +gint +load_layer_resource (PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error) +{ + /* Set file position to start of layer resource data block */ + if (fseek (f, res_a->data_start, SEEK_SET) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + /* Process layer resource blocks */ + if (memcmp (res_a->sig, "8BIM", 4) != 0) + { + IFDBG(1) g_debug ("Unknown layer resource signature %.4s", res_a->sig); + } + else + { + if (memcmp (res_a->key, PSD_LADJ_LEVEL, 4) == 0 + || memcmp (res_a->key, PSD_LADJ_CURVE, 4) == 0 + || memcmp (res_a->key, PSD_LADJ_BRIGHTNESS, 4) == 0 + || memcmp (res_a->key, PSD_LADJ_BALANCE, 4) == 0 + || memcmp (res_a->key, PSD_LADJ_BLACK_WHITE, 4) == 0 + || memcmp (res_a->key, PSD_LADJ_HUE, 4) == 0 + || memcmp (res_a->key, PSD_LADJ_HUE2, 4) == 0 + || memcmp (res_a->key, PSD_LADJ_SELECTIVE, 4) == 0 + || memcmp (res_a->key, PSD_LADJ_MIXER, 4) == 0 + || memcmp (res_a->key, PSD_LADJ_GRAD_MAP, 4) == 0 + || memcmp (res_a->key, PSD_LADJ_PHOTO_FILT, 4) == 0 + || memcmp (res_a->key, PSD_LADJ_EXPOSURE, 4) == 0 + || memcmp (res_a->key, PSD_LADJ_THRESHOLD, 4) == 0 + || memcmp (res_a->key, PSD_LADJ_INVERT, 4) == 0 + || memcmp (res_a->key, PSD_LADJ_POSTERIZE, 4) == 0) + load_resource_ladj (res_a, lyr_a, f, error); + + else if (memcmp (res_a->key, PSD_LFIL_SOLID, 4) == 0 + || memcmp (res_a->key, PSD_LFIL_PATTERN, 4) == 0 + || memcmp (res_a->key, PSD_LFIL_GRADIENT, 4) == 0) + load_resource_lfil (res_a, lyr_a, f, error); + + else if (memcmp (res_a->key, PSD_LFX_FX, 4) == 0 + || memcmp (res_a->key, PSD_LFX_FX2, 4) == 0) + load_resource_lfx (res_a, lyr_a, f, error); + + else if (memcmp (res_a->key, PSD_LTYP_TYPE, 4) == 0 + || memcmp (res_a->key, PSD_LTYP_TYPE2, 4) == 0) + load_resource_ltyp (res_a, lyr_a, f, error); + + else if (memcmp (res_a->key, PSD_LPRP_UNICODE, 4) == 0) + load_resource_luni (res_a, lyr_a, f, error); + + else if (memcmp (res_a->key, PSD_LPRP_ID, 4) == 0) + load_resource_lyid (res_a, lyr_a, f, error); + + else if (memcmp (res_a->key, PSD_LPRP_COLOR, 4) == 0) + load_resource_lclr (res_a, lyr_a, f, error); + + else if (memcmp (res_a->key, PSD_LOTH_SECTION, 4) == 0 + || memcmp (res_a->key, PSD_LOTH_SECTION2, 4) == 0) /* bug #789981 */ + load_resource_lsct (res_a, lyr_a, f, error); + + else if (memcmp (res_a->key, PSD_LFX_FX, 4) == 0) + load_resource_lrfx (res_a, lyr_a, f, error); + + else if (memcmp (res_a->key, PSD_LPRP_VERSION, 4) == 0) + load_resource_lyvr (res_a, lyr_a, f, error); + + else if (memcmp (res_a->key, PSD_LPRP_SOURCE, 4) == 0) + load_resource_lnsr (res_a, lyr_a, f, error); + + else + load_resource_unknown (res_a, lyr_a, f, error); + } + + /* Set file position to end of layer resource block */ + if (fseek (f, res_a->data_start + res_a->data_len, SEEK_SET) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + return 0; +} + +/* Private Functions */ + +static gint +load_resource_unknown (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error) +{ + IFDBG(2) g_debug ("Process unknown layer resource block: %.4s", res_a->key); + + return 0; +} + +static gint +load_resource_ladj (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error) +{ + /* Load adjustment layer */ + static gboolean msg_flag = FALSE; + + IFDBG(2) g_debug ("Process layer resource block %.4s: Adjustment layer", res_a->key); + lyr_a->drop = TRUE; + if (! msg_flag && CONVERSION_WARNINGS) + { + g_message ("Warning:\n" + "The image file contains adjustment layers. " + "These are not supported by the GIMP and will " + "be dropped."); + msg_flag = TRUE; + } + + return 0; +} + +static gint +load_resource_lfil (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error) +{ + /* Load fill layer */ + static gboolean msg_flag = FALSE; + + IFDBG(2) g_debug ("Process layer resource block %.4s: Fill layer", res_a->key); + if (! msg_flag && CONVERSION_WARNINGS) + { + g_message ("Warning:\n" + "The image file contains fill layers. " + "These are not supported by the GIMP and will " + "be rasterized."); + msg_flag = TRUE; + } + + return 0; +} + +static gint +load_resource_lfx (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error) +{ + /* Load layer effects */ + static gboolean msg_flag = FALSE; + + IFDBG(2) g_debug ("Process layer resource block %.4s: Layer effects", res_a->key); + if (! msg_flag && CONVERSION_WARNINGS) + { + g_message ("Warning:\n" + "The image file contains layer effects. " + "These are not supported by the GIMP and will " + "be dropped."); + msg_flag = TRUE; + } + + return 0; +} + +static gint +load_resource_ltyp (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error) +{ + /* Load type tool layer */ + gint16 version; + gint16 text_desc_vers; + gint32 desc_version; + gint32 read_len; + gint32 write_len; + guint64 t_xx; + guint64 t_xy; + guint64 t_yx; + guint64 t_yy; + guint64 t_tx; + guint64 t_ty; + gchar *classID; + + static gboolean msg_flag = FALSE; + + IFDBG(2) g_debug ("Process layer resource block %.4s: Type tool layer", res_a->key); + if (! msg_flag && CONVERSION_WARNINGS) + { + g_message ("Warning:\n" + "The image file contains type tool layers. " + "These are not supported by the GIMP and will " + "be dropped."); + msg_flag = TRUE; + } + + /* New style type tool layers (ps6) */ + if (memcmp (res_a->key, PSD_LTYP_TYPE2, 4) == 0) + { + if (fread (&version, 2, 1, f) < 1 + || fread (&t_xx, 8, 1, f) < 1 + || fread (&t_xy, 8, 1, f) < 1 + || fread (&t_yx, 8, 1, f) < 1 + || fread (&t_yy, 8, 1, f) < 1 + || fread (&t_tx, 8, 1, f) < 1 + || fread (&t_ty, 8, 1, f) < 1 + || fread (&text_desc_vers, 2, 1, f) < 1 + || fread (&desc_version, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + version = GINT16_FROM_BE (version); + text_desc_vers = GINT16_FROM_BE (text_desc_vers); + desc_version = GINT32_FROM_BE (desc_version); + // t_xx = GUINT64_FROM_BE (t_xx); + // t_xy = GUINT64_FROM_BE (t_xy); + // t_yx = GUINT64_FROM_BE (t_yx); + // t_yy = GUINT64_FROM_BE (t_yy); + // t_tx = GUINT64_FROM_BE (t_tx); + // t_ty = GUINT64_FROM_BE (t_ty); + + lyr_a->text.xx = t_xx >> 11; + lyr_a->text.xy = t_xy >> 11; + lyr_a->text.yx = t_yx >> 11; + lyr_a->text.yy = t_yy >> 11; + lyr_a->text.tx = t_tx >> 11; + lyr_a->text.ty = t_ty >> 11; + + IFDBG(2) g_debug ("Version: %d, Text desc. vers.: %d, Desc. vers.: %d", + version, text_desc_vers, desc_version); + + IFDBG(2) g_debug ("Transform\n\txx: %f\n\txy: %f\n\tyx: %f" + "\n\tyy: %f\n\ttx: %f\n\tty: %f", + lyr_a->text.xx, lyr_a->text.xy, lyr_a->text.yx, + lyr_a->text.yy, lyr_a->text.tx, lyr_a->text.ty); + + classID = fread_unicode_string (&read_len, &write_len, 4, f, error); + IFDBG(2) g_debug ("Unicode name: %s", classID); + } + + return 0; +} + +static gint +load_resource_luni (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error) +{ + /* Load layer name in unicode (length padded to multiple of 4 bytes) */ + gint32 read_len; + gint32 write_len; + + IFDBG(2) g_debug ("Process layer resource block luni: Unicode Name"); + if (lyr_a->name) + g_free (lyr_a->name); + + lyr_a->name = fread_unicode_string (&read_len, &write_len, 4, f, error); + if (*error) + return -1; + IFDBG(3) g_debug ("Unicode name: %s", lyr_a->name); + + return 0; +} + +static gint +load_resource_lyid (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error) +{ + /* Load layer id (tattoo) */ + + IFDBG(2) g_debug ("Process layer resource block lyid: Layer ID"); + if (fread (&lyr_a->id, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + lyr_a->id = GUINT32_FROM_BE (lyr_a->id); + IFDBG(3) g_debug ("Layer id: %i", lyr_a->id); + + return 0; +} + +static gint +load_resource_lclr (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error) +{ + /* Load layer sheet color code */ + IFDBG(2) g_debug ("Process layer resource block %.4s: Sheet color", + res_a->key); + + if (fread (lyr_a->color_tag, 8, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + /* Photoshop only uses color_tag[0] to store a color code */ + lyr_a->color_tag[0] = GUINT16_FROM_BE(lyr_a->color_tag[0]); + + IFDBG(3) g_debug ("Layer sheet color: %i", lyr_a->color_tag[0]); + + return 0; +} + +static gint +load_resource_lsct (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error) +{ + /* Load layer group & type information + * Type 0: not a group + * Type 1: Open folder + * Type 2: Closed folder + * Type 3: End of most recent group */ + guint32 type; + + IFDBG(2) g_debug ("Process layer resource block %.4s: Section divider", res_a->key); + if (fread (&type, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + type = GUINT32_FROM_BE (type); + IFDBG(3) g_debug ("Section divider type: %i", type); + + lyr_a->group_type = type; + + if (res_a->data_len >= 12) + { + gchar signature[4]; + gchar blend_mode[4]; + + if (fread (signature, 4, 1, f) < 1 || + fread (blend_mode, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + if (memcmp (signature, "8BIM", 4) == 0) + { + memcpy (lyr_a->blend_mode, blend_mode, 4); + IFDBG(3) g_debug ("Section divider layer mode sig: %.4s, blend mode: %.4s", + signature, blend_mode); + } + else + { + IFDBG(1) g_debug ("Incorrect layer mode signature %.4s", signature); + } + } + + return 0; +} + +static gint +load_resource_lrfx (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error) +{ + gint16 version; + gint16 count; + gchar signature[4]; + gchar effectname[4]; + gint i; + + IFDBG(2) g_debug ("Process layer resource block %.4s: Layer effects", res_a->key); + + if (fread (&version, 2, 1, f) < 1 + || fread (&count, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + for (i = 0; i < count; i++) + { + if (fread (&signature, 4, 1, f) < 1 + || fread(&effectname, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + if (memcmp (signature, "8BIM", 4) != 0) + { + IFDBG(1) g_debug ("Unknown layer resource signature %.4s", signature); + } + else + { + if (memcmp (effectname, "cmnS", 4) == 0) + { + gint32 size; + gint32 ver; + gchar visible; + gint16 unused; + + if (fread (&size, 4, 1, f) < 1 + || fread(&ver, 4, 1, f) < 1 + || fread(&visible, 1, 1, f) < 1 + || fread(&unused, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + } + else if (memcmp (effectname, "dsdw", 4) == 0 + || memcmp (effectname, "isdw", 4) == 0) + { + gint32 size; + gint32 ver; + gint32 blur; + gint32 intensity; + gint32 angle; + gint32 distance; + gint16 color[5]; + gint32 blendsig; + gint32 effect; + gchar effecton; + gchar anglefx; + gchar opacity; + gint16 natcolor[5]; + + if (fread (&size, 4, 1, f) < 1 + || fread(&ver, 4, 1, f) < 1 + || fread(&blur, 4, 1, f) < 1 + || fread(&intensity, 4, 1, f) < 1 + || fread(&angle, 4, 1, f) < 1 + || fread(&distance, 4, 1, f) < 1 + || fread(&color[0], 2, 1, f) < 1 + || fread(&color[1], 2, 1, f) < 1 + || fread(&color[2], 2, 1, f) < 1 + || fread(&color[3], 2, 1, f) < 1 + || fread(&color[4], 2, 1, f) < 1 + || fread(&blendsig, 4, 1, f) < 1 + || fread(&effect, 4, 1, f) < 1 + || fread(&effecton, 1, 1, f) < 1 + || fread(&anglefx, 1, 1, f) < 1 + || fread(&opacity, 1, 1, f) < 1 + || fread(&natcolor[0], 2, 1, f) < 1 + || fread(&natcolor[1], 2, 1, f) < 1 + || fread(&natcolor[2], 2, 1, f) < 1 + || fread(&natcolor[3], 2, 1, f) < 1 + || fread(&natcolor[4], 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + } + else if (memcmp (effectname, "oglw", 4) == 0) + { + gint32 size; + gint32 ver; + gint32 blur; + gint32 intensity; + gint16 color[5]; + gint32 blendsig; + gint32 effect; + gchar effecton; + gchar opacity; + gint16 natcolor[5]; + + if (fread (&size, 4, 1, f) < 1 + || fread(&ver, 4, 1, f) < 1 + || fread(&blur, 4, 1, f) < 1 + || fread(&intensity, 4, 1, f) < 1 + || fread(&color[0], 2, 1, f) < 1 + || fread(&color[1], 2, 1, f) < 1 + || fread(&color[2], 2, 1, f) < 1 + || fread(&color[3], 2, 1, f) < 1 + || fread(&color[4], 2, 1, f) < 1 + || fread(&blendsig, 4, 1, f) < 1 + || fread(&effect, 4, 1, f) < 1 + || fread(&effecton, 1, 1, f) < 1 + || fread(&opacity, 1, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + if (size == 42) + { + if (fread(&natcolor[0], 2, 1, f) < 1 + || fread(&natcolor[1], 2, 1, f) < 1 + || fread(&natcolor[2], 2, 1, f) < 1 + || fread(&natcolor[3], 2, 1, f) < 1 + || fread(&natcolor[4], 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + } + } + else if (memcmp (effectname, "iglw", 4) == 0) + { + gint32 size; + gint32 ver; + gint32 blur; + gint32 intensity; + gint32 angle; + gint32 distance; + gint16 color[5]; + gint32 blendsig; + gint32 effect; + gchar effecton; + gchar anglefx; + gchar opacity; + gchar invert; + gint16 natcolor[5]; + + if (fread (&size, 4, 1, f) < 1 + || fread(&ver, 4, 1, f) < 1 + || fread(&blur, 4, 1, f) < 1 + || fread(&intensity, 4, 1, f) < 1 + || fread(&angle, 4, 1, f) < 1 + || fread(&distance, 4, 1, f) < 1 + || fread(&color[0], 2, 1, f) < 1 + || fread(&color[1], 2, 1, f) < 1 + || fread(&color[2], 2, 1, f) < 1 + || fread(&color[3], 2, 1, f) < 1 + || fread(&color[4], 2, 1, f) < 1 + || fread(&blendsig, 4, 1, f) < 1 + || fread(&effect, 4, 1, f) < 1 + || fread(&effecton, 1, 1, f) < 1 + || fread(&anglefx, 1, 1, f) < 1 + || fread(&opacity, 1, 1, f) < 1 + || fread(&natcolor[0], 2, 1, f) < 1 + || fread(&natcolor[1], 2, 1, f) < 1 + || fread(&natcolor[2], 2, 1, f) < 1 + || fread(&natcolor[3], 2, 1, f) < 1 + || fread(&natcolor[4], 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + if (size == 43) + { + if (fread (&invert, 1, 1, f) < 1 + || fread(&natcolor[0], 2, 1, f) < 1 + || fread(&natcolor[0], 2, 1, f) < 1 + || fread(&natcolor[1], 2, 1, f) < 1 + || fread(&natcolor[2], 2, 1, f) < 1 + || fread(&natcolor[3], 2, 1, f) < 1 + || fread(&natcolor[4], 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + } + } + else if (memcmp (effectname, "bevl", 4) == 0) + { + gint32 size; + gint32 ver; + gint32 angle; + gint32 strength; + gint32 blur; + gint32 highlightsig; + gint32 highlighteffect; + gint32 shadowsig; + gint32 shadoweffect; + gint16 highlightcolor[5]; + gint16 shadowcolor[5]; + gchar style; + gchar highlightopacity; + gchar shadowopacity; + gchar enabled; + gchar global; + gchar direction; + gint16 highlightnatcolor[5]; + gint16 shadownatcolor[5]; + + if (fread (&size, 4, 1, f) < 1 + || fread(&ver, 4, 1, f) < 1 + || fread(&angle, 4, 1, f) < 1 + || fread(&strength, 4, 1, f) < 1 + || fread(&blur, 4, 1, f) < 1 + || fread(&highlightsig, 4, 1, f) < 1 + || fread(&highlighteffect, 4, 1, f) < 1 + || fread(&shadowsig, 4, 1, f) < 1 + || fread(&highlightcolor[0], 2, 1, f) < 1 + || fread(&shadoweffect, 4, 1, f) < 1 + || fread(&highlightcolor[1], 2, 1, f) < 1 + || fread(&highlightcolor[2], 2, 1, f) < 1 + || fread(&highlightcolor[3], 2, 1, f) < 1 + || fread(&highlightcolor[4], 2, 1, f) < 1 + || fread(&shadowcolor[0], 2, 1, f) < 1 + || fread(&shadowcolor[1], 2, 1, f) < 1 + || fread(&shadowcolor[2], 2, 1, f) < 1 + || fread(&shadowcolor[3], 2, 1, f) < 1 + || fread(&shadowcolor[4], 2, 1, f) < 1 + || fread(&style, 1, 1, f) < 1 + || fread(&highlightopacity, 1, 1, f) < 1 + || fread(&shadowopacity, 1, 1, f) < 1 + || fread(&enabled, 1, 1, f) < 1 + || fread(&global, 1, 1, f) < 1 + || fread(&direction, 1, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + if (size == 78) + { + if (fread(&highlightnatcolor[0], 2, 1, f) < 1 + || fread(&highlightnatcolor[0], 2, 1, f) < 1 + || fread(&highlightnatcolor[1], 2, 1, f) < 1 + || fread(&highlightnatcolor[2], 2, 1, f) < 1 + || fread(&highlightnatcolor[3], 2, 1, f) < 1 + || fread(&highlightnatcolor[4], 2, 1, f) < 1 + || fread(&shadownatcolor[0], 2, 1, f) < 1 + || fread(&shadownatcolor[0], 2, 1, f) < 1 + || fread(&shadownatcolor[1], 2, 1, f) < 1 + || fread(&shadownatcolor[2], 2, 1, f) < 1 + || fread(&shadownatcolor[3], 2, 1, f) < 1 + || fread(&shadownatcolor[4], 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + } + } + else if (memcmp (effectname, "sofi", 4) == 0) + { + gint32 size; + gint32 ver; + gint32 key; + gint16 color[5]; + gchar opacity; + gchar enabled; + gint16 natcolor[5]; + + if (fread (&size, 4, 1, f) < 1 + || fread(&ver, 4, 1, f) < 1 + || fread(&key, 4, 1, f) < 1 + || fread(&color[0], 2, 1, f) < 1 + || fread(&color[1], 2, 1, f) < 1 + || fread(&color[2], 2, 1, f) < 1 + || fread(&color[3], 2, 1, f) < 1 + || fread(&color[4], 2, 1, f) < 1 + || fread(&opacity, 1, 1, f) < 1 + || fread(&enabled, 1, 1, f) < 1 + || fread(&natcolor[0], 2, 1, f) < 1 + || fread(&natcolor[1], 2, 1, f) < 1 + || fread(&natcolor[2], 2, 1, f) < 1 + || fread(&natcolor[3], 2, 1, f) < 1 + || fread(&natcolor[4], 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + } + else + { + IFDBG(1) g_debug ("Unknown layer effect signature %.4s", effectname); + } + } + } + + return 0; +} + +static gint +load_resource_lyvr (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error) +{ + gint32 version; + + IFDBG(2) g_debug ("Process layer resource block %.4s: layer version", + res_a->key); + + if (fread (&version, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + version = GINT32_FROM_BE(version); + + /* minimum value is 70 according to specs but there's no reason to + * stop the loading + */ + if (version < 70) + { + g_message ("Invalid version layer"); + } + + return 0; +} + +static gint +load_resource_lnsr (const PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error) +{ + gchar layername[4]; + + IFDBG(2) g_debug ("Process layer resource block %.4s: layer source name", + res_a->key); + + if (fread (&layername, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + /* nowadays psd files, layer name are encoded in unicode, cf "luni" + * moreover lnsr info is encoded in MacRoman, see + * https://bugzilla.gnome.org/show_bug.cgi?id=753986#c4 + */ + + return 0; +} diff --git a/plug-ins/file-psd/psd-layer-res-load.h b/plug-ins/file-psd/psd-layer-res-load.h new file mode 100644 index 0000000..516ed95 --- /dev/null +++ b/plug-ins/file-psd/psd-layer-res-load.h @@ -0,0 +1,35 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GIMP PSD Plug-in + * Copyright 2007 by John Marshall + * + * 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 __PSD_LAYER_RES_LOAD_H__ +#define __PSD_LAYER_RES_LOAD_H__ + + +gint get_layer_resource_header (PSDlayerres *res_a, + FILE *f, + GError **error); + +gint load_layer_resource (PSDlayerres *res_a, + PSDlayer *lyr_a, + FILE *f, + GError **error); + + +#endif /* __PSD_LAYER_RES_LOAD_H__ */ diff --git a/plug-ins/file-psd/psd-load.c b/plug-ins/file-psd/psd-load.c new file mode 100644 index 0000000..b03f8cf --- /dev/null +++ b/plug-ins/file-psd/psd-load.c @@ -0,0 +1,2807 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GIMP PSD Plug-in + * Copyright 2007 by John Marshall + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> +#include <errno.h> + +#include <glib/gstdio.h> +#include <zlib.h> +#include <libgimp/gimp.h> + +#include "psd.h" +#include "psd-util.h" +#include "psd-image-res-load.h" +#include "psd-layer-res-load.h" +#include "psd-load.h" + +#include "libgimp/stdplugins-intl.h" + + +#define COMP_MODE_SIZE sizeof(guint16) + + +/* Local function prototypes */ +static gint read_header_block (PSDimage *img_a, + FILE *f, + GError **error); + +static gint read_color_mode_block (PSDimage *img_a, + FILE *f, + GError **error); + +static gint read_image_resource_block (PSDimage *img_a, + FILE *f, + GError **error); + +static PSDlayer ** read_layer_block (PSDimage *img_a, + FILE *f, + GError **error); + +static gint read_merged_image_block (PSDimage *img_a, + FILE *f, + GError **error); + +static gint32 create_gimp_image (PSDimage *img_a, + const gchar *filename); + +static gint add_color_map (gint32 image_id, + PSDimage *img_a); + +static gint add_image_resources (gint32 image_id, + PSDimage *img_a, + FILE *f, + gboolean *resolution_loaded, + gboolean *profile_loaded, + GError **error); + +static gint add_layers (gint32 image_id, + PSDimage *img_a, + PSDlayer **lyr_a, + FILE *f, + GError **error); + +static gint add_merged_image (gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error); + +/* Local utility function prototypes */ +static gint32 add_clipping_group (gint32 image_id, + gint32 parent_id); + +static gchar * get_psd_color_mode_name (PSDColorMode mode); + +static void psd_to_gimp_color_map (guchar *map256); + +static GimpImageType get_gimp_image_type (GimpImageBaseType image_base_type, + gboolean alpha); + +static gint read_channel_data (PSDchannel *channel, + guint16 bps, + guint16 compression, + const guint16 *rle_pack_len, + FILE *f, + guint32 comp_len, + GError **error); + +static void convert_1_bit (const gchar *src, + gchar *dst, + guint32 rows, + guint32 columns); + +static const Babl* get_layer_format (PSDimage *img_a, + gboolean alpha); +static const Babl* get_channel_format (PSDimage *img_a); +static const Babl* get_mask_format (PSDimage *img_a); + + +/* Main file load function */ +gint32 +load_image (const gchar *filename, + gboolean merged_image_only, + gboolean *resolution_loaded, + gboolean *profile_loaded, + GError **load_error) +{ + FILE *f; + GStatBuf st; + PSDimage img_a; + PSDlayer **lyr_a; + gint32 image_id = -1; + GError *error = NULL; + + img_a.cmyk_transform = img_a.cmyk_transform_alpha = NULL; + img_a.cmyk_profile = NULL; + /* ----- Open PSD file ----- */ + if (g_stat (filename, &st) == -1) + return -1; + + gimp_progress_init_printf (_("Opening '%s'"), + gimp_filename_to_utf8 (filename)); + + IFDBG(1) g_debug ("Open file %s", gimp_filename_to_utf8 (filename)); + f = g_fopen (filename, "rb"); + if (f == NULL) + { + g_set_error (load_error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + + img_a.merged_image_only = merged_image_only; + + /* ----- Read the PSD file Header block ----- */ + IFDBG(2) g_debug ("Read header block"); + if (read_header_block (&img_a, f, &error) < 0) + goto load_error; + gimp_progress_update (0.1); + + /* ----- Read the PSD file Color Mode block ----- */ + IFDBG(2) g_debug ("Read color mode block"); + if (read_color_mode_block (&img_a, f, &error) < 0) + goto load_error; + gimp_progress_update (0.2); + + /* ----- Read the PSD file Image Resource block ----- */ + IFDBG(2) g_debug ("Read image resource block"); + if (read_image_resource_block (&img_a, f, &error) < 0) + goto load_error; + gimp_progress_update (0.3); + + /* ----- Read the PSD file Layer & Mask block ----- */ + IFDBG(2) g_debug ("Read layer & mask block"); + lyr_a = read_layer_block (&img_a, f, &error); + if (! img_a.merged_image_only && img_a.num_layers != 0 && lyr_a == NULL) + goto load_error; + gimp_progress_update (0.4); + + /* ----- Read the PSD file Merged Image Data block ----- */ + IFDBG(2) g_debug ("Read merged image and extra alpha channel block"); + if (read_merged_image_block (&img_a, f, &error) < 0) + goto load_error; + gimp_progress_update (0.5); + + /* ----- Create GIMP image ----- */ + IFDBG(2) g_debug ("Create GIMP image"); + image_id = create_gimp_image (&img_a, filename); + if (image_id < 0) + goto load_error; + gimp_progress_update (0.6); + + /* ----- Add color map ----- */ + IFDBG(2) g_debug ("Add color map"); + if (add_color_map (image_id, &img_a) < 0) + goto load_error; + gimp_progress_update (0.7); + + /* ----- Add image resources ----- */ + IFDBG(2) g_debug ("Add image resources"); + if (add_image_resources (image_id, &img_a, f, + resolution_loaded, profile_loaded, + &error) < 0) + goto load_error; + gimp_progress_update (0.8); + + /* ----- Add layers -----*/ + IFDBG(2) g_debug ("Add layers"); + if (add_layers (image_id, &img_a, lyr_a, f, &error) < 0) + goto load_error; + gimp_progress_update (0.9); + + /* ----- Add merged image data and extra alpha channels ----- */ + IFDBG(2) g_debug ("Add merged image data and extra alpha channels"); + if (add_merged_image (image_id, &img_a, f, &error) < 0) + goto load_error; + gimp_progress_update (1.0); + + IFDBG(2) g_debug ("Close file & return, image id: %d", image_id); + IFDBG(1) g_debug ("\n----------------------------------------" + "----------------------------------------\n"); + + gimp_image_clean_all (image_id); + gimp_image_undo_enable (image_id); + fclose (f); + return image_id; + + /* ----- Process load errors ----- */ + load_error: + if (error) + { + g_set_error (load_error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error loading PSD file: %s"), error->message); + g_error_free (error); + } + + /* Delete partially loaded image */ + if (image_id > 0) + gimp_image_delete (image_id); + + /* Close file if Open */ + if (! (f == NULL)) + fclose (f); + + return -1; +} + + +/* Local functions */ + +static gint +read_header_block (PSDimage *img_a, + FILE *f, + GError **error) +{ + guint16 version; + gchar sig[4]; + gchar buf[6]; + + if (fread (sig, 4, 1, f) < 1 + || fread (&version, 2, 1, f) < 1 + || fread (buf, 6, 1, f) < 1 + || fread (&img_a->channels, 2, 1, f) < 1 + || fread (&img_a->rows, 4, 1, f) < 1 + || fread (&img_a->columns, 4, 1, f) < 1 + || fread (&img_a->bps, 2, 1, f) < 1 + || fread (&img_a->color_mode, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + version = GUINT16_FROM_BE (version); + img_a->channels = GUINT16_FROM_BE (img_a->channels); + img_a->rows = GUINT32_FROM_BE (img_a->rows); + img_a->columns = GUINT32_FROM_BE (img_a->columns); + img_a->bps = GUINT16_FROM_BE (img_a->bps); + img_a->color_mode = GUINT16_FROM_BE (img_a->color_mode); + + IFDBG(1) g_debug ("\n\n\tSig: %.4s\n\tVer: %d\n\tChannels: " + "%d\n\tSize: %dx%d\n\tBPS: %d\n\tMode: %d\n", + sig, version, img_a->channels, + img_a->columns, img_a->rows, + img_a->bps, img_a->color_mode); + + if (memcmp (sig, "8BPS", 4) != 0) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Not a valid Photoshop document file")); + return -1; + } + + if (version != 1) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported file format version: %d"), version); + return -1; + } + + if (img_a->channels > MAX_CHANNELS) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Too many channels in file: %d"), img_a->channels); + return -1; + } + + /* Photoshop CS (version 8) supports 300000 x 300000, but this + is currently larger than GIMP_MAX_IMAGE_SIZE */ + + if (img_a->rows < 1 || img_a->rows > GIMP_MAX_IMAGE_SIZE) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported or invalid image height: %d"), + img_a->rows); + return -1; + } + + if (img_a->columns < 1 || img_a->columns > GIMP_MAX_IMAGE_SIZE) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported or invalid image width: %d"), + img_a->columns); + return -1; + } + + /* img_a->rows is sanitized above, so a division by zero is avoided here */ + if (img_a->columns > G_MAXINT32 / img_a->rows) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported or invalid image size: %dx%d"), + img_a->columns, img_a->rows); + return -1; + } + + if (img_a->color_mode != PSD_BITMAP + && img_a->color_mode != PSD_GRAYSCALE + && img_a->color_mode != PSD_INDEXED + && img_a->color_mode != PSD_RGB + && img_a->color_mode != PSD_MULTICHANNEL + && img_a->color_mode != PSD_CMYK + && img_a->color_mode != PSD_DUOTONE) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported color mode: %s"), + get_psd_color_mode_name (img_a->color_mode)); + return -1; + } + + if (img_a->color_mode == PSD_CMYK) + { + if (img_a->bps != 8) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported color mode: %s"), + get_psd_color_mode_name (img_a->color_mode)); + return -1; + } + } + + /* Warning for unsupported bit depth */ + switch (img_a->bps) + { + case 32: + IFDBG(3) g_debug ("32 Bit Data"); + break; + + case 16: + IFDBG(3) g_debug ("16 Bit Data"); + break; + + case 8: + IFDBG(3) g_debug ("8 Bit Data"); + break; + + case 1: + IFDBG(3) g_debug ("1 Bit Data"); + break; + + default: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported bit depth: %d"), img_a->bps); + return -1; + break; + } + + return 0; +} + +static gint +read_color_mode_block (PSDimage *img_a, + FILE *f, + GError **error) +{ + static guchar cmap[] = { 0, 0, 0, 255, 255, 255 }; + guint32 block_len; + + img_a->color_map_entries = 0; + img_a->color_map_len = 0; + if (fread (&block_len, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + block_len = GUINT32_FROM_BE (block_len); + + IFDBG(1) g_debug ("Color map block size = %d", block_len); + + if (block_len == 0) + { + if (img_a->color_mode == PSD_INDEXED || + img_a->color_mode == PSD_DUOTONE ) + { + IFDBG(1) g_debug ("No color block for indexed or duotone image"); + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("The file is corrupt!")); + return -1; + } + } + else if (img_a->color_mode == PSD_INDEXED) + { + if (block_len != 768) + { + IFDBG(1) g_debug ("Invalid color block size for indexed image"); + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("The file is corrupt!")); + return -1; + } + else + { + img_a->color_map_len = block_len; + img_a->color_map = g_malloc (img_a->color_map_len); + if (fread (img_a->color_map, block_len, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + else + { + psd_to_gimp_color_map (img_a->color_map); + img_a->color_map_entries = 256; + } + } + } + else if (img_a->color_mode == PSD_DUOTONE) + { + img_a->color_map_len = block_len; + img_a->color_map = g_malloc (img_a->color_map_len); + if (fread (img_a->color_map, block_len, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + } + + /* Create color map for bitmap image */ + if (img_a->color_mode == PSD_BITMAP) + { + img_a->color_map_len = 6; + img_a->color_map = g_malloc (img_a->color_map_len); + memcpy (img_a->color_map, cmap, img_a->color_map_len); + img_a->color_map_entries = 2; + } + IFDBG(2) g_debug ("Color map data length %d", img_a->color_map_len); + + return 0; +} + +static gint +read_image_resource_block (PSDimage *img_a, + FILE *f, + GError **error) +{ + guint32 block_len; + guint32 block_end; + + if (fread (&block_len, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + img_a->image_res_len = GUINT32_FROM_BE (block_len); + + IFDBG(1) g_debug ("Image resource block size = %d", (int)img_a->image_res_len); + + img_a->image_res_start = ftell (f); + block_end = img_a->image_res_start + img_a->image_res_len; + + if (fseek (f, block_end, SEEK_SET) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + return 0; +} + +static PSDlayer ** +read_layer_info (PSDimage *img_a, + FILE *f, + GError **error) +{ + PSDlayer **lyr_a = NULL; + guint32 block_len; + guint32 block_rem; + gint32 read_len; + gint32 write_len; + gint lidx; /* Layer index */ + gint cidx; /* Channel index */ + + /* Get number of layers */ + if (fread (&img_a->num_layers, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + img_a->num_layers = -1; + return NULL; + } + + img_a->num_layers = GINT16_FROM_BE (img_a->num_layers); + IFDBG(2) g_debug ("Number of layers: %d", img_a->num_layers); + + if (img_a->num_layers < 0) + { + img_a->transparency = TRUE; + img_a->num_layers = -img_a->num_layers; + } + + if (! img_a->merged_image_only && img_a->num_layers) + { + /* Read layer records */ + PSDlayerres res_a; + + /* Create pointer array for the layer records */ + lyr_a = g_new (PSDlayer *, img_a->num_layers); + + for (lidx = 0; lidx < img_a->num_layers; ++lidx) + { + /* Allocate layer record */ + lyr_a[lidx] = (PSDlayer *) g_malloc (sizeof (PSDlayer) ); + + /* Initialise record */ + lyr_a[lidx]->drop = FALSE; + lyr_a[lidx]->id = 0; + lyr_a[lidx]->group_type = 0; + + if (fread (&lyr_a[lidx]->top, 4, 1, f) < 1 + || fread (&lyr_a[lidx]->left, 4, 1, f) < 1 + || fread (&lyr_a[lidx]->bottom, 4, 1, f) < 1 + || fread (&lyr_a[lidx]->right, 4, 1, f) < 1 + || fread (&lyr_a[lidx]->num_channels, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + + lyr_a[lidx]->top = GINT32_FROM_BE (lyr_a[lidx]->top); + lyr_a[lidx]->left = GINT32_FROM_BE (lyr_a[lidx]->left); + lyr_a[lidx]->bottom = GINT32_FROM_BE (lyr_a[lidx]->bottom); + lyr_a[lidx]->right = GINT32_FROM_BE (lyr_a[lidx]->right); + lyr_a[lidx]->num_channels = GUINT16_FROM_BE (lyr_a[lidx]->num_channels); + + if (lyr_a[lidx]->num_channels > MAX_CHANNELS) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Too many channels in layer: %d"), + lyr_a[lidx]->num_channels); + return NULL; + } + if (lyr_a[lidx]->bottom < lyr_a[lidx]->top || + lyr_a[lidx]->bottom - lyr_a[lidx]->top > GIMP_MAX_IMAGE_SIZE) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported or invalid layer height: %d"), + lyr_a[lidx]->bottom - lyr_a[lidx]->top); + return NULL; + } + if (lyr_a[lidx]->right < lyr_a[lidx]->left || + lyr_a[lidx]->right - lyr_a[lidx]->left > GIMP_MAX_IMAGE_SIZE) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported or invalid layer width: %d"), + lyr_a[lidx]->right - lyr_a[lidx]->left); + return NULL; + } + + if ((lyr_a[lidx]->right - lyr_a[lidx]->left) > + G_MAXINT32 / MAX (lyr_a[lidx]->bottom - lyr_a[lidx]->top, 1)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported or invalid layer size: %dx%d"), + lyr_a[lidx]->right - lyr_a[lidx]->left, + lyr_a[lidx]->bottom - lyr_a[lidx]->top); + return NULL; + } + + IFDBG(2) g_debug ("Layer %d, Coords %d %d %d %d, channels %d, ", + lidx, lyr_a[lidx]->left, lyr_a[lidx]->top, + lyr_a[lidx]->right, lyr_a[lidx]->bottom, + lyr_a[lidx]->num_channels); + + lyr_a[lidx]->chn_info = g_new (ChannelLengthInfo, lyr_a[lidx]->num_channels); + + for (cidx = 0; cidx < lyr_a[lidx]->num_channels; ++cidx) + { + if (fread (&lyr_a[lidx]->chn_info[cidx].channel_id, 2, 1, f) < 1 + || fread (&lyr_a[lidx]->chn_info[cidx].data_len, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + lyr_a[lidx]->chn_info[cidx].channel_id = + GINT16_FROM_BE (lyr_a[lidx]->chn_info[cidx].channel_id); + lyr_a[lidx]->chn_info[cidx].data_len = + GUINT32_FROM_BE (lyr_a[lidx]->chn_info[cidx].data_len); + img_a->layer_data_len += lyr_a[lidx]->chn_info[cidx].data_len; + IFDBG(3) g_debug ("Channel ID %d, data len %d", + lyr_a[lidx]->chn_info[cidx].channel_id, + lyr_a[lidx]->chn_info[cidx].data_len); + } + + if (fread (lyr_a[lidx]->mode_key, 4, 1, f) < 1 + || fread (lyr_a[lidx]->blend_mode, 4, 1, f) < 1 + || fread (&lyr_a[lidx]->opacity, 1, 1, f) < 1 + || fread (&lyr_a[lidx]->clipping, 1, 1, f) < 1 + || fread (&lyr_a[lidx]->flags, 1, 1, f) < 1 + || fread (&lyr_a[lidx]->filler, 1, 1, f) < 1 + || fread (&lyr_a[lidx]->extra_len, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + if (memcmp (lyr_a[lidx]->mode_key, "8BIM", 4) != 0) + { + IFDBG(1) g_debug ("Incorrect layer mode signature %.4s", + lyr_a[lidx]->mode_key); + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("The file is corrupt!")); + return NULL; + } + + lyr_a[lidx]->layer_flags.trans_prot = lyr_a[lidx]->flags & 1 ? TRUE : FALSE; + lyr_a[lidx]->layer_flags.visible = lyr_a[lidx]->flags & 2 ? FALSE : TRUE; + + if (lyr_a[lidx]->flags & 8) + lyr_a[lidx]->layer_flags.irrelevant = lyr_a[lidx]->flags & 16 ? TRUE : FALSE; + else + lyr_a[lidx]->layer_flags.irrelevant = FALSE; + + lyr_a[lidx]->extra_len = GUINT32_FROM_BE (lyr_a[lidx]->extra_len); + block_rem = lyr_a[lidx]->extra_len; + IFDBG(2) g_debug ("\n\tLayer mode sig: %.4s\n\tBlend mode: %.4s\n\t" + "Opacity: %d\n\tClipping: %d\n\tExtra data len: %d\n\t" + "Alpha lock: %d\n\tVisible: %d\n\tIrrelevant: %d", + lyr_a[lidx]->mode_key, + lyr_a[lidx]->blend_mode, + lyr_a[lidx]->opacity, + lyr_a[lidx]->clipping, + lyr_a[lidx]->extra_len, + lyr_a[lidx]->layer_flags.trans_prot, + lyr_a[lidx]->layer_flags.visible, + lyr_a[lidx]->layer_flags.irrelevant); + IFDBG(3) g_debug ("Remaining length %d", block_rem); + + /* Layer mask data */ + if (fread (&block_len, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + block_len = GUINT32_FROM_BE (block_len); + IFDBG(3) g_debug ("Layer mask record size %u", block_len); + if (block_len + 4 > block_rem) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported or invalid mask info size: %d"), + block_len); + return NULL; + } + + block_rem -= (block_len + 4); + IFDBG(3) g_debug ("Remaining length %d", block_rem); + + lyr_a[lidx]->layer_mask_extra.top = 0; + lyr_a[lidx]->layer_mask_extra.left = 0; + lyr_a[lidx]->layer_mask_extra.bottom = 0; + lyr_a[lidx]->layer_mask_extra.right = 0; + lyr_a[lidx]->layer_mask.top = 0; + lyr_a[lidx]->layer_mask.left = 0; + lyr_a[lidx]->layer_mask.bottom = 0; + lyr_a[lidx]->layer_mask.right = 0; + lyr_a[lidx]->layer_mask.def_color = 0; + lyr_a[lidx]->layer_mask.extra_def_color = 0; + lyr_a[lidx]->layer_mask.flags = 0; + lyr_a[lidx]->layer_mask.extra_flags = 0; + lyr_a[lidx]->layer_mask.mask_params = 0; + lyr_a[lidx]->layer_mask.mask_flags.relative_pos = FALSE; + lyr_a[lidx]->layer_mask.mask_flags.disabled = FALSE; + lyr_a[lidx]->layer_mask.mask_flags.invert = FALSE; + lyr_a[lidx]->layer_mask.mask_flags.rendered = FALSE; + lyr_a[lidx]->layer_mask.mask_flags.params_present = FALSE; + + if (block_len > 0) + { + if (fread (&lyr_a[lidx]->layer_mask.top, 4, 1, f) < 1 + || fread (&lyr_a[lidx]->layer_mask.left, 4, 1, f) < 1 + || fread (&lyr_a[lidx]->layer_mask.bottom, 4, 1, f) < 1 + || fread (&lyr_a[lidx]->layer_mask.right, 4, 1, f) < 1 + || fread (&lyr_a[lidx]->layer_mask.def_color, 1, 1, f) < 1 + || fread (&lyr_a[lidx]->layer_mask.flags, 1, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + + lyr_a[lidx]->layer_mask.top = + GINT32_FROM_BE (lyr_a[lidx]->layer_mask.top); + lyr_a[lidx]->layer_mask.left = + GINT32_FROM_BE (lyr_a[lidx]->layer_mask.left); + lyr_a[lidx]->layer_mask.bottom = + GINT32_FROM_BE (lyr_a[lidx]->layer_mask.bottom); + lyr_a[lidx]->layer_mask.right = + GINT32_FROM_BE (lyr_a[lidx]->layer_mask.right); + lyr_a[lidx]->layer_mask.mask_flags.relative_pos = + lyr_a[lidx]->layer_mask.flags & 1 ? TRUE : FALSE; + lyr_a[lidx]->layer_mask.mask_flags.disabled = + lyr_a[lidx]->layer_mask.flags & 2 ? TRUE : FALSE; + lyr_a[lidx]->layer_mask.mask_flags.invert = + lyr_a[lidx]->layer_mask.flags & 4 ? TRUE : FALSE; + lyr_a[lidx]->layer_mask.mask_flags.rendered = + lyr_a[lidx]->layer_mask.flags & 8 ? TRUE : FALSE; + lyr_a[lidx]->layer_mask.mask_flags.params_present = + lyr_a[lidx]->layer_mask.flags & 16 ? TRUE : FALSE; + IFDBG(3) + { + if (lyr_a[lidx]->layer_mask.flags & 32) g_debug ("Layer mask flags bit 5 set."); + if (lyr_a[lidx]->layer_mask.flags & 64) g_debug ("Layer mask flags bit 6 set."); + if (lyr_a[lidx]->layer_mask.flags & 128) g_debug ("Layer mask flags bit 7 set."); + } + + block_len -= 18; + + /* According to psd-tools this part comes before reading the + * mask parameters. However if all mask parameter flags are + * set the size of that would also be more than 18. I'm not + * sure if all those flags could be set at the same time or + * how to distinguish them. */ + if (block_len >= 18) + { + if (fread (&lyr_a[lidx]->layer_mask.extra_flags, 1, 1, f) < 1 + || fread (&lyr_a[lidx]->layer_mask.extra_def_color, 1, 1, f) < 1 + || fread (&lyr_a[lidx]->layer_mask_extra.top, 4, 1, f) < 1 + || fread (&lyr_a[lidx]->layer_mask_extra.left, 4, 1, f) < 1 + || fread (&lyr_a[lidx]->layer_mask_extra.bottom, 4, 1, f) < 1 + || fread (&lyr_a[lidx]->layer_mask_extra.right, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + block_len -= 18; + + lyr_a[lidx]->layer_mask_extra.top = + GINT32_FROM_BE (lyr_a[lidx]->layer_mask_extra.top); + lyr_a[lidx]->layer_mask_extra.left = + GINT32_FROM_BE (lyr_a[lidx]->layer_mask_extra.left); + lyr_a[lidx]->layer_mask_extra.bottom = + GINT32_FROM_BE (lyr_a[lidx]->layer_mask_extra.bottom); + lyr_a[lidx]->layer_mask_extra.right = + GINT32_FROM_BE (lyr_a[lidx]->layer_mask_extra.right); + + IFDBG(2) g_debug ("Rect enclosing Layer mask %d %d %d %d", + lyr_a[lidx]->layer_mask_extra.left, + lyr_a[lidx]->layer_mask_extra.top, + lyr_a[lidx]->layer_mask_extra.right, + lyr_a[lidx]->layer_mask_extra.bottom); + } + + if (block_len > 2 && lyr_a[lidx]->layer_mask.mask_flags.params_present) + { + gint extra_bytes = 0; + + if (fread (&lyr_a[lidx]->layer_mask.mask_params, 1, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + block_len--; + IFDBG(3) g_debug ("Mask params: %u", lyr_a[lidx]->layer_mask.mask_params); + + /* FIXME Extra bytes with user/vector mask density and feather follow. + * We currently can't handle that so we will skip it. */ + extra_bytes += (lyr_a[lidx]->layer_mask.mask_params & 1 ? 1 : 0); + extra_bytes += (lyr_a[lidx]->layer_mask.mask_params & 2 ? 8 : 0); + extra_bytes += (lyr_a[lidx]->layer_mask.mask_params & 4 ? 1 : 0); + extra_bytes += (lyr_a[lidx]->layer_mask.mask_params & 8 ? 8 : 0); + IFDBG(3) g_debug ("Extra bytes according to mask parameters %d", extra_bytes); + if (fseek (f, extra_bytes, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + block_len -= extra_bytes; + } + + if (block_len > 0) + { + /* We have some remaining unknown mask data. + * If size is less than 4 it is most likely padding. */ + IFDBG(1) + { + if (block_len > 3) + g_debug ("Skipping %u bytes of unknown layer mask data", block_len); + } + + if (fseek (f, block_len, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + } + + IFDBG(2) g_debug ("Layer mask coords %d %d %d %d", + lyr_a[lidx]->layer_mask.left, + lyr_a[lidx]->layer_mask.top, + lyr_a[lidx]->layer_mask.right, + lyr_a[lidx]->layer_mask.bottom); + + IFDBG(3) g_debug ("Default mask color %d, real color %d", + lyr_a[lidx]->layer_mask.def_color, + lyr_a[lidx]->layer_mask.extra_def_color); + + IFDBG(3) g_debug ("Mask flags %u, real flags %u, mask params %u", + lyr_a[lidx]->layer_mask.flags, + lyr_a[lidx]->layer_mask.extra_flags, + lyr_a[lidx]->layer_mask.mask_params); + + /* Rendered masks can have invalid dimensions: 0, 0, 0, -1 */ + if (! lyr_a[lidx]->layer_mask.mask_flags.rendered) + { + /* sanity checks */ + if (lyr_a[lidx]->layer_mask.bottom < lyr_a[lidx]->layer_mask.top || + lyr_a[lidx]->layer_mask.bottom - lyr_a[lidx]->layer_mask.top > GIMP_MAX_IMAGE_SIZE) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported or invalid layer mask height: %d"), + lyr_a[lidx]->layer_mask.bottom - lyr_a[lidx]->layer_mask.top); + return NULL; + } + if (lyr_a[lidx]->layer_mask.right < lyr_a[lidx]->layer_mask.left || + lyr_a[lidx]->layer_mask.right - lyr_a[lidx]->layer_mask.left > GIMP_MAX_IMAGE_SIZE) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported or invalid layer mask width: %d"), + lyr_a[lidx]->layer_mask.right - lyr_a[lidx]->layer_mask.left); + return NULL; + } + + if ((lyr_a[lidx]->layer_mask.right - lyr_a[lidx]->layer_mask.left) > + G_MAXINT32 / MAX (lyr_a[lidx]->layer_mask.bottom - lyr_a[lidx]->layer_mask.top, 1)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported or invalid layer mask size: %dx%d"), + lyr_a[lidx]->layer_mask.right - lyr_a[lidx]->layer_mask.left, + lyr_a[lidx]->layer_mask.bottom - lyr_a[lidx]->layer_mask.top); + return NULL; + } + } + } + + /* Layer blending ranges */ /* FIXME */ + if (fread (&block_len, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + + block_len = GUINT32_FROM_BE (block_len); + block_rem -= (block_len + 4); + IFDBG(3) g_debug ("Remaining length %d", block_rem); + + if (block_len > 0) + { + if (fseek (f, block_len, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + } + + lyr_a[lidx]->name = fread_pascal_string (&read_len, &write_len, + 4, f, error); + if (*error) + return NULL; + + block_rem -= read_len; + IFDBG(3) g_debug ("Remaining length %d", block_rem); + + /* Adjustment layer info */ /* FIXME */ + + while (block_rem > 7) + { + if (get_layer_resource_header (&res_a, f, error) < 0) + return NULL; + + block_rem -= 12; + + if (res_a.data_len % 2 != 0) + { + /* Warn the user about an invalid length value but + * try to recover graciously. See bug #771558. + */ + g_printerr ("psd-load: Layer extra data length should " + "be even, but it is %d.", res_a.data_len); + } + + if (res_a.data_len > block_rem) + { + IFDBG(1) g_debug ("Unexpected end of layer resource data"); + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("The file is corrupt!")); + return NULL; + } + + if (load_layer_resource (&res_a, lyr_a[lidx], f, error) < 0) + return NULL; + block_rem -= res_a.data_len; + } + if (block_rem > 0) + { + if (fseek (f, block_rem, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + } + } + + img_a->layer_data_start = ftell(f); + if (fseek (f, img_a->layer_data_len, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + + IFDBG(1) g_debug ("Layer image data block size %d", + img_a->layer_data_len); + } + + return lyr_a; +} + + +static PSDlayer ** +read_layer_block (PSDimage *img_a, + FILE *f, + GError **error) +{ + PSDlayer **lyr_a = NULL; + guint32 block_len; + guint32 block_end; + + if (fread (&block_len, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + img_a->num_layers = -1; + return NULL; + } + + img_a->mask_layer_len = GUINT32_FROM_BE (block_len); + + IFDBG(1) g_debug ("Layer and mask block size = %d", img_a->mask_layer_len); + + img_a->transparency = FALSE; + img_a->layer_data_len = 0; + + if (!img_a->mask_layer_len) + { + img_a->num_layers = 0; + return NULL; + } + else + { + guint32 total_len = img_a->mask_layer_len; + + img_a->mask_layer_start = ftell (f); + block_end = img_a->mask_layer_start + img_a->mask_layer_len; + + /* Layer info */ + if (fread (&block_len, 4, 1, f) == 1 && block_len) + { + block_len = GUINT32_FROM_BE (block_len); + IFDBG(1) g_debug ("Layer info size = %d", block_len); + + lyr_a = read_layer_info (img_a, f, error); + + total_len -= block_len; + } + else + { + img_a->num_layers = 0; + lyr_a = NULL; + } + + /* Global layer mask info */ + if (fread (&block_len, 4, 1, f) == 1 && block_len) + { + block_len = GUINT32_FROM_BE (block_len); + IFDBG(1) g_debug ("Global layer mask info size = %d", block_len); + + /* read_global_layer_mask_info (img_a, f, error); */ + fseek (f, block_len, SEEK_CUR); + + total_len -= block_len; + } + + /* Additional Layer Information */ + if (total_len > 12) + { + gchar signature_key[8]; + + if (fread (&signature_key, 4, 2, f) == 2 && + (memcmp (signature_key, "8BIMLr16", 8) == 0 || + memcmp (signature_key, "8BIMLr32", 8) == 0) && + fread (&block_len, 4, 1, f) == 1 && block_len) + lyr_a = read_layer_info (img_a, f, error); + } + + /* Skip to end of block */ + if (fseek (f, block_end, SEEK_SET) < 0) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + } + + return lyr_a; +} + +static gint +read_merged_image_block (PSDimage *img_a, + FILE *f, + GError **error) +{ + img_a->merged_image_start = ftell(f); + if (fseek (f, 0, SEEK_END) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + img_a->merged_image_len = ftell(f) - img_a->merged_image_start; + + IFDBG(1) g_debug ("Merged image data block: Start: %d, len: %d", + img_a->merged_image_start, img_a->merged_image_len); + + return 0; +} + +static gint32 +create_gimp_image (PSDimage *img_a, + const gchar *filename) +{ + gint32 image_id = -1; + GimpPrecision precision; + + switch (img_a->color_mode) + { + case PSD_MULTICHANNEL: + case PSD_GRAYSCALE: + case PSD_DUOTONE: + img_a->base_type = GIMP_GRAY; + break; + + case PSD_BITMAP: + case PSD_INDEXED: + img_a->base_type = GIMP_INDEXED; + break; + + case PSD_CMYK: + case PSD_RGB: + img_a->base_type = GIMP_RGB; + break; + + default: + /* Color mode already validated - should not be here */ + g_warning ("Invalid color mode"); + return -1; + break; + } + + switch (img_a->bps) + { + case 32: + precision = GIMP_PRECISION_U32_GAMMA; + break; + + case 16: + precision = GIMP_PRECISION_U16_GAMMA; + break; + + case 8: + case 1: + if (img_a->color_mode == PSD_CMYK) + precision = GIMP_PRECISION_FLOAT_GAMMA; + else + precision = GIMP_PRECISION_U8_GAMMA; + break; + + default: + /* Precision not supported */ + g_warning ("Invalid precision"); + return -1; + break; + } + + /* Create gimp image */ + IFDBG(2) g_debug ("Create image"); + image_id = gimp_image_new_with_precision (img_a->columns, img_a->rows, + img_a->base_type, precision); + gimp_image_set_filename (image_id, filename); + gimp_image_undo_disable (image_id); + + return image_id; +} + +static gint +add_color_map (gint32 image_id, + PSDimage *img_a) +{ + GimpParasite *parasite; + + if (img_a->color_map_len) + { + if (img_a->color_mode != PSD_DUOTONE) + { + gimp_image_set_colormap (image_id, img_a->color_map, + img_a->color_map_entries); + } + else + { + /* Add parasite for Duotone color data */ + IFDBG(2) g_debug ("Add Duotone color data parasite"); + parasite = gimp_parasite_new (PSD_PARASITE_DUOTONE_DATA, 0, + img_a->color_map_len, img_a->color_map); + gimp_image_attach_parasite (image_id, parasite); + gimp_parasite_free (parasite); + } + g_free (img_a->color_map); + } + + return 0; +} + +static gint +add_image_resources (gint32 image_id, + PSDimage *img_a, + FILE *f, + gboolean *resolution_loaded, + gboolean *profile_loaded, + GError **error) +{ + PSDimageres res_a; + + if (fseek (f, img_a->image_res_start, SEEK_SET) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + /* Initialise image resource variables */ + img_a->no_icc = FALSE; + img_a->layer_state = 0; + img_a->alpha_names = NULL; + img_a->alpha_display_info = NULL; + img_a->alpha_display_count = 0; + img_a->alpha_id = NULL; + img_a->alpha_id_count = 0; + img_a->quick_mask_id = 0; + + while (ftell (f) < img_a->image_res_start + img_a->image_res_len) + { + if (get_image_resource_header (&res_a, f, error) < 0) + return -1; + + if (res_a.data_start + res_a.data_len > + img_a->image_res_start + img_a->image_res_len) + { + IFDBG(1) g_debug ("Unexpected end of image resource data"); + return 0; + } + + if (load_image_resource (&res_a, image_id, img_a, f, + resolution_loaded, profile_loaded, + error) < 0) + return -1; + } + + return 0; +} + +static guchar * +psd_convert_cmyk_to_srgb (PSDimage *img_a, + guchar *dst, + guchar *src, + gint width, + gint height, + gboolean alpha) +{ + if (img_a->cmyk_profile) + { + if (alpha) + { + if (! img_a->cmyk_transform_alpha) + { + GimpColorProfile *srgb = gimp_color_profile_new_rgb_srgb (); + + img_a->cmyk_transform_alpha = gimp_color_transform_new (img_a->cmyk_profile, babl_format ("cmykA u8"), + srgb, babl_format ("R'G'B'A float"), + 0, 0); + + g_object_unref (srgb); + } + + gimp_color_transform_process_pixels (img_a->cmyk_transform_alpha, + babl_format ("cmykA u8"), + src, + babl_format ("R'G'B'A float"), + dst, + width * height); + } + else + { + if (! img_a->cmyk_transform) + { + GimpColorProfile *srgb = gimp_color_profile_new_rgb_srgb (); + + img_a->cmyk_transform = gimp_color_transform_new (img_a->cmyk_profile, babl_format ("cmyk u8"), + srgb, babl_format ("R'G'B' float"), + 0, 0); + + g_object_unref (srgb); + } + + gimp_color_transform_process_pixels (img_a->cmyk_transform, + babl_format ("cmyk u8"), + src, + babl_format ("R'G'B' float"), + dst, + width * height); + } + } + else + { + const Babl *fish; + + if (alpha) + fish = babl_fish ("cmykA u8", "R'G'B'A float"); + else + fish = babl_fish ("cmyk u8", "R'G'B' float"); + + babl_process (fish, src, dst, width * height); + } + + return (guchar*) dst; +} + +static gint +add_layers (gint32 image_id, + PSDimage *img_a, + PSDlayer **lyr_a, + FILE *f, + GError **error) +{ + PSDchannel **lyr_chn; + GArray *parent_group_stack; + gint32 parent_group_id = -1; + gint32 clipping_group_id = -1; + guint16 alpha_chn; + guint16 user_mask_chn; + guint16 layer_channels, base_channels; + guint16 channel_idx[MAX_CHANNELS]; + guint16 *rle_pack_len; + guint16 bps; + gint32 l_x; /* Layer x */ + gint32 l_y; /* Layer y */ + gint32 l_w; /* Layer width */ + gint32 l_h; /* Layer height */ + gint32 lm_x; /* Layer mask x */ + gint32 lm_y; /* Layer mask y */ + gint32 lm_w; /* Layer mask width */ + gint32 lm_h; /* Layer mask height */ + gint32 layer_id = -1; + gint32 mask_id = -1; + gint32 active_layer_id = -1; + gint lidx; /* Layer index */ + gint cidx; /* Channel index */ + gint gidx; /* Clipping group start index */ + gint rowi; /* Row index */ + gboolean alpha; + gboolean user_mask; + gboolean empty; + gboolean empty_mask; + gboolean use_clipping_group; + GeglBuffer *buffer; + GimpImageType image_type; + LayerModeInfo mode_info; + + + IFDBG(2) g_debug ("Number of layers: %d", img_a->num_layers); + + if (img_a->merged_image_only || img_a->num_layers == 0) + { + IFDBG(2) g_debug ("No layers to process"); + return 0; + } + + /* Layered image - Photoshop 3 style */ + if (fseek (f, img_a->layer_data_start, SEEK_SET) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + IFDBG(3) g_debug ("Pre process layers..."); + use_clipping_group = FALSE; + gidx = -1; + for (lidx = 0; lidx < img_a->num_layers; ++lidx) + { + if (lyr_a[lidx]->clipping == 1) + { + /* Photoshop handles layers with clipping differently than GIMP does. + * To correctly show these layers we need to make a new group + * starting with the first non-clipping layer and including all + * the clipping layers above it. + */ + if (lidx > 0) + { + if (gidx == -1) + { + use_clipping_group = TRUE; + + /* Looking at the results we should ignore layer groups */ + if (lyr_a[lidx-1]->group_type == 0) + gidx = lidx - 1; + else + gidx = lidx; + + lyr_a[gidx]->clipping_group_type = 1; /* start clipping group */ + IFDBG(3) g_debug ("Layer: %s - start of clipping group", lyr_a[gidx]->name); + } + else if (lidx + 1 == img_a->num_layers && use_clipping_group) + { + /* end clipping group at the top of the layer stack */ + lyr_a[lidx]->clipping_group_type = 2; /* end clipping group */ + IFDBG(3) g_debug ("Layer: %s - end of clipping group", lyr_a[lidx]->name); + + use_clipping_group = FALSE; + gidx = -1; + } + else + { + lyr_a[lidx]->clipping_group_type = 0; + } + } + } + else if (use_clipping_group) + { + /* end clipping group */ + lyr_a[lidx-1]->clipping_group_type = 2; + IFDBG(3) g_debug ("Layer: %s - end of clipping group", lyr_a[lidx-1]->name); + + use_clipping_group = FALSE; + gidx = -1; + } + else + { + lyr_a[lidx]->clipping_group_type = 0; + } + } + + /* set the root of the group hierarchy */ + parent_group_stack = g_array_new (FALSE, FALSE, sizeof (gint32)); + g_array_append_val (parent_group_stack, parent_group_id); + + for (lidx = 0; lidx < img_a->num_layers; ++lidx) + { + IFDBG(2) g_debug ("Process Layer No %d (%s).", lidx, lyr_a[lidx]->name); + + if (lyr_a[lidx]->drop) + { + IFDBG(2) g_debug ("Drop layer %d", lidx); + + /* Step past layer data */ + for (cidx = 0; cidx < lyr_a[lidx]->num_channels; ++cidx) + { + if (fseek (f, lyr_a[lidx]->chn_info[cidx].data_len, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + } + } + else + { + /* Empty layer */ + if (lyr_a[lidx]->bottom - lyr_a[lidx]->top == 0 + || lyr_a[lidx]->right - lyr_a[lidx]->left == 0) + empty = TRUE; + else + empty = FALSE; + + /* Empty mask */ + if (lyr_a[lidx]->layer_mask.bottom - lyr_a[lidx]->layer_mask.top == 0 + || lyr_a[lidx]->layer_mask.right - lyr_a[lidx]->layer_mask.left == 0) + empty_mask = TRUE; + else + empty_mask = FALSE; + + IFDBG(3) g_debug ("Empty mask %d, size %d %d", empty_mask, + lyr_a[lidx]->layer_mask.bottom - lyr_a[lidx]->layer_mask.top, + lyr_a[lidx]->layer_mask.right - lyr_a[lidx]->layer_mask.left); + + /* Load layer channel data */ + IFDBG(2) g_debug ("Number of channels: %d", lyr_a[lidx]->num_channels); + /* Create pointer array for the channel records */ + lyr_chn = g_new (PSDchannel *, lyr_a[lidx]->num_channels); + for (cidx = 0; cidx < lyr_a[lidx]->num_channels; ++cidx) + { + guint16 comp_mode = PSD_COMP_RAW; + + /* Allocate channel record */ + lyr_chn[cidx] = g_malloc (sizeof (PSDchannel) ); + + lyr_chn[cidx]->id = lyr_a[lidx]->chn_info[cidx].channel_id; + lyr_chn[cidx]->rows = lyr_a[lidx]->bottom - lyr_a[lidx]->top; + lyr_chn[cidx]->columns = lyr_a[lidx]->right - lyr_a[lidx]->left; + lyr_chn[cidx]->data = NULL; + + if (lyr_chn[cidx]->id == PSD_CHANNEL_EXTRA_MASK) + { + if (fseek (f, lyr_a[lidx]->chn_info[cidx].data_len, SEEK_CUR) != 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + continue; + } + else if (lyr_chn[cidx]->id == PSD_CHANNEL_MASK) + { + /* Works around a bug in panotools psd files where the layer mask + size is given as 0 but data exists. Set mask size to layer size. + */ + if (empty_mask && lyr_a[lidx]->chn_info[cidx].data_len - 2 > 0) + { + empty_mask = FALSE; + if (lyr_a[lidx]->layer_mask.top == lyr_a[lidx]->layer_mask.bottom) + { + lyr_a[lidx]->layer_mask.top = lyr_a[lidx]->top; + lyr_a[lidx]->layer_mask.bottom = lyr_a[lidx]->bottom; + } + if (lyr_a[lidx]->layer_mask.right == lyr_a[lidx]->layer_mask.left) + { + lyr_a[lidx]->layer_mask.right = lyr_a[lidx]->right; + lyr_a[lidx]->layer_mask.left = lyr_a[lidx]->left; + } + } + lyr_chn[cidx]->rows = (lyr_a[lidx]->layer_mask.bottom - + lyr_a[lidx]->layer_mask.top); + lyr_chn[cidx]->columns = (lyr_a[lidx]->layer_mask.right - + lyr_a[lidx]->layer_mask.left); + } + + IFDBG(3) g_debug ("Channel id %d, %dx%d", + lyr_chn[cidx]->id, + lyr_chn[cidx]->columns, + lyr_chn[cidx]->rows); + + /* Only read channel data if there is any channel + * data. Note that the channel data can contain a + * compression method but no actual data. + */ + if (lyr_a[lidx]->chn_info[cidx].data_len >= COMP_MODE_SIZE) + { + if (fread (&comp_mode, COMP_MODE_SIZE, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + comp_mode = GUINT16_FROM_BE (comp_mode); + IFDBG(3) g_debug ("Compression mode: %d", comp_mode); + } + if (lyr_a[lidx]->chn_info[cidx].data_len > COMP_MODE_SIZE) + { + switch (comp_mode) + { + case PSD_COMP_RAW: /* Planar raw data */ + IFDBG(3) g_debug ("Raw data length: %d", + lyr_a[lidx]->chn_info[cidx].data_len - 2); + if (read_channel_data (lyr_chn[cidx], img_a->bps, + PSD_COMP_RAW, NULL, f, 0, + error) < 1) + return -1; + break; + + case PSD_COMP_RLE: /* Packbits */ + IFDBG(3) g_debug ("RLE channel length %d, RLE length data: %d, " + "RLE data block: %d", + lyr_a[lidx]->chn_info[cidx].data_len - 2, + lyr_chn[cidx]->rows * 2, + (lyr_a[lidx]->chn_info[cidx].data_len - 2 - + lyr_chn[cidx]->rows * 2)); + rle_pack_len = g_malloc (lyr_chn[cidx]->rows * 2); + for (rowi = 0; rowi < lyr_chn[cidx]->rows; ++rowi) + { + if (fread (&rle_pack_len[rowi], 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + g_free (rle_pack_len); + return -1; + } + rle_pack_len[rowi] = GUINT16_FROM_BE (rle_pack_len[rowi]); + } + + IFDBG(3) g_debug ("RLE decode - data"); + if (read_channel_data (lyr_chn[cidx], img_a->bps, + PSD_COMP_RLE, rle_pack_len, f, 0, + error) < 1) + return -1; + + g_free (rle_pack_len); + break; + + case PSD_COMP_ZIP: /* ? */ + case PSD_COMP_ZIP_PRED: + if (read_channel_data (lyr_chn[cidx], img_a->bps, + comp_mode, NULL, f, + lyr_a[lidx]->chn_info[cidx].data_len - 2, + error) < 1) + return -1; + break; + + default: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported compression mode: %d"), comp_mode); + return -1; + break; + } + } + } + + /* Draw layer */ + + alpha = FALSE; + alpha_chn = -1; + user_mask = FALSE; + user_mask_chn = -1; + layer_channels = 0; + l_x = 0; + l_y = 0; + l_w = img_a->columns; + l_h = img_a->rows; + if (parent_group_stack->len > 0) + parent_group_id = g_array_index (parent_group_stack, gint32, + parent_group_stack->len - 1); + else + parent_group_id = -1; /* root */ + + if (img_a->color_mode == PSD_CMYK) + base_channels = 4; + else if (img_a->color_mode == PSD_RGB || img_a->color_mode == PSD_LAB) + base_channels = 3; + else + base_channels = 1; + + IFDBG(3) g_debug ("Re-hash channel indices"); + for (cidx = 0; cidx < lyr_a[lidx]->num_channels; ++cidx) + { + if (lyr_chn[cidx]->id == PSD_CHANNEL_MASK) + { + user_mask = TRUE; + user_mask_chn = cidx; + } + else if (lyr_chn[cidx]->id == PSD_CHANNEL_ALPHA) + { + alpha = TRUE; + alpha_chn = cidx; + } + else if (lyr_chn[cidx]->data) + { + if (layer_channels < base_channels) + { + channel_idx[layer_channels] = cidx; /* Assumes in sane order */ + layer_channels++; /* RGB, Lab, CMYK etc. */ + } + else + { + /* channel_idx[base_channels] is reserved for alpha channel, + * but this layer apparently has extra channels. + * From the one example I have (see #8411) it looks like + * that channel is the same as the alpha channel. */ + IFDBG(2) g_debug ("This layer has an extra channel (id: %d)", lyr_chn[cidx]->id); + channel_idx[layer_channels+1] = cidx; /* Assumes in sane order */ + layer_channels += 2; /* RGB, Lab, CMYK etc. */ + } + } + else + { + IFDBG(4) g_debug ("Channel %d (id: %d) has no data", cidx, lyr_chn[cidx]->id); + } + } + + if (alpha) + { + if (layer_channels <= base_channels) + { + channel_idx[layer_channels] = alpha_chn; + layer_channels++; + } + else + { + channel_idx[base_channels] = alpha_chn; + } + base_channels++; + } + + IFDBG(4) g_debug ("Create the layer (group type: %d, clipping group type: %d)", + lyr_a[lidx]->group_type, lyr_a[lidx]->clipping_group_type); + + /* Create the layer */ + if (lyr_a[lidx]->group_type != 0) + { + if (lyr_a[lidx]->group_type == 3) + { + /* the </Layer group> marker layers are used to + * assemble the layer structure in a single pass + */ + IFDBG(2) g_debug ("Create placeholder group layer"); + layer_id = gimp_layer_group_new (image_id); + /* add this group layer as the new parent */ + g_array_append_val (parent_group_stack, layer_id); + } + else /* group-type == 1 || group_type == 2 */ + { + if (parent_group_stack->len) + { + layer_id = g_array_index (parent_group_stack, gint32, + parent_group_stack->len - 1); + IFDBG(2) g_debug ("End group layer id %d.", layer_id); + /* since the layers are stored in reverse, the group + * layer start marker actually means we're done with + * that layer group + */ + g_array_remove_index (parent_group_stack, + parent_group_stack->len - 1); + + gimp_drawable_offsets (layer_id, &l_x, &l_y); + + l_w = gimp_drawable_width (layer_id); + l_h = gimp_drawable_height (layer_id); + } + else + { + IFDBG(1) g_debug ("WARNING: Unmatched group layer start marker."); + layer_id = -1; + } + } + } + else + { + + if (lyr_a[lidx]->clipping_group_type == 1) + { + clipping_group_id = add_clipping_group (image_id, parent_group_id); + } + + if (empty) + { + IFDBG(2) g_debug ("Create blank layer"); + } + else + { + IFDBG(2) g_debug ("Create normal layer"); + l_x = lyr_a[lidx]->left; + l_y = lyr_a[lidx]->top; + l_w = lyr_a[lidx]->right - lyr_a[lidx]->left; + l_h = lyr_a[lidx]->bottom - lyr_a[lidx]->top; + } + + image_type = get_gimp_image_type (img_a->base_type, TRUE); + IFDBG(3) g_debug ("Layer type %d", image_type); + + layer_id = gimp_layer_new (image_id, lyr_a[lidx]->name, + l_w, l_h, image_type, + 100, GIMP_LAYER_MODE_NORMAL); + } + + if (layer_id != -1) + { + /* Set the layer name. Note that we do this even for group-end + * markers, to avoid having the default group name collide with + * subsequent layers; the real group name is set by the group + * start marker. + */ + gimp_item_set_name (layer_id, lyr_a[lidx]->name); + + /* Set the layer properties (skip this for layer group end + * markers; we set their properties when processing the start + * marker.) + */ + if (lyr_a[lidx]->group_type != 3) + { + /* Mode */ + psd_to_gimp_blend_mode (lyr_a[lidx], &mode_info); + gimp_layer_set_mode (layer_id, mode_info.mode); + gimp_layer_set_blend_space (layer_id, mode_info.blend_space); + gimp_layer_set_composite_space (layer_id, mode_info.composite_space); + gimp_layer_set_composite_mode (layer_id, mode_info.composite_mode); + + /* Opacity */ + gimp_layer_set_opacity (layer_id, + lyr_a[lidx]->opacity * 100.0 / 255.0); + + /* Flags */ + gimp_layer_set_lock_alpha (layer_id, lyr_a[lidx]->layer_flags.trans_prot); + gimp_item_set_visible (layer_id, lyr_a[lidx]->layer_flags.visible); +#if 0 + /* according to the spec, the 'irrelevant' flag indicates + * that the layer's "pixel data is irrelevant to the + * appearance of the document". what this seems to mean is + * not that the layer doesn't contribute to the image, but + * rather that its appearance can be entirely derived from + * sources other than the pixel data, such as vector data. + * in particular, this doesn't mean that the layer is + * invisible. since we only support raster layers atm, we can + * just ignore this flag. + */ + if (lyr_a[lidx]->layer_flags.irrelevant && + lyr_a[lidx]->group_type == 0) + { + gimp_item_set_visible (layer_id, FALSE); + } +#endif + + /* Position */ + if (l_x != 0 || l_y != 0) + gimp_layer_set_offsets (layer_id, l_x, l_y); + + /* Color tag */ + gimp_item_set_color_tag (layer_id, + psd_to_gimp_layer_color_tag (lyr_a[lidx]->color_tag[0])); + + /* Tattoo */ + if (lyr_a[lidx]->id) + gimp_item_set_tattoo (layer_id, lyr_a[lidx]->id); + + /* For layer groups, expand or collapse the group */ + if (lyr_a[lidx]->group_type != 0) + { + gimp_item_set_expanded (layer_id, + lyr_a[lidx]->group_type == 1); + } + } + + /* Remember the active layer ID */ + if (lidx == img_a->layer_state) + { + active_layer_id = layer_id; + } + + /* Set the layer data */ + if (lyr_a[lidx]->group_type == 0) + { + IFDBG(3) g_debug ("Draw layer"); + + if (empty) + { + gimp_drawable_fill (layer_id, GIMP_FILL_TRANSPARENT); + } + else + { + GeglBufferIterator *iter; + + bps = img_a->bps / 8; + if (bps == 0) + bps++; + + buffer = gimp_drawable_get_buffer (layer_id); + + iter = gegl_buffer_iterator_new ( + buffer, NULL, 0, get_layer_format (img_a, alpha), + GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 1); + + while (gegl_buffer_iterator_next (iter)) + { + const GeglRectangle *roi = &iter->items[0].roi; + guint8 *dst0 = iter->items[0].data; + gint src_step = bps; + gint dst_step = bps * base_channels; + + if (img_a->color_mode == PSD_CMYK) + { + dst0 = gegl_scratch_alloc (base_channels * + iter->length); + } + + for (cidx = 0; cidx < base_channels; ++cidx) + { + gint b; + + if (roi->x == 0 && roi->y == 0) + IFDBG(3) g_debug ("Start channel %d", channel_idx[cidx]); + + for (b = 0; b < bps; ++b) + { + guint8 *dst; + gint y; + + dst = &dst0[cidx * bps + b]; + + for (y = 0; y < roi->height; y++) + { + const guint8 *src; + gint x; + + src = (const guint8 *) + &lyr_chn[channel_idx[cidx]]->data[ + ((roi->y + y) * l_w + + roi->x) * bps + + b]; + + for (x = 0; x < roi->width; ++x) + { + *dst = *src; + + src += src_step; + dst += dst_step; + } + } + } + } + + if (img_a->color_mode == PSD_CMYK) + { + psd_convert_cmyk_to_srgb ( + img_a, + iter->items[0].data, dst0, + roi->width, roi->height, + alpha); + + gegl_scratch_free (dst0); + } + } + + for (cidx = 0; cidx < layer_channels; ++cidx) + g_free (lyr_chn[channel_idx[cidx]]->data); + + g_object_unref (buffer); + } + } + + /* Layer mask */ + if (user_mask && lyr_a[lidx]->group_type != 3) + { + if (empty_mask) + { + IFDBG(3) g_debug ("Create empty mask"); + if (lyr_a[lidx]->layer_mask.def_color == 255) + mask_id = gimp_layer_create_mask (layer_id, + GIMP_ADD_MASK_WHITE); + else + mask_id = gimp_layer_create_mask (layer_id, + GIMP_ADD_MASK_BLACK); + gimp_layer_add_mask (layer_id, mask_id); + gimp_layer_set_apply_mask (layer_id, + ! lyr_a[lidx]->layer_mask.mask_flags.disabled); + } + else + { + GeglRectangle mask_rect; + + /* Load layer mask data */ + lm_x = lyr_a[lidx]->layer_mask.left - l_x; + lm_y = lyr_a[lidx]->layer_mask.top - l_y; + lm_w = lyr_a[lidx]->layer_mask.right - lyr_a[lidx]->layer_mask.left; + lm_h = lyr_a[lidx]->layer_mask.bottom - lyr_a[lidx]->layer_mask.top; + IFDBG(3) g_debug ("Mask channel index %d", user_mask_chn); + IFDBG(3) g_debug ("Original Mask %d %d %d %d", lm_x, lm_y, lm_w, lm_h); + /* Crop mask at layer boundary, and draw layer mask data, + * if any + */ + if (gegl_rectangle_intersect ( + &mask_rect, + GEGL_RECTANGLE (0, 0, l_w, l_h), + GEGL_RECTANGLE (lm_x, lm_y, lm_w, lm_h))) + { + IFDBG(3) g_debug ("Layer %d %d %d %d", l_x, l_y, l_w, l_h); + IFDBG(3) g_debug ("Mask %d %d %d %d", + mask_rect.x, mask_rect.y, + mask_rect.width, mask_rect.height); + + if (lyr_a[lidx]->layer_mask.def_color == 255) + mask_id = gimp_layer_create_mask (layer_id, + GIMP_ADD_MASK_WHITE); + else + mask_id = gimp_layer_create_mask (layer_id, + GIMP_ADD_MASK_BLACK); + + bps = img_a->bps / 8; + if (bps == 0) + bps++; + + IFDBG(3) g_debug ("New layer mask %d", mask_id); + gimp_layer_add_mask (layer_id, mask_id); + buffer = gimp_drawable_get_buffer (mask_id); + gegl_buffer_set (buffer, + &mask_rect, + 0, get_mask_format (img_a), + lyr_chn[user_mask_chn]->data + ( + (mask_rect.y - lm_y) * lm_w + + (mask_rect.x - lm_x)) * bps, + lm_w * bps); + g_object_unref (buffer); + gimp_layer_set_apply_mask (layer_id, + ! lyr_a[lidx]->layer_mask.mask_flags.disabled); + } + g_free (lyr_chn[user_mask_chn]->data); + } + } + + /* Insert the layer */ + if (lyr_a[lidx]->group_type == 0 || /* normal layer */ + lyr_a[lidx]->group_type == 3 /* group layer end marker */) + { + if (clipping_group_id != -1) + { + gimp_image_insert_layer (image_id, layer_id, clipping_group_id, 0); + + if (lyr_a[lidx]->clipping_group_type == 2) + { + /* End of our clipping group. */ + clipping_group_id = -1; + } + } + else + { + gimp_image_insert_layer (image_id, layer_id, parent_group_id, 0); + } + + } + } + + for (cidx = 0; cidx < lyr_a[lidx]->num_channels; ++cidx) + if (lyr_chn[cidx]) + g_free (lyr_chn[cidx]); + g_free (lyr_chn); + } + g_free (lyr_a[lidx]->chn_info); + g_free (lyr_a[lidx]->name); + g_free (lyr_a[lidx]); + } + g_free (lyr_a); + g_array_free (parent_group_stack, FALSE); + + /* Set the active layer */ + if (active_layer_id >= 0) + gimp_image_set_active_layer (image_id, active_layer_id); + + return 0; +} + +static gint +add_merged_image (gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error) +{ + PSDchannel chn_a[MAX_CHANNELS]; + gchar *alpha_name; + guchar *pixels; + guint16 comp_mode; + guint16 base_channels; + guint16 extra_channels; + guint16 total_channels; + guint16 bps; + guint16 *rle_pack_len[MAX_CHANNELS]; + guint32 alpha_id; + gint32 layer_size; + gint32 layer_id = -1; + gint32 channel_id = -1; + gint16 alpha_opacity; + gint cidx; /* Channel index */ + gint rowi; /* Row index */ + gint offset; + gint i; + gboolean alpha_visible; + gboolean alpha_channel = FALSE; + GeglBuffer *buffer; + GimpImageType image_type; + GimpRGB alpha_rgb; + + total_channels = img_a->channels; + extra_channels = 0; + bps = img_a->bps / 8; + if (bps == 0) + bps++; + + if (img_a->num_layers > 0 && img_a->color_mode == PSD_CMYK) + { + /* In this case there is no conversion. Merged image is RGB. */ + img_a->color_mode = PSD_RGB; + if (! img_a->transparency) + total_channels--; + } + + if ((img_a->color_mode == PSD_BITMAP || + img_a->color_mode == PSD_MULTICHANNEL || + img_a->color_mode == PSD_GRAYSCALE || + img_a->color_mode == PSD_DUOTONE || + img_a->color_mode == PSD_INDEXED) && + total_channels > 1) + { + extra_channels = total_channels - 1; + } + else if ((img_a->color_mode == PSD_RGB || + img_a->color_mode == PSD_LAB) && + total_channels > 3) + { + extra_channels = total_channels - 3; + } + else if ((img_a->color_mode == PSD_CMYK) && + total_channels > 4) + { + extra_channels = total_channels - 4; + } + if (img_a->transparency && extra_channels > 0) + extra_channels--; + base_channels = total_channels - extra_channels; + + if (img_a->merged_image_only) + { + if (! img_a->transparency && extra_channels > 0) + { + alpha_channel = TRUE; + base_channels += 1; + } + extra_channels = 0; + total_channels = base_channels; + } + + /* ----- Read merged image & extra channel pixel data ----- */ + if (img_a->merged_image_only || + img_a->num_layers == 0 || + extra_channels > 0) + { + guint32 block_len; + guint32 block_start; + + block_start = img_a->merged_image_start; + block_len = img_a->merged_image_len; + + fseek (f, block_start, SEEK_SET); + + if (fread (&comp_mode, COMP_MODE_SIZE, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + comp_mode = GUINT16_FROM_BE (comp_mode); + + switch (comp_mode) + { + case PSD_COMP_RAW: /* Planar raw data */ + IFDBG(3) g_debug ("Raw data length: %d", block_len); + for (cidx = 0; cidx < total_channels; ++cidx) + { + chn_a[cidx].columns = img_a->columns; + chn_a[cidx].rows = img_a->rows; + if (read_channel_data (&chn_a[cidx], img_a->bps, + PSD_COMP_RAW, NULL, f, 0, + error) < 1) + return -1; + } + break; + + case PSD_COMP_RLE: /* Packbits */ + /* Image data is stored as packed scanlines in planar order + with all compressed length counters stored first */ + IFDBG(3) g_debug ("RLE length data: %d, RLE data block: %d", + total_channels * img_a->rows * 2, + block_len - (total_channels * img_a->rows * 2)); + for (cidx = 0; cidx < total_channels; ++cidx) + { + chn_a[cidx].columns = img_a->columns; + chn_a[cidx].rows = img_a->rows; + rle_pack_len[cidx] = g_malloc (img_a->rows * 2); + for (rowi = 0; rowi < img_a->rows; ++rowi) + { + if (fread (&rle_pack_len[cidx][rowi], 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + rle_pack_len[cidx][rowi] = GUINT16_FROM_BE (rle_pack_len[cidx][rowi]); + } + } + + /* Skip channel length data for unloaded channels */ + if (fseek (f, (img_a->channels - total_channels) * img_a->rows * 2, + SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + IFDBG(3) g_debug ("RLE decode - data"); + for (cidx = 0; cidx < total_channels; ++cidx) + { + if (read_channel_data (&chn_a[cidx], img_a->bps, + PSD_COMP_RLE, rle_pack_len[cidx], f, 0, + error) < 1) + return -1; + g_free (rle_pack_len[cidx]); + } + break; + + case PSD_COMP_ZIP: /* ? */ + case PSD_COMP_ZIP_PRED: + default: + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported compression mode: %d"), comp_mode); + return -1; + break; + } + } + + /* ----- Draw merged image ----- */ + if (img_a->merged_image_only || + img_a->num_layers == 0) /* Merged image - Photoshop 2 style */ + { + image_type = get_gimp_image_type (img_a->base_type, + img_a->transparency || alpha_channel); + + layer_size = img_a->columns * img_a->rows; + pixels = g_malloc (layer_size * base_channels * bps); + for (cidx = 0; cidx < base_channels; ++cidx) + { + for (i = 0; i < layer_size; ++i) + { + memcpy (&pixels[((i * base_channels) + cidx) * bps], + &chn_a[cidx].data[i * bps], bps); + } + g_free (chn_a[cidx].data); + } + + /* Add background layer */ + IFDBG(2) g_debug ("Draw merged image"); + layer_id = gimp_layer_new (image_id, _("Background"), + img_a->columns, img_a->rows, + image_type, + 100, + gimp_image_get_default_new_layer_mode (image_id)); + gimp_image_insert_layer (image_id, layer_id, -1, 0); + + buffer = gimp_drawable_get_buffer (layer_id); + if (img_a->color_mode == PSD_CMYK) + { + guchar *dst0; + + dst0 = g_malloc (base_channels * layer_size * sizeof(float)); + psd_convert_cmyk_to_srgb ( img_a, + dst0, pixels, + img_a->columns, img_a->rows, + alpha_channel); + g_free (pixels); + pixels = dst0; + } + + gegl_buffer_set (buffer, + GEGL_RECTANGLE (0, 0, + gegl_buffer_get_width (buffer), + gegl_buffer_get_height (buffer)), + 0, get_layer_format (img_a, + img_a->transparency || + alpha_channel), + pixels, GEGL_AUTO_ROWSTRIDE); + + if (img_a->color_mode == PSD_CMYK) + img_a->color_mode = PSD_RGB; + + /* Merged image data is blended against white. Unblend it. */ + if (img_a->transparency) + { + GeglBufferIterator *iter; + + iter = gegl_buffer_iterator_new (buffer, NULL, 0, + babl_format ("R'G'B'A float"), + GEGL_ACCESS_READWRITE, + GEGL_ABYSS_NONE, 1); + + while (gegl_buffer_iterator_next (iter)) + { + gfloat *data = iter->items[0].data; + + for (i = 0; i < iter->length; i++) + { + gint c; + + if (data[3]) + { + for (c = 0; c < 3; c++) + data[c] = (data[c] + data[3] - 1.0f) / data[3]; + } + + data += 4; + } + } + } + + g_object_unref (buffer); + g_free (pixels); + } + else + { + /* Free merged image data for layered image */ + if (extra_channels) + for (cidx = 0; cidx < base_channels; ++cidx) + g_free (chn_a[cidx].data); + } + + if (img_a->transparency) + { + /* Free "Transparency" channel name */ + if (img_a->alpha_names) + { + alpha_name = g_ptr_array_index (img_a->alpha_names, 0); + if (alpha_name) + g_free (alpha_name); + } + } + + /* ----- Draw extra alpha channels ----- */ + if (extra_channels /* Extra alpha channels */ + && image_id > -1) + { + IFDBG(2) g_debug ("Add extra channels"); + pixels = g_malloc(0); + + /* Get channel resource data */ + if (img_a->transparency) + offset = 1; + else + offset = 0; + + /* Draw channels */ + IFDBG(2) g_debug ("Number of channels: %d", extra_channels); + for (i = 0; i < extra_channels; ++i) + { + /* Alpha channel name */ + alpha_name = NULL; + alpha_visible = FALSE; + /* Quick mask channel*/ + if (img_a->quick_mask_id) + if (i == img_a->quick_mask_id - base_channels + offset) + { + /* Free "Quick Mask" channel name */ + alpha_name = g_ptr_array_index (img_a->alpha_names, i + offset); + if (alpha_name) + g_free (alpha_name); + alpha_name = g_strdup (GIMP_IMAGE_QUICK_MASK_NAME); + alpha_visible = TRUE; + } + if (! alpha_name && img_a->alpha_names) + if (offset < img_a->alpha_names->len + && i + offset <= img_a->alpha_names->len) + alpha_name = g_ptr_array_index (img_a->alpha_names, i + offset); + if (! alpha_name) + alpha_name = g_strdup (_("Extra")); + + if (offset < img_a->alpha_id_count && + offset + i <= img_a->alpha_id_count) + alpha_id = img_a->alpha_id[i + offset]; + else + alpha_id = 0; + if (offset < img_a->alpha_display_count && + i + offset <= img_a->alpha_display_count) + { + alpha_rgb = img_a->alpha_display_info[i + offset]->gimp_color; + alpha_opacity = img_a->alpha_display_info[i + offset]->opacity; + } + else + { + gimp_rgba_set (&alpha_rgb, 1.0, 0.0, 0.0, 1.0); + alpha_opacity = 50; + } + + cidx = base_channels + i; + pixels = g_realloc (pixels, chn_a[cidx].columns * chn_a[cidx].rows * bps); + memcpy (pixels, chn_a[cidx].data, chn_a[cidx].columns * chn_a[cidx].rows * bps); + channel_id = gimp_channel_new (image_id, alpha_name, + chn_a[cidx].columns, chn_a[cidx].rows, + alpha_opacity, &alpha_rgb); + gimp_image_insert_channel (image_id, channel_id, -1, i); + g_free (alpha_name); + buffer = gimp_drawable_get_buffer (channel_id); + if (alpha_id) + gimp_item_set_tattoo (channel_id, alpha_id); + gimp_item_set_visible (channel_id, alpha_visible); + gegl_buffer_set (buffer, + GEGL_RECTANGLE (0, 0, + gegl_buffer_get_width (buffer), + gegl_buffer_get_height (buffer)), + 0, get_channel_format (img_a), + pixels, GEGL_AUTO_ROWSTRIDE); + g_object_unref (buffer); + g_free (chn_a[cidx].data); + } + + g_free (pixels); + if (img_a->alpha_names) + g_ptr_array_free (img_a->alpha_names, TRUE); + + if (img_a->alpha_id) + g_free (img_a->alpha_id); + + if (img_a->alpha_display_info) + { + for (cidx = 0; cidx < img_a->alpha_display_count; ++cidx) + g_free (img_a->alpha_display_info[cidx]); + g_free (img_a->alpha_display_info); + } + } + + /* FIXME gimp image tattoo state */ + + return 0; +} + + +/* Local utility functions */ +static gint32 +add_clipping_group (gint32 image_id, + gint32 parent_id) +{ + gint32 clipping_group_id = -1; + + /* We need to create a group because GIMP handles clipping and + * composition mode in a different manner than PS. */ + IFDBG(2) g_debug ("Creating a layer group to handle PS transparency clipping correctly."); + + clipping_group_id = gimp_layer_group_new (image_id); + + gimp_item_set_name (clipping_group_id, "Group added by GIMP"); + gimp_layer_set_blend_space (clipping_group_id, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL); + gimp_layer_set_composite_space (clipping_group_id, GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL); + gimp_layer_set_composite_mode (clipping_group_id, GIMP_LAYER_COMPOSITE_UNION); + + gimp_image_insert_layer (image_id, clipping_group_id, parent_id, 0); + + return clipping_group_id; +} + +static gchar * +get_psd_color_mode_name (PSDColorMode mode) +{ + static gchar * const psd_color_mode_names[] = + { + "BITMAP", + "GRAYSCALE", + "INDEXED", + "RGB", + "CMYK", + "UNKNOWN (5)", + "UNKNOWN (6)", + "MULTICHANNEL", + "DUOTONE", + "LAB" + }; + + static gchar *err_name = NULL; + + if (mode >= PSD_BITMAP && mode <= PSD_LAB) + return psd_color_mode_names[mode]; + + g_free (err_name); + err_name = g_strdup_printf ("UNKNOWN (%d)", mode); + + return err_name; +} + +static void +psd_to_gimp_color_map (guchar *map256) +{ + guchar *tmpmap; + gint i; + + tmpmap = g_malloc (3 * 256); + + for (i = 0; i < 256; ++i) + { + tmpmap[i*3 ] = map256[i]; + tmpmap[i*3+1] = map256[i+256]; + tmpmap[i*3+2] = map256[i+512]; + } + + memcpy (map256, tmpmap, 3 * 256); + g_free (tmpmap); +} + +static GimpImageType +get_gimp_image_type (GimpImageBaseType image_base_type, + gboolean alpha) +{ + GimpImageType image_type; + + switch (image_base_type) + { + case GIMP_GRAY: + image_type = (alpha) ? GIMP_GRAYA_IMAGE : GIMP_GRAY_IMAGE; + break; + + case GIMP_INDEXED: + image_type = (alpha) ? GIMP_INDEXEDA_IMAGE : GIMP_INDEXED_IMAGE; + break; + + case GIMP_RGB: + image_type = (alpha) ? GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE; + break; + + default: + image_type = -1; + break; + } + + return image_type; +} + +static voidpf +zzalloc (voidpf opaque, uInt items, uInt size) +{ + /* overflow check missing */ + return g_malloc (items * size); +} + +static void +zzfree (voidpf opaque, voidpf address) +{ + g_free (address); +} + +static gint +read_channel_data (PSDchannel *channel, + guint16 bps, + guint16 compression, + const guint16 *rle_pack_len, + FILE *f, + guint32 comp_len, + GError **error) +{ + gchar *raw_data; + gchar *src; + guint32 readline_len; + gint i, j; + + if (bps == 1) + readline_len = ((channel->columns + 7) / 8); + else + readline_len = (channel->columns * bps / 8); + + IFDBG(3) g_debug ("raw data size %d x %d = %d", readline_len, + channel->rows, readline_len * channel->rows); + + /* sanity check, int overflow check (avoid divisions by zero) */ + if ((channel->rows == 0) || (channel->columns == 0) || + (channel->rows > G_MAXINT32 / channel->columns / MAX (bps / 8, 1))) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unsupported or invalid channel size")); + return -1; + } + + raw_data = g_malloc (readline_len * channel->rows); + switch (compression) + { + case PSD_COMP_RAW: + if (fread (raw_data, readline_len, channel->rows, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + break; + + case PSD_COMP_RLE: + for (i = 0; i < channel->rows; ++i) + { + src = gegl_scratch_alloc (rle_pack_len[i]); +/* FIXME check for over-run + if (ftell (f) + rle_pack_len[i] > block_end) + { + psd_set_error (TRUE, errno, error); + return -1; + } +*/ + if (fread (src, rle_pack_len[i], 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + gegl_scratch_free (src); + return -1; + } + /* FIXME check for errors returned from decode packbits */ + decode_packbits (src, raw_data + i * readline_len, + rle_pack_len[i], readline_len); + gegl_scratch_free (src); + } + break; + case PSD_COMP_ZIP: + case PSD_COMP_ZIP_PRED: + { + z_stream zs; + + src = g_malloc (comp_len); + if (fread (src, comp_len, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + g_free (src); + return -1; + } + + zs.next_in = (guchar*) src; + zs.avail_in = comp_len; + zs.next_out = (guchar*) raw_data; + zs.avail_out = readline_len * channel->rows; + zs.zalloc = zzalloc; + zs.zfree = zzfree; + + if (inflateInit (&zs) == Z_OK && + inflate (&zs, Z_FINISH) == Z_STREAM_END) + { + inflateEnd (&zs); + } + else + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Failed to decompress data")); + g_free (src); + return -1; + } + + g_free (src); + break; + } + } + + /* Convert channel data to GIMP format */ + switch (bps) + { + case 32: + { + guint32 *data = (guint32*) raw_data; + + channel->data = raw_data; + raw_data = NULL; + + for (i = 0; i < channel->rows * channel->columns; ++i) + data[i] = GUINT32_FROM_BE (data[i]); + + if (compression == PSD_COMP_ZIP_PRED) + { + for (i = 0; i < channel->rows; ++i) + for (j = 1; j < channel->columns; ++j) + data[i * channel->columns + j] += data[i * channel->columns + j - 1]; + } + break; + } + + case 16: + { + guint16 *data = (guint16*) raw_data; + + channel->data = raw_data; + raw_data = NULL; + + for (i = 0; i < channel->rows * channel->columns; ++i) + data[i] = GUINT16_FROM_BE (data[i]); + + if (compression == PSD_COMP_ZIP_PRED) + { + for (i = 0; i < channel->rows; ++i) + for (j = 1; j < channel->columns; ++j) + data[i * channel->columns + j] += data[i * channel->columns + j - 1]; + } + break; + } + + case 8: + channel->data = raw_data; + raw_data = NULL; + + if (compression == PSD_COMP_ZIP_PRED) + { + for (i = 0; i < channel->rows; ++i) + for (j = 1; j < channel->columns; ++j) + channel->data[i * channel->columns + j] += channel->data[i * channel->columns + j - 1]; + } + break; + + case 1: + channel->data = (gchar *) g_malloc (channel->rows * channel->columns); + convert_1_bit (raw_data, channel->data, channel->rows, channel->columns); + break; + + default: + return -1; + break; + } + + g_free (raw_data); + + return 1; +} + +static void +convert_1_bit (const gchar *src, + gchar *dst, + guint32 rows, + guint32 columns) +{ +/* Convert bits to bytes left to right by row. + Rows are padded out to a byte boundary. +*/ + guint32 row_pos = 0; + gint i, j; + + IFDBG(3) g_debug ("Start 1 bit conversion"); + + for (i = 0; i < rows * ((columns + 7) / 8); ++i) + { + guchar mask = 0x80; + for (j = 0; j < 8 && row_pos < columns; ++j) + { + *dst = (*src & mask) ? 0 : 1; + IFDBG(3) g_debug ("byte %d, bit %d, offset %d, src %d, dst %d", + i , j, row_pos, *src, *dst); + dst++; + mask >>= 1; + row_pos++; + } + if (row_pos >= columns) + row_pos = 0; + src++; + } + IFDBG(3) g_debug ("End 1 bit conversion"); +} + +static const Babl* +get_layer_format (PSDimage *img_a, + gboolean alpha) +{ + const Babl *format = NULL; + + switch (get_gimp_image_type (img_a->base_type, alpha)) + { + case GIMP_GRAY_IMAGE: + switch (img_a->bps) + { + case 32: + format = babl_format ("Y' u32"); + break; + + case 16: + format = babl_format ("Y' u16"); + break; + + case 8: + case 1: + format = babl_format ("Y' u8"); + break; + + default: + return NULL; + break; + } + break; + + case GIMP_GRAYA_IMAGE: + switch (img_a->bps) + { + case 32: + format = babl_format ("Y'A u32"); + break; + + case 16: + format = babl_format ("Y'A u16"); + break; + + case 8: + case 1: + format = babl_format ("Y'A u8"); + break; + + default: + return NULL; + break; + } + break; + + case GIMP_RGB_IMAGE: + switch (img_a->bps) + { + case 32: + format = babl_format ("R'G'B' u32"); + break; + + case 16: + format = babl_format ("R'G'B' u16"); + break; + + case 8: + case 1: + format = babl_format (img_a->color_mode == PSD_CMYK ? "R'G'B' float" : "R'G'B' u8"); + break; + + default: + return NULL; + break; + } + break; + + case GIMP_RGBA_IMAGE: + switch (img_a->bps) + { + case 32: + format = babl_format ("R'G'B'A u32"); + break; + + case 16: + format = babl_format ("R'G'B'A u16"); + break; + + case 8: + case 1: + format = babl_format (img_a->color_mode == PSD_CMYK ? "R'G'B'A float" : "R'G'B'A u8"); + break; + + default: + return NULL; + break; + } + break; + + default: + return NULL; + break; + } + + return format; +} + +static const Babl* +get_channel_format (PSDimage *img_a) +{ + const Babl *format = NULL; + + switch (img_a->bps) + { + case 32: + format = babl_format ("Y u32"); + break; + + case 16: + format = babl_format ("Y u16"); + break; + + case 8: + case 1: + /* see gimp_image_get_channel_format() */ + format = babl_format ("Y' u8"); + break; + + default: + break; + } + + return format; +} + +static const Babl* +get_mask_format (PSDimage *img_a) +{ + const Babl *format = NULL; + + switch (img_a->bps) + { + case 32: + format = babl_format ("Y u32"); + break; + + case 16: + format = babl_format ("Y u16"); + break; + + case 8: + case 1: + format = babl_format ("Y u8"); + break; + + default: + break; + } + + return format; +} diff --git a/plug-ins/file-psd/psd-load.h b/plug-ins/file-psd/psd-load.h new file mode 100644 index 0000000..42eb516 --- /dev/null +++ b/plug-ins/file-psd/psd-load.h @@ -0,0 +1,32 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GIMP PSD Plug-in + * Copyright 2007 by John Marshall + * + * 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 __PSD_LOAD_H__ +#define __PSD_LOAD_H__ + + +gint32 load_image (const gchar *filename, + gboolean merged_image_only, + gboolean *resolution_loaded, + gboolean *profile_loaded, + GError **error); + + +#endif /* __PSD_LOAD_H__ */ diff --git a/plug-ins/file-psd/psd-save.c b/plug-ins/file-psd/psd-save.c new file mode 100644 index 0000000..e506610 --- /dev/null +++ b/plug-ins/file-psd/psd-save.c @@ -0,0 +1,2097 @@ +/* + * PSD Export Plugin version 1.0 (BETA) + * This GIMP plug-in is designed to export Adobe Photoshop(tm) files (.PSD) + * + * Monigotes + * + * If this plug-in fails to export a file which you think it should, + * please tell me what seemed to go wrong, and anything you know + * about the image you tried to export. Please don't send big PSD + * files to me without asking first. + * + * Copyright (C) 2000 Monigotes + * + * 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/>. + */ + +/* + * Adobe and Adobe Photoshop are trademarks of Adobe Systems + * Incorporated that may be registered in certain jurisdictions. + */ + +/* + * Revision history: + * + * 2000.02 / v1.0 / Monigotes + * First version. + * + * 2003-05-10 Pedro Gimeno <pggimeno@wanadoo.es> + * - Cleaned up and GNUstylized. + * - Translated all comments and vars in Spanish to English. + * + * 2005-2-11 Jay Cox <jaycox@gimp.org> + * Rewrote all the code that deals with pixels to be stingy with + * memory and operate on tile-size chunks. Create a flattened + * copy of the image when necessary. Fixes file corruption bug + * #167139 and memory bug #121871 + * + * 2006-03-29 Guillermo S. Romero <gsr.bugs@infernal-iceberg.com> + * - Added/enabled basic support for layer masks based in psd.c + * and whatever was already here. + * Layers that are not canvas sized need investigation, here + * or in the import plugin something seems wrong. + * - Cosmetic changes about the debug messages, more like psd.c. + */ + +/* + * TODO: + * Export preview + */ + +/* + * BUGS: + */ + +#include "config.h" + +#include <errno.h> +#include <string.h> + +#include <glib/gstdio.h> + +#include "libgimp/gimp.h" +#include "libgimp/gimpui.h" + +#include "libgimpmath/gimpmath.h" + +#include "psd.h" +#include "psd-util.h" +#include "psd-save.h" + +#include "libgimp/stdplugins-intl.h" + + +/* set to TRUE if you want debugging, FALSE otherwise */ +#define DEBUG FALSE + +/* 1: Normal debuggin, 2: Deep debuggin */ +#define DEBUG_LEVEL 2 + +#undef IFDBG /* previously defined in psd.h */ + +#define IFDBG if (DEBUG) +#define IF_DEEP_DBG if (DEBUG && DEBUG_LEVEL == 2) + +#define PSD_UNIT_INCH 1 +#define PSD_UNIT_CM 2 + + +/* Local types etc + */ + +typedef enum PsdLayerType +{ + PSD_LAYER_TYPE_LAYER, + PSD_LAYER_TYPE_GROUP_START, + PSD_LAYER_TYPE_GROUP_END +} PSD_Layer_Type; + +typedef struct PsdLayer +{ + gint id; + PSD_Layer_Type type; +} PSD_Layer; + +typedef struct PsdImageData +{ + gboolean compression; + + gint32 image_height; + gint32 image_width; + + GimpImageBaseType baseType; + + gint32 merged_layer;/* Merged image, + to be used for the image data section */ + + gint nChannels; /* Number of user channels in the image */ + gint32 *lChannels; /* User channels in the image */ + + gint nLayers; /* Number of layers in the image */ + PSD_Layer *lLayers; /* Layer list */ +} PSD_Image_Data; + +static PSD_Image_Data PSDImageData; + +/* Declare some local functions. + */ + +static const gchar * psd_lmode_layer (gint32 idLayer, + gboolean section_divider); + +static void reshuffle_cmap_write (guchar *mapGimp); + +static void save_header (FILE *fd, + gint32 image_id); + +static void save_color_mode_data (FILE *fd, + gint32 image_id); + +static void save_resources (FILE *fd, + gint32 image_id); + +static void save_paths (FILE *fd, + gint32 image_id); + +static void save_layer_and_mask (FILE *fd, + gint32 image_id); + +static void save_data (FILE *fd, + gint32 image_id); + +static void double_to_psd_fixed (gdouble value, + gchar *target); + +static void xfwrite (FILE *fd, + gconstpointer buf, + glong len, + const gchar *why); + +static void write_pascalstring (FILE *fd, + const gchar *val, + gint padding, + const gchar *why); + +static void write_string (FILE *fd, + const gchar *val, + const gchar *why); + +static void write_gchar (FILE *fd, + guchar val, + const gchar *why); + +static void write_gint16 (FILE *fd, + gint16 val, + const gchar *why); + +static void write_gint32 (FILE *fd, + gint32 val, + const gchar *why); + +static void write_datablock_luni (FILE *fd, + const gchar *val, + const gchar *why); + + +static void write_pixel_data (FILE *fd, + gint32 drawableID, + glong *ChanLenPosition, + gint32 rowlenOffset, + gboolean write_mask); + +static gint32 create_merged_image (gint32 imageID); + +static gint get_bpc (gint32 imageID); +static const Babl * get_pixel_format (gint32 drawableID); +static const Babl * get_channel_format (gint32 drawableID); +static const Babl * get_mask_format (gint32 drawableID); + +static PSD_Layer * image_get_all_layers (gint32 imageID, + gint *n_layers); + +static const gchar * +psd_lmode_layer (gint32 idLayer, + gboolean section_divider) +{ + LayerModeInfo mode_info; + + mode_info.mode = gimp_layer_get_mode (idLayer); + mode_info.blend_space = gimp_layer_get_blend_space (idLayer); + mode_info.composite_space = gimp_layer_get_composite_space (idLayer); + mode_info.composite_mode = gimp_layer_get_composite_mode (idLayer); + + /* pass-through groups use normal mode in their layer record; the + * pass-through mode is specified in their section divider resource. + */ + if (mode_info.mode == GIMP_LAYER_MODE_PASS_THROUGH && ! section_divider) + mode_info.mode = GIMP_LAYER_MODE_NORMAL; + + return gimp_to_psd_blend_mode (&mode_info); +} + +static void +write_string (FILE *fd, + const gchar *val, + const gchar *why) +{ + write_gchar (fd, strlen (val), why); + xfwrite (fd, val, strlen (val), why); +} + +static void +write_pascalstring (FILE *fd, + const gchar *val, + gint padding, + const gchar *why) +{ + guchar len; + gint i; + + /* Calculate string length to write and limit it to 255 */ + + len = (strlen (val) > 255) ? 255 : (guchar) strlen (val); + + /* Perform actual writing */ + + if (len != 0) + { + write_gchar (fd, len, why); + xfwrite (fd, val, len, why); + } + else + { + write_gchar (fd, 0, why); + } + + /* If total length (length byte + content) is not a multiple of PADDING, + add zeros to pad it. */ + + len++; /* Add the length field */ + + if ((len % padding) == 0) + return; + + for (i = 0; i < (padding - (len % padding)); i++) + write_gchar (fd, 0, why); +} + +static void +xfwrite (FILE *fd, + gconstpointer buf, + glong len, + const gchar *why) +{ + if (len == 0) + return; + + if (fwrite (buf, len, 1, fd) == 0) + { + g_printerr ("%s: Error while writing '%s'\n", G_STRFUNC, why); + gimp_quit (); + } +} + +static void +write_gchar (FILE *fd, + guchar val, + const gchar *why) +{ + guchar b[2]; + glong pos; + + b[0] = val; + b[1] = 0; + + pos = ftell (fd); + if (fwrite (&b, 1, 2, fd) == 0) + { + g_printerr ("%s: Error while writing '%s'\n", G_STRFUNC, why); + gimp_quit (); + } + fseek (fd, pos + 1, SEEK_SET); +} + +static void +write_gint16 (FILE *fd, + gint16 val, + const gchar *why) +{ + guchar b[2]; + /* b[0] = val & 255; + b[1] = (val >> 8) & 255;*/ + + b[1] = val & 255; + b[0] = (val >> 8) & 255; + + if (fwrite (&b, 1, 2, fd) == 0) + { + g_printerr ("%s: Error while writing '%s'\n", G_STRFUNC, why); + gimp_quit (); + } +} + +static void +write_gint32 (FILE *fd, + gint32 val, + const gchar *why) +{ + guchar b[4]; + + b[3] = val & 255; + b[2] = (val >> 8) & 255; + b[1] = (val >> 16) & 255; + b[0] = (val >> 24) & 255; + + if (fwrite (&b, 1, 4, fd) == 0) + { + g_printerr ("%s: Error while writing '%s'\n", G_STRFUNC, why); + gimp_quit (); + } +} + +static void +write_datablock_luni (FILE *fd, + const gchar *val, + const gchar *why) +{ + if (val) + { + guint32 count; + guint32 xdBlockSize; + glong numchars; + gunichar2 *luniName; + + luniName = g_utf8_to_utf16 (val, -1, NULL, &numchars, NULL); + + if (luniName) + { + guchar len = MIN (numchars, 255); + + /* Only pad to even num of chars */ + if( len % 2 ) + xdBlockSize = len + 1; + else + xdBlockSize = len; + + /* 2 bytes / char + 4 bytes for pascal num chars */ + xdBlockSize = (xdBlockSize * 2) + 4; + + xfwrite (fd, "8BIMluni", 8, "luni xdb signature"); + write_gint32 (fd, xdBlockSize, "luni xdb size"); + write_gint32 (fd, len, "luni xdb pascal string"); + + for (count = 0; count < len; count++) + write_gint16 (fd, luniName[count], "luni xdb pascal string"); + + /* Pad to an even number of chars */ + if (len % 2) + write_gint16 (fd, 0x0000, "luni xdb pascal string padding"); + } + } +} + +static gint32 +pack_pb_line (guchar *start, + gint32 length, + guchar *dest_ptr) +{ + gint32 remaining = length; + gint i, j; + + length = 0; + while (remaining > 0) + { + /* Look for characters matching the first */ + + i = 0; + while ((i < 128) && + (remaining - i > 0) && + (start[0] == start[i])) + i++; + + if (i > 1) /* Match found */ + { + + *dest_ptr++ = -(i - 1); + *dest_ptr++ = *start; + + start += i; + remaining -= i; + length += 2; + } + else /* Look for characters different from the previous */ + { + i = 0; + while ((i < 128) && + (remaining - (i + 1) > 0) && + (start[i] != start[i + 1] || + remaining - (i + 2) <= 0 || start[i] != start[i+2])) + i++; + + /* If there's only 1 remaining, the previous WHILE stmt doesn't + catch it */ + + if (remaining == 1) + { + i = 1; + } + + if (i > 0) /* Some distinct ones found */ + { + *dest_ptr++ = i - 1; + for (j = 0; j < i; j++) + { + *dest_ptr++ = start[j]; + } + start += i; + remaining -= i; + length += i + 1; + } + + } + } + return length; +} + +static gint +gimpBaseTypeToPsdMode (GimpImageBaseType gimpBaseType) +{ + switch (gimpBaseType) + { + case GIMP_RGB: + return 3; /* RGB */ + case GIMP_GRAY: + return 1; /* Grayscale */ + case GIMP_INDEXED: + return 2; /* Indexed */ + default: + g_message (_("Error: Can't convert GIMP base imagetype to PSD mode")); + IFDBG printf ("PSD Export: gimpBaseType value is %d, " + "can't convert to PSD mode", gimpBaseType); + gimp_quit (); + return 3; /* Return RGB by default */ + } +} + +static gint +nChansLayer (gint gimpBaseType, + gint hasAlpha, + gint hasMask) +{ + gint incAlpha = 0; + gint incMask = 0; + + incAlpha = (hasAlpha == 0) ? 0 : 1; + incMask = (hasMask == 0) ? 0 : 1; + + switch (gimpBaseType) + { + case GIMP_RGB: + return 3 + incAlpha + incMask; /* R,G,B & Alpha & Mask (if any) */ + case GIMP_GRAY: + return 1 + incAlpha + incMask; /* G & Alpha & Mask (if any) */ + case GIMP_INDEXED: + return 1 + incAlpha + incMask; /* I & Alpha & Mask (if any) */ + default: + return 0; /* Return 0 channels by default */ + } +} + +static void +reshuffle_cmap_write (guchar *mapGimp) +{ + guchar *mapPSD; + gint i; + + mapPSD = g_malloc (768); + + for (i = 0; i < 256; i++) + { + mapPSD[i] = mapGimp[i * 3]; + mapPSD[i + 256] = mapGimp[i * 3 + 1]; + mapPSD[i + 512] = mapGimp[i * 3 + 2]; + } + + for (i = 0; i < 768; i++) + { + mapGimp[i] = mapPSD[i]; + } + + g_free (mapPSD); +} + +static void +save_header (FILE *fd, + gint32 image_id) +{ + IFDBG printf (" Function: save_header\n"); + IFDBG printf ("\tRows: %d\n", PSDImageData.image_height); + IFDBG printf ("\tColumns: %d\n", PSDImageData.image_width); + IFDBG printf ("\tBase type: %d\n", PSDImageData.baseType); + IFDBG printf ("\tNumber of channels: %d\n", PSDImageData.nChannels); + + xfwrite (fd, "8BPS", 4, "signature"); + write_gint16 (fd, 1, "version"); + write_gint32 (fd, 0, "reserved 1"); /* 6 for the 'reserved' field + 4 bytes for a long */ + write_gint16 (fd, 0, "reserved 1"); /* and 2 bytes for a short */ + write_gint16 (fd, (PSDImageData.nChannels + + nChansLayer (PSDImageData.baseType, + gimp_drawable_has_alpha (PSDImageData.merged_layer), 0)), + "channels"); + write_gint32 (fd, PSDImageData.image_height, "rows"); + write_gint32 (fd, PSDImageData.image_width, "columns"); + write_gint16 (fd, 8 * get_bpc (image_id), "depth"); + write_gint16 (fd, gimpBaseTypeToPsdMode (PSDImageData.baseType), "mode"); +} + +static void +save_color_mode_data (FILE *fd, + gint32 image_id) +{ + guchar *cmap; + guchar *cmap_modified; + gint i; + gint32 nColors; + + IFDBG printf (" Function: save_color_mode_data\n"); + + switch (PSDImageData.baseType) + { + case GIMP_INDEXED: + IFDBG printf ("\tImage type: INDEXED\n"); + + cmap = gimp_image_get_colormap (image_id, &nColors); + IFDBG printf ("\t\tLength of colormap returned by gimp_image_get_colormap: %d\n", nColors); + + if (nColors == 0) + { + IFDBG printf ("\t\tThe indexed image lacks a colormap\n"); + write_gint32 (fd, 0, "color data length"); + } + else if (nColors != 256) + { + IFDBG printf ("\t\tThe indexed image has %d!=256 colors\n", nColors); + IFDBG printf ("\t\tPadding with zeros up to 256\n"); + write_gint32 (fd, 768, "color data length"); + /* For this type, length is always 768 */ + + cmap_modified = g_malloc (768); + for (i = 0; i < nColors * 3; i++) + cmap_modified[i] = cmap[i]; + + for (i = nColors * 3; i < 768; i++) + cmap_modified[i] = 0; + + reshuffle_cmap_write (cmap_modified); + xfwrite (fd, cmap_modified, 768, "colormap"); /* Write readjusted colormap */ + + g_free (cmap_modified); + } + else /* nColors equals 256 */ + { + write_gint32 (fd, 768, "color data length"); /* For this type, length is always 768 */ + reshuffle_cmap_write (cmap); + xfwrite (fd, cmap, 768, "colormap"); /* Write readjusted colormap */ + } + break; + + default: + IFDBG printf ("\tImage type: Not INDEXED\n"); + write_gint32 (fd, 0, "color data length"); + } +} + +static void +save_resources (FILE *fd, + gint32 image_id) +{ + gint i; + gchar *fileName; /* Image file name */ + gint32 idActLayer; /* Id of the active layer */ + guint nActiveLayer = 0; /* Number of the active layer */ + gboolean ActiveLayerPresent; /* TRUE if there's an active layer */ + + glong eof_pos; /* Position for End of file */ + glong rsc_pos; /* Position for Lengths of Resources section */ + glong name_sec; /* Position for Lengths of Channel Names */ + + + /* Only relevant resources in GIMP are: 0x03EE, 0x03F0 & 0x0400 */ + /* For Adobe Photoshop version 4.0 these can also be considered: + 0x0408, 0x040A & 0x040B (1006, 1008, 1024, 1032, 1034, and 1035) */ + + IFDBG printf (" Function: save_resources\n"); + + + /* Get the image title from its filename */ + + fileName = gimp_image_get_filename (image_id); + IFDBG printf ("\tImage title: %s\n", fileName); + + /* Get the active layer number id */ + + idActLayer = gimp_image_get_active_layer (image_id); + IFDBG printf ("\tCurrent layer id: %d\n", idActLayer); + + ActiveLayerPresent = FALSE; + for (i = 0; i < PSDImageData.nLayers; i++) + if (idActLayer == PSDImageData.lLayers[i].id) + { + nActiveLayer = PSDImageData.nLayers - i - 1; + ActiveLayerPresent = TRUE; + break; + } + + if (ActiveLayerPresent) + { + IFDBG printf ("\t\tActive layer is number %d\n", nActiveLayer); + } + else + { + IFDBG printf ("\t\tNo active layer\n"); + } + + + /* Here's where actual writing starts */ + + rsc_pos = ftell (fd); + write_gint32 (fd, 0, "image resources length"); + + + /* --------------- Write Channel names --------------- */ + + if (PSDImageData.nChannels > 0 || + gimp_drawable_has_alpha (PSDImageData.merged_layer)) + { + xfwrite (fd, "8BIM", 4, "imageresources signature"); + write_gint16 (fd, 0x03EE, "0x03EE Id"); /* 1006 */ + /* write_pascalstring (fd, Name, "Id name"); */ + write_gint16 (fd, 0, "Id name"); /* Set to null string (two zeros) */ + + /* Mark current position in the file */ + + name_sec = ftell (fd); + write_gint32 (fd, 0, "0x03EE resource size"); + + /* Write all strings */ + + /* if the merged_image contains transparency, write a name for it first */ + if (gimp_drawable_has_alpha (PSDImageData.merged_layer)) + write_string (fd, "Transparency", "channel name"); + + for (i = 0; i < PSDImageData.nChannels; i++) + { + char *chName = gimp_item_get_name (PSDImageData.lChannels[i]); + write_string (fd, chName, "channel name"); + g_free (chName); + } + /* Calculate and write actual resource's length */ + + eof_pos = ftell (fd); + + fseek (fd, name_sec, SEEK_SET); + write_gint32 (fd, eof_pos - name_sec - sizeof (gint32), "0x03EE resource size"); + IFDBG printf ("\tTotal length of 0x03EE resource: %d\n", + (int) (eof_pos - name_sec - sizeof (gint32))); + + /* Return to EOF to continue writing */ + + fseek (fd, eof_pos, SEEK_SET); + + /* Pad if length is odd */ + + if ((eof_pos - name_sec - sizeof (gint32)) & 1) + write_gchar (fd, 0, "pad byte"); + } + + /* --------------- Write Channel properties --------------- */ + + if (PSDImageData.nChannels > 0 || + gimp_drawable_has_alpha (PSDImageData.merged_layer)) + { + xfwrite (fd, "8BIM", 4, "imageresources signature"); + write_gint16 (fd, 0x0435, "0x0435 Id"); /* 1077 */ + /* write_pascalstring (fd, Name, "Id name"); */ + write_gint16 (fd, 0, "Id name"); /* Set to null string (two zeros) */ + write_gint32 (fd, + 4 + + 13 * (gimp_drawable_has_alpha (PSDImageData.merged_layer) + + PSDImageData.nChannels), + "0x0435 resource size"); + + /* The function of the first 4 bytes is unclear. As per + * load_resource_1077() in psd-image-res-load.c, it seems to be a version + * number that is always one. + */ + write_gint32 (fd, 1, "0x0435 version"); + + /* Write all channel properties */ + + #define DOUBLE_TO_INT16(x) ROUND (SAFE_CLAMP (x, 0.0, 1.0) * 0xffff) + + /* if the merged_image contains transparency, write its properties first */ + if (gimp_drawable_has_alpha (PSDImageData.merged_layer)) + { + write_gint16 (fd, PSD_CS_RGB, "channel color space"); + write_gint16 (fd, DOUBLE_TO_INT16 (1.0), "channel color r"); + write_gint16 (fd, DOUBLE_TO_INT16 (0.0), "channel color g"); + write_gint16 (fd, DOUBLE_TO_INT16 (0.0), "channel color b"); + write_gint16 (fd, 0, "channel color padding"); + write_gint16 (fd, 100, "channel opacity"); + write_gchar (fd, 1, "channel mode"); + } + + for (i = 0; i < PSDImageData.nChannels; i++) + { + GimpRGB color; + gdouble opacity; + + gimp_channel_get_color (PSDImageData.lChannels[i], &color); + opacity = gimp_channel_get_opacity (PSDImageData.lChannels[i]); + + write_gint16 (fd, PSD_CS_RGB, "channel color space"); + write_gint16 (fd, DOUBLE_TO_INT16 (color.r), "channel color r"); + write_gint16 (fd, DOUBLE_TO_INT16 (color.g), "channel color g"); + write_gint16 (fd, DOUBLE_TO_INT16 (color.b), "channel color b"); + write_gint16 (fd, 0, "channel color padding"); + write_gint16 (fd, ROUND (opacity), "channel opacity"); + write_gchar (fd, 1, "channel mode"); + } + + #undef DOUBLE_TO_INT16 + + /* Pad if length is odd */ + + if (ftell (fd) & 1) + write_gchar (fd, 0, "pad byte"); + } + + /* --------------- Write Guides --------------- */ + if (gimp_image_find_next_guide(image_id, 0)) + { + gint n_guides = 0; + gint guide_id =0; + + /* Count the guides */ + while ((guide_id = gimp_image_find_next_guide(image_id, guide_id))) + n_guides++; + + xfwrite (fd, "8BIM", 4, "imageresources signature"); + write_gint16 (fd, 0x0408, "0x0408 Id (Guides)"); /* 1032 */ + /* write_pascalstring (fd, Name, "Id name"); */ + write_gint16 (fd, 0, "Id name"); /* Set to null string (two zeros) */ + write_gint32 (fd, 16 + 5 * n_guides, "0x0408 resource size"); + /* Save grid and guide header */ + write_gint32 (fd, 1, "grid/guide header version"); + write_gint32 (fd, 576, "grid custom spacing horizontal");/* dpi*32/4??*/ + write_gint32 (fd, 576, "grid custom spacing vertical"); /* dpi*32/4??*/ + write_gint32 (fd, n_guides, "number of guides"); + + /* write the guides */ + while ((guide_id = gimp_image_find_next_guide(image_id, guide_id))) + { + gchar orientation; + gint32 position; + orientation = gimp_image_get_guide_orientation(image_id, guide_id); + position = 32 * gimp_image_get_guide_position(image_id, guide_id); + orientation ^= 1; /* in the psd vert =0 , horiz = 1 */ + write_gint32 (fd, position, "Position of guide"); + write_gchar (fd, orientation, "Orientation of guide"); + n_guides--; + } + if ((ftell(fd) & 1)) + write_gchar(fd, 0, "pad byte"); + if (n_guides != 0) + g_warning("Screwed up guide resource:: wrong number of guides\n"); + IFDBG printf ("\tTotal length of 0x0400 resource: %d\n", (int) sizeof (gint16)); + } + + /* --------------- Write paths ------------------- */ + save_paths (fd, image_id); + + /* --------------- Write resolution data ------------------- */ + { + gdouble xres = 0, yres = 0; + guint32 xres_fix, yres_fix; + GimpUnit g_unit; + gint16 psd_unit; + + g_unit = gimp_image_get_unit (image_id); + gimp_image_get_resolution (image_id, &xres, &yres); + + if (g_unit == GIMP_UNIT_MM) + { + psd_unit = PSD_UNIT_CM; + } + else + { + psd_unit = PSD_UNIT_INCH; + } + + /* Don't convert resolution based on g_unit which is a display unit. + * PSD resolution is always in pixels/inch. */ + xres_fix = xres * 65536.0 + .5; /* Convert to 16.16 fixed point */ + yres_fix = yres * 65536.0 + .5; /* Convert to 16.16 fixed point */ + + xfwrite (fd, "8BIM", 4, "imageresources signature (for resolution)"); + write_gint16(fd, 0x03ed, "0x03ed Id (resolution)"); /* 1005 */ + write_gint16 (fd, 0, "Id name"); /* Set to null string (two zeros) */ + write_gint32 (fd, 16, "0x0400 resource size"); + write_gint32 (fd, xres_fix, "hRes (16.16 fixed point)"); + write_gint16 (fd, psd_unit, "hRes unit"); + write_gint16 (fd, psd_unit, "width unit"); + write_gint32 (fd, yres_fix, "vRes (16.16 fixed point)"); + write_gint16 (fd, psd_unit, "vRes unit"); + write_gint16 (fd, psd_unit, "height unit"); + } + + /* --------------- Write Active Layer Number --------------- */ + + if (ActiveLayerPresent) + { + xfwrite (fd, "8BIM", 4, "imageresources signature"); + write_gint16 (fd, 0x0400, "0x0400 Id"); /* 1024 */ + /* write_pascalstring (fd, Name, "Id name"); */ + write_gint16 (fd, 0, "Id name"); /* Set to null string (two zeros) */ + write_gint32 (fd, sizeof (gint16), "0x0400 resource size"); + + /* Save title as gint16 (length always even) */ + + write_gint16 (fd, nActiveLayer, "active layer"); + + IFDBG printf ("\tTotal length of 0x0400 resource: %d\n", (int) sizeof (gint16)); + } + + /* --------------- Write ICC profile data ------------------- */ + { + GimpColorProfile *profile; + + profile = gimp_image_get_effective_color_profile (image_id); + + if (profile) + { + const guint8 *icc_data; + gsize icc_length; + + icc_data = gimp_color_profile_get_icc_profile (profile, &icc_length); + + xfwrite (fd, "8BIM", 4, "imageresources signature"); + write_gint16 (fd, 0x040f, "0x040f Id"); + write_gint16 (fd, 0, "Id name"); /* Set to null string (two zeros) */ + write_gint32 (fd, icc_length, "0x040f resource size"); + xfwrite (fd, icc_data, icc_length, "ICC profile"); + + g_object_unref (profile); + } + } + + /* --------------- Write Total Section Length --------------- */ + + eof_pos = ftell (fd); + + fseek (fd, rsc_pos, SEEK_SET); + write_gint32 (fd, eof_pos - rsc_pos - sizeof (gint32), "image resources length"); + IFDBG printf ("\tResource section total length: %d\n", + (int) (eof_pos - rsc_pos - sizeof (gint32))); + + /* Return to EOF to continue writing */ + + fseek (fd, eof_pos, SEEK_SET); +} + +static int +get_compress_channel_data (guchar *channel_data, + gint32 channel_cols, + gint32 channel_rows, + gint32 stride, + gint32 bpc, + gint16 *LengthsTable, + guchar *remdata) +{ + gint i; + gint32 len; /* Length of compressed data */ + guchar *start; /* Starting position of a row in channel_data */ + + stride /= bpc; + + /* Pack channel data, and perform byte-order conversion */ + switch (bpc) + { + case 1: + { + if (stride > 1) + { + const guint8 *src = (const guint8 *) channel_data; + guint8 *dest = (guint8 *) channel_data; + + for (i = 0; i < channel_rows * channel_cols; i++) + { + *dest = *src; + + dest++; + src += stride; + } + } + } + break; + + case 2: + { + const guint16 *src = (const guint16 *) channel_data; + guint16 *dest = (guint16 *) channel_data; + + for (i = 0; i < channel_rows * channel_cols; i++) + { + *dest = GUINT16_TO_BE (*src); + + dest++; + src += stride; + } + } + break; + + case 4: + { + const guint32 *src = (const guint32 *) channel_data; + guint32 *dest = (guint32 *) channel_data; + + for (i = 0; i < channel_rows * channel_cols; i++) + { + *dest = GUINT32_TO_BE (*src); + + dest++; + src += stride; + } + } + break; + + default: + g_return_val_if_reached (0); + } + + /* For every row in the channel */ + + len = 0; + for (i = 0; i < channel_rows; i++) + { + start = channel_data + i * channel_cols * bpc; + + /* Create packed data for this row */ + LengthsTable[i] = pack_pb_line (start, channel_cols * bpc, + &remdata[len]); + len += LengthsTable[i]; + } + + /* return((len + channel_rows * sizeof (gint16)) + sizeof (gint16));*/ + return len; +} + +/* Ported /from plug-ins/file-tiff/file-tiff-save.c */ +static void +double_to_psd_fixed (gdouble value, + gchar *target) +{ + gdouble in, frac; + gint i, f; + + frac = modf (value, &in); + if (frac < 0) + { + in -= 1; + frac += 1; + } + + i = (gint) CLAMP (in, -16, 15); + f = CLAMP ((gint) (frac * 0xFFFFFF), 0, 0xFFFFFF); + + target[0] = i & 0xFF; + target[1] = (f >> 16) & 0xFF; + target[2] = (f >> 8) & 0xFF; + target[3] = f & 0xFF; +} + +/* Ported from /plug-ins/file-tiff/file-tiff-save.c */ +static void +save_paths (FILE *fd, + gint32 image_id) +{ + gshort id = 0x07D0; /* Photoshop paths have IDs >= 2000 */ + gdouble width = gimp_image_width (image_id); + gdouble height = gimp_image_height (image_id); + gint num_vectors; + gint *vectors; + gint v; + gint num_strokes; + gint *strokes; + gint s; + + vectors = gimp_image_get_vectors (image_id, &num_vectors); + + if (num_vectors <= 0) + return; + + /* Only up to 997 paths supported */ + for (v = 0; v < MIN (num_vectors, 1000); v++) + { + GString *data; + gchar *name, *nameend; + gsize len; + gint lenpos; + gchar pointrecord[26] = { 0, }; + gchar *tmpname; + GError *err = NULL; + + data = g_string_new ("8BIM"); + g_string_append_c (data, id / 256); + g_string_append_c (data, id % 256); + + /* + * - use iso8859-1 if possible + * - otherwise use UTF-8, prepended with \xef\xbb\xbf (Byte-Order-Mark) + */ + name = gimp_item_get_name (vectors[v]); + tmpname = g_convert (name, -1, "iso8859-1", "utf-8", NULL, &len, &err); + + if (tmpname && err == NULL) + { + g_string_append_c (data, MIN (len, 255)); + g_string_append_len (data, tmpname, MIN (len, 255)); + g_free (tmpname); + } + else + { + /* conversion failed, we fall back to UTF-8 */ + len = g_utf8_strlen (name, 255 - 3); /* need three marker-bytes */ + + nameend = g_utf8_offset_to_pointer (name, len); + len = nameend - name; /* in bytes */ + g_assert (len + 3 <= 255); + + g_string_append_c (data, len + 3); + g_string_append_len (data, "\xEF\xBB\xBF", 3); /* Unicode 0xfeff */ + g_string_append_len (data, name, len); + + if (tmpname) + g_free (tmpname); + } + + if (data->len % 2) /* padding to even size */ + g_string_append_c (data, 0); + g_free (name); + + lenpos = data->len; + g_string_append_len (data, "\0\0\0\0", 4); /* will be filled in later */ + len = data->len; /* to calculate the data size later */ + + pointrecord[1] = 6; /* fill rule record */ + g_string_append_len (data, pointrecord, 26); + + strokes = gimp_vectors_get_strokes (vectors[v], &num_strokes); + + for (s = 0; s < num_strokes; s++) + { + GimpVectorsStrokeType type; + gdouble *points; + gint num_points; + gboolean closed; + gint p = 0; + + type = gimp_vectors_stroke_get_points (vectors[v], strokes[s], + &num_points, &points, &closed); + + if (type != GIMP_VECTORS_STROKE_TYPE_BEZIER || + num_points > 65535 || + num_points % 6) + { + g_printerr ("psd-save: unsupported stroke type: " + "%d (%d points)\n", type, num_points); + continue; + } + + memset (pointrecord, 0, 26); + pointrecord[1] = closed ? 0 : 3; + pointrecord[2] = (num_points / 6) / 256; + pointrecord[3] = (num_points / 6) % 256; + g_string_append_len (data, pointrecord, 26); + + for (p = 0; p < num_points; p += 6) + { + pointrecord[1] = closed ? 2 : 5; + + double_to_psd_fixed (points[p+1] / height, pointrecord + 2); + double_to_psd_fixed (points[p+0] / width, pointrecord + 6); + double_to_psd_fixed (points[p+3] / height, pointrecord + 10); + double_to_psd_fixed (points[p+2] / width, pointrecord + 14); + double_to_psd_fixed (points[p+5] / height, pointrecord + 18); + double_to_psd_fixed (points[p+4] / width, pointrecord + 22); + + g_string_append_len (data, pointrecord, 26); + } + } + + g_free (strokes); + + /* fix up the length */ + + len = data->len - len; + data->str[lenpos + 0] = (len & 0xFF000000) >> 24; + data->str[lenpos + 1] = (len & 0x00FF0000) >> 16; + data->str[lenpos + 2] = (len & 0x0000FF00) >> 8; + data->str[lenpos + 3] = (len & 0x000000FF) >> 0; + + xfwrite (fd, data->str, data->len, "path resources data"); + g_string_free (data, TRUE); + id += 0x01; + } + + g_free (vectors); +} + +static void +save_layer_and_mask (FILE *fd, + gint32 image_id) +{ + gint i,j; + gint idChannel; + gint offset_x; /* X offset for each layer */ + gint offset_y; /* Y offset for each layer */ + gint32 layerWidth; /* Width of each layer */ + gint32 layerHeight; /* Height of each layer */ + const gchar *blendMode; /* Blending mode of the layer */ + guchar layerOpacity; /* Opacity of the layer */ + guchar flags; /* Layer flags */ + gint nChannelsLayer; /* Number of channels of a layer */ + gint32 ChanSize; /* Data length for a channel */ + gchar *layerName; /* Layer name */ + gint mask; /* Layer mask */ + gint depth; /* Layer group nesting depth */ + gint bpc; /* Image BPC */ + + glong eof_pos; /* Position: End of file */ + glong ExtraDataPos; /* Position: Extra data length */ + glong LayerMaskPos; /* Position: Layer & Mask section length */ + glong LayerInfoPos; /* Position: Layer info section length*/ + glong **ChannelLengthPos; /* Position: Channel length */ + + + IFDBG printf (" Function: save_layer_and_mask\n"); + + /* Create first array dimension (layers, channels) */ + + ChannelLengthPos = g_newa (glong *, PSDImageData.nLayers); + + /* Layer and mask information section */ + + LayerMaskPos = ftell (fd); + write_gint32 (fd, 0, "layers & mask information length"); + + /* Layer info section */ + + LayerInfoPos = ftell (fd); + write_gint32 (fd, 0, "layers info section length"); + + /* Layer structure section */ + + if (gimp_drawable_has_alpha (PSDImageData.merged_layer)) + write_gint16 (fd, -PSDImageData.nLayers, "Layer structure count"); + else + write_gint16 (fd, PSDImageData.nLayers, "Layer structure count"); + + depth = 0; + + bpc = get_bpc (image_id); + + /* Layer records section */ + /* GIMP layers must be written in reverse order */ + + for (i = PSDImageData.nLayers - 1; i >= 0; i--) + { + gint hasMask = 0; + + if (PSDImageData.lLayers[i].type == PSD_LAYER_TYPE_LAYER) + { + gimp_drawable_offsets (PSDImageData.lLayers[i].id, &offset_x, &offset_y); + layerWidth = gimp_drawable_width (PSDImageData.lLayers[i].id); + layerHeight = gimp_drawable_height (PSDImageData.lLayers[i].id); + } + else + { + /* groups don't specify their dimensions, and have empty channel + * data + */ + offset_x = 0; + offset_y = 0; + layerWidth = 0; + layerHeight = 0; + } + + IFDBG printf ("\tLayer number: %d\n", i); + IFDBG + { + const gchar *type; + + switch (PSDImageData.lLayers[i].type) + { + case PSD_LAYER_TYPE_LAYER: type = "normal layer"; break; + case PSD_LAYER_TYPE_GROUP_START: type = "group start marker"; break; + case PSD_LAYER_TYPE_GROUP_END: type = "group end marker"; break; + } + + printf ("\t\tType: %s\n", type); + } + IFDBG printf ("\t\tX offset: %d\n", offset_x); + IFDBG printf ("\t\tY offset: %d\n", offset_y); + IFDBG printf ("\t\tWidth: %d\n", layerWidth); + IFDBG printf ("\t\tHeight: %d\n", layerHeight); + + write_gint32 (fd, offset_y, "Layer top"); + write_gint32 (fd, offset_x, "Layer left"); + write_gint32 (fd, offset_y + layerHeight, "Layer bottom"); + write_gint32 (fd, offset_x + layerWidth, "Layer right"); + + hasMask = (PSDImageData.lLayers[i].type != PSD_LAYER_TYPE_GROUP_END && + gimp_layer_get_mask (PSDImageData.lLayers[i].id) != -1); + nChannelsLayer = nChansLayer (PSDImageData.baseType, + gimp_drawable_has_alpha (PSDImageData.lLayers[i].id), + hasMask); + + + write_gint16 (fd, nChannelsLayer, "Number channels in the layer"); + IFDBG printf ("\t\tNumber of channels: %d\n", nChannelsLayer); + + /* Create second array dimension (layers, channels) */ + + ChannelLengthPos[i] = g_new (glong, nChannelsLayer); + + /* Try with gimp_drawable_bpp() */ + + for (j = 0; j < nChannelsLayer; j++) + { + if (gimp_drawable_has_alpha (PSDImageData.lLayers[i].id)) + idChannel = j - 1; + else + idChannel = j; + if (hasMask && (j+1 == nChannelsLayer)) /* Last channel ... */ + idChannel = -2; /* ... will be layer mask */ + + write_gint16 (fd, idChannel, "Channel ID"); + IFDBG printf ("\t\t\tChannel Identifier: %d\n", idChannel); + + /* Write the length assuming no compression. In case there is, + will modify it later when writing data. */ + + ChannelLengthPos[i][j] = ftell (fd); + ChanSize = sizeof (gint16) + (layerWidth * layerHeight * bpc); + + write_gint32 (fd, ChanSize, "Channel Size"); + IFDBG printf ("\t\t\tLength: %d\n", ChanSize); + } + + xfwrite (fd, "8BIM", 4, "blend mode signature"); + + blendMode = psd_lmode_layer (PSDImageData.lLayers[i].id, FALSE); + IFDBG printf ("\t\tBlend mode: %s\n", blendMode); + xfwrite (fd, blendMode, 4, "blend mode key"); + + layerOpacity = RINT ((gimp_layer_get_opacity (PSDImageData.lLayers[i].id) * 255.0) / 100.0); + IFDBG printf ("\t\tOpacity: %u\n", layerOpacity); + write_gchar (fd, layerOpacity, "Opacity"); + + if (gimp_layer_get_composite_mode (PSDImageData.lLayers[i].id) == GIMP_LAYER_COMPOSITE_CLIP_TO_BACKDROP) + write_gchar (fd, 1, "Clipping"); + else + write_gchar (fd, 0, "Clipping"); + + flags = 0; + if (gimp_layer_get_lock_alpha (PSDImageData.lLayers[i].id)) flags |= 1; + if (! gimp_item_get_visible (PSDImageData.lLayers[i].id)) flags |= 2; + if (PSDImageData.lLayers[i].type != PSD_LAYER_TYPE_LAYER) flags |= 0x18; + IFDBG printf ("\t\tFlags: %u\n", flags); + write_gchar (fd, flags, "Flags"); + + /* Padding byte to make the length even */ + write_gchar (fd, 0, "Filler"); + + ExtraDataPos = ftell (fd); /* Position of Extra Data size */ + write_gint32 (fd, 0, "Extra data size"); + + if (hasMask) + { + gint maskOffset_x; + gint maskOffset_y; + gint maskWidth; + gint maskHeight; + gboolean apply; + + mask = gimp_layer_get_mask (PSDImageData.lLayers[i].id); + + gimp_drawable_offsets (mask, &maskOffset_x, &maskOffset_y); + + maskWidth = gimp_drawable_width (mask); + maskHeight = gimp_drawable_height (mask); + apply = gimp_layer_get_apply_mask (PSDImageData.lLayers[i].id); + + IFDBG printf ("\t\tLayer mask size: %d\n", 20); + write_gint32 (fd, 20, "Layer mask size"); + write_gint32 (fd, maskOffset_y, "Layer mask top"); + write_gint32 (fd, maskOffset_x, "Layer mask left"); + write_gint32 (fd, maskOffset_y + maskHeight, "Layer mask bottom"); + write_gint32 (fd, maskOffset_x + maskWidth, "Layer mask right"); + write_gchar (fd, 0, "Layer mask default color"); + flags = (0 | /* position relative to layer */ + (apply ? 0 : 1) << 1 | /* layer mask disabled */ + 0 << 2); /* invert layer mask */ + write_gchar (fd, flags, "Layer mask flags"); + write_gint16 (fd, 0, "Layer mask Padding"); + } + else + { + /* NOTE Writing empty Layer mask / adjustment layer data */ + write_gint32 (fd, 0, "Layer mask size"); + IFDBG printf ("\t\tLayer mask size: %d\n", 0); + } + + /* NOTE Writing empty Layer blending ranges data */ + write_gint32 (fd, 0, "Layer blending size"); + IFDBG printf ("\t\tLayer blending size: %d\n", 0); + + if (PSDImageData.lLayers[i].type != PSD_LAYER_TYPE_GROUP_END) + layerName = gimp_item_get_name (PSDImageData.lLayers[i].id); + else + layerName = g_strdup ("</Layer group>"); + write_pascalstring (fd, layerName, 4, "layer name"); + IFDBG printf ("\t\tLayer name: %s\n", layerName); + + /* Additional layer information blocks */ + /* Unicode layer name */ + write_datablock_luni(fd, layerName, "luni extra data block"); + + g_free (layerName); + + /* Layer color tag */ + xfwrite (fd, "8BIMlclr", 8, "sheet color signature"); + write_gint32 (fd, 8, "sheet color size"); + write_gint16 (fd, + gimp_to_psd_layer_color_tag(gimp_item_get_color_tag(PSDImageData.lLayers[i].id)), + "sheet color code"); + write_gint16 (fd, 0, "sheet color unused value"); + write_gint16 (fd, 0, "sheet color unused value"); + write_gint16 (fd, 0, "sheet color unused value"); + + /* Group layer section divider */ + if (PSDImageData.lLayers[i].type != PSD_LAYER_TYPE_LAYER) + { + gint32 size; + gint32 type; + + size = 12; + + if (PSDImageData.lLayers[i].type == PSD_LAYER_TYPE_GROUP_START) + { + type = gimp_item_get_expanded (PSDImageData.lLayers[i].id) ? 1 : 2; + + depth--; + } + else + { + type = 3; + + depth++; + } + + blendMode = psd_lmode_layer (PSDImageData.lLayers[i].id, TRUE); + + if (type < 3 || depth <= 5) + { + xfwrite (fd, "8BIMlsct", 8, "section divider"); + } + else + { + /* layer groups whose nesting depth is above 5 are only supported + * by Photoshop CS5 and up, and their end markers use the + * (undocumented) "lsdk" key, instead of "lsct". + */ + xfwrite (fd, "8BIMlsdk", 8, "nested section divider"); + } + write_gint32 (fd, size, "section divider size"); + write_gint32 (fd, type, "section divider type"); + xfwrite (fd, "8BIM", 4, "section divider blend mode signature"); + xfwrite (fd, blendMode, 4, "section divider blend mode key"); + } + + /* Write real length for: Extra data */ + + eof_pos = ftell (fd); + + fseek (fd, ExtraDataPos, SEEK_SET); + write_gint32 (fd, eof_pos - ExtraDataPos - sizeof (gint32), "Extra data size"); + IFDBG printf ("\t\tExtraData size: %d\n", + (int) (eof_pos - ExtraDataPos - sizeof (gint32))); + + /* Return to EOF to continue writing */ + + fseek (fd, eof_pos, SEEK_SET); + } + + + /* Channel image data section */ + /* Gimp layers must be written in reverse order */ + + for (i = PSDImageData.nLayers - 1; i >= 0; i--) + { + gimp_progress_update ((PSDImageData.nLayers - i - 1.0) / (PSDImageData.nLayers + 1.0)); + + IFDBG printf ("\t\tWriting pixel data for layer slot %d\n", i); + write_pixel_data (fd, PSDImageData.lLayers[i].id, ChannelLengthPos[i], 0, + PSDImageData.lLayers[i].type != PSD_LAYER_TYPE_GROUP_END); + g_free (ChannelLengthPos[i]); + } + + gimp_progress_update (PSDImageData.nLayers / (PSDImageData.nLayers + 1.0)); + eof_pos = ftell (fd); + + /* Write actual size of Layer info section */ + + fseek (fd, LayerInfoPos, SEEK_SET); + write_gint32 (fd, eof_pos - LayerInfoPos - sizeof (gint32), "layers info section length"); + IFDBG printf ("\t\tTotal layers info section length: %d\n", + (int) (eof_pos - LayerInfoPos - sizeof (gint32))); + + /* Write actual size of Layer and mask information section */ + + fseek (fd, LayerMaskPos, SEEK_SET); + write_gint32 (fd, eof_pos - LayerMaskPos - sizeof (gint32), "layers & mask information length"); + IFDBG printf ("\t\tTotal layers & mask information length: %d\n", + (int) (eof_pos - LayerMaskPos - sizeof (gint32))); + + /* Return to EOF to continue writing */ + + fseek (fd, eof_pos, SEEK_SET); +} + +static void +write_pixel_data (FILE *fd, + gint32 drawableID, + glong *ChanLenPosition, + gint32 ltable_offset, + gboolean write_mask) +{ + GeglBuffer *buffer = gimp_drawable_get_buffer (drawableID); + const Babl *format; + gint32 maskID; + gint32 tile_height = gimp_tile_height (); + gint32 height = gegl_buffer_get_height (buffer); + gint32 width = gegl_buffer_get_width (buffer); + gint32 bytes; + gint32 components; + gint32 bpc; + gint32 colors; + gint32 y; + gint32 len; /* Length of compressed data */ + gint16 *LengthsTable; /* Lengths of every compressed row */ + guchar *rledata; /* Compressed data from a region */ + guchar *data; /* Temporary copy of pixel data */ + glong length_table_pos; /* position in file of the length table */ + int i, j; + + IFDBG printf (" Function: write_pixel_data, drw %d, lto %d\n", + drawableID, ltable_offset); + + if (write_mask) + maskID = gimp_layer_get_mask (drawableID); + else + maskID = -1; + + /* groups have empty channel data, but may have a mask */ + if (gimp_item_is_group (drawableID) && maskID == -1) + { + width = 0; + height = 0; + } + + if (gimp_item_is_channel (drawableID)) + format = get_channel_format (drawableID); + else + format = get_pixel_format (drawableID); + + bytes = babl_format_get_bytes_per_pixel (format); + components = babl_format_get_n_components (format); + bpc = bytes / components; + + colors = components; + + if (gimp_drawable_has_alpha (drawableID) && + ! gimp_drawable_is_indexed (drawableID)) + colors -= 1; + + LengthsTable = g_new (gint16, height); + rledata = g_new (guchar, (MIN (height, tile_height) * + (width + 10 + (width / 100))) * bpc); + + data = g_new (guchar, MIN (height, tile_height) * width * bytes); + + /* groups have empty channel data */ + if (gimp_item_is_group (drawableID)) + { + width = 0; + height = 0; + } + + for (i = 0; i < components; i++) + { + gint chan; + + len = 0; + + if (components != colors && ltable_offset == 0) /* Need to write alpha channel first, except in image data section */ + { + if (i == 0) + { + chan = components - 1; + } + else + { + chan = i - 1; + } + } + else + { + chan = i; + } + + if (ChanLenPosition) + { + write_gint16 (fd, 1, "Compression type (RLE)"); + len += 2; + } + + if (ltable_offset > 0) + { + length_table_pos = ltable_offset + 2 * chan * height; + } + else + { + length_table_pos = ftell(fd); + + xfwrite (fd, LengthsTable, height * sizeof(gint16), + "Dummy RLE length"); + len += height * sizeof(gint16); + IF_DEEP_DBG printf ("\t\t\t\t. ltable, pos %ld len %d\n", length_table_pos, len); + } + + for (y = 0; y < height; y += tile_height) + { + int tlen; + gegl_buffer_get (buffer, + GEGL_RECTANGLE (0, y, + width, + MIN (height - y, tile_height)), + 1.0, format, data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + tlen = get_compress_channel_data (&data[chan * bpc], + width, + MIN(height - y, tile_height), + bytes, bpc, + &LengthsTable[y], + rledata); + len += tlen; + xfwrite (fd, rledata, tlen, "Compressed pixel data"); + IF_DEEP_DBG printf ("\t\t\t\t. Writing compressed pixels, stream of %d\n", tlen); + } + + /* Write compressed lengths table */ + fseek (fd, length_table_pos, SEEK_SET); + for (j = 0; j < height; j++) /* write real length table */ + write_gint16 (fd, LengthsTable[j], "RLE length"); + + if (ChanLenPosition) /* Update total compressed length */ + { + fseek (fd, ChanLenPosition[i], SEEK_SET); + write_gint32 (fd, len, "channel data length"); + IFDBG printf ("\t\tUpdating data len to %d\n", len); + } + fseek (fd, 0, SEEK_END); + IF_DEEP_DBG printf ("\t\t\t\t. Cur pos %ld\n", ftell(fd)); + } + + /* Write layer mask, as last channel, id -2 */ + if (maskID != -1) + { + GeglBuffer *mbuffer = gimp_drawable_get_buffer (maskID); + const Babl *mformat = get_mask_format(maskID); + + width = gegl_buffer_get_width (buffer); + height = gegl_buffer_get_height (buffer); + + len = 0; + + if (ChanLenPosition) + { + write_gint16 (fd, 1, "Compression type (RLE)"); + len += 2; + IF_DEEP_DBG printf ("\t\t\t\t. ChanLenPos, len %d\n", len); + } + + if (ltable_offset > 0) + { + length_table_pos = ltable_offset + 2 * (components+1) * height; + IF_DEEP_DBG printf ("\t\t\t\t. ltable, pos %ld\n", + length_table_pos); + } + else + { + length_table_pos = ftell(fd); + + xfwrite (fd, LengthsTable, height * sizeof(gint16), + "Dummy RLE length"); + len += height * sizeof(gint16); + IF_DEEP_DBG printf ("\t\t\t\t. ltable, pos %ld len %d\n", + length_table_pos, len); + } + + for (y = 0; y < height; y += tile_height) + { + int tlen; + gegl_buffer_get (mbuffer, + GEGL_RECTANGLE (0, y, + width, + MIN (height - y, tile_height)), + 1.0, mformat, data, + GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); + tlen = get_compress_channel_data (&data[0], + width, + MIN(height - y, tile_height), + bpc, bpc, + &LengthsTable[y], + rledata); + len += tlen; + xfwrite (fd, rledata, tlen, "Compressed mask data"); + IF_DEEP_DBG printf ("\t\t\t\t. Writing compressed mask, stream of %d\n", tlen); + } + + /* Write compressed lengths table */ + fseek (fd, length_table_pos, SEEK_SET); /*POS WHERE???*/ + for (j = 0; j < height; j++) /* write real length table */ + { + write_gint16 (fd, LengthsTable[j], "RLE length"); + IF_DEEP_DBG printf ("\t\t\t\t. Updating RLE len %d\n", + LengthsTable[j]); + } + + if (ChanLenPosition) /* Update total compressed length */ + { + /* Mask follows other components so use that as offset. */ + fseek (fd, ChanLenPosition[components], SEEK_SET); + + write_gint32 (fd, len, "channel data length"); + IFDBG printf ("\t\tUpdating data len to %d, at %ld\n", len, ftell(fd)); + } + fseek (fd, 0, SEEK_END); + IF_DEEP_DBG printf ("\t\t\t\t. Cur pos %ld\n", ftell(fd)); + + g_object_unref (mbuffer); + } + + g_object_unref (buffer); + + g_free (data); + g_free (rledata); + g_free (LengthsTable); +} + +static void +save_data (FILE *fd, + gint32 image_id) +{ + gint ChanCount; + gint i, j; + gint32 imageHeight; /* Height of image */ + glong offset; /* offset in file of rle lengths */ + gint chan; + + IFDBG printf (" Function: save_data\n"); + + ChanCount = (PSDImageData.nChannels + + nChansLayer (PSDImageData.baseType, + gimp_drawable_has_alpha (PSDImageData.merged_layer), + 0)); + + imageHeight = gimp_image_height (image_id); + + write_gint16 (fd, 1, "RLE compression"); + + /* All line lengths go before the rle pixel data */ + + offset = ftell(fd); /* Offset in file of line lengths */ + + for (i = 0; i < ChanCount; i++) + for (j = 0; j < imageHeight; j++) + write_gint16 (fd, 0, "junk line lengths"); + + IFDBG printf ("\t\tWriting compressed image data\n"); + write_pixel_data (fd, PSDImageData.merged_layer, + NULL, offset, FALSE); + + chan = nChansLayer (PSDImageData.baseType, + gimp_drawable_has_alpha(PSDImageData.merged_layer), 0); + + for (i = 0; i < PSDImageData.nChannels; i++) + { + IFDBG printf ("\t\tWriting compressed channel data for channel %d\n", + i); + write_pixel_data (fd, PSDImageData.lChannels[i], NULL, + offset + 2*imageHeight*chan, FALSE); //check how imgs are channels here + chan++; + } +} + +static gint32 +create_merged_image (gint32 image_id) +{ + gint32 projection; + + projection = gimp_layer_new_from_visible (image_id, image_id, "psd-save"); + + if (! gimp_drawable_has_alpha (projection)) + return projection; + + if (gimp_image_base_type (image_id) != GIMP_INDEXED) + { + GeglBuffer *buffer = gimp_drawable_get_buffer (projection); + const Babl *format = get_pixel_format (projection); + gboolean transparency_found = FALSE; + gint bpp = babl_format_get_bytes_per_pixel (format); + GeglBufferIterator *iter; + + iter = gegl_buffer_iterator_new (buffer, NULL, 0, format, + GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1); + + while (gegl_buffer_iterator_next (iter)) + { + guchar *d = iter->items[0].data; + gint i; + + for (i = 0; i < iter->length; i++) + { + gint32 alpha = d[bpp - 1]; + + if (alpha < 255) + { + gint c; + + transparency_found = TRUE; + + /* blend against white, photoshop does this. */ + for (c = 0; c < bpp - 1; c++) + d[c] = ((guint32) d[c] * alpha + 128) / 255 + 255 - alpha; + } + + d += bpp; + } + } + + g_object_unref (buffer); + + if (! transparency_found) + gimp_layer_flatten (projection); + } + else + { + gimp_layer_flatten (projection); /* PSDs don't support transparency information in indexed images*/ + } + + return projection; +} + +static void +get_image_data (gint32 image_id) +{ + IFDBG printf (" Function: get_image_data\n"); + + PSDImageData.compression = FALSE; + + PSDImageData.image_height = gimp_image_height (image_id); + IFDBG printf ("\tGot number of rows: %d\n", PSDImageData.image_height); + + PSDImageData.image_width = gimp_image_width (image_id); + IFDBG printf ("\tGot number of cols: %d\n", PSDImageData.image_width); + + PSDImageData.baseType = gimp_image_base_type (image_id); + IFDBG printf ("\tGot base type: %d\n", PSDImageData.baseType); + + PSDImageData.merged_layer = create_merged_image (image_id); + + PSDImageData.lChannels = gimp_image_get_channels (image_id, + &PSDImageData.nChannels); + IFDBG printf ("\tGot number of channels: %d\n", PSDImageData.nChannels); + + PSDImageData.lLayers = image_get_all_layers (image_id, + &PSDImageData.nLayers); + IFDBG printf ("\tGot number of layers: %d\n", PSDImageData.nLayers); +} + +static void +clear_image_data (void) +{ + IFDBG printf (" Function: clear_image_data\n"); + + g_free (PSDImageData.lChannels); + PSDImageData.lChannels = NULL; + + g_free (PSDImageData.lLayers); + PSDImageData.lLayers = NULL; +} + +gboolean +save_image (const gchar *filename, + gint32 image_id, + GError **error) +{ + FILE *fd; + gint i; + GeglBuffer *buffer; + + IFDBG printf (" Function: save_image\n"); + + if (gimp_image_width (image_id) > 30000 || + gimp_image_height (image_id) > 30000) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unable to export '%s'. The PSD file format does not " + "support images that are more than 30,000 pixels wide " + "or tall."), + gimp_filename_to_utf8 (filename)); + return FALSE; + } + + gimp_progress_init_printf (_("Exporting '%s'"), + gimp_filename_to_utf8 (filename)); + + get_image_data (image_id); + + /* Need to check each of the layers size individually also */ + for (i = 0; i < PSDImageData.nLayers; i++) + { + if (PSDImageData.lLayers[i].type == PSD_LAYER_TYPE_LAYER) + { + buffer = gimp_drawable_get_buffer (PSDImageData.lLayers[i].id); + if (gegl_buffer_get_width (buffer) > 30000 || gegl_buffer_get_height (buffer) > 30000) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Unable to export '%s'. The PSD file format does not " + "support images with layers that are more than 30,000 " + "pixels wide or tall."), + gimp_filename_to_utf8 (filename)); + clear_image_data (); + return FALSE; + } + g_object_unref (buffer); + } + } + + fd = g_fopen (filename, "wb"); + if (fd == NULL) + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for writing: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + clear_image_data (); + return FALSE; + } + + IFDBG g_print ("\tFile '%s' has been opened\n", + gimp_filename_to_utf8 (filename)); + + save_header (fd, image_id); + save_color_mode_data (fd, image_id); + save_resources (fd, image_id); + + /* PSD format does not support layers in indexed images */ + + if (PSDImageData.baseType == GIMP_INDEXED) + write_gint32 (fd, 0, "layers info section length"); + else + save_layer_and_mask (fd, image_id); + + /* If this is an indexed image, write now channel and layer info */ + + save_data (fd, image_id); + + /* Delete merged image now */ + + gimp_item_delete (PSDImageData.merged_layer); + + clear_image_data (); + + IFDBG printf ("----- Closing PSD file, done -----\n\n"); + + fclose (fd); + + gimp_progress_update (1.0); + + return TRUE; +} + +static gint +get_bpc (gint32 image_id) +{ + switch (gimp_image_get_precision (image_id)) + { + case GIMP_PRECISION_U8_LINEAR: + case GIMP_PRECISION_U8_GAMMA: + return 1; + + case GIMP_PRECISION_U16_LINEAR: + case GIMP_PRECISION_U16_GAMMA: + case GIMP_PRECISION_HALF_LINEAR: + case GIMP_PRECISION_HALF_GAMMA: + return 2; + + case GIMP_PRECISION_U32_LINEAR: + case GIMP_PRECISION_U32_GAMMA: + case GIMP_PRECISION_FLOAT_LINEAR: + case GIMP_PRECISION_FLOAT_GAMMA: + default: + /* FIXME: we *should* encode the image as u32 in this case, but simply + * using the same code as for the other cases produces invalid psd files + * (they're rejected by photoshop, although they can be read by the + * corresponding psd-load.c code, which in turn can't actually read + * photoshop-generated u32 files.) + * + * simply encode the image as u16 for now. + */ + /* return 4; */ + return 2; + } +} + +static const Babl * +get_pixel_format (gint32 drawableID) +{ + gint32 image_id = gimp_item_get_image (drawableID); + const gchar *model; + gint bpc; + gchar format[32]; + + switch (gimp_drawable_type (drawableID)) + { + case GIMP_GRAY_IMAGE: + model = "Y'"; + break; + + case GIMP_GRAYA_IMAGE: + model = "Y'A"; + break; + + case GIMP_RGB_IMAGE: + model = "R'G'B'"; + break; + + case GIMP_RGBA_IMAGE: + model = "R'G'B'A"; + break; + + case GIMP_INDEXED_IMAGE: + case GIMP_INDEXEDA_IMAGE: + return gimp_drawable_get_format (drawableID); + + default: + g_return_val_if_reached (NULL); + } + + bpc = get_bpc (image_id); + + sprintf (format, "%s u%d", model, 8 * bpc); + + return babl_format (format); +} + +static const Babl * +get_channel_format (gint32 drawableID) +{ + gint32 image_id = gimp_item_get_image (drawableID); + gint bpc; + gchar format[32]; + + /* see gimp_image_get_channel_format() */ + if (gimp_image_get_precision (image_id) == GIMP_PRECISION_U8_GAMMA) + return babl_format ("Y' u8"); + + bpc = get_bpc (image_id); + + sprintf (format, "Y u%d", 8 * bpc); + + return babl_format (format); +} + +static const Babl * +get_mask_format (gint32 drawableID) +{ + gint32 image_id = gimp_item_get_image (drawableID); + gint bpc; + gchar format[32]; + + bpc = get_bpc (image_id); + + sprintf (format, "Y u%d", 8 * bpc); + + return babl_format (format); +} + +static void +append_layers (const gint *layers, + gint n_layers, + GArray *array) +{ + gint i; + + for (i = 0; i < n_layers; i++) + { + PSD_Layer layer = {}; + gboolean is_group; + + layer.id = layers[i]; + + is_group = gimp_item_is_group (layer.id); + + if (! is_group) + layer.type = PSD_LAYER_TYPE_LAYER; + else + layer.type = PSD_LAYER_TYPE_GROUP_START; + + g_array_append_val (array, layer); + + if (is_group) + { + gint32 *group_layers; + gint n; + + group_layers = gimp_item_get_children (layer.id, &n); + append_layers (group_layers, n, array); + g_free (group_layers); + + layer.type = PSD_LAYER_TYPE_GROUP_END; + g_array_append_val (array, layer); + } + } +} + +static PSD_Layer * +image_get_all_layers (gint32 image_id, + gint *n_layers) +{ + GArray *array = g_array_new (FALSE, FALSE, sizeof (PSD_Layer)); + gint32 *layers; + gint n; + + layers = gimp_image_get_layers (image_id, &n); + + append_layers (layers, n, array); + + g_free (layers); + + *n_layers = array->len; + + return (PSD_Layer *) g_array_free (array, FALSE); +} diff --git a/plug-ins/file-psd/psd-save.h b/plug-ins/file-psd/psd-save.h new file mode 100644 index 0000000..9308271 --- /dev/null +++ b/plug-ins/file-psd/psd-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 __PSD_SAVE_H__ +#define __PSD_SAVE_H__ + + +gboolean save_image (const gchar *filename, + gint32 image_id, + GError **error); + + +#endif /* __PSD_SAVE_H__ */ diff --git a/plug-ins/file-psd/psd-thumb-load.c b/plug-ins/file-psd/psd-thumb-load.c new file mode 100644 index 0000000..7ce646f --- /dev/null +++ b/plug-ins/file-psd/psd-thumb-load.c @@ -0,0 +1,309 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GIMP PSD Plug-in + * Copyright 2007 by John Marshall + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> +#include <errno.h> + +#include <glib/gstdio.h> +#include <libgimp/gimp.h> + +#include "psd.h" +#include "psd-util.h" +#include "psd-image-res-load.h" +#include "psd-thumb-load.h" + +#include "libgimp/stdplugins-intl.h" + +/* Local function prototypes */ +static gint read_header_block (PSDimage *img_a, + FILE *f, + GError **error); + +static gint read_color_mode_block (PSDimage *img_a, + FILE *f, + GError **error); + +static gint read_image_resource_block (PSDimage *img_a, + FILE *f, + GError **error); + +static gint32 create_gimp_image (PSDimage *img_a, + const gchar *filename); + +static gint add_image_resources (gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error); + +/* Main file load function */ +gint32 +load_thumbnail_image (const gchar *filename, + gint *width, + gint *height, + GError **load_error) +{ + FILE *f; + GStatBuf st; + PSDimage img_a; + gint32 image_id = -1; + GError *error = NULL; + + /* ----- Open PSD file ----- */ + if (g_stat (filename, &st) == -1) + return -1; + + gimp_progress_init_printf (_("Opening thumbnail for '%s'"), + gimp_filename_to_utf8 (filename)); + + IFDBG(1) g_debug ("Open file %s", gimp_filename_to_utf8 (filename)); + f = g_fopen (filename, "rb"); + if (f == NULL) + { + g_set_error (load_error, G_FILE_ERROR, g_file_error_from_errno (errno), + _("Could not open '%s' for reading: %s"), + gimp_filename_to_utf8 (filename), g_strerror (errno)); + return -1; + } + + /* ----- Read the PSD file Header block ----- */ + IFDBG(2) g_debug ("Read header block"); + if (read_header_block (&img_a, f, &error) < 0) + goto load_error; + gimp_progress_update (0.2); + + /* ----- Read the PSD file Color Mode block ----- */ + IFDBG(2) g_debug ("Read color mode block"); + if (read_color_mode_block (&img_a, f, &error) < 0) + goto load_error; + gimp_progress_update (0.4); + + /* ----- Read the PSD file Image Resource block ----- */ + IFDBG(2) g_debug ("Read image resource block"); + if (read_image_resource_block (&img_a, f, &error) < 0) + goto load_error; + gimp_progress_update (0.6); + + /* ----- Create GIMP image ----- */ + IFDBG(2) g_debug ("Create GIMP image"); + image_id = create_gimp_image (&img_a, filename); + if (image_id < 0) + goto load_error; + + /* ----- Add image resources ----- */ + IFDBG(2) g_debug ("Add image resources"); + if (add_image_resources (image_id, &img_a, f, &error) < 1) + goto load_error; + gimp_progress_update (1.0); + + gimp_image_clean_all (image_id); + gimp_image_undo_enable (image_id); + fclose (f); + + *width = img_a.columns; + *height = img_a.rows; + return image_id; + + /* ----- Process load errors ----- */ + load_error: + if (error) + { + g_set_error (load_error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + _("Error loading PSD file: %s"), error->message); + g_error_free (error); + } + + /* Delete partially loaded image */ + if (image_id > 0) + gimp_image_delete (image_id); + + /* Close file if Open */ + if (! (f == NULL)) + fclose (f); + + return -1; +} + + +/* Local functions */ + +static gint +read_header_block (PSDimage *img_a, + FILE *f, + GError **error) +{ + guint16 version; + gchar sig[4]; + gchar buf[6]; + + if (fread (sig, 4, 1, f) < 1 + || fread (&version, 2, 1, f) < 1 + || fread (buf, 6, 1, f) < 1 + || fread (&img_a->channels, 2, 1, f) < 1 + || fread (&img_a->rows, 4, 1, f) < 1 + || fread (&img_a->columns, 4, 1, f) < 1 + || fread (&img_a->bps, 2, 1, f) < 1 + || fread (&img_a->color_mode, 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + version = GUINT16_FROM_BE (version); + img_a->channels = GUINT16_FROM_BE (img_a->channels); + img_a->rows = GUINT32_FROM_BE (img_a->rows); + img_a->columns = GUINT32_FROM_BE (img_a->columns); + img_a->bps = GUINT16_FROM_BE (img_a->bps); + img_a->color_mode = GUINT16_FROM_BE (img_a->color_mode); + + IFDBG(1) g_debug ("\n\n\tSig: %.4s\n\tVer: %d\n\tChannels: " + "%d\n\tSize: %dx%d\n\tBPS: %d\n\tMode: %d\n", + sig, version, img_a->channels, + img_a->columns, img_a->rows, + img_a->bps, img_a->color_mode); + + if (memcmp (sig, "8BPS", 4) != 0) + return -1; + + if (version != 1) + return -1; + + if (img_a->channels > MAX_CHANNELS) + return -1; + + if (img_a->rows < 1 || img_a->rows > GIMP_MAX_IMAGE_SIZE) + return -1; + + if (img_a->columns < 1 || img_a->columns > GIMP_MAX_IMAGE_SIZE) + return -1; + + return 0; +} + +static gint +read_color_mode_block (PSDimage *img_a, + FILE *f, + GError **error) +{ + guint32 block_len; + guint32 block_start; + guint32 block_end; + + if (fread (&block_len, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + block_len = GUINT32_FROM_BE (block_len); + + block_start = ftell (f); + block_end = block_start + block_len; + + if (fseek (f, block_end, SEEK_SET) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + return 0; +} + +static gint +read_image_resource_block (PSDimage *img_a, + FILE *f, + GError **error) +{ + guint32 block_len; + guint32 block_end; + + if (fread (&block_len, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + img_a->image_res_len = GUINT32_FROM_BE (block_len); + + IFDBG(1) g_debug ("Image resource block size = %d", (int)img_a->image_res_len); + + img_a->image_res_start = ftell (f); + block_end = img_a->image_res_start + img_a->image_res_len; + + if (fseek (f, block_end, SEEK_SET) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + return 0; +} + +static gint32 +create_gimp_image (PSDimage *img_a, + const gchar *filename) +{ + gint32 image_id = -1; + + img_a->base_type = GIMP_RGB; + + /* Create gimp image */ + IFDBG(2) g_debug ("Create image"); + image_id = gimp_image_new (img_a->columns, img_a->rows, img_a->base_type); + + gimp_image_set_filename (image_id, filename); + gimp_image_undo_disable (image_id); + + return image_id; +} + +static gint +add_image_resources (gint32 image_id, + PSDimage *img_a, + FILE *f, + GError **error) +{ + PSDimageres res_a; + gint status; + + if (fseek (f, img_a->image_res_start, SEEK_SET) < 0) + { + psd_set_error (feof (f), errno, error); + return -1; + } + + while (ftell (f) < img_a->image_res_start + img_a->image_res_len) + { + if (get_image_resource_header (&res_a, f, error) < 0) + return -1; + + if (res_a.data_start + res_a.data_len > + img_a->image_res_start + img_a->image_res_len) + return 0; + + status = load_thumbnail_resource (&res_a, image_id, f, error); + /* Error */ + if (status < 0) + return -1; + /* Thumbnail Loaded */ + if (status > 0) + return 1; + } + + return 0; +} diff --git a/plug-ins/file-psd/psd-thumb-load.h b/plug-ins/file-psd/psd-thumb-load.h new file mode 100644 index 0000000..a5e1281 --- /dev/null +++ b/plug-ins/file-psd/psd-thumb-load.h @@ -0,0 +1,31 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GIMP PSD Plug-in + * Copyright 2007 by John Marshall + * + * 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 __PSD_THUMB_LOAD_H__ +#define __PSD_THUMB_LOAD_H__ + + +gint32 load_thumbnail_image (const gchar *filename, + gint *width, + gint *height, + GError **error); + + +#endif /* __PSD_THUMB_LOAD_H__ */ diff --git a/plug-ins/file-psd/psd-util.c b/plug-ins/file-psd/psd-util.c new file mode 100644 index 0000000..1eccdd6 --- /dev/null +++ b/plug-ins/file-psd/psd-util.c @@ -0,0 +1,933 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GIMP PSD Plug-in + * Copyright 2007 by John Marshall + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> +#include <errno.h> + +#include <glib/gstdio.h> +#include <libgimp/gimp.h> + +#include "psd.h" +#include "psd-util.h" + +#include "libgimp/stdplugins-intl.h" + +/* Local constants */ +#define MIN_RUN 3 + +/* Local types */ +typedef struct +{ + const gchar *name; + const gchar *psd_mode; + GimpLayerMode gimp_mode; + gboolean exact; /* does the modes behave (more-or-less) the same in + * Photoshop and in GIMP? + */ +} LayerModeMapping; + +/* Local function prototypes */ +static const gchar * get_enum_value_nick (GType type, + gint value); + +/* Local variables */ + +/* mapping table between Photoshop and GIMP modes. in case a mode matches more + * than one entry (in either direction), the first entry wins. + */ +static const LayerModeMapping layer_mode_map[] = +{ +/* Name PSD GIMP Exact? */ + + /* Normal (ps3) */ + { "Normal", "norm", GIMP_LAYER_MODE_NORMAL, TRUE }, + { "Normal", "norm", GIMP_LAYER_MODE_NORMAL_LEGACY, TRUE }, + + /* Dissolve (ps3) */ + { "Dissolve", "diss", GIMP_LAYER_MODE_DISSOLVE, TRUE }, + + /* Multiply (ps3) */ + { "Multiply", "mul ", GIMP_LAYER_MODE_MULTIPLY, TRUE }, + { "Multiply", "mul ", GIMP_LAYER_MODE_MULTIPLY_LEGACY, TRUE }, + + /* Screen (ps3) */ + { "Screen", "scrn", GIMP_LAYER_MODE_SCREEN, TRUE }, + { "Screen", "scrn", GIMP_LAYER_MODE_SCREEN_LEGACY, TRUE }, + + /* Overlay (ps3) */ + { "Overlay", "over", GIMP_LAYER_MODE_OVERLAY, TRUE }, + + /* Difference (ps3) */ + { "Difference", "diff", GIMP_LAYER_MODE_DIFFERENCE, TRUE }, + { "Difference", "diff", GIMP_LAYER_MODE_DIFFERENCE_LEGACY, TRUE }, + + /* Linear Dodge (cs2) */ + { "Linear Dodge", "lddg", GIMP_LAYER_MODE_ADDITION, TRUE }, + { "Linear Dodge", "lddg", GIMP_LAYER_MODE_ADDITION_LEGACY, TRUE }, + + /* Subtract (??) */ + { "Subtract", "fsub", GIMP_LAYER_MODE_SUBTRACT, TRUE }, + { "Subtract", "fsub", GIMP_LAYER_MODE_SUBTRACT_LEGACY, TRUE }, + + /* Darken (ps3) */ + { "Darken", "dark", GIMP_LAYER_MODE_DARKEN_ONLY, TRUE }, + { "Darken", "dark", GIMP_LAYER_MODE_DARKEN_ONLY_LEGACY, TRUE }, + + /* Lighten (ps3) */ + { "Ligten", "lite", GIMP_LAYER_MODE_LIGHTEN_ONLY, TRUE }, + { "Ligten", "lite", GIMP_LAYER_MODE_LIGHTEN_ONLY_LEGACY, TRUE }, + + /* Hue (ps3) */ + { "Hue", "hue ", GIMP_LAYER_MODE_LCH_HUE, FALSE }, + { "Hue", "hue ", GIMP_LAYER_MODE_HSV_HUE, FALSE }, + { "Hue", "hue ", GIMP_LAYER_MODE_HSV_HUE_LEGACY, FALSE }, + + /* Stauration (ps3) */ + { "Saturation", "sat ", GIMP_LAYER_MODE_LCH_CHROMA, FALSE }, + { "Saturation", "sat ", GIMP_LAYER_MODE_HSV_SATURATION, FALSE }, + { "Saturation", "sat ", GIMP_LAYER_MODE_HSV_SATURATION_LEGACY, FALSE }, + + /* Color (ps3) */ + { "Color", "colr", GIMP_LAYER_MODE_LCH_COLOR, FALSE }, + { "Color", "colr", GIMP_LAYER_MODE_HSL_COLOR, FALSE }, + { "Color", "colr", GIMP_LAYER_MODE_HSL_COLOR_LEGACY, FALSE }, + + /* Luminosity (ps3) */ + { "Luminosity", "lum ", GIMP_LAYER_MODE_LCH_LIGHTNESS, FALSE }, + { "Luminosity", "lum ", GIMP_LAYER_MODE_HSV_VALUE, FALSE }, + { "Luminosity", "lum ", GIMP_LAYER_MODE_HSV_VALUE_LEGACY, FALSE }, + { "Luminosity", "lum ", GIMP_LAYER_MODE_LUMINANCE, FALSE }, + + /* Divide (??) */ + { "Divide", "fdiv", GIMP_LAYER_MODE_DIVIDE, TRUE }, + { "Divide", "fdiv", GIMP_LAYER_MODE_DIVIDE_LEGACY, TRUE }, + + /* Color Dodge (ps6) */ + { "Color Dodge", "div ", GIMP_LAYER_MODE_DODGE, TRUE }, + { "Color Dodge", "div ", GIMP_LAYER_MODE_DODGE_LEGACY, TRUE }, + + /* Color Burn (ps6) */ + { "Color Burn", "idiv", GIMP_LAYER_MODE_BURN, TRUE }, + { "Color Burn", "idiv", GIMP_LAYER_MODE_BURN_LEGACY, TRUE }, + + /* Hard Light (ps3) */ + { "Hard Light", "hLit", GIMP_LAYER_MODE_HARDLIGHT, TRUE }, + { "Hard Light", "hLit", GIMP_LAYER_MODE_HARDLIGHT_LEGACY, TRUE }, + + /* Soft Light (ps3) */ + { "Soft Light", "sLit", GIMP_LAYER_MODE_SOFTLIGHT, FALSE }, + { "Soft Light", "sLit", GIMP_LAYER_MODE_SOFTLIGHT_LEGACY, FALSE }, + { "Soft Light", "sLit", GIMP_LAYER_MODE_OVERLAY_LEGACY, FALSE }, + + /* Vivid Light (ps7)*/ + { "Vivid Light", "vLit", GIMP_LAYER_MODE_VIVID_LIGHT, TRUE }, + + /* Pin Light (ps7)*/ + { "Pin Light", "pLit", GIMP_LAYER_MODE_PIN_LIGHT, TRUE }, + + /* Linear Light (ps7)*/ + { "Linear Light", "lLit", GIMP_LAYER_MODE_LINEAR_LIGHT, TRUE }, + + /* Hard Mix (CS)*/ + { "Hard Mix", "hMix", GIMP_LAYER_MODE_HARD_MIX, TRUE }, + + /* Exclusion (ps6) */ + { "Exclusion", "smud", GIMP_LAYER_MODE_EXCLUSION, TRUE }, + + /* Linear Burn (ps7)*/ + { "Linear Burn", "lbrn", GIMP_LAYER_MODE_LINEAR_BURN, TRUE }, + + /* Darker Color (??)*/ + { "Darker Color", "dkCl", GIMP_LAYER_MODE_LUMA_DARKEN_ONLY, FALSE }, + + /* Lighter Color (??)*/ + { "Lighter Color", "lgCl", GIMP_LAYER_MODE_LUMA_LIGHTEN_ONLY, FALSE }, + + /* Pass Through (CS)*/ + { "Pass Through", "pass", GIMP_LAYER_MODE_PASS_THROUGH, TRUE }, +}; + + +/* Utility function */ +void +psd_set_error (gboolean file_eof, + gint err_no, + GError **error) +{ + if (file_eof) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "%s", _("Unexpected end of file")); + } + else + { + g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (err_no), + "%s", g_strerror (err_no)); + } + + return; +} + +gchar * +fread_pascal_string (gint32 *bytes_read, + gint32 *bytes_written, + guint16 mod_len, + FILE *f, + GError **error) +{ + /* + * Reads a pascal string from the file padded to a multiple of mod_len + * and returns a utf-8 string. + */ + + gchar *str; + gchar *utf8_str; + guchar len; + gint32 padded_len; + + *bytes_read = 0; + *bytes_written = -1; + + if (fread (&len, 1, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + (*bytes_read)++; + IFDBG(3) g_debug ("Pascal string length %d", len); + + if (len == 0) + { + if (fseek (f, mod_len - 1, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + *bytes_read += (mod_len - 1); + *bytes_written = 0; + return NULL; + } + + str = g_malloc (len); + if (fread (str, len, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + g_free (str); + return NULL; + } + *bytes_read += len; + + if (mod_len > 0) + { + padded_len = len + 1; + while (padded_len % mod_len != 0) + { + if (fseek (f, 1, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + g_free (str); + return NULL; + } + (*bytes_read)++; + padded_len++; + } + } + + utf8_str = gimp_any_to_utf8 (str, len, NULL); + *bytes_written = strlen (utf8_str); + g_free (str); + + IFDBG(3) g_debug ("Pascal string: %s, bytes_read: %d, bytes_written: %d", + utf8_str, *bytes_read, *bytes_written); + + return utf8_str; +} + +gint32 +fwrite_pascal_string (const gchar *src, + guint16 mod_len, + FILE *f, + GError **error) +{ + /* + * Converts utf-8 string to current locale and writes as pascal + * string with padding to a multiple of mod_len. + */ + + gchar *str; + gchar *pascal_str; + gchar null_str = 0x0; + guchar pascal_len; + gint32 bytes_written = 0; + gsize len; + + if (src == NULL) + { + /* Write null string as two null bytes (0x0) */ + if (fwrite (&null_str, 1, 1, f) < 1 + || fwrite (&null_str, 1, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + bytes_written += 2; + } + else + { + str = g_locale_from_utf8 (src, -1, NULL, &len, NULL); + if (len > 255) + pascal_len = 255; + else + pascal_len = len; + pascal_str = g_strndup (str, pascal_len); + g_free (str); + if (fwrite (&pascal_len, 1, 1, f) < 1 + || fwrite (pascal_str, pascal_len, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + g_free (pascal_str); + return -1; + } + bytes_written++; + bytes_written += pascal_len; + IFDBG(2) g_debug ("Pascal string: %s, bytes_written: %d", + pascal_str, bytes_written); + g_free (pascal_str); + } + + /* Pad with nulls */ + if (mod_len > 0) + { + while (bytes_written % mod_len != 0) + { + if (fwrite (&null_str, 1, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + bytes_written++; + } + } + + return bytes_written; +} + +gchar * +fread_unicode_string (gint32 *bytes_read, + gint32 *bytes_written, + guint16 mod_len, + FILE *f, + GError **error) +{ + /* + * Reads a utf-16 string from the file padded to a multiple of mod_len + * and returns a utf-8 string. + */ + + gchar *utf8_str; + gunichar2 *utf16_str; + gint32 len; + gint32 i; + gint32 padded_len; + glong utf8_str_len; + + *bytes_read = 0; + *bytes_written = -1; + + if (fread (&len, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + *bytes_read += 4; + len = GINT32_FROM_BE (len); + IFDBG(3) g_debug ("Unicode string length %d", len); + + if (len == 0) + { + if (fseek (f, mod_len - 1, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + return NULL; + } + *bytes_read += (mod_len - 1); + *bytes_written = 0; + return NULL; + } + + utf16_str = g_malloc (len * 2); + for (i = 0; i < len; ++i) + { + if (fread (&utf16_str[i], 2, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + g_free (utf16_str); + return NULL; + } + *bytes_read += 2; + utf16_str[i] = GINT16_FROM_BE (utf16_str[i]); + } + + if (mod_len > 0) + { + padded_len = len + 1; + while (padded_len % mod_len != 0) + { + if (fseek (f, 1, SEEK_CUR) < 0) + { + psd_set_error (feof (f), errno, error); + g_free (utf16_str); + return NULL; + } + (*bytes_read)++; + padded_len++; + } + } + + utf8_str = g_utf16_to_utf8 (utf16_str, len, NULL, &utf8_str_len, NULL); + *bytes_written = utf8_str_len; + g_free (utf16_str); + + IFDBG(3) g_debug ("Unicode string: %s, bytes_read: %d, bytes_written: %d", + utf8_str, *bytes_read, *bytes_written); + + return utf8_str; +} + +gint32 +fwrite_unicode_string (const gchar *src, + guint16 mod_len, + FILE *f, + GError **error) +{ + /* + * Converts utf-8 string to utf-16 and writes 4 byte length + * then string padding to multiple of mod_len. + */ + + gunichar2 *utf16_str; + gchar null_str = 0x0; + gint32 utf16_len = 0; + gint32 bytes_written = 0; + gint i; + glong len; + + if (src == NULL) + { + /* Write null string as four byte 0 int32 */ + if (fwrite (&utf16_len, 4, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + bytes_written += 4; + } + else + { + utf16_str = g_utf8_to_utf16 (src, -1, NULL, &len, NULL); + /* Byte swap as required */ + utf16_len = len; + for (i = 0; i < utf16_len; ++i) + utf16_str[i] = GINT16_TO_BE (utf16_str[i]); + utf16_len = GINT32_TO_BE (utf16_len); + + if (fwrite (&utf16_len, 4, 1, f) < 1 + || fwrite (utf16_str, 2, utf16_len + 1, f) < utf16_len + 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + bytes_written += (4 + 2 * utf16_len + 2); + IFDBG(2) g_debug ("Unicode string: %s, bytes_written: %d", + src, bytes_written); + } + + /* Pad with nulls */ + if (mod_len > 0) + { + while (bytes_written % mod_len != 0) + { + if (fwrite (&null_str, 1, 1, f) < 1) + { + psd_set_error (feof (f), errno, error); + return -1; + } + bytes_written++; + } + } + + return bytes_written; +} + +gint +decode_packbits (const gchar *src, + gchar *dst, + guint16 packed_len, + guint32 unpacked_len) +{ + /* + * Decode a PackBits chunk. + */ + + gint n; + gint32 unpack_left = unpacked_len; + gint32 pack_left = packed_len; + gint32 error_code = 0; + gint32 return_val = 0; + + while (unpack_left > 0 && pack_left > 0) + { + n = *(const guchar *) src; + src++; + pack_left--; + + if (n == 128) /* nop */ + continue; + else if (n > 128) + n -= 256; + + if (n < 0) /* replicate next gchar |n|+ 1 times */ + { + n = 1 - n; + if (pack_left < 1) + { + IFDBG(2) g_debug ("Input buffer exhausted in replicate"); + error_code = 1; + break; + } + if (unpack_left < n) + { + IFDBG(2) g_debug ("Overrun in packbits replicate of %d chars", n - unpack_left); + error_code = 2; + } + memset (dst, *src, n); + src++; + pack_left--; + dst += n; + unpack_left -= n; + } + else /* copy next n+1 gchars literally */ + { + n++; + if (pack_left < n) + { + IFDBG(2) g_debug ("Input buffer exhausted in copy"); + error_code = 3; + break; + } + if (unpack_left < n) + { + IFDBG(2) g_debug ("Output buffer exhausted in copy"); + error_code = 4; + break; + } + memcpy (dst, src, n); + src += n; + pack_left -= n; + dst += n; + unpack_left -= n; + } + } + + if (unpack_left > 0) + { + /* Pad with zeros to end of output buffer */ + memset (dst, 0, unpack_left); + } + + if (unpack_left) + { + IFDBG(2) g_debug ("Packbits decode - unpack left %d", unpack_left); + return_val -= unpack_left; + } + if (pack_left) + { + /* Some images seem to have a pad byte at the end of the packed data */ + if (error_code || pack_left != 1) + { + IFDBG(2) g_debug ("Packbits decode - pack left %d", pack_left); + return_val = pack_left; + } + } + + IFDBG(2) + if (error_code) + g_debug ("Error code %d", error_code); + + return return_val; +} + +gchar * +encode_packbits (const gchar *src, + guint32 unpacked_len, + guint16 *packed_len) +{ + /* + * Encode a PackBits chunk. + */ + + GString *dst_str; /* destination string */ + gint curr_char; /* current character */ + gchar char_buff[128]; /* buffer of already read characters */ + guchar run_len; /* number of characters in a run */ + gint32 unpack_left = unpacked_len; + + IFDBG(2) g_debug ("Encode packbits"); + + /* Initialise destination string */ + dst_str = g_string_sized_new (unpacked_len); + + /* prime the read loop */ + curr_char = *src; + src++; + run_len = 0; + + /* read input until there's nothing left */ + while (unpack_left > 0) + { + char_buff[run_len] = (gchar) curr_char; + IFDBG(2) g_debug ("buff %x, run len %d, curr char %x", + char_buff[run_len], run_len, (gchar) curr_char); + run_len++; + + if (run_len >= MIN_RUN) + { + gint i; + + /* check for run */ + for (i = 2; i <= MIN_RUN; ++i) + { + if (curr_char != char_buff[run_len - i]) + { + /* no run */ + i = 0; + break; + } + } + + if (i != 0) + { + /* we have a run write out buffer before run*/ + gint next_char; + + if (run_len > MIN_RUN) + { + /* block size - 1 followed by contents */ + g_string_append_c (dst_str, (run_len - MIN_RUN - 1)); + g_string_append_len (dst_str, char_buff, (run_len - MIN_RUN)); + IFDBG(2) g_debug ("(1) Number of chars: %d, run length tag: %d", + run_len - MIN_RUN, run_len - MIN_RUN - 1); + } + + /* determine run length (MIN_RUN so far) */ + run_len = MIN_RUN; + + /* get next character */ + next_char = *src; + src++; + unpack_left--; + while (next_char == curr_char) + { + run_len++; + if (run_len == 128) + { + /* run is at max length */ + break; + } + next_char = *src; + src++; + unpack_left--; + } + + /* write out encoded run length and run symbol */ + g_string_append_c (dst_str, (gchar)(1 - (gint)(run_len))); + g_string_append_c (dst_str, curr_char); + IFDBG(2) g_debug ("(2) Number of chars: %d, run length tag: %d", + run_len, (1 - (gint)(run_len))); + + if (unpack_left > 0) + { + /* make run breaker start of next buffer */ + char_buff[0] = next_char; + run_len = 1; + } + else + { + /* file ends in a run */ + run_len = 0; + } + } + } + + if (run_len == 128) + { + /* write out buffer */ + g_string_append_c (dst_str, 127); + g_string_append_len (dst_str, char_buff, 128); + IFDBG(2) g_debug ("(3) Number of chars: 128, run length tag: 127"); + + /* start a new buffer */ + run_len = 0; + } + + curr_char = *src; + src++; + unpack_left--; + } + + /* write out last buffer */ + if (run_len != 0) + { + if (run_len <= 128) + { + /* write out entire copy buffer */ + g_string_append_c (dst_str, run_len - 1); + g_string_append_len (dst_str, char_buff, run_len); + IFDBG(2) g_debug ("(4) Number of chars: %d, run length tag: %d", + run_len, run_len - 1); + } + else + { + IFDBG(2) g_debug ("(5) Very bad - should not be here"); + } + } + + *packed_len = dst_str->len; + IFDBG(2) g_debug ("Packed len %d, unpacked %d", *packed_len, unpacked_len); + + return g_string_free (dst_str, FALSE); +} + +void +psd_to_gimp_blend_mode (PSDlayer *psd_layer, + LayerModeInfo *mode_info) +{ + gint i; + + mode_info->mode = GIMP_LAYER_MODE_NORMAL; + /* FIXME: use the image mode to select the correct color spaces. for now, + * we use rgb-perceptual blending/compositing unconditionally. + */ + mode_info->blend_space = GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL; + mode_info->composite_space = GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL; + if (psd_layer->clipping == 1) + mode_info->composite_mode = GIMP_LAYER_COMPOSITE_CLIP_TO_BACKDROP; + else + mode_info->composite_mode = GIMP_LAYER_COMPOSITE_UNION; + + for (i = 0; i < G_N_ELEMENTS (layer_mode_map); i++) + { + if (g_ascii_strncasecmp (psd_layer->blend_mode, layer_mode_map[i].psd_mode, 4) == 0) + { + if (! layer_mode_map[i].exact && CONVERSION_WARNINGS) + { + g_message ("GIMP uses a different equation than Photoshop for " + "blend mode: %s. Results will differ.", + layer_mode_map[i].name); + } + + mode_info->mode = layer_mode_map[i].gimp_mode; + + return; + } + } + + if (CONVERSION_WARNINGS) + { + gchar *mode_name = g_strndup (psd_layer->blend_mode, 4); + g_message ("Unsupported blend mode: %s. Mode reverts to normal", + mode_name); + g_free (mode_name); + } +} + +const gchar * +gimp_to_psd_blend_mode (const LayerModeInfo *mode_info) +{ + gint i; + + /* FIXME: select the image mode based on the layer mode color spaces. for + * now, we assume rgb-perceptual blending/compositing unconditionally. + */ + if (mode_info->blend_space != GIMP_LAYER_COLOR_SPACE_AUTO && + mode_info->blend_space != GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL) + { + if (CONVERSION_WARNINGS) + g_message ("Unsupported blend color space: %s. " + "Blend color space reverts to rgb-perceptual", + get_enum_value_nick (GIMP_TYPE_LAYER_COLOR_SPACE, + mode_info->blend_space)); + } + + if (mode_info->composite_space != GIMP_LAYER_COLOR_SPACE_AUTO && + mode_info->composite_space != GIMP_LAYER_COLOR_SPACE_RGB_PERCEPTUAL) + { + if (CONVERSION_WARNINGS) + g_message ("Unsupported composite color space: %s. " + "Composite color space reverts to rgb-perceptual", + get_enum_value_nick (GIMP_TYPE_LAYER_COLOR_SPACE, + mode_info->composite_space)); + } + + if (mode_info->composite_mode != GIMP_LAYER_COMPOSITE_AUTO && + mode_info->composite_mode != GIMP_LAYER_COMPOSITE_UNION && + mode_info->composite_mode != GIMP_LAYER_COMPOSITE_CLIP_TO_BACKDROP) + { + if (CONVERSION_WARNINGS) + g_message ("Unsupported composite mode: %s. " + "Composite mode reverts to union", + get_enum_value_nick (GIMP_TYPE_LAYER_COMPOSITE_MODE, + mode_info->composite_mode)); + } + + for (i = 0; i < G_N_ELEMENTS (layer_mode_map); i++) + { + if (layer_mode_map[i].gimp_mode == mode_info->mode) + { + if (! layer_mode_map[i].exact && CONVERSION_WARNINGS) + { + g_message ("GIMP uses a different equation than Photoshop for " + "blend mode: %s. Results may differ.", + get_enum_value_nick (GIMP_TYPE_LAYER_MODE, + mode_info->mode)); + } + + return layer_mode_map[i].psd_mode; + } + } + + if (CONVERSION_WARNINGS) + g_message ("Unsupported blend mode: %s. Mode reverts to normal", + get_enum_value_nick (GIMP_TYPE_LAYER_MODE, mode_info->mode)); + + return "norm"; +} + +GimpColorTag +psd_to_gimp_layer_color_tag (guint16 layer_color_tag) +{ + GimpColorTag colorTag; + + switch (layer_color_tag) + { + case 1: + colorTag = GIMP_COLOR_TAG_RED; + break; + + case 2: + colorTag = GIMP_COLOR_TAG_ORANGE; + break; + + case 3: + colorTag = GIMP_COLOR_TAG_YELLOW; + break; + + case 4: + colorTag = GIMP_COLOR_TAG_GREEN; + break; + + case 5: + colorTag = GIMP_COLOR_TAG_BLUE; + break; + + case 6: + colorTag = GIMP_COLOR_TAG_VIOLET; + break; + + case 7: + colorTag = GIMP_COLOR_TAG_GRAY; + break; + + default: + if (CONVERSION_WARNINGS) + g_message ("Unsupported Photoshop layer color tag: %i. GIMP layer color tag set to none.", + layer_color_tag); + colorTag = GIMP_COLOR_TAG_NONE; + } + + return colorTag; +} + +guint16 +gimp_to_psd_layer_color_tag (GimpColorTag layer_color_tag) +{ + guint16 color_tag; + + switch (layer_color_tag) + { + case GIMP_COLOR_TAG_RED: + color_tag = 1; + break; + + case GIMP_COLOR_TAG_ORANGE: + color_tag = 2; + break; + + case GIMP_COLOR_TAG_YELLOW: + color_tag = 3; + break; + + case GIMP_COLOR_TAG_GREEN: + color_tag = 4; + break; + + case GIMP_COLOR_TAG_BLUE: + color_tag = 5; + break; + + case GIMP_COLOR_TAG_VIOLET: + color_tag = 6; + break; + + case GIMP_COLOR_TAG_GRAY: + color_tag = 7; + break; + + default: + if (CONVERSION_WARNINGS) + g_message ("Photoshop doesn't support GIMP layer color tag: %i. Photoshop layer color tag set to none.", + layer_color_tag); + color_tag = 0; + } + + return color_tag; +} + +static const gchar * +get_enum_value_nick (GType type, + gint value) +{ + const gchar *nick; + + if (gimp_enum_get_value (type, value, NULL, &nick, NULL, NULL)) + { + return nick; + } + else + { + static gchar err_name[32]; + + snprintf (err_name, sizeof (err_name), "UNKNOWN (%d)", value); + + return err_name; + } +} diff --git a/plug-ins/file-psd/psd-util.h b/plug-ins/file-psd/psd-util.h new file mode 100644 index 0000000..efa8aa7 --- /dev/null +++ b/plug-ins/file-psd/psd-util.h @@ -0,0 +1,87 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GIMP PSD Plug-in + * Copyright 2007 by John Marshall + * + * 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 __PSD_UTIL_H__ +#define __PSD_UTIL_H__ + +/* + * Set file read error + */ +void psd_set_error (gboolean file_eof, + gint err_no, + GError **error); + +/* + * Reads a pascal string from the file padded to a multiple of mod_len + * and returns a utf-8 string. + */ +gchar * fread_pascal_string (gint32 *bytes_read, + gint32 *bytes_written, + guint16 mod_len, + FILE *f, + GError **error); + +/* + * Converts utf-8 string to current locale and writes as pascal + * string with padding to a multiple of mod_len. + */ +gint32 fwrite_pascal_string (const gchar *src, + guint16 mod_len, + FILE *f, + GError **error); + +/* + * Reads a utf-16 string from the file padded to a multiple of mod_len + * and returns a utf-8 string. + */ +gchar * fread_unicode_string (gint32 *bytes_read, + gint32 *bytes_written, + guint16 mod_len, + FILE *f, + GError **error); + +/* + * Converts utf-8 string to utf-16 and writes 4 byte length + * then string padding to multiple of mod_len. + */ +gint32 fwrite_unicode_string (const gchar *src, + guint16 mod_len, + FILE *f, + GError **error); + +gint decode_packbits (const gchar *src, + gchar *dst, + guint16 packed_len, + guint32 unpacked_len); + +gchar * encode_packbits (const gchar *src, + guint32 unpacked_len, + guint16 *packed_len); + +void psd_to_gimp_blend_mode (PSDlayer *psd_layer, + LayerModeInfo *mode_info); + +const gchar * gimp_to_psd_blend_mode (const LayerModeInfo *mode_info); + +GimpColorTag psd_to_gimp_layer_color_tag (guint16 layer_color_tag); + +guint16 gimp_to_psd_layer_color_tag (GimpColorTag layer_color_tag); + +#endif /* __PSD_UTIL_H__ */ diff --git a/plug-ins/file-psd/psd.c b/plug-ins/file-psd/psd.c new file mode 100644 index 0000000..083a5a9 --- /dev/null +++ b/plug-ins/file-psd/psd.c @@ -0,0 +1,385 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GIMP PSD Plug-in + * Copyright 2007 by John Marshall + * + * 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 <libgimp/gimp.h> +#include <libgimp/gimpui.h> + +#include "psd.h" +#include "psd-load.h" +#include "psd-save.h" +#include "psd-thumb-load.h" + +#include "libgimp/stdplugins-intl.h" + + +/* Local function prototypes */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + + +/* Local variables */ + +GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + + +MAIN () + +static void +query (void) +{ + /* File Load */ + static const GimpParamDef load_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to load" } + }; + + static const GimpParamDef load_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Output image" } + }; + + /* Thumbnail Load */ + static const GimpParamDef thumb_args[] = + { + { GIMP_PDB_STRING, "filename", "The name of the file to load" }, + { GIMP_PDB_INT32, "thumb-size", "Preferred thumbnail size" } + }; + + static const GimpParamDef thumb_return_vals[] = + { + { GIMP_PDB_IMAGE, "image", "Thumbnail image" }, + { GIMP_PDB_INT32, "image-width", "Width of full-sized image" }, + { GIMP_PDB_INT32, "image-height", "Height of full-sized image" } + }; + + /* File save */ + static const GimpParamDef save_args[] = + { + { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" }, + { GIMP_PDB_DRAWABLE, "drawable", "Drawable to save" }, + { GIMP_PDB_STRING, "filename", "The name of the file to save the image in" }, + { GIMP_PDB_STRING, "raw-filename", "The name of the file to save the image in" }, + { GIMP_PDB_INT32, "compression", "Compression type: { NONE (0), LZW (1), PACKBITS (2)" }, + { GIMP_PDB_INT32, "fill-order", "Fill Order: { MSB to LSB (0), LSB to MSB (1)" } + }; + + /* File load */ + gimp_install_procedure (LOAD_PROC, + "Loads images from the Photoshop PSD file format", + "This plug-in loads images in Adobe " + "Photoshop (TM) native PSD format.", + "John Marshall", + "John Marshall", + "2007", + N_("Photoshop image"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_mime (LOAD_PROC, "image/x-psd"); + gimp_register_magic_load_handler (LOAD_PROC, + "psd", + "", + "0,string,8BPS"); + + /* File load (merged) */ + gimp_install_procedure (LOAD_MERGED_PROC, + "Loads merged images from the Photoshop PSD file format", + "This plug-in loads the merged image data in Adobe " + "Photoshop (TM) native PSD format.", + "Ell", + "Ell", + "2018", + N_("Photoshop image (merged)"), + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (load_args), + G_N_ELEMENTS (load_return_vals), + load_args, load_return_vals); + + gimp_register_file_handler_priority (LOAD_MERGED_PROC, +1); + gimp_register_file_handler_mime (LOAD_MERGED_PROC, "image/x-psd"); + gimp_register_magic_load_handler (LOAD_MERGED_PROC, + "psd", + "", + "0,string,8BPS"); + + /* Thumbnail load */ + gimp_install_procedure (LOAD_THUMB_PROC, + "Loads thumbnails from the Photoshop PSD file format", + "This plug-in loads thumbnail images from Adobe " + "Photoshop (TM) native PSD format files.", + "John Marshall", + "John Marshall", + "2007", + NULL, + NULL, + GIMP_PLUGIN, + G_N_ELEMENTS (thumb_args), + G_N_ELEMENTS (thumb_return_vals), + thumb_args, thumb_return_vals); + + gimp_register_thumbnail_loader (LOAD_PROC, LOAD_THUMB_PROC); + + gimp_install_procedure (SAVE_PROC, + "saves files in the Photoshop(tm) PSD file format", + "This filter saves files of Adobe Photoshop(tm) native PSD format. These files may be of any image type supported by GIMP, with or without layers, layer masks, aux channels and guides.", + "Monigotes", + "Monigotes", + "2000", + N_("Photoshop image"), + "RGB*, GRAY*, INDEXED*", + GIMP_PLUGIN, + G_N_ELEMENTS (save_args), 0, + save_args, NULL); + + gimp_register_file_handler_mime (SAVE_PROC, "image/x-psd"); + gimp_register_save_handler (SAVE_PROC, "psd", ""); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[4]; + GimpRunMode run_mode; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + gint32 image_ID; + GError *error = NULL; + + run_mode = param[0].data.d_int32; + + INIT_I18N (); + gegl_init (NULL, NULL); + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (strcmp (name, LOAD_PROC) == 0 || + strcmp (name, LOAD_MERGED_PROC) == 0) + { + gboolean resolution_loaded = FALSE; + gboolean profile_loaded = FALSE; + gboolean interactive; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + interactive = TRUE; + break; + default: + interactive = FALSE; + break; + } + + image_ID = load_image (param[1].data.d_string, + strcmp (name, LOAD_MERGED_PROC) == 0, + &resolution_loaded, + &profile_loaded, + &error); + + if (image_ID != -1) + { + GFile *file = g_file_new_for_path (param[1].data.d_string); + GimpMetadata *metadata; + + metadata = gimp_image_metadata_load_prepare (image_ID, "image/x-psd", + file, NULL); + + if (metadata) + { + GimpMetadataLoadFlags flags = GIMP_METADATA_LOAD_ALL; + + if (resolution_loaded) + flags &= ~GIMP_METADATA_LOAD_RESOLUTION; + + if (profile_loaded) + flags &= ~GIMP_METADATA_LOAD_COLORSPACE; + + gimp_image_metadata_load_finish (image_ID, "image/x-psd", + metadata, flags, + interactive); + + g_object_unref (metadata); + } + + g_object_unref (file); + + *nreturn_vals = 2; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + else if (strcmp (name, LOAD_THUMB_PROC) == 0) + { + if (nparams < 2) + { + status = GIMP_PDB_CALLING_ERROR; + } + else + { + const gchar *filename = param[0].data.d_string; + gint width = 0; + gint height = 0; + + image_ID = load_thumbnail_image (filename, &width, &height, &error); + + if (image_ID != -1) + { + *nreturn_vals = 4; + values[1].type = GIMP_PDB_IMAGE; + values[1].data.d_image = image_ID; + values[2].type = GIMP_PDB_INT32; + values[2].data.d_int32 = width; + values[3].type = GIMP_PDB_INT32; + values[3].data.d_int32 = height; + } + else + { + status = GIMP_PDB_EXECUTION_ERROR; + } + } + } + else if (strcmp (name, SAVE_PROC) == 0) + { + gint32 drawable_id; + GimpMetadata *metadata; + GimpMetadataSaveFlags metadata_flags; + GimpExportReturn export = GIMP_EXPORT_IGNORE; + + IFDBG(2) g_debug ("\n---------------- %s ----------------\n", + param[3].data.d_string); + + image_ID = param[1].data.d_int32; + drawable_id = param[2].data.d_int32; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + + export = gimp_export_image (&image_ID, &drawable_id, "PSD", + GIMP_EXPORT_CAN_HANDLE_RGB | + GIMP_EXPORT_CAN_HANDLE_GRAY | + GIMP_EXPORT_CAN_HANDLE_INDEXED | + GIMP_EXPORT_CAN_HANDLE_ALPHA | + GIMP_EXPORT_CAN_HANDLE_LAYERS | + GIMP_EXPORT_CAN_HANDLE_LAYER_MASKS); + + if (export == GIMP_EXPORT_CANCEL) + { + values[0].data.d_status = GIMP_PDB_CANCEL; + return; + } + break; + + default: + break; + } + + metadata = gimp_image_metadata_save_prepare (image_ID, + "image/x-psd", + &metadata_flags); + + if (save_image (param[3].data.d_string, image_ID, &error)) + { + if (metadata) + { + GFile *file; + + gimp_metadata_set_bits_per_sample (metadata, 8); + + file = g_file_new_for_path (param[3].data.d_string); + gimp_image_metadata_save_finish (image_ID, + "image/x-psd", + metadata, metadata_flags, + file, NULL); + g_object_unref (file); + } + + values[0].data.d_status = GIMP_PDB_SUCCESS; + } + else + { + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + if (error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + } + + if (export == GIMP_EXPORT_EXPORT) + gimp_image_delete (image_ID); + + if (metadata) + g_object_unref (metadata); + } + + /* Unknown procedure */ + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + if (status != GIMP_PDB_SUCCESS && error) + { + *nreturn_vals = 2; + values[1].type = GIMP_PDB_STRING; + values[1].data.d_string = error->message; + } + + values[0].data.d_status = status; +} diff --git a/plug-ins/file-psd/psd.h b/plug-ins/file-psd/psd.h new file mode 100644 index 0000000..febb003 --- /dev/null +++ b/plug-ins/file-psd/psd.h @@ -0,0 +1,688 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * GIMP PSD Plug-in + * Copyright 2007 by John Marshall + * + * 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 __PSD_H__ +#define __PSD_H__ + + +/* Set to the level of debugging output you want, 0 for none. + * Setting higher than 2 will result in a very large amount of debug + * output being produced. */ +#define PSD_DEBUG 3 +#define IFDBG(level) if (PSD_DEBUG >= level) + +/* Set to FALSE to suppress pop-up warnings about lossy file conversions */ +#define CONVERSION_WARNINGS FALSE + +#define LOAD_PROC "file-psd-load" +#define LOAD_MERGED_PROC "file-psd-load-merged" +#define LOAD_THUMB_PROC "file-psd-load-thumb" +#define SAVE_PROC "file-psd-save" +#define PLUG_IN_BINARY "file-psd" +#define PLUG_IN_ROLE "gimp-file-psd" + +#define GIMP_PARASITE_COMMENT "gimp-comment" + +#define PSD_PARASITE_DUOTONE_DATA "psd-duotone-data" + +/* Copied from app/base/gimpimage-quick-mask.h - internal identifier for quick mask channel */ +#define GIMP_IMAGE_QUICK_MASK_NAME "Qmask" + +#define MAX_RAW_SIZE 0 /* FIXME all images are raw if 0 */ + +/* PSD spec defines */ +#define MAX_CHANNELS 56 /* Photoshop CS to CS3 support 56 channels */ + +/* PSD spec constants */ + +/* Layer resource IDs */ + +/* Adjustment layer IDs */ +#define PSD_LADJ_LEVEL "levl" /* Adjustment layer - levels (PS4) */ +#define PSD_LADJ_CURVE "curv" /* Adjustment layer - curves (PS4) */ +#define PSD_LADJ_BRIGHTNESS "brit" /* Adjustment layer - brightness/contrast (PS4) */ +#define PSD_LADJ_BALANCE "blnc" /* Adjustment layer - color balance (PS4) */ +#define PSD_LADJ_BLACK_WHITE "blwh" /* Adjustment layer - black & white (PS10) */ +#define PSD_LADJ_HUE "hue " /* Adjustment layer - old hue/saturation (PS4) */ +#define PSD_LADJ_HUE2 "hue2" /* Adjustment layer - hue/saturation (PS5) */ +#define PSD_LADJ_SELECTIVE "selc" /* Adjustment layer - selective color (PS4) */ +#define PSD_LADJ_MIXER "mixr" /* Adjustment layer - channel mixer (PS9) */ +#define PSD_LADJ_GRAD_MAP "grdm" /* Adjustment layer - gradient map (PS9) */ +#define PSD_LADJ_PHOTO_FILT "phfl" /* Adjustment layer - photo filter (PS9) */ +#define PSD_LADJ_EXPOSURE "expA" /* Adjustment layer - exposure (PS10) */ +#define PSD_LADJ_INVERT "nvrt" /* Adjustment layer - invert (PS4) */ +#define PSD_LADJ_THRESHOLD "thrs" /* Adjustment layer - threshold (PS4) */ +#define PSD_LADJ_POSTERIZE "post" /* Adjustment layer - posterize (PS4) */ +#define PSD_LADJ_VIBRANCE "vibA" /* Adjustment layer - vibrance (PS10) */ +#define PSD_LADJ_COLOR_LOOKUP "clrL" /* Adjustment layer - color lookup (PS13) */ + +/* Fill Layer IDs */ +#define PSD_LFIL_SOLID "SoCo" /* Solid color sheet setting (PS6) */ +#define PSD_LFIL_PATTERN "PtFl" /* Pattern fill setting (PS6) */ +#define PSD_LFIL_GRADIENT "GdFl" /* Gradient fill setting (PS6) */ + +/* Effects Layer IDs */ +#define PSD_LFX_FX "lrFX" /* Effects layer info (PS5) */ +#define PSD_LFX_FX2 "lfx2" /* Object based effects layer info (PS6) */ + +/* Type Tool Layers */ +#define PSD_LTYP_TYPE "tySh" /* Type tool layer (PS5) */ +#define PSD_LTYP_TYPE2 "TySh" /* Type tool object setting (PS6) */ + +/* Layer Properties */ +#define PSD_LPRP_UNICODE "luni" /* Unicode layer name (PS5) */ +#define PSD_LPRP_SOURCE "lnsr" /* Layer name source setting (PS6) */ +#define PSD_LPRP_ID "lyid" /* Layer ID (PS5) */ +#define PSD_LPRP_BLEND_CLIP "clbl" /* Blend clipping elements (PS6) */ +#define PSD_LPRP_BLEND_INT "infx" /* Blend interior elements (PS6) */ +#define PSD_LPRP_KNOCKOUT "knko" /* Knockout setting (PS6) */ +#define PSD_LPRP_PROTECT "lspf" /* Protected setting (PS6) */ +#define PSD_LPRP_COLOR "lclr" /* Sheet color setting (PS6) */ +#define PSD_LPRP_REF_POINT "fxrp" /* Reference point (PS6) */ +#define PSD_LPRP_VERSION "lyvr" /* Layer version (PS7) */ + +/* Vector mask */ +#define PSD_LMSK_VMASK "vmsk" /* Vector mask setting (PS6) */ + +/* Parasites */ +#define PSD_LPAR_ANNOTATE "Anno" /* Annotation (PS6) */ + +/* Other */ +#define PSD_LOTH_SECTION "lsct" /* Section divider setting - Layer groups (PS6) */ +#define PSD_LOTH_SECTION2 "lsdk" /* Nested section divider setting - Layer groups (CS5) */ +#define PSD_LOTH_PATTERN "Patt" /* Patterns (PS6) */ +#define PSD_LOTH_PATTERN_2 "Pat2" /* Patterns 2nd key (PS6) */ +#define PSD_LOTH_PATTERN_3 "Pat3" /* Patterns 3rd key (PS6) */ +#define PSD_LOTH_GRADIENT "grdm" /* Gradient settings (PS6) */ +#define PSD_LOTH_RESTRICT "brst" /* Channel blending restriction setting (PS6) */ +#define PSD_LOTH_FOREIGN_FX "ffxi" /* Foreign effect ID (PS6) */ +#define PSD_LOTH_PATT_DATA "shpa" /* Pattern data (PS6) */ +#define PSD_LOTH_META_DATA "shmd" /* Meta data setting (PS6) */ +#define PSD_LOTH_LAYER_DATA "layr" /* Layer data (PS6) */ +#define PSD_LOTH_CONTENT_GEN "CgEd" /* Content generator extra data (PS12) */ +#define PSD_LOTH_TEXT_ENGINE "Txt2" /* Text engine data (PS10) */ +#define PSD_LOTH_PATH_NAME "pths" /* Unicode path name (PS13) */ +#define PSD_LOTH_ANIMATION_FX "anFX" /* Animation effects (PS13) */ +#define PSD_LOTH_FILTER_MASK "FMsk" /* Filter mask (PS10) */ +#define PSD_LOTH_VECTOR_STROKE "vscg" /* Vector stroke data (PS13) */ +#define PSD_LOTH_ALIGN_RENDER "sn2P" /* Aligned rendering flag (?) */ +#define PSD_LOTH_USER_MASK "LMsk" /* User mask (?) */ + +/* Effects layer resource IDs */ +#define PSD_LFX_COMMON "cmnS" /* Effects layer - common state (PS5) */ +#define PSD_LFX_DROP_SDW "dsdw" /* Effects layer - drop shadow (PS5) */ +#define PSD_LFX_INNER_SDW "isdw" /* Effects layer - inner shadow (PS5) */ +#define PSD_LFX_OUTER_GLW "oglw" /* Effects layer - outer glow (PS5) */ +#define PSD_LFX_INNER_GLW "iglw" /* Effects layer - inner glow (PS5) */ +#define PSD_LFX_BEVEL "bevl" /* Effects layer - bevel (PS5) */ + +/* Placed Layer */ +#define PSD_LPL_PLACE_LAYER "plLd" /* Placed layer (?) */ +#define PSD_LPL_PLACE_LAYER_NEW "SoLd" /* Placed layer (PS10) */ + +/* Linked Layer */ +#define PSD_LLL_LINKED_LAYER "lnkD" /* Linked layer (?) */ +#define PSD_LLL_LINKED_LAYER_2 "lnk2" /* Linked layer 2nd key */ +#define PSD_LLL_LINKED_LAYER_3 "lnk3" /* Linked layer 3rd key */ + +/* Merged Transparency */ +#define PSD_LMT_MERGE_TRANS "Mtrn" /* Merged transparency save flag (?) */ +#define PSD_LMT_MERGE_TRANS_16 "Mt16" /* Merged transparency save flag 2 */ +#define PSD_LMT_MERGE_TRANS_32 "Mt32" /* Merged transparency save flag 3 */ + +/* Filter Effects */ +#define PSD_LFFX_FILTER_FX "FXid" /* Filter effects (?) */ +#define PSD_LFFX_FILTER_FX_2 "FEid" /* Filter effects 2 */ + +/* PSD spec enums */ + +/* Image color modes */ +typedef enum { + PSD_BITMAP = 0, /* Bitmap image */ + PSD_GRAYSCALE = 1, /* Greyscale image */ + PSD_INDEXED = 2, /* Indexed image */ + PSD_RGB = 3, /* RGB image */ + PSD_CMYK = 4, /* CMYK */ + PSD_MULTICHANNEL = 7, /* Multichannel image*/ + PSD_DUOTONE = 8, /* Duotone image*/ + PSD_LAB = 9 /* L*a*b image */ +} PSDColorMode; + +/* Image color spaces */ +typedef enum { + PSD_CS_RGB = 0, /* RGB */ + PSD_CS_HSB = 1, /* Hue, Saturation, Brightness */ + PSD_CS_CMYK = 2, /* CMYK */ + PSD_CS_PANTONE = 3, /* Pantone matching system (Lab)*/ + PSD_CS_FOCOLTONE = 4, /* Focoltone color system (CMYK)*/ + PSD_CS_TRUMATCH = 5, /* Trumatch color (CMYK)*/ + PSD_CS_TOYO = 6, /* Toyo 88 colorfinder 1050 (Lab)*/ + PSD_CS_LAB = 7, /* L*a*b*/ + PSD_CS_GRAYSCALE = 8, /* Grey scale */ + PSD_CS_HKS = 10, /* HKS colors (CMYK)*/ + PSD_CS_DIC = 11, /* DIC color guide (Lab)*/ + PSD_CS_ANPA = 3000, /* Anpa color (Lab)*/ +} PSDColorSpace; + +/* Image Resource IDs */ +typedef enum { + PSD_PS2_IMAGE_INFO = 1000, /* 0x03e8 - Obsolete - ps 2.0 image info */ + PSD_MAC_PRINT_INFO = 1001, /* 0x03e9 - Optional - Mac print manager print info record */ + PSD_PS2_COLOR_TAB = 1003, /* 0x03eb - Obsolete - ps 2.0 indexed color table */ + PSD_RESN_INFO = 1005, /* 0x03ed - ResolutionInfo structure */ + PSD_ALPHA_NAMES = 1006, /* 0x03ee - Alpha channel names */ + PSD_DISPLAY_INFO = 1007, /* 0x03ef - Superceded by PSD_DISPLAY_INFO_NEW for ps CS3 and higher - DisplayInfo structure */ + PSD_CAPTION = 1008, /* 0x03f0 - Optional - Caption string */ + PSD_BORDER_INFO = 1009, /* 0x03f1 - Border info */ + PSD_BACKGROUND_COL = 1010, /* 0x03f2 - Background color */ + PSD_PRINT_FLAGS = 1011, /* 0x03f3 - Print flags */ + PSD_GREY_HALFTONE = 1012, /* 0x03f4 - Greyscale and multichannel halftoning info */ + PSD_COLOR_HALFTONE = 1013, /* 0x03f5 - Color halftoning info */ + PSD_DUOTONE_HALFTONE = 1014, /* 0x03f6 - Duotone halftoning info */ + PSD_GREY_XFER = 1015, /* 0x03f7 - Greyscale and multichannel transfer functions */ + PSD_COLOR_XFER = 1016, /* 0x03f8 - Color transfer functions */ + PSD_DUOTONE_XFER = 1017, /* 0x03f9 - Duotone transfer functions */ + PSD_DUOTONE_INFO = 1018, /* 0x03fa - Duotone image information */ + PSD_EFFECTIVE_BW = 1019, /* 0x03fb - Effective black & white values for dot range */ + PSD_OBSOLETE_01 = 1020, /* 0x03fc - Obsolete */ + PSD_EPS_OPT = 1021, /* 0x03fd - EPS options */ + PSD_QUICK_MASK = 1022, /* 0x03fe - Quick mask info */ + PSD_OBSOLETE_02 = 1023, /* 0x03ff - Obsolete */ + PSD_LAYER_STATE = 1024, /* 0x0400 - Layer state info */ + PSD_WORKING_PATH = 1025, /* 0x0401 - Working path (not saved) */ + PSD_LAYER_GROUP = 1026, /* 0x0402 - Layers group info */ + PSD_OBSOLETE_03 = 1027, /* 0x0403 - Obsolete */ + PSD_IPTC_NAA_DATA = 1028, /* 0x0404 - IPTC-NAA record (IMV4.pdf) */ + PSD_IMAGE_MODE_RAW = 1029, /* 0x0405 - Image mode for raw format files */ + PSD_JPEG_QUAL = 1030, /* 0x0406 - JPEG quality */ + PSD_GRID_GUIDE = 1032, /* 0x0408 - Grid & guide info */ + PSD_THUMB_RES = 1033, /* 0x0409 - Thumbnail resource */ + PSD_COPYRIGHT_FLG = 1034, /* 0x040a - Copyright flag */ + PSD_URL = 1035, /* 0x040b - URL string */ + PSD_THUMB_RES2 = 1036, /* 0x040c - Thumbnail resource */ + PSD_GLOBAL_ANGLE = 1037, /* 0x040d - Superceded by PSD_NEW_COLOR_SAMPLER for ps CS3 and higher - Global angle */ + PSD_COLOR_SAMPLER = 1038, /* 0x040e - Superceded by PSD_NEW_COLOR_SAMPLER for ps CS3 and higher - Color samplers resource */ + PSD_ICC_PROFILE = 1039, /* 0x040f - ICC Profile */ + PSD_WATERMARK = 1040, /* 0x0410 - Watermark */ + PSD_ICC_UNTAGGED = 1041, /* 0x0411 - Do not use ICC profile flag */ + PSD_EFFECTS_VISIBLE = 1042, /* 0x0412 - Show / hide all effects layers */ + PSD_SPOT_HALFTONE = 1043, /* 0x0413 - Spot halftone */ + PSD_DOC_IDS = 1044, /* 0x0414 - Document specific IDs */ + PSD_ALPHA_NAMES_UNI = 1045, /* 0x0415 - Unicode alpha names */ + PSD_IDX_COL_TAB_CNT = 1046, /* 0x0416 - Indexed color table count */ + PSD_IDX_TRANSPARENT = 1047, /* 0x0417 - Index of transparent color (if any) */ + PSD_GLOBAL_ALT = 1049, /* 0x0419 - Global altitude */ + PSD_SLICES = 1050, /* 0x041a - Slices */ + PSD_WORKFLOW_URL_UNI = 1051, /* 0x041b - Workflow URL - Unicode string */ + PSD_JUMP_TO_XPEP = 1052, /* 0x041c - Jump to XPEP (?) */ + PSD_ALPHA_ID = 1053, /* 0x041d - Alpha IDs */ + PSD_URL_LIST_UNI = 1054, /* 0x041e - URL list - unicode */ + PSD_VERSION_INFO = 1057, /* 0x0421 - Version info */ + PSD_EXIF_DATA = 1058, /* 0x0422 - Exif data block 1 */ + PSD_EXIF_DATA_3 = 1059, /* 0X0423 - Exif data block 3 (?) */ + PSD_XMP_DATA = 1060, /* 0x0424 - XMP data block */ + PSD_CAPTION_DIGEST = 1061, /* 0x0425 - Caption digest */ + PSD_PRINT_SCALE = 1062, /* 0x0426 - Print scale */ + PSD_PIXEL_AR = 1064, /* 0x0428 - Pixel aspect ratio */ + PSD_LAYER_COMPS = 1065, /* 0x0429 - Layer comps */ + PSD_ALT_DUOTONE_COLOR = 1066, /* 0x042A - Alternative Duotone colors */ + PSD_ALT_SPOT_COLOR = 1067, /* 0x042B - Alternative Spot colors */ + PSD_LAYER_SELECT_ID = 1069, /* 0x042D - Layer selection ID */ + PSD_HDR_TONING_INFO = 1070, /* 0x042E - HDR toning information */ + PSD_PRINT_INFO_SCALE = 1071, /* 0x042F - Print scale */ + PSD_LAYER_GROUP_E_ID = 1072, /* 0x0430 - Layer group(s) enabled ID */ + PSD_COLOR_SAMPLER_NEW = 1073, /* 0x0431 - Color sampler resource for ps CS3 and higher PSD files */ + PSD_MEASURE_SCALE = 1074, /* 0x0432 - Measurement scale */ + PSD_TIMELINE_INFO = 1075, /* 0x0433 - Timeline information */ + PSD_SHEET_DISCLOSE = 1076, /* 0x0434 - Sheet discloser */ + PSD_DISPLAY_INFO_NEW = 1077, /* 0x0435 - DisplayInfo structure for ps CS3 and higher PSD files */ + PSD_ONION_SKINS = 1078, /* 0x0436 - Onion skins */ + PSD_COUNT_INFO = 1080, /* 0x0438 - Count information*/ + PSD_PRINT_INFO = 1082, /* 0x043A - Print information added in ps CS5*/ + PSD_PRINT_STYLE = 1083, /* 0x043B - Print style */ + PSD_MAC_NSPRINTINFO = 1084, /* 0x043C - Mac NSPrintInfo*/ + PSD_WIN_DEVMODE = 1085, /* 0x043D - Windows DEVMODE */ + PSD_AUTO_SAVE_PATH = 1086, /* 0x043E - Auto save file path */ + PSD_AUTO_SAVE_FORMAT = 1087, /* 0x043F - Auto save format */ + PSD_PATH_INFO_FIRST = 2000, /* 0x07d0 - First path info block */ + PSD_PATH_INFO_LAST = 2998, /* 0x0bb6 - Last path info block */ + PSD_CLIPPING_PATH = 2999, /* 0x0bb7 - Name of clipping path */ + PSD_PLUGIN_R_FIRST = 4000, /* 0x0FA0 - First plugin resource */ + PSD_PLUGIN_R_LAST = 4999, /* 0x1387 - Last plugin resource */ + PSD_IMAGEREADY_VARS = 7000, /* 0x1B58 - Imageready variables */ + PSD_IMAGEREADY_DATA = 7001, /* 0x1B59 - Imageready data sets */ + PSD_LIGHTROOM_WORK = 8000, /* 0x1F40 - Lightroom workflow */ + PSD_PRINT_FLAGS_2 = 10000 /* 0x2710 - Print flags */ +} PSDImageResID; + +/* Display resolution units */ +typedef enum { + PSD_RES_INCH = 1, /* Pixels / inch */ + PSD_RES_CM = 2, /* Pixels / cm */ +} PSDDisplayResUnit; + +/* Width and height units */ +typedef enum { + PSD_UNIT_INCH = 1, /* inches */ + PSD_UNIT_CM = 2, /* cm */ + PSD_UNIT_POINT = 3, /* points (72 points = 1 inch) */ + PSD_UNIT_PICA = 4, /* pica ( 6 pica = 1 inch) */ + PSD_UNIT_COLUMN = 5, /* columns ( column defined in ps prefs, default = 2.5 inches) */ +} PSDUnit; + +/* Thumbnail image data encoding */ +typedef enum { + kRawRGB = 0, /* RAW data format (never used?) */ + kJpegRGB = 1 /* JPEG compression */ +} PSDThumbFormat; + +/* Path record types */ +typedef enum { + PSD_PATH_CL_LEN = 0, /* Closed sub-path length record */ + PSD_PATH_CL_LNK = 1, /* Closed sub-path Bezier knot, linked */ + PSD_PATH_CL_UNLNK = 2, /* Closed sub-path Bezier knot, unlinked */ + PSD_PATH_OP_LEN = 3, /* Open sub-path length record */ + PSD_PATH_OP_LNK = 4, /* Open sub-path Bezier knot, linked */ + PSD_PATH_OP_UNLNK = 5, /* Open sub-path Bezier knot, unlinked */ + PSD_PATH_FILL_RULE = 6, /* Path fill rule record */ + PSD_PATH_CLIPBOARD = 7, /* Path clipboard record */ + PSD_PATH_FILL_INIT = 8 /* Path initial fill record */ +} PSDpathtype; + +/* Channel ID */ +typedef enum { + PSD_CHANNEL_EXTRA_MASK= -3, /* User supplied extra layer mask */ + PSD_CHANNEL_MASK = -2, /* User supplied layer mask */ + PSD_CHANNEL_ALPHA = -1, /* Transparency mask */ + PSD_CHANNEL_RED = 0, /* Red channel data */ + PSD_CHANNEL_GREEN = 1, /* Green channel data */ + PSD_CHANNEL_BLUE = 2 /* Blue channel data */ +} PSDChannelID; + +/* Clipping */ +typedef enum { + PSD_CLIPPING_BASE = 0, /* Base clipping */ + PSD_CLIPPING_NON_BASE = 1 /* Non-base clipping */ +} PSDClipping; + +/* Image compression mode */ +typedef enum { + PSD_COMP_RAW = 0, /* Raw data */ + PSD_COMP_RLE, /* RLE compressed */ + PSD_COMP_ZIP, /* ZIP without prediction */ + PSD_COMP_ZIP_PRED /* ZIP with prediction */ +} PSDCompressMode; + +/* Vertical - horizontal selection */ +typedef enum { + PSD_VERTICAL = 0, /* Vertical */ + PSD_HORIZONTAL = 1 /* Horizontal */ +} VHSelect; + + +/* PSD spec data structures */ + +/* PSD field types */ +typedef gint32 Fixed; /* Represents a fixed point implied decimal */ + + +/* Apple color space data structures */ + +/* RGB Color Value + A color value expressed in the RGB color space is composed of red, green, + and blue component values. Each color component is expressed as a numeric + value within the range of 0 to 65535. +*/ +typedef struct +{ + guint16 red; + guint16 green; + guint16 blue; +} CMRGBColor; + +/* HSV Color Value + A color value expressed in the HSV color space is composed of hue, + saturation, and value component values. Each color component is + expressed as a numeric value within the range of 0 to 65535 inclusive. + The hue value represents a fraction of a circle in which red is + positioned at 0. +*/ + +typedef struct +{ + guint16 hue; + guint16 saturation; + guint16 value; +} CMHSVColor; + +/* CMYK Color Value + A color value expressed in the CMYK color space is composed of cyan, magenta, + yellow, and black component values. Each color component is expressed as a + numeric value within the range of 0 to 65535 inclusive, with 0 representing + 100% ink (e.g. pure cyan = 0, 65535, 65535, 65535). +*/ + +typedef struct +{ + guint16 cyan; + guint16 magenta; + guint16 yellow; + guint16 black; +} CMCMYKColor; + +/* L*a*b* Color Value + The first three values in the color data are, respectively, the colors + lightness, a chrominance, and b chrominance components. The lightness + component is a 16bit value ranging from 0 to 10000. The chrominance + components are each 16bit values ranging from 12800 to 12700. Gray + values are represented by chrominance components of 0 (e.g. pure white + is defined as 10000, 0, 0). +*/ +typedef struct +{ + guint16 L; + gint16 a; + gint16 b; +} CMLabColor; + +/* Gray Color Value + A color value expressed in the Gray color space is composed of a single component, + gray, represented as a numeric value within the range of 0 to 10000. +*/ +typedef struct +{ + guint16 gray; +} CMGrayColor ; + +/* The color union is defined by the CMColor type definition. + */ +typedef union +{ + CMRGBColor rgb; + CMHSVColor hsv; + CMLabColor Lab; + CMCMYKColor cmyk; + CMGrayColor gray; +} CMColor; + +/* GIMP layer mode info */ +typedef struct +{ + GimpLayerMode mode; + GimpLayerColorSpace blend_space; + GimpLayerColorSpace composite_space; + GimpLayerCompositeMode composite_mode; +} LayerModeInfo; + +/* Image resolution data */ +typedef struct { + Fixed hRes; /* Horizontal resolution pixels/inch */ + gint16 hResUnit; /* Horizontal display resolution unit (1=pixels per inch, 2=pixels per cm) */ + gint16 widthUnit; /* Display width unit (1=inches; 2=cm; 3=points; 4=picas; 5=columns) */ + Fixed vRes; /* Vertical resolution pixels/inch */ + gint16 vResUnit; /* Vertical display resolution unit */ + gint16 heightUnit; /* Display height unit */ +} ResolutionInfo; + +/* Grid & guide header */ +typedef struct { + guint32 fVersion; /* Version - always 1 for PS */ + guint32 fGridCycleV; /* Vertical grid size */ + guint32 fGridCycleH; /* Horizontal grid size */ + guint32 fGuideCount; /* Number of guides */ +} GuideHeader; + +/* Guide resource block */ +typedef struct { + guint32 fLocation; /* Guide position in Pixels * 100 */ + gchar fDirection; /* Guide orientation */ +} GuideResource; + +/* Thumbnail data */ +typedef struct { + gint32 format; /* Thumbnail image data format (1 = JPEG) */ + gint32 width; /* Thumbnail width in pixels */ + gint32 height; /* Thumbnail height in pixels */ + gint32 widthbytes; /* Padded row bytes ((width * bitspixel + 31) / 32 * 4) */ + gint32 size; /* Total size (widthbytes * height * planes */ + gint32 compressedsize; /* Size after compression for consistency */ + gint16 bitspixel; /* Bits per pixel (always 24) */ + gint16 planes; /* Number of planes (always 1) */ +} ThumbnailInfo; + +/* Channel display info data for Adobe Photoshop CS2 and lower */ +typedef struct { + gint16 colorSpace; /* Color space from PSDColorSpace */ + guint16 color[4]; /* 4 * 16 bit color components */ + gint16 opacity; /* Opacity 0 to 100 */ + gchar kind; /* Selected = 0, Protected = 1 */ + gchar padding; /* Padding */ +} DisplayInfo; + +/* Channel display info data for Adobe Photoshop CS3 and higher to support floating point colors */ +typedef struct { + gint16 colorSpace; /* Color space from PSDColorSpace */ + guint16 color[4]; /* 4 * 16 bit color components */ + gint16 opacity; /* Opacity 0 to 100 */ + gchar mode; /* Alpha = 0, Inverted alpha = 1, Spot = 2 */ +} DisplayInfoNew; + +/* PSD Channel length info data structure */ +typedef struct +{ + gint16 channel_id; /* Channel ID */ + guint32 data_len; /* Layer left */ +} ChannelLengthInfo; + +/* PSD Layer flags */ +typedef struct +{ + gboolean trans_prot; /* Transparency protected */ + gboolean visible; /* Visible */ + gboolean obsolete; /* Obsolete */ + gboolean bit4; /* Bit 4 in use */ + gboolean irrelevant; /* Pixel data irrelevant to image appearance */ +} LayerFlags; + +/* PSD Layer mask flags */ +typedef struct +{ + gboolean relative_pos; /* Mask position recorded relative to layer */ + gboolean disabled; /* Mask disabled */ + gboolean invert; /* Invert mask on blending (obsolete according to online specs) */ + gboolean rendered; /* User mask actually came from rendering other data */ + gboolean params_present; /* User and/or vector masks have parameters applied to them */ +} MaskFlags; + +/* PSD Slices */ +typedef struct +{ + gint32 id; /* ID */ + gint32 groupid; /* Group ID */ + gint32 origin; /* Origin */ + gint32 associatedid; /* Associated Layer ID */ + gchar *name; /* Name */ + gint32 type; /* Type */ + gint32 left; /* Position coordinates */ + gint32 top; + gint32 right; + gint32 bottom; + gchar *url; /* URL */ + gchar *target; /* Target */ + gchar *message; /* Message */ + gchar *alttag; /* Alt Tag */ + gchar html; /* Boolean for if cell text is HTML */ + gchar *celltext; /* Cell text */ + gint32 horizontal; /* Horizontal alignment */ + gint32 vertical; /* Vertical alignment */ + gchar alpha; /* Alpha */ + gchar red; /* Red */ + gchar green; /* Green */ + gchar blue; /* Blue */ +} PSDSlice; + +/* PSD Layer mask data (length 20) */ +typedef struct +{ + gint32 top; /* Layer top */ + gint32 left; /* Layer left */ + gint32 bottom; /* Layer bottom */ + gint32 right; /* Layer right */ + guchar def_color; /* Default background color */ + guchar flags; /* Layer flags */ + guchar mask_params; /* Mask parameters. Only present if bit 4 of flags is set. */ + guchar extra_flags; /* Real layer flags */ + guchar extra_def_color; /* Real user mask background */ + MaskFlags mask_flags; /* Flags */ +} LayerMask; + +/* PSD Layer mask data (length 36) */ +typedef struct +{ + gint32 top; /* Layer top */ + gint32 left; /* Layer left */ + gint32 bottom; /* Layer bottom */ + gint32 right; /* Layer right */ +} LayerMaskExtra; + +/* PSD text reading */ +typedef struct +{ + gdouble xx; /* Transform information */ + gdouble xy; + gdouble yx; + gdouble yy; + gdouble tx; + gdouble ty; + gchar *info; /* Text information */ +} PSDText; + +/* PSD Layer data structure */ +typedef struct +{ + gboolean drop; /* Do not add layer to GIMP image */ + gint32 top; /* Layer top */ + gint32 left; /* Layer left */ + gint32 bottom; /* Layer bottom */ + gint32 right; /* Layer right */ + guint16 num_channels; /* Number of channels */ + ChannelLengthInfo *chn_info; /* Channel length info */ + gchar mode_key[4]; /* Blend mode key */ + gchar blend_mode[4]; /* Blend mode */ + guchar opacity; /* Opacity - 0 = transparent ... 255 = opaque */ + guchar clipping; /* Clipping */ + guchar clipping_group_type; /* Used to track group needed for clipping (1 = group start, 2 = group end) */ + guchar flags; /* Layer flags */ + guchar filler; /* Filler */ + guint32 extra_len; /* Extra data length */ + gchar *name; /* Layer name */ + guint32 mask_len; /* Layer mask data length */ + LayerMask layer_mask; /* Layer mask data */ + LayerMaskExtra layer_mask_extra; /* Layer mask extra data */ + LayerFlags layer_flags; /* Layer flags */ + PSDText text; /* PSD text */ + guint32 id; /* Layer ID (Tattoo) */ + guchar group_type; /* 0 -> not a group; 1 -> open folder; 2 -> closed folder; 3 -> end of group */ + guint16 color_tag[4]; /* 4 * 16 bit color components */ +} PSDlayer; + +/* PSD Channel data structure */ +typedef struct +{ + gint16 id; /* Channel ID */ + gchar *name; /* Channel name */ + gchar *data; /* Channel image data */ + guint32 rows; /* Channel rows */ + guint32 columns; /* Channel columns */ +} PSDchannel; + +/* PSD Channel data structure */ +typedef struct +{ + GimpRGB gimp_color; /* Gimp RGB color */ + gint16 opacity; /* Opacity */ + guchar ps_mode; /* PS mode flag */ + guchar ps_kind; /* PS type flag */ + gint16 ps_cspace; /* PS color space */ + CMColor ps_color; /* PS color */ +} PSDchanneldata; + +/* PSD Image Resource data structure */ +typedef struct +{ + gchar type[4]; /* Image resource type */ + gint16 id; /* Image resource ID */ + gchar name[256]; /* Image resource name (pascal string) */ + guint32 data_start; /* Image resource data start */ + guint32 data_len; /* Image resource data length */ +} PSDimageres; + +/* PSD Layer Resource data structure */ +typedef struct +{ + gchar sig[4]; /* Layer resource signature */ + gchar key[4]; /* Layer resource key */ + guint32 data_start; /* Layer resource data start */ + guint32 data_len; /* Layer resource data length */ +} PSDlayerres; + +/* PSD File data structures */ +typedef struct +{ + gboolean merged_image_only; /* Whether to load only the merged image data */ + + guint16 channels; /* Number of channels: 1- 56 */ + gboolean transparency; /* Image has merged transparency alpha channel */ + guint32 rows; /* Number of rows: 1 - 30000 */ + guint32 columns; /* Number of columns: 1 - 30000 */ + guint16 bps; /* Bits per sample: 1, 8, 16, or 32 */ + guint16 color_mode; /* Image color mode: {PSDColorMode} */ + GimpImageBaseType base_type; /* Image base color mode: (GIMP) */ + guint16 comp_mode; /* Merged image compression mode */ + guchar *color_map; /* Color map data */ + guint32 color_map_len; /* Color map data length */ + guint32 color_map_entries; /* Color map number of entries */ + guint32 image_res_start; /* Image resource block start address */ + guint32 image_res_len; /* Image resource block length */ + guint32 mask_layer_start; /* Mask & layer block start address */ + guint32 mask_layer_len; /* Mask & layer block length */ + gint16 num_layers; /* Number of layers */ + guint32 layer_data_start; /* Layer pixel data start */ + guint32 layer_data_len; /* Layer pixel data length */ + guint32 merged_image_start; /* Merged image pixel data block start address */ + guint32 merged_image_len; /* Merged image pixel data block length */ + gboolean no_icc; /* Do not use ICC profile */ + guint16 layer_state; /* Active layer number counting from bottom up */ + GPtrArray *alpha_names; /* Alpha channel names */ + PSDchanneldata **alpha_display_info; /* Alpha channel display info */ + guint16 alpha_display_count; /* Number of alpha channel display info recs */ + guint32 *alpha_id; /* Alpha channel ids (tattoos) */ + guint16 alpha_id_count; /* Number of alpha channel id items */ + guint16 quick_mask_id; /* Channel number containing quick mask */ + + GimpColorProfile *cmyk_profile; + gpointer cmyk_transform; + gpointer cmyk_transform_alpha; +} PSDimage; + +/* Public functions */ + + +#endif /* __PSD_H__ */ |